Nightmare is a high-level browser automation library. 和之前的 PhantomJS 很像,但是 Nightmare 是基于 Electron 的,也就是还是基于 Chromium 和 Node.js。但是感觉其写法比 PhantomJS 简单一些:
安装
使用
创建实例
1 2 3
| var Nightmare = require('nightmare');
var nightmare = Nightmare({ show: true });
|
这里的 options.show = true
可以在运行时打开 Electron 窗口,方便 debug,更多的 options 请看 new BrowserWindow(options)。
操作页面
因为需要使用了 ES6 的 generator,所以这里还要加入 co,官网的 Examples 使用的是 vo。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| var co = require('co'); var RES_CLS = 'r';
var googleUrl = 'https://www.google.com/'; co(function* () { var url = yield nightmare .goto(googleUrl) .type('input[name="q"]', 'csbun npm') .click('input[type="submit"]') .wait('.' + RES_CLS) .evaluate(function (RES_CLS) { var resHref = ''; var resH3 = document.getElementsByClassName(RES_CLS)[0]; if (resH3) { console.log(resH3); var resA = resH3.getElementsByTagName('A')[0]; if (resA) { resHref = resA.href; } } return resHref; }, RES_CLS); yield nightmare.end(); return url; }).then(function (res) { console.log('res: ' + res); });
|
运行上述代码,将获得 Google 搜索 csbun npm
的第一个链接: https://www.npmjs.com/~csbun。
其他
evaluate
在上面的例子里面,我们可以看到,.evaluate(fn, arg1)
中的 fn
是浏览器的运行环境,是不能直接使用闭包中的其他变量的,必须通过 evaluate
的参数 arg1
传入。详情请看 官方解释。
wait
wait(ms)
,.wait(selector)
,.wait(fn)
这三个方法在页面进行异步操作的时候比较好用。接口文档。
例如发送请求,等待图片下载等,都可以通过上面的方式实现。
测试
有了 Nightmare,我们就可以结合 Mocha,给我们的项目编写测试例,下面将以我的一个小项目 resize-image 为例:
同样的,因为使用了 generator,我们加入 mocha-generators:
1 2 3 4 5 6
|
require('mocha-generators').install();
var Nightmare = require('nightmare'); var assert = require('assert');
|
本地服务器
然后我们需要一个本地服务器,基于 Node,我们可以很简单写就写一个出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| var fs = require('fs'); var path = require('path'); var http = require('http');
var html = fs.readFileSync(path.join(__dirname, 'test.html'), 'utf8');
var pngFile = path.join(__dirname, '../example/google.png'); var pngStat = fs.statSync(pngFile);
var jsFile = path.join(__dirname, '../index.js'); var jsStat = fs.statSync(jsFile);
module.exports = http.createServer(function (req, res) { if (req.url === '/index.js') { res.writeHead(200, { 'Content-Type': 'text/javascript', 'Content-Length': jsStat.size }); fs.createReadStream(jsFile).pipe(res); } else if (req.url === '/google.png') { res.writeHead(200, { 'Content-Type': 'image/png', 'Content-Length': pngStat.size }); var readStream = fs.createReadStream(pngFile); readStream.pipe(res); } else { res.writeHead(200, {'Content-Type': 'text/html'}); res.end(html); } });
|
其中,google.png
只是一张普通的图片,index.js
则为 resize-image 的源码,test.html
则为一个简单的测试页面:
1 2 3 4 5 6 7 8 9 10 11
| <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Resize Image</title> <script type="text/javascript" src="index.js"></script> </head> <body> <img id="img" src="./google.png"> </body> </html>
|
测试代码
下面我们就能开始测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| var PORT = 7500; var ASSERT_SIZE = 200;
describe('resize-image', function () { before(function (done) { require('./server').listen(PORT, done); });
it('.resize: Resize any image to ' + ASSERT_SIZE, function * () { var nightmare = Nightmare();
var min = yield nightmare .goto('http://0.0.0.0:' + PORT + '/') .evaluate(function (ASSERT_SIZE) { var img = document.getElementById('img'); var base64 = window.ResizeImage.resize(img, ASSERT_SIZE, ASSERT_SIZE, ResizeImage.PNG); var resizedImg = new Image(); resizedImg.src = base64; return Math.min(resizedImg.width, resizedImg.height); }, ASSERT_SIZE)
yield nightmare.end(); assert.equal(min, ASSERT_SIZE); }); });
|
运行 mocha
,执行结果如下:
1 2 3 4 5
| resize-image ✓ .resize: Resize any image to 200 (582ms)
1 passing (595ms)
|
完成!没问题!完整的例子请看 这里。