# 6.测试环境&帮手Karma
Karma 是一个基于 Node.js 的 JavaScript 测试执行过程管理工具(Test Runner)。该工具可用于测试所有主流 Web 浏览器,也可以集成到 CI(Continuous integration)工具,还可以和其他代码编辑器一起使用。
Karma 会监控配置文件中所指定的每一个文件,每当文件发生改变,它都会向测试服务器发送信号,来通知所有的浏览器再次运行测试代码。此时,浏览器会重新加载源文件,并执行测试代码。其结果会传递回服务器,并以某种形式显示给开发者。
访问浏览器执行结果,可通过以下的方式
- 手工方式 - 通过浏览器
- 自动方式 - 让 karma 来启动对应的浏览器
# 工作原理简介
karma
是一个典型的 C/S
程序,包含 client 和 server ,通讯方式基于 Http
,通常情况下,客户端和服务端基本都运行在开发者本地机器上。
一个服务端实例对应一个项目,假如想同时运行多个项目,得同时开启多个服务端实例。
Server
Server
是框架的主要组成部分之一,它内部保存了所有的程序运行状态,比如 client 连接,当前运行的单测文件,根据这些数据状态,它提供了下面几个功能, 下图是 server 的结构

- 监听文件
- 与 client 进行通讯
- 向开发者输出测试结果
- 提供 client 端所需的资源文件
Client
client 是单测最终运行的地方,类似一个 web app , 跟 server 端通讯利用 socket.io
, 执行单测在一个独立的 iframe
中。下面是它的结构图

client 和 server 端通讯采用 socket.io
- client 端会发送这些消息

- server 端会发送这些消息

# 安装Karma
对于Nodejs版本的要求:
Karma currently works on Node.js 6.x, 8.x, and 10.x. See FAQ (opens new window) for more info.
全局安装
$npm install -g karma
1安装 Karma 命令会到全局的
node_modules
目录下,我们可以在任何位置直接运行 karma 命令。npm install -g karma-cli
1此命令用来安装
karma-cli
,它会在当前目录下寻找 karma 的可执行文件。这样我们就可以在一个系统内运行多个版本的 Karma。本地安装
$ npm install karma --save-dev
1安装 Karma 命令到当前
node_modules
目录下,此时,如果需要执行 karma 命令,就需要这样./node_modules/.bin/karma npx karma --version
1
2
3
# 配置Karma
karma配置文件可以用JavaScript,CoffeeScript或TypeScript编写,并作为常规Node.js模块加载。
除非作为参数提供,否则Karma CLI将在以下位置以该顺序(从上至下)查找配置文件
./karma.conf.js
./karma.conf.coffee
./karma.conf.ts
./.config/karma.conf.js
./.config/karma.conf.coffee
./.config/karma.conf.ts
在配置文件中,配置代码通过设置module.exports
指向一个接受一个参数的函数:配置对象。
// karma.conf.js
module.exports = function(config) {
config.set({
basePath: '../..',
frameworks: ['jasmine'],
//...
});
};
# karma.conf.coffee
module.exports = (config) ->
config.set
basePath: '../..'
frameworks: ['jasmine']
# ...
// karma.conf.ts
module.exports = (config) => {
config.set({
basePath: '../..',
frameworks: ['jasmine'],
//...
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
关于typescript的支持,需要使用到
ts-node
,配置ts-node以使用commonjs
模块格
配置文件中的基本的属性介绍:Overview (opens new window)
使用CLI工具,快速创建配置
开始配置
~/Downloads/Demo is 📦 v1.0.0 via ⬢ v10.16.0
➜ npx karma init
# 如果在应用中用到了其它的测试框架,那就需要我们安装它们所对应的插件,并在配置文件中标注它们(详见 karma.conf.js 中的 plugins 项)
Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> jasmine
# mocha
# qunit
# nodeunit
# nunit
# Require.js 是异步加载规范(AMD)的实现。常被作为基础代码库,应用在了很多的项目与框架之中,例如 Dojo, AngularJs 等
Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no
# yes
# 选择需要运行测试用例的浏览器。需要注意的就是,必须保证所对应的浏览器插件已经安装成功。
Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> Chrome
# ChromeHeadless
# ChromeCanary
# Firefox
# Safari
# PhantomJS
# Opera
# IE
# 选择测试用例所在的目录位置。Karma 支持通配符的方式配置文件或目录,例如 *.js, test/**/*.js 等。如果目录或文件使用相对位置,要清楚地是,此时的路径是相对于当前运行 karma 命令时所在的目录。
What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
> src/*js
# 目录中不包括的那些文件。
Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
# 是否需要 Karma 自动监听文件?并且文件一旦被修改,就重新运行测试用例?
Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes
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
38
39
40
41
42
43
44
45
46
生成了一个karma.conf.js
文件:
// Karma configuration
// Generated on Wed Jul 10 2019 22:46:32 GMT+0800 (GMT+08:00)
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha'],
// list of files / patterns to load in the browser
files: [
'src/*js'
],
// list of files / patterns to exclude
exclude: [
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: false,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity
})
}
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# karma示例
目标 :
- babel支持,ES6语法支持
- mocha与chai支持
- karma与chrome、webpack对接
说明:
Karma对babel支持的,一个可选项:karma-babel-preprocessor (opens new window),但是:
babel and karma-babel-preprocessor only convert ES6 modules to CommonJS/AMD/SystemJS/UMD**. If you choose CommonJS, you still need to resolve and concatenate CommonJS modules on your own**. We recommend karma-browserify + babelify or webpack + babel-loader in such cases.
所以,我们选择了webpack
安装依赖
npm install @babel/core @babel/preset-env chai mocha webpack webpack-cli babel-loader -D
1安装karma的适配器
npm install karma-webpack karma-chrome-launcher karma-mocha karma-chai -D
1配置
karma.config.js
// Karma configuration // Generated on Thu Jul 11 2019 23:23:44 GMT+0800 (GMT+08:00) module.exports = function (config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '', // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['mocha'], // list of files / patterns to load in the browser files: [ 'src/**/*.js', 'test/**/*.js' ], // .... // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { 'src/**/*.js': ['webpack'], 'test/**/*.js': ['webpack'] }, webpack: { mode: "none", node: { fs: 'empty' }, module: { rules: [ { test: /\.js?$/, loader: "babel-loader", options: { presets: ["@babel/env"] }, } ] } }, // .... plugins: [ 'karma-mocha', 'karma-chai', 'karma-chrome-launcher', 'karma-webpack' ] }) }
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54书写测试用例
test.js
:import { describe } from "mocha"; import { expect } from 'chai' describe('first test', () => { it('hello mocha and karma', () => { console.log('hello mocha') expect(true).to.be.equal(true) }) })
1
2
3
4
5
6
7
8
9开始测试:
npx karma start
1添加到
package.json
"scripts": { "karma": "karma start" },
1
2
3然后使用,
npm run karma
➜ npx karma start ℹ 「wdm」: Hash: 9d2c943b68425fd58dd0 Version: webpack 4.35.3 Time: 53ms Built at: 2019-07-12 5:07:49 PM ℹ 「wdm」: Compiled successfully. ℹ 「wdm」: Compiling... ⚠ 「wdm」: Hash: cca8316f4bd5855fc4de Version: webpack 4.35.3 Time: 2414ms Built at: 2019-07-12 5:07:52 PM Asset Size Chunks Chunk Names src/index.js 3.62 KiB 0 [emitted] src/index test/test.js 1010 KiB 1 [emitted] test/test Entrypoint src/index = src/index.js Entrypoint test/test = test/test.js [0] ./src/index.js 27 bytes {0} [built] [1] ./test/test.js 223 bytes {1} [built] [2] ./node_modules/mocha/browser-entry.js 4.19 KiB {1} [built] [3] ./node_modules/process/browser.js 4.96 KiB {1} [built] [4] (webpack)/buildin/global.js 878 bytes {1} [built] [5] ./node_modules/browser-stdout/index.js 662 bytes {1} [built] [6] ./node_modules/stream-browserify/index.js 3.53 KiB {1} [built] [36] ./node_modules/util/util.js 19 KiB {1} [built] [38] ./node_modules/mocha/lib/mocha.js 21.8 KiB {1} [built] [39] (webpack)/buildin/module.js 552 bytes {1} [built] [40] ./node_modules/escape-string-regexp/index.js 230 bytes {1} [built] [41] path (ignored) 15 bytes {1} [built] [42] ./node_modules/mocha/lib/reporters/index.js 945 bytes {1} [built] [105] ./node_modules/chai/index.js 39 bytes {1} [built] [106] ./node_modules/chai/lib/chai.js 1.22 KiB {1} [built] + 128 hidden modules WARNING in ./node_modules/mocha/lib/mocha.js 217:20-37 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js WARNING in ./node_modules/mocha/lib/mocha.js 222:24-70 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js WARNING in ./node_modules/mocha/lib/mocha.js 266:24-35 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js WARNING in ./node_modules/mocha/lib/mocha.js 313:35-48 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js WARNING in ./node_modules/mocha/lib/mocha.js 329:23-44 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js ℹ 「wdm」: Compiled with warnings. 12 07 2019 17:07:52.761:WARN [karma]: No captured browser, open http://localhost:9876/ 12 07 2019 17:07:52.770:INFO [karma-server]: Karma v4.1.0 server started at http://0.0.0.0:9876/ 12 07 2019 17:07:52.770:INFO [launcher]: Launching browsers Chrome with concurrency unlimited 12 07 2019 17:07:52.773:INFO [launcher]: Starting browser Chrome 12 07 2019 17:07:54.135:INFO [Chrome 75.0.3770 (Mac OS X 10.14.5)]: Connected on socket R9R31QB1GtR_kayNAAAA with id 55412577 Chrome 75.0.3770 (Mac OS X 10.14.5) LOG: 'hello karma' LOG: 'hello mocha' Chrome 75.0.3770 (Mac OS X 10.14.5): Executed 1 of 1 SUCCESS (0 secs / 0.001 secs ~/Downloads/karma-demo is 📦 v1.0.0 via ⬢ v10.16.0 ➜ npm run karma > karma-demo@1.0.0 karma /Users/itheima/Downloads/karma-demo > karma start ℹ 「wdm」: Hash: 9d2c943b68425fd58dd0 Version: webpack 4.35.3 Time: 48ms Built at: 2019-07-12 5:24:02 PM ℹ 「wdm」: Compiled successfully. ℹ 「wdm」: Compiling... ⚠ 「wdm」: Hash: cca8316f4bd5855fc4de Version: webpack 4.35.3 Time: 2307ms Built at: 2019-07-12 5:24:04 PM Asset Size Chunks Chunk Names src/index.js 3.62 KiB 0 [emitted] src/index test/test.js 1010 KiB 1 [emitted] test/test Entrypoint src/index = src/index.js Entrypoint test/test = test/test.js [0] ./src/index.js 27 bytes {0} [built] [1] ./test/test.js 223 bytes {1} [built] [2] ./node_modules/mocha/browser-entry.js 4.19 KiB {1} [built] [3] ./node_modules/process/browser.js 4.96 KiB {1} [built] [4] (webpack)/buildin/global.js 878 bytes {1} [built] [5] ./node_modules/browser-stdout/index.js 662 bytes {1} [built] [6] ./node_modules/stream-browserify/index.js 3.53 KiB {1} [built] [36] ./node_modules/util/util.js 19 KiB {1} [built] [38] ./node_modules/mocha/lib/mocha.js 21.8 KiB {1} [built] [39] (webpack)/buildin/module.js 552 bytes {1} [built] [40] ./node_modules/escape-string-regexp/index.js 230 bytes {1} [built] [41] path (ignored) 15 bytes {1} [built] [42] ./node_modules/mocha/lib/reporters/index.js 945 bytes {1} [built] [105] ./node_modules/chai/index.js 39 bytes {1} [built] [106] ./node_modules/chai/lib/chai.js 1.22 KiB {1} [built] + 128 hidden modules WARNING in ./node_modules/mocha/lib/mocha.js 217:20-37 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js WARNING in ./node_modules/mocha/lib/mocha.js 222:24-70 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js WARNING in ./node_modules/mocha/lib/mocha.js 266:24-35 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js WARNING in ./node_modules/mocha/lib/mocha.js 313:35-48 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js WARNING in ./node_modules/mocha/lib/mocha.js 329:23-44 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js ℹ 「wdm」: Compiled with warnings. 12 07 2019 17:24:04.905:WARN [karma]: No captured browser, open http://localhost:9876/ 12 07 2019 17:24:04.914:INFO [karma-server]: Karma v4.1.0 server started at http://0.0.0.0:9876/ 12 07 2019 17:24:04.914:INFO [launcher]: Launching browsers Chrome with concurrency unlimited 12 07 2019 17:24:04.922:INFO [launcher]: Starting browser Chrome 12 07 2019 17:24:06.289:INFO [Chrome 75.0.3770 (Mac OS X 10.14.5)]: Connected on socket EbCCSbQRPbxHbq3OAAAA with id 92342032 Chrome 75.0.3770 (Mac OS X 10.14.5) LOG: 'hello karma' LOG: 'hello mocha' Chrome 75.0.3770 (Mac OS X 10.14.5): Executed 1 of 1 SUCCESS (0 secs / 0.001 secs Chrome 75.0.3770 (Mac OS X 10.14.5): Executed 1 of 1 SUCCESS (0.006 secs / 0.001 secs) TOTAL: 1 SUCCESS
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# UI测试利器Nightmare
Nightmare是Segment (opens new window)的高级浏览器自动化库。
目标是公开一些模仿用户操作(例如goto
,type
和click
)的简单方法,使用对每个脚本块感觉同步的API,而不是深层嵌套的回调。它最初设计用于在没有API的站点之间自动执行任务,但最常用于UI测试和爬网。
它使用Electron (opens new window),它与PhantomJS (opens new window)类似,但大约快两倍 (opens new window),更现代。
# 安装与起步
安装
nightmare
// 初始化项目 npm init -y npm install --save-dev nightmare
1
2
3
4
5
npm install --save-dev mocha
2. 淘宝源加速
2
3
// 使用淘宝源加速electron的安装 export ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/"
3. 起步测试:
```js
const Nightmare = require('nightmare')
const assert = require('assert')
describe('Load a Page', function () {
// Recommended: 5s locally, 10s to remote server, 30s from airplane ¯\_(ツ)_/¯
this.timeout('30s')
let nightmare = null
beforeEach(() => {
nightmare = new Nightmare()
})
describe('/ (Home Page)', () => {
it('should load without error', done => {
// your actual testing urls will likely be `http://localhost:port/path`
nightmare.goto('https://www.baidu.com')
.end()
.then(function (result) {
done()
})
.catch(done)
})
})
})
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
# nightmare配合mocha测试
nightmare可以进行网页的抓取,配合mocha进行页面的测试:
安装mocha
npm install --save-dev mocha
还可以安装一些断言库,如:
chai
新建测试:
const Nightmare = require('nightmare')
const assert = require('assert')
describe('Search nightmare', () => {
this.timeout('30s')
let nightmare = null
beforeEach(() => {
nightmare = new Nightmare()
})
it('should load with result nightmare', done => {
const selector = 'em'
nightmare.goto('https://www.baidu.com')
.type('#kw', 'nightmare')
.click('#su')
.wait('em')
.evaluate(selector => {
// now we're executing inside the browser scope.
return document.querySelector(selector).innerText
}, selector) // <-- that's how you pass parameters from Node scope to browser scope
.end()
.then(function (result) {
console.log(result)
assert.equal(result, 'nightmare')
done()
})
.catch(done)
})
})
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
将mocha作为测试脚本添加到您的 package.json
:
"scripts": {
"test": "mocha"
}
2
3
# API介绍
# nightmare的配置项
waitTimeout (default: 30s)
gotoTimeout (default: 30s)
loadTimeout (default: infinite)
executionTimeout (default: 30s)
paths
switches
electronPath
dock
openDevTools
typeInterval (default: 100ms)
pollInterval (default: 250ms)
maxAuthRetries (default: 3)
certificateSubjectName
.engineVersions()
.useragent(useragent)
.authentication(user, password)
.authentication(user, password)
.halt(error, done)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
配置链接:https://github.com/segmentio/nightmare#nightmareoptions
# 页面交互相关
.back()
.forward()
.refresh()
.click(selector)
.mousedown(selector)
.mouseup(selector)
.mouseover(selector)
.mouseout(selector)
.type(selector[, text])
.insert(selector[, text])
.check(selector)
.uncheck(selector)
.select(selector, option)
.scrollTo(top, left)
.viewport(width, height)
.inject(type, file)
.evaluate(fn[, arg1, arg2,...])
.wait(ms)
.wait(selector)
.wait(fn[, arg1, arg2,...])
.header(header, value)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 页面提取
.exists(selector)
.visible(selector)
.on(event, callback)
.once(event, callback)
.removeListener(event, callback)
.screenshot([path][, clip])
.html(path, saveType)
.pdf(path, options)
.title()
.url()
.path()
2
3
4
5
6
7
8
9
10
11