【金沙澳门官网】在京东网站前端监控平台的极品实践,代码测试必备工具

PhantomJS 和 NodeJS 在京东网站前端监控平台的特级实践

2016/11/21 · JavaScript
· NodeJS,
phantomjs

本文小编: 伯乐在线 –
keelii
。未经小编许可,禁止转发!
欢迎出席伯乐在线 【金沙澳门官网】在京东网站前端监控平台的极品实践,代码测试必备工具。专辑撰稿人。

金沙澳门官网 1

内容要点

一步一脚印达成一个爬虫,前边的内容相比较简单,有询问可以直接跳过同时小说内容较长和多图,提出在pc下阅读
源码地址
phantomjs捕获内容
详尽介绍通过async.mapLimit并发处理,结合定时器举行延时执行
数据存放到mongodb
数码输出成文件
(如有错误请大家提出,一起上学)

每日都会暴发新的代码、用户测试工具和框架。上面的列表列出了足以做到种种测试必要的代码工具。你应有调查探讨一下,看那几个工具是或不是适用于您的技术栈和技巧需要。

为何须要一个前端监控类别

一般说来在一个大型的 Web 项目中有过多监督,比如后端的劳动 API
监控,接口存活、调用、延迟等监督,那几个相似都用来监控后台接口数据层面的音讯。而且对于大型网站系统来说,从后端服务到前台体现会有广大层:内网
VIP、CDN
等。可是这个监控并不可能规范地影响用户观望的前端页面状态,比如:页面第三方系统数据调用失利,模块加载非常,数据不正确,空白开天窗等。那时候就要求以前端
DOM
体现的角度去分析和征集用户真正看到的东西,从而检测出页面是或不是出现极度难点

PhantomJS是一款webkit内核的headelsss的浏览器,使用QtWebkit,
协理DOM操作、CSS选用器、JSON、Canvas和SVG,能够效仿浏览器的劳动。

介绍(有打探可以平素跳过)

关于PhantomJS
先是介绍一下phantomjs

PhantomJS是一个根据WebKit的服务器端JavaScript API,它按照BSD开源协议公布。PhantomJS无需浏览器的匡助即可落成对Web的辅助,且原生匡助种种Web标准,如DOM
处理、JavaScript、CSS选用器、JSON、Canvas和可缩放矢量图形SVG。PhantomJS紧要是经过JavaScript和CoffeeScript控制WebKit的CSS选取器、可缩放矢量图形SVG和HTTP网络等各种模块。

  1. Jasmine

亟需监控连串缓解的题材

页面寻常出现以下难点时需求使用邮件、短信公告有关人口修复难题

  • 状态码再次回到错误(50x, 40x)不能开拓
  • 模块加载战败
  • 页面乱码
  • 多少正确

接触报警时要有现场快照,以便复现难题

 

phantomjs的利用场景

不用浏览器的Web测试:无需浏览器的景观下开展高效的Web测试,且帮忙广大测试框架,如YUI
Test、贾斯敏、WebDriver、Capybara、QUnit、Mocha等。
页面自动化操作:使用规范的DOM
API或局地JavaScript框架(如jQuery)访问和操作Web页面。
显示器捕获:以编程格局抓起CSS、SVG和Canvas等页面内容,即可兑现网络爬虫应用。营造服务端Web图形应用,如截图服务、矢量光栅图应用。
互连网监控:自动举行互联网性能监控、跟踪页面加载景况以及将有关监控的音讯以专业的HAR格式导出。


依照phantomjs2.0拓展落实有二种达成方案,一种是应用基于全局的
http://phantomjs.org/
,此外一种是包装的模块 phantom – 法斯特 NodeJS API for PhantomJS
-https://github.com/amir20/phantomjs-node
那里选取phantomjs-node
有关phantomjs-node的设置以及入门
选取可以依照百度前端大学2017中的网页抓取分析服务连串连锁内容中学习,那里放一下事先phantomjs-node
上学的笔记和demo
中的phantomjs_1~4目录下
后文也会愈来愈求证使用格局。

金沙澳门官网 2

技能选型

监察的意思和回归测试的在真相上是均等的,都是对已上线效能进行回归测试,但差其余是督查须求做深入的可不断可循环的回归测试,而测试只有需求在上线之后做两遍回归

既然如此监控和测试的原形一致,那大家全然能够利用测试的艺术来做监控连串。在自动化测试技术随处开花的时期,不乏很多好用的自动化工具,大家只须要把这么些自动化工具举办整合为大家所用即可

  • NodeJS – 越发适用于互连网密集型职务
  • PhantomJS – 模拟无界面的浏览器,提供充分的基石交互 API

安装

贯彻思路和进度

Jasmine 是一个作为使得的测试开发框架,用于对 JavaScript
代码举办测试。它不借助于其余任何 JavaScript 框架,也不须要DOM。它的语法简洁、明确,写测试很是不难。

NodeJS

NodeJS 是一个 JavaScript 运行条件,非阻塞 I/O
和异步、事件驱动,这几点对于大家打造基于 DOM 元素的监控是至极关键的

mac同学利用 brew install casperjs

完毕思路

phantomjs就一定于一个无图形界面的浏览器,那么大家提供连接给phantomjs就意味着大家能博得那么些url的始末。
本次爬虫的始末是希望收获到小说的拥有章节以及其情节,直接以笔阁网为例,因为这一次爬虫是直接爬笔阁网的。
我们开拓http://www.qu.la/book/5443,

![Uploading 14995025771427_828375.jpg . . .]## 内容要点
一步一脚印落到实处一个爬虫,小说内容较长,提出在pc下阅读
源码地址
phantomjs捕获内容
详尽介绍通过async.mapLimit并发处理,结合定时器举行延时执行
数据存放到mongodb
数码输出成文件
(如有错误请大家提出,一起学学)

  1. Mocha

PhantomJS

PhantomJS 是一个基于 webkit 的浏览器引擎,可以使用 JavaScript API
来效仿浏览器的操作。它选择 QtWebKit 作为它的浏览器主题,使用 webkit
来编译解释施行 JavaScript 代码。也就是说任何你可以在 webkit
浏览器里做的事体,它都能不辱职责

它不光是个暗藏的浏览器,提供了例如 CSS 拔取器、帮衬 Web 标准、DOM
操作、JSON、HTML5、Canvas、SVG 等,同时也提供了拍卖公事 I/O
的操作等。PhantomJS
的用处可谓格外普遍,诸如互联网监测、网页截屏、无浏览器的 Web
测试、页面访问自动化等

干什么不是 Selenium

做自动化测试的校友肯定都明白 Selenium。可以使用 Selenium
将测试用例在浏览器中实施,而且 Selenium
对种种平台和普遍浏览器帮忙相比较好,不过 Selenium
上手难度周全略高,而且使用Selenium 须要在服务器端安装浏览器

设想到监督重点任务在督察不在测试。系统并不需求太多着想包容性,而且监控效用相对单一,首要对页面举办职能上的回归测试,所以拔取了
PhantomJS

 

介绍(有打探可以直接跳过)

关于PhantomJS
率先介绍一下phantomjs

PhantomJS是一个基于WebKit的服务器端JavaScript API,它根据BSD开源协议公布。PhantomJS无需浏览器的支撑即可兑现对Web的支撑,且原生辅助各个Web标准,如DOM
处理、JavaScript、CSS采用器、JSON、Canvas和可缩放矢量图形SVG。PhantomJS主若是通过JavaScript和CoffeeScript控制WebKit的CSS接纳器、可缩放矢量图形SVG和HTTP互连网等逐个模块。

Mocha 是一个功效丰盛的 JavaScript 测试框架,既运行于 Node.js
环境中,也得以运作于浏览器环境中。Mocha
以串行形式运行测试,能做出灵活而准确的告诉,也能将测试中未捕捉的更加映射到正确的测试用例。

架构设计

可以做什么样?

phantomjs的运用场景

无需浏览器的Web测试:无需浏览器的动静下进展高效的Web测试,且帮助广大测试框架,如YUI
Test、Jasmine、WebDriver、Capybara、QUnit、Mocha等。
页面自动化操作:使用正式的DOM
API或部分JavaScript框架(如jQuery)访问和操作Web页面。
显示器捕获:以编程方式抓起CSS、SVG和Canvas等页面内容,即可兑现网络爬虫应用。创设服务端Web图形应用,如截图服务、矢量光栅图应用。
互联网监督:自动举办互联网质量监控、跟踪页面加载景况以及将有关监督的信息以专业的HAR格式导出。


按照phantomjs2.0进展落实有两种完成方案,一种是行使基于全局的
http://phantomjs.org/
,此外一种是包裹的模块 phantom – 法斯特 NodeJS API for PhantomJS
-https://github.com/amir20/phantomjs-node
此间接纳phantomjs-node
关于phantomjs-node的安装以及入门
使用可以按照百度前端高校2017中的网页抓取分析服务多元相关内容中读书,这里放一下在此以前phantomjs-node
学学的笔记和demo
中的phantomjs_1~4目录下
后文也会愈发求证使用格局。

金沙澳门官网 3

架构概览

金沙澳门官网 4

  1. Headless的网站集成测试

落成思路和进度

  1. Chai

架构简述

对于 DOM 监控服务,在应用规模上进行了垂直细分:

  • 规则管理连串
  • 平整队列生成器
  • 长时持续处理器
  • PhantomJS 服务
  • 服务化 API

在应用范围上展开的垂直细分可以对运用做分布式安排,进步处理能力。前期也有利做质量优化、系统改造增添等

可以和单元测试框架如Jasmine、Mocha和WebDriver集成

落成思路

phantomjs就一定于一个无图形界面的浏览器,那么大家提供连接给phantomjs就意味着大家能收获这一个url的内容。
这一次爬虫的内容是期待取得到随笔的享有章节以及其内容,直接以笔阁网为例,因为这一次爬虫是一向爬笔阁网的。
大家打开http://www.qu.la/book/5443,

14995025771427.jpg

上边就有那本小说的重重章节,所以就有了第一步,或者这一个页面上拥有章节,通过”开发者工具”中的检查共成效

14995028395237.jpg

我们可以观察知道内容是那样的构造

<div id ="list">
<dd>
<a href="/**">第xx章</a>
</dd>
....
</div>

由此只要我们得到 id为list
中享有的dd,就收获了小说的有着章节,同时通过dd中a标签的href属性就可以源源不断到独具章节的始末。

爬虫方面的笔触表明到此处

Chai 是个协理 BDD / TDD 的库,可用以
node 和浏览器,可合作其余 JavaScript
测试框架使用。

不留余地方案

  1. 屏幕捕捉

落到实处进程

(请确保node版本高于7.9,本文基于7.10.0)
(最好先驾驭es7中async/await 以及child_process)
如何采用phantomjs-nodejs

如何运作代码?。。
将代码保存在一个js文件中诸如test.js
下一场运行

node test.js

本人的栗子

const phantom = require('phantom');//导入模块
//async解决回调问题,es7的内容
(async function() {
     // await解决回调问题,创建一个phantom实例
    const instance = await phantom.create();
    //通过phantom实例创建一个page对象,page对象可以理解成一个对页面发起请求和处理结果这一集合的对象
    const page = await instance.createPage();
    //页面指向的是哪个一个url
    await page.on("onResourceRequested", function(requestData) {
        console.info('Requesting', requestData.url)
    });
  //得到打开该页面的状态码
    const status = await page.open('https://stackoverflow.com/');
    console.log(status);
//输出该页面的内容
    const content = await page.property('content');
    console.log(content);
    //输出内容
   //退出该phantom实例
    await instance.exit();
}());

输出结果

14995107245755.jpg

当然不能一直动用那几个情节,所以就须求通过

//这个方法,我的理解是跟你在chrome中的输出台的操作是一样的所以看看下面栗子
await page.evaluate(function() {});

const phantom = require('phantom');
let url = encodeURI(`https://www.baidu.com/s?wd="hello"`);
(async function() {
    const instance = await phantom.create();
    const page = await instance.createPage();
    const status = await page.open(url);
    if (status !== 'success') {
        console.log("访问失败");
        return;
    } else {
        let start = Date.now();
        let result = await page.evaluate(function() {
            return document.title
        });
        let data = {
            cose: 1,
            msg: "抓取成功",
            time: Date.now() - start,
            dataList: result
        }
        console.log(JSON.stringify(data));
        await instance.exit();
    }

}());

输出结果

14995115113865.jpg

  1. QUnit

前台规则录入

那是一个单身的 Web
系统,系统重点用以收集用户录入的页面音讯、页面对应的规则、浮现错误新闻。通过调用后端页面抓取服务来形成页面检测的职分,系统可以创立三种类型的检测页面:常规监控、高级督察、可用性监控

可以捕捉的web页面

模块完成

得到具有章节fetchAllChapters.js

const phantom = require('phantom');
const program = require('commander');
/*
  命令行参数帮助工具
  设置 option b 代表 book ,[book]表示该参数可以通过program访问,这个参数表示书本编号
  命令 eg:
  node fetchAllChapters.js -b 5443  
*/
program
    .version('0.1.0')
    .option('-b, --book [book]', 'book number')
    .parse(process.argv);

//缺少书本参数直接退出
if (!program.book) {
    return
}
// example "5443",获取书本编号
const bookNumber = program.book
    //访问的url
const url = encodeURI(`http://www.qu.la/book/${bookNumber}/`);
//设置用户代理头
const userAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36`
try {
    //提供async环境
    (async function() {
        //创建实例
        const instance = await phantom.create()
            //创建页面容器
        const page = await instance.createPage()
            //设置
        page.setting("userAgent", userAgent)
            //判断是否访问成功
        const status = await page.open(url),
            code = 1;
        if (status !== 'success') {
            //访问失败修改状态码
            code = -1;
        } else {
            //获取当前时间
            var start = Date.now();
            var result = await page.evaluate(function() {
                var count = 1;
                return $('#list dl dd').map(function() {
                    return ({
                        index: count++,
                        title: $(this).find('a').html(),
                        link: url + ($(this).find('a').attr('href')).substring(($(this).find('a').attr('href')).lastIndexOf("/")),
                    })
                }).toArray()
            })
            let data = {
                code: code,
                bookNumber: "5443",
                url: url,
                time: Date.now() - start,
                dataList: result
            }
            console.log(JSON.stringify(data));
        }
        //退出实例
        await instance.exit();
    })()
} catch (e) {
    console.log(e)
}

输出结果

14995146067785.jpg

在获取具有章节之后,大家须求取得具有章节的内容了

fetchChapter

const phantom = require('phantom');
const mkdirp = require('mkdirp')
const program = require('commander');
const fs = require('async-file')
const path = require('path')
    //设置用户代理
const userAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36`
    /*
    命令行参数
    p -替换原文本中的换行空格
    f -保存为文件
    t 自定义输出路径
    u 抓取单章的url
    */
program
    .version('0.1.0')
    .option('-p, --puer', 'puerMode')
    .option('-f, --file', 'save2File')
    .option('-t, --path [path]', 'outPutPath')
    .option('-u, --url [url]', 'url')
    .parse(process.argv);
if (!program.url) {
    return;

}
const URL = program.url;
const DEFAULT_PATH = '/book/default/';

/*
替换br和&nbsp标签
*/
function puer(str) {
    if (!str) {
        return
    }
    str = str.replace(/<br\s*\/?>/gi, "\r\n");
    str = str.replace(/&nbsp;/g, " ")
    return str
}
/*
test url 
node fetchChapter.js -u http://www.qu.la/book/5443/3179374.html -f -p
*/

(async function() {
    //创建实例
    const instance = await phantom.create()
        //创建页面容器
    const page = await instance.createPage()
    page.setting("userAgent", userAgent)
    const status = await page.open(URL),
        code = 1;
    if (status !== 'success') {
        code = -1;
        return;
    } else {
        // await page.includeJs("https://cdn.bootcss.com/jquery/1.12.4/jquery.js")
        // await page.render('germy.png');
        var start = Date.now();
        var result = await page.evaluate(function() {
            //移除一些无关内容(等于直接在结果网页上的dom上进行操作)
            //请注意这里如果调用console.log()是无效的!
            $("#content a:last-child").remove()
            $("#content script:last-child").remove()
            $("#content div:last-child").remove()
            $("#content script:last-child").remove()
            return ({
                title: $("h1").html(),
                content: $("#content").html()
            });
        })
        if (result.title == '' || result.content == '') {
            //内容为空捕获失败
            console.log(JSON.stringify({
                code: -1
            }))
            return
        } else {
            //判断参数进一步处理
            if (program.puer) {
                var context = puer(result.content)
            }
            //文件模式处理后进行保存到文件.返回文件路径
            if (program.file) {

                let path = ""
                if (program.path) {
                    //自定义路径
                } else {
                    path = DEFAULT_PATH;
                    //避免文件夹不存在,__dirname指向的是文件所在路径
                    mkdirp(__dirname + path, (err) => {
                        if (err) {
                            console.log(err);
                        }
                    });
                    //拼接出文件输出的路径
                    path += result.title + ".txt";
                    await fs.writeFile(__dirname + path, context)
                        // return;
                        //输出文件名
                    console.log(JSON.stringify({
                        code: 1,
                        filePath: path
                    }))
                }
            } else {
                console.log(JSON.stringify({
                    code: 1,
                    content: result
                }));
            }

        }
    }
    //exit
    await instance.exit();
})()

金沙澳门官网 5

正规监控

录入一个页面地址,和若干检测规则。注意那里的检测规则,大家把常用的一对检测点抽象成了一条看似测试用例的言辞。每条规则用来协作页面上的一个
DOM 元素,用 DOM
元素的习性来和预期做合作,如若匹配失利系统就会发出一条错误音信,后续交由报警系统处理

协作类型 一般有诸如此类三种:长度、文本、HTML、属性

处理器
类似编程语言中的操作符:大于、大于等于、小于、小于等于、等于、正则

诸如此类做的处就是,录入规则的人只要驾驭一些 DOM
接纳器的学识就足以上手操作了,在我们之中平常是交由测试工程师统一落成规则的录入

金沙澳门官网 6

3.网络监控 品质分析

拓展

 await page.includeJs("https://cdn.bootcss.com/jquery/1.12.4/jquery.js")
 //可以导入其他js lib
 await page.render('germy.png');
 //渲染当前页面为图片输出

在那边说一下怎么可以直接选取jquery,以百度为例子

14995249028718.jpg

因为脚下页面加载的时候加载了jquery 这么些lib,所以那里就可以向来利用了

QUnit 是个作用强大又不难使用的 JavaScript 单元测试框架。jQuery、jQuery
UI 和 jQuey Mobile 项目都选拔那么些框架,它能测试普通的 JavaScript 代码。

高档督察

最紧要用于提供高级页面测试的功用,一般由有经验的工程师来创作测试用例。这几个测试用例写起来会有局地读书花费,然而足以照猫画虎Web 页面操作,如:点击、鼠标移动等事件就此做到规范捕捉页面新闻

金沙澳门官网 7

监理page
loading接济出口HAR标准文件,辅助选用YSlow和Jenkins进行机动的属性分析。

组成使用

taskHandler

const exec = require('child_process').exec;
const execAsync = require('async-child-process').execAsync;
const delayAsync = require('./asyncFetch').delayAsync;
const program = require('commander');
let cmd;
/*
s 是章节开始(下标是0,所以需要手动减一,第一章就是 0)
e 是结束章节数
l 是并发数
m 模式
b 书的编号
test command:
node taskHandler.js -s 0 -e 10 -l 3 -b 5443
*/
program
    .version('0.1.0')
    .option('-s, --start [start]', 'start chapter', 0)
    .option('-e, --end [end]', 'end chapter')
    .option('-l, --limit [limit]', 'limit async', 3)
    .option('-m, --mode [mode]', 'Add bbq sauce', 2)
    .option('-b, --book [book]', 'book number')
    .parse(process.argv);
/*
 第一步获取章节连接,第二部获取章节内容并进行输出
 输出方式一 输出到数据库.(未实现)
 输出方式二 文件输出(在关注react-pdf,希望支持pdf输出)
*/
if (!program.book) {
    return
} else {
    cmd = `node fetchAllChapters.js -b ${program.book}`;
}
if (!program.start || !program.end) {
    console.log("must input with start-chapter and end-chapter ")
    return;
}

//
(async function() {

    const {
        stdout
        //调取子进程 执行cmd
    } = await execAsync(cmd, {
        //default value of maxBuffer is 200KB.
        maxBuffer: 1024 * 500
    });
    let data = JSON.parse(stdout),
        start = program.start,
        end = program.end,
        limit = program.limit,
        dataList = data['dataList'],
        fetchResult = null;
        //use to debug 
        // let dataList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        if (!dataList || data.length <= 0) {
            return
        }



        // console.log(dataList)
        //分发任务 每10s调取一次并发抓取10条记录 
        //截取需要的章节数
        /*根据章节,章节是一开始,默认无序章*/
        //dataList, start, end, limit
        //下面是把要抓取的内容放置到delayAsync中,后文讲述delayAsync
    try {
        fetchResult = await delayAsync(dataList, parseInt(start), parseInt(end), parseInt(limit));
    } catch (e) {
        console.log(e)
    }
})()

此地是将七个模块组成起来,先抓取所有章节数再展开拍卖

此地运用async-child-process调起子进度,然后直接获取输出在控制塞内加尔达喀尔的数据作为出口结果,由于async-child-process默认控制台出口的最大字节流是5kb所以要调整最大字节流的限量,不然会报错;

  1. Sinon

可用性监控

可用性监控侧重于对页面的可访问性、内容科学等相比较 严重的题材
做即时监控。寻常那类页面大家只需求在程序里面启一个 Worker
不断的去获取页面 HTML 就足以对结果举行检测匹配了,所以我们挑选了 NodeJS
来做异步的页面抓取队列,高效便捷的做到这种网络密集型职务

金沙澳门官网 8

  1. 爬虫

  2. 运行自动化测试QA 

组合async 与计时器完成延迟并发加载

那里先要说一下async.js本条库提供了重重决定并发的法门,关于async的demo可以看一下唐大大的async
demo,里面有不少async
method 的施用 

而大家在此处运用的是 async.mapLimit()

/*
mapLimit(coll, limit, iteratee, callbackopt)
params coll 是数据集合
       limit 并发数量
       iteratee 迭代器fun(fun 提供item 和callback,通过ca)
       callcackopt collection执行完毕或者是错误出现执行的回调函数

A callback which is called when all iteratee 
functions have finished, or an error occurs.
 Results is an array of the transformed items 
 from the coll. Invoked with (err, results).      
*/
//
var arr = [{name:'Jack', delay:200}, 
{name:'Mike', delay: 100},
 {name:'Freewind', delay:300}, 
 {name:'Test', delay: 50}];
async.mapLimit(arr,2, function(item, callback) {
    log('1.5 enter: ' + item.name);
    setTimeout(function() {
        log('1.5 handle: ' + item.name);
        if(item.name==='Jack') callback('myerr');
        else callback(null, item.name+'!!!');
    }, item.delay);
}, function(err, results) {
    log('1.5 err: ', err);
    log('1.5 results: ', results);
});
/*
20.675> 1.5 enter: Jack
20.682> 1.5 enter: Mike
20.786> 1.5 handle: Mike
20.787> 1.5 enter: Freewind
20.887> 1.5 handle: Jack
20.887> 1.5 err: myerr
20.887> 1.5 results: [ undefined, 'Mike!!!' ]
21.091> 1.5 handle: Freewind
*/

//在看另外一段

const async = require('async');
const moment = require('moment');
var arr = [{
    name: 'Jack',
    delay: 200
}, {
    name: 'Mike',
    delay: 100
}, {
    name: 'Freewind',
    delay: 300
}, {
    name: 'Test',
    delay: 50
}];
var log = function(msg, obj) {
    //对log进行了封装。主要是增加了秒钟的输出,通过秒数的差值方便大家对async的理解。
    process.stdout.write(moment().format('ss.SSS') + '> ');
    if (obj !== undefined) {
        process.stdout.write(msg);
        console.log(obj);
    } else {
        console.log(msg);
    }
}
async.mapLimit(arr, 2, function(item, callback) {
    log('1.5 enter: ' + item.name);
    setTimeout(function() {
        log('1.5 handle: ' + item.name);
        // if (item.name === 'Jack') callback('myerr');
        callback(null, item.name + '!!!');
    }, item.delay);
}, function(err, results) {
    log('1.5 err: ', err);
    log('1.5 results: ', results);
});

/*
18.951> 1.5 enter: Jack
18.958> 1.5 enter: Mike
19.062> 1.5 handle: Mike
19.063> 1.5 enter: Freewind
19.162> 1.5 handle: Jack
19.162> 1.5 enter: Test
19.217> 1.5 handle: Test
19.367> 1.5 handle: Freewind
19.367> 1.5 err: null
19.369> 1.5 results: [ 'Jack!!!', 'Mike!!!', 'Freewind!!!', 'Test!!!' ]
*/

更直观的观察callcackopt的调用是在error或者全部落成后调用的,result里放着的是每一回callback(null,result)调用的结果以数组的格局储存,注意假使某个函数没有选用该回调,在结果里显示就是undefined
关于截至后仍输出,就是异步机制的标题(或者说是cpu调度难题?),已经调起了控制台的输出后
callcackopt才调用

约莫明白async.mapLimit的应用后来看一下当下本人的贯彻和存在的难题

const async = require('async')
const execAsync = require('async-child-process').execAsync;
/*实现并发抓取的函数*/
var asyncFetch = function(data, number, method) {
        return new Promise(function(resolve, reject) {
            if (!data || data.length <= 0) {
                reject("data not exist")
            }
            let result = [];
            async.mapLimit(data, number, async(data, callback) => {
                //需要设置延时不然ip会被封掉
                let cmd = `node fetchChapter.js  -u ${data.link} -f -p`,
                    json,
                    //获取一个内容就输出一个
                    {
                        stdout
                    } = await execAsync(cmd, {
                        //default value of maxBuffer is 200KB.
                        maxBuffer: 1024 * 500
                    });
                /*将内容保存到json中*/
                json = JSON.parse(stdout);
                //保存index
                json.index = data.index;
                /*
                由于设置成了async,出现了多次触发err的情况,callback 不能正常工作,
                手动推入result中,但是这样顺序是不确定的,有待解决这个问题
                */
                result.push(json);
                callback(null, json) //not work 
            }, function(err) {
                //回调函数在全部都执行完以后执行
                if (err) {
                    reject(err)
                }
                resolve(result)
            })
        })
    }
    /*实现延时加载的函数*/
var delayAsync = function(dataList, start, end, limit) {
    return new Promise(function(resolve, reject) {
        var result = [],
            counter = 0,
            checkTimer,
            checkTimeOut,
            fetchTimers = [],
            count = Math.ceil((end - start) / limit),
            remain = start - end,
            i = 0;
        if (dataList.length <= 0) {
            //数据长度为空就返回
            reject("error")
            return;
        }
        //打印一下输入情况
        console.log(dataList)
        try {
            /*章数的开始和结束*/
            console.log(`从${start}到 ${end}`)
            let startIndex = start,
                endIndex;
            while (startIndex != end) {
                /*
                需要注意的是当剩余的任务不足以达到并发数的时候
                要保证任务分割不能出界
                */
                if (startIndex + limit < end) {
                    endIndex = startIndex + limit;
                } else {
                    //截取出界
                    endIndex = end;
                }
                /*分割任务*/
                chapter = dataList.slice(startIndex, endIndex);
                //通过闭包实现IIFE保存当时抓取的情况,不使用闭包绑定的数据则是运行之后的值
                (function(startIndex, endIndex, chapter) {
                    //通过tempTimer 保存下来
                    let tempTimer = setTimeout(async function() {
                        //获得此次任务开始执行的时间
                        let startTime = new Date(),
                            time, chapterResult = [];
                        //进行并发捕获执行命令
                        try {
                            chapterResult = await asyncFetch(chapter, limit);
                        } catch (e) {
                            // console.log(e)
                        }
                        result = result.concat(chapterResult)
                            //用于判断任务标记 
                        counter++;
                        time = new Date() - startTime;
                        console.log(`完成抓取 ${startIndex} 到 ${endIndex} 计数器是${counter} 时间是${time}`)
                    }, i * 1000);
                    fetchTimers.push(tempTimer);

                })(startIndex, endIndex, chapter)
                i++; //控制延时
                //推进任务进行
                startIndex = endIndex;
            }
        } catch (e) {
            reject(e)
        }
        /*定时判断任务是否完成*/
        checkTimer = setInterval(function() {
            console.log(`counter is ${counter} count is ${count}`)
            if (counter == count) {
                //清除定时器
                clearTimeout(checkTimeOut);
                //清除定时器
                clearInterval(checkTimer);
                resolve(result)
            }
        }, 1000);
        //or use promise all ?
        //30s计时器判断超时,超时时间暂做距离
        checkTimeOut = setTimeout(function() {
            //超时清除所有定时器
            for (let i = 0; i < fetchTimers.length; i++) {
                clearTimeout(fetchTimers[i]);
            }
            //清除定时判断
            clearInterval(checkTimer);
            console.log("timout")
            reject(result)
        }, 30000);
    })
}

module.exports = {
    asyncFetch: asyncFetch,
    delayAsync: delayAsync,
}

当前在async中设有难题,callback函数无法健康工作,所以每一回都是手动将结果推入结果集,导致结果集的次第不可以和原数据顺序对应,
然则async官方文档中

The callback must be called exactly once, ideally on a later tick of
the JavaScript event loop.

最少要调用几回callback? 然而

在延时出现中考虑用await Promise.all[] 取代定时器判断义务是不是得了

出口结果

14995701558979.jpg

Sinon.JS 为 JavaScript 提供了单独的 spies、stubs 和 mocks
[翻译注:Spy、Stub 和 Mock 都是测试专用名词,Stub 常被翻译为桩,spies
是 Spy
的复数方式,是一种可以监视措施、调用和参数的技术]。它不借助任何事物,可以匹配其余单元测试框架工作。

主动错误反馈

 

储存到mongodb

那边运用的数据库驱动模块是
mongolass

  1. Karma

页面脚本执行错误监控

页面引入一段监控脚本来收集页面产成 error
事件再次回到的错误音信,自动上报给后端服务,在系统内部能够集中所有报错新闻,以及对应的客户端浏览器版本、操作系统、IP
地址等

phantomjs的生态圈

首先步配置mongolass并丰盛模型

const Mongolass = require('mongolass');
const moment = require('moment');
const objectIdToTimestamp = require('objectid-to-timestamp');
const mongolass = new Mongolass();
//储存的库的url 
mongolass.connect('mongodb://localhost:27017/novel');
// 根据 id 生成创建时间 created_at
mongolass.plugin('addCreatedAt', {
  afterFind: function(results) {
    results.forEach(function(item) {
      item.created_at = moment(objectIdToTimestamp(item._id)).format('YYYY-MM-DD HH:mm');
    });
    return results;
  },
  afterFindOne: function(result) {
    if (result) {
      result.created_at = moment(objectIdToTimestamp(result._id)).format('YYYY-MM-DD HH:mm');
    }
    return result;
  }
});
/*
  下面模型的意思是
  Book表
  字段      属性
  bookNum  string
  url      stirng
  chapters 对象数组 - 对象的属性是index - number ...类推
*/
exports.Book = mongolass.model('Book', {
  bookNum: {
    type: 'string'
  },
  url: {
    type: 'string'
  },
  chapters: [{
    index: {
      type: "number"
    },
    link: {
      type: "string"
    },
    title: {
      type: "string"
    }
  }]
});
//书模型
exports.Book.index({
  bookNum: 1
}, {
  unique: true
}).exec(); // 根据书本编号找到书本的章节,书编号全局唯一

/*
  下面模型的意思是
  Chapter表
  字段      属性
  bookNum  string
  start    number
  end      number
  chapters 对象数组 - 对象的属性是code - number ...类推
*/
exports.Chapter = mongolass.model('Chapter', {
  bookNum: {
    type: 'string'
  },
  start: {
    type: 'number'
  },
  end: {
    type: 'number'
  },
  chapters: [{
    code: {
      type: 'number'
    },
    filePath: {
      type: 'string'
    },
    index: {
      type: 'number'
    }
  }]
});

//抓取一次章节的模型
exports.Chapter.index({
  bookNum: 1
}, {
  unique: true
}).exec(); // 根据书本编号找到书本的章节,用户名全局唯一

Karma
是指向连通浏览器的一个框架非亲非故测试运行器。每一个测试结果对应每个浏览器,它的测试和展现都是由此命令行暴光给开发者的,这样他们就可以看到浏览器测试的经过或破产。

页面主动报告

本条职能须要相应的前端工程师在代码中调用错误上报
API,来主动提交错误音讯。紧要利用的场景有,页面异步服务延时无响应、模块降级兜底主动打招呼等。监控脚本提供多少个简易的
API 来成功那项义务

// error 方法调用后旋即上报错误音信并爆发邮件、短信文告errorTracker.error(‘错误描述’) // info
方法调用后立即上报新闻,并在单位时间内仅暴发一条邮件、短信通知errorTracker.info(‘音讯描述’) // log
方法调用后由报错检测是还是不是达到设置阀值,最终肯定是还是不是报错
errorTracker.log(‘日志新闻’)

1
2
3
4
5
6
7
// error 方法调用后立即上报错误信息并发出邮件、短信通知
errorTracker.error(‘错误描述’)
// info 方法调用后立即上报信息,并在单位时间内仅产生一条邮件、短信通知
errorTracker.info(‘信息描述’)
// log 方法调用后由报错检测是否达到设置阀值,最终确认是否报错
errorTracker.log(‘日志信息’)
 

1. CasperJS:一个开源的导航脚本处理和高等测试工具

添加模型

Book

const Book = require('../lib/mongo').Book;

module.exports = {
  // 保存章节内容
  create: (book) => {
    return Book.create(book).exec();
  },
  //通过书编号获取记录
  getBookByBookNum: (bookNum) => {
    return Book
      .findOne({
        bookNum: bookNum
      })
    .addCreatedAt()
      .exec();
  },
  //通过编号更新书数据
  updateBookByBookNum: (bookNum, book) => {
    return Book.update({
      bookNum: bookNum,
    }, {
      $set: book
    }).exec();
  },
};

Chapter

const Chapter = require('../lib/mongo').Chapter;

module.exports = {
  // 保存章节内容
  create: (chapter) => {
    return Chapter.create(chapter).exec();
  },
  //通过书编号获取记录
  getChapterByBookNum: (bookNum) => {
    return Chapter
      .find({
        bookNum: bookNum
      })
      .addCreatedAt()
      .exec();
  },
  //通过抓取结果序号获取记录
  getChapterById: (id) => {
    return Chapter
      .findOne({
        _id: id
      })
      .addCreatedAt()
      .exec();
  },
  updateChapterByBookNum: (id, chapter) => {
    return Chapter.update({
      _id: id
    }, {
      $set: chapter
    }).exec();
  },
};

测试(暂未利用断言库进行正规的测试)

const BookModel = require('../model/Books.js');
const ChapterModel = require('../model/Chapters.js');



var testStoreBook = async() => {
    //模拟数据
    let data = {
            bookNum: "4445",
            url: "www.google123.com",
            chapters: [{
                index: 5,
                link: "333",
                title: "123132"
            }, {
                index: 6,
                link: "333",
                title: "123132"
            }, {
                index: 7,
                link: "333",
                title: "123132"
            }]
        },
        bookNum = "4445"

    try {
        var query = await BookModel.getBookByBookNum(bookNum);
        // var result = await BookModel.create(data);
    } catch (e) {
        console.log(e)
    }
    console.log(result.result.ok)
        // process.exit()
}
var testStoreChapters = async() => {
        //模拟数据
        let data = {
                bookNum: "4445",
                start: 0,
                end: 10,
                chapters: [{
                    index: 5,
                    code: 1,
                    filePath: "123132"
                }, {
                    index: 6,
                    code: 1,
                    filePath: "123132"
                }, {
                    index: 7,
                    code: 1,
                    filePath: "123132"
                }]
            },
            bookNum = "4445"

        try {
            // var result = await ChapterModel.updateChapterByBookNum(bookNum, data);
            var result = await ChapterModel.getChapterByBookNum(bookNum);
            console.log(result)
        } catch (e) {
            console.log(e)
        }
    // console.log(result.result.ok)
            // process.exit()
    }
    (async function() {
        try {
            // await testStoreChapters()
            // var query = await testStoreBook()
            var query = await testStoreChapters()
        } catch (e) {
            console.log(e.message)
        }
    })()
  1. Selenium

后端页面抓取服务

出于京东为数不少页面内容是异步加载的,像首页、单品等系统有过多第三方异步接口调用,使用后端程序抓取到的页面数据是联名的,并不可能取到动态的
JavaScript 渲染的始末,所以就务须利用像 PhantomJS 那种能模仿浏览器的工具

好端端监控我们运用 PhantomJS
模拟浏览器打开页面进行抓取,然后将监督规则解析成 JavaScript
代码片段执行并收集结果

高档督察我们选取 PhantomJS 打开页面后向页面注入像 jasmine, mocha
等相近的前端 JavaScript
测试框架,然后在页面执行相应的录入测试用例并回到结果

2. Mocha-PhantomJS:JavaScript测试框架Mocha的客户端

组合mongolass保存抓取数据

储存章节新闻

const BookModel = require('./model/Books.js');
// ...
if (!dataList || data.length <= 0) {
        return
    }
    /*储存数据*/
    let book = {
            bookNum: data.bookNumber,
            url: data.url,
            chapters: dataList,
        },
        result = await BookModel.create(book);
    console.log(result)
//...

出口结果

14996015073753.jpg

存储章节内容

const ChapterModel = require('./model/Chapters.js');

//....
    try {
        fetchResult = await delayAsync(dataList, start, end, limit);
        console.log(fetchResult)
        var chapters = await Chapter.create({
            bookNum: data.bookNumber,
            start: start,
            end: end,
            chapters: fetchResult,
        });
        console.log(chapters)
    } catch (e) {
        console.log(e)
    }

输出结果

image.png

金沙澳门官网 9

平整队列生成器

规则队列生成器会将采集的条条框框转化类成音信队列,然后交由长时频频处理器三次拍卖

为什么使用类音信队列的处理形式?

那和 PhantomJS 的属性是密不可分的,由多次推行发现,PhantomJS
并不能很好地展开并发处理,当并发过多,会造成 CPU 过载,从而致使机器宕机

在本机环境下的虚拟机中举行并发测试,数据并不佳好,极限基本在 ab -n 100
-c 50 左右。
所以为了以免万一出现导致的题材,就挑选了选用类音信队列来防止因为并发过高导致的劳务不可用

 

反思

脚下感到总体设计上并不是丰裕靠边。

图书的章节可以捕获五回保存在数据库中,输入书本后判断书本是否业已捕获过章节了

抓获过就从数据库里获取必要的章节,提供格局检验是否有新型章节,

以文件方式储存阅读并不便民,怎么着更有利于的翻阅

在大批量捕获的时候仍会被封停,缺乏应对封停的编制

添加phantom proxy
进行代理,那里引出必要写一个抓取代理并测试的劳务来提供代理池

(ps =,=寝室只好用热点上网 实在网络事与愿违)

Selenium 有一个简短的靶子:就是自动化浏览器。它根本用于自动化测试 web
应用程序,然而只是很简短地考虑到了基于互联网的管住任务。

类音信队列的贯彻

大家那边通过调用内部的分布式缓存系统生成类新闻队列,队列的生成其实可以参照数据结构–队列。最基本的模型就是在缓存中创建一个
KEY ,然后依据队列数据结构的方式开展多少的插入和读取

本来,类音信队列的中游介质可依照你实在的标准化来挑选,你也足以应用本机内存已毕。那说不定会促成应用和类音讯队列竞争内存

前端页面监控

  1. WebdriverIO

长时持续处理器

长时持续处理器是要效益就是消费规则队列生成器生成的类音信队列

前者页面监控:比如,页面第三方系统数据调用失利,模块加载非常、用户白屏、数据不科学。那时候就需求往日端DOM显示的角度来分析。并且出现难点,须求动用邮件、短信布告有关人口修复bug。并且触发报警是要有现场快照,以便复现难点。

WebdriverIO
允许用户仅增进几行代码就足以操纵浏览器或移动应用程序,使测试代码更简便、简洁、易读。集成的
TestRunner 同样允许你以一头的章程调用异步命令,那样您不必要关爱什么处理
Promise
以避免竞态条件。其余,它撤销了具备的累赘的设置工作,并且会为你管理的 Selenium 会话。

长时持续处理达成

在长时持续处理器的求实贯彻中,大家运用了 JavaScript 的 setInterval
方法来持续取得累信息队列的始末下发给规则转化器,然后转载给负载均衡调度器。之后再对回到的结果开展联合处理,比如邮件或者短信报警

UI测试包涵网页元素的不错突显,网页交互之后的要素变化等,人工测试相当头痛,并且UI层面的测试用例也不佳写。

  1. Nightwatch

API

PhantomJS 服务可以做为公共 API 提需要客户端进行测试需要的拍卖, API 通过
HTTP 格局调用。在 API 的处理上急需提供 HTTP 数据到规则和 PhantomJS
的变换。从而又衍生和变化出了 HTTP 数据到规则转换器

 

金沙澳门官网 10

PhantomJS 服务

PhantomJS 服务是指将 PhantomJS 结合 HTTP 服务和子进度展开服务化的拍卖

率先、启动 HTTP
服务,然后将长时电脑下发的条条框框进行更进一步转化,转化后启动子进程,HTTP
服务会监听子进度的处理结果,并在处理已毕之后回来

注入JS文件

Nightwatch.js 是一个简单使用的 Node.js,它是为依据浏览器的 app
和网站设计的终点到顶点(E2E)的测试方法。它使用强劲的 W3C WebDriver
API
 ,用于在 DOM
元素上执行命令和断言。

报警系统

报警系统大家脚下采用的是京东之中协调的集合监督平台
UMP,通过调用平台提供的有些 API 来贯彻报警邮件与短信文告

var webPage = require('webpage');
var page = webPage.create();
page.includeJs('http://code.jquery.com/jquery-1.10.2.min.js', function() {
// 模拟登录
var $testForm = $('form#login');
$testForm.find('input[name="username"]').value('barret');
$testForm.find('input[name="password"]').value('1234');
$testForm.submit();
});
  1. PhantomCSS

怎么样根据报警定位到现实页面?

用户通过监控管理序列录入规则后,监控系统会依照 UMP
规则针对用户录入的页面生成 UMP 使用的 key。当长时不断处理器发现
PhantomJS 服务再次回到的结果标示为丰富后,就会接纳 key 来拓展日志记录

 

PhantomCSS 获得 CasperJS 捕获的显示器截图,并采取 Resemble.js
将其与原则图举办对照,以测试 RGB 像素差距。 PhantomCSS
然后生成图像差距对比,用于救助你找到原因。

几时出发报警?

报警主要分为了短信和邮件报警。邮件报警是在每条万分之后就会发放指定系统用户。短信则是基于至极次数来进展处理的,当十分次数过大,就会发出短信文告

执行JS代码

  1. PhantomFlow

部署

对此系统布局可以分成两大块举办。因为机器资源数量少于,没有将具有片段都独立布署

平整管理体系以及规则队列生成器和缕缕处理器整合安插在一台机械上,PhantomJS
服务配置在了此外的机器上。进程管理应用了满世界有名的 NPM 模块 —— PM2

PM2 是一个包含负载均衡成效的 NodeJS 应用的进程管理器。可充足利用
CPU,并保障进度平稳存活

PM2 特性:

  • 内建负载均衡(使用 Node cluster 集群模块)
  • 无缝重启类似 nginx reload
  • 装有 Ubuntu 和 CentOS 的开机启动脚本
  • 控制台检测

只是在时下布局任务中,并不曾动用内建负载均衡的特色,没用经过集群的章程部署代理。仅使用了后台运行和无缝重启的性状

1 var webPage = require('webpage');
2 var page = webPage.create();
3 page.open('http://www.taobao.com', function(status) {
4 var title = page.evaluate(function() {
5 return document.title;
6 });
7 console.log(title);
8 phantom.exit();
9 });

金沙澳门官网,PhantomFlow 使用决策树提供 UI
测试方案。针对 PhantomJS, CasperJS 和 PhantomCSS 的
NodeJS 包装器—— PhantomFlow
可以流畅地在代码中讲述用户流程,同时生成用于可视化的构造化树数据。

小结与展望

实则大家明天支出的那套监督系统并不复杂,只是合理的接纳了一部分存世的技术框架。抽象出来大家自己索要的一部分效果。但却使得的已毕了大家的料想效应,并且节省了广大事先须求人肉测试的岁月资产。系统自身还有许多标题在待化解情形,比如报警系统的条条框框处理与阀值设定,JavaScript
报错的纯粹过滤机制等,这几个题材大家都会逐个缓解,并且以后的前端监控连串会化为一个平台,主题服务在后端爬取页面服务,应用端可以有三种方式,比如监控、测试工具等

局地方可持续优化点:

  • 监督系统就算在接纳规模开展了僵直细分,但是由于机械资源等范围,并从未进展独立功效的计划。那一点可能会在晚期的采纳中开展优化
  • PhantomJS
    服务还亟需越来越优化,以承载大产出,大处理量。提供稳定的劳动
  • 报警是因为看重于集团内部的 UMP
    系统,所以并不是特地灵巧,前期可以考虑自己完结一套报警机制

博客原文同步:https://keelii.github.io

1 赞 1 收藏
评论

 

  1. Percy.io

有关小编:keelii

金沙澳门官网 11

It’s not who you are underneath,
it’s what you do that defines you
个人主页 ·
我的篇章 ·
5 ·
     

金沙澳门官网 12

测试页面加载速度

 Percy
提供有关视觉变化的迭代及疾速反馈,带来了所谓的连接视觉集成。它是因此下边格局落成的:运行测试套件,获取
DOM 快照并上传到 Percy 服务,最终在浏览器中渲染之。

var page = require('webpage').create(),
  system = require('system'),
  t, address;

if (system.args.length === 1) {
  console.log('Usage: loadspeed.js <some URL>');
  phantom.exit();
}

t = Date.now();
address = system.args[1];
page.open(address, function(status) {
  if (status !== 'success') {
    console.log('FAIL to load the address');
  } else {
    t = Date.now() - t;
    console.log('Loading ' + system.args[1]);
    console.log('Loading time ' + t + ' msec');
  }
  phantom.exit();
});

phantomjs loadspeed.js http://www.baidu.com 


输出 Loading http://www.baidu.com Loading time 3802msc

屏幕截图

var page = require('webpage').create();
page.open('http://github.com/', function() {
  page.render('github.png');
  phantom.exit();
});

英文原稿:12 must-have code testing
tools 译文:

 

设置页面背景颜色

page.evaluate(function() {
  document.body.bgColor = 'white';
});

 确保在render之前

无界面的测试

PhantomJS itself is not a test framework, it is only used to launch the
tests via a suitable test runner.

例如Mocha Jasmine WebDriver 

和CI系统持续集成
例如Jenkins或者TeamCity,Travis CI已经内置了对PhantomJS的支持。

最好的实践
和测试框架CasperJS集成。

 

 相关连接

官网: www.phantomjs.org

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图