Skip to content
This repository was archived by the owner on Sep 21, 2022. It is now read-only.

Commit e8a427e

Browse files
committed
fix: emit browser start/stop events in correct places
1 parent 88a0fc0 commit e8a427e

File tree

9 files changed

+188
-92
lines changed

9 files changed

+188
-92
lines changed

doc/events.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@
1212

1313
* `INTERRUPT` - emitted on signal events `SIGHUP`, `SIGINT` or `SIGTERM`. The event is emitted with 1 argument `data`:
1414
* `data.exitCode` - exit code with which gemini will be interrupted
15+
16+
* `START_BROWSER` - emitted on browser session start. Emitted with [browser instance](../lib/browser/new-browser.js). If handler returns a promise tests will be executed in this session only after the promise is resolved.
17+
18+
* `STOP_BROWSER` - emitted right before browser session end. Emitted with [browser instance](../lib/browser/new-browser.js). If handler returns a promise quit will be performed only after the promise is resolved.

lib/browser-pool/basic-pool.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,27 @@ const Promise = require('bluebird');
44
const _ = require('lodash');
55
const Browser = require('../browser');
66
const CancelledError = require('../errors/cancelled-error');
7+
const Events = require('../constants/events');
78
const Pool = require('./pool');
9+
const log = require('debug')('gemini:pool:basic');
810

911
module.exports = class BasicPool extends Pool {
12+
static create(config, calibrator, emitter) {
13+
return new BasicPool(config, calibrator, emitter);
14+
}
15+
1016
/**
1117
* @constructor
1218
* @extends Pool
1319
* @param {Config} config
1420
* @param {Calibrator} calibrator
1521
*/
16-
constructor(config, calibrator) {
22+
constructor(config, calibrator, emitter) {
1723
super();
1824

1925
this._config = config;
2026
this._calibrator = calibrator;
27+
this._emitter = emitter;
2128

2229
this._activeSessions = {};
2330
}
@@ -26,6 +33,10 @@ module.exports = class BasicPool extends Pool {
2633
const browser = Browser.create(this._config.forBrowser(id));
2734

2835
return browser.launch(this._calibrator)
36+
.then(() => {
37+
log(`browser ${id} started`);
38+
return this._emitter.emitAndWait(Events.START_BROWSER, browser);
39+
})
2940
.then(() => {
3041
if (this._cancelled) {
3142
return Promise.reject(new CancelledError());
@@ -40,7 +51,11 @@ module.exports = class BasicPool extends Pool {
4051

4152
freeBrowser(browser) {
4253
delete this._activeSessions[browser.sessionId];
43-
return browser.quit();
54+
log(`stop browser ${browser.id}`);
55+
56+
return this._emitter.emitAndWait(Events.STOP_BROWSER, browser)
57+
.catch((err) => console.warn(err && err.stack || err))
58+
.then(() => browser.quit());
4459
}
4560

4661
cancel() {

lib/browser-pool/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ var _ = require('lodash'),
1010
* @param {Config} config
1111
* @returns {BasicPool}
1212
*/
13-
exports.create = function(config) {
13+
exports.create = function(config, emitter) {
1414
var calibrator = new Calibrator(),
15-
pool = new BasicPool(config, calibrator);
15+
pool = BasicPool.create(config, calibrator, emitter);
1616

1717
pool = new CachingPool(config, pool);
1818
pool = new PerBrowserLimitedPool(config, pool);

lib/runner/browser-runner/index.js

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ const Runner = require('../runner');
77
const SuiteRunner = require('../suite-runner');
88
const Events = require('../../constants/events');
99

10-
const log = require('debug')('gemini:runner');
11-
1210
module.exports = class BrowserRunner extends Runner {
1311
constructor(browserId, config, browserPool) {
1412
super();
@@ -24,20 +22,6 @@ module.exports = class BrowserRunner extends Runner {
2422
}
2523

2624
run(suiteCollection, stateProcessor) {
27-
log('start browser %s', this._browserId);
28-
this.emit(Events.START_BROWSER, {browserId: this._browserId});
29-
return this._runSuites(suiteCollection, stateProcessor)
30-
.finally(() => {
31-
log('stop browser %s', this._browserId);
32-
this.emit(Events.STOP_BROWSER, {browserId: this._browserId});
33-
});
34-
}
35-
36-
cancel() {
37-
this._suiteRunners.forEach((runner) => runner.cancel());
38-
}
39-
40-
_runSuites(suiteCollection, stateProcessor) {
4125
const suites = suiteCollection.clone().allSuites();
4226

4327
return _(suites)
@@ -47,6 +31,10 @@ module.exports = class BrowserRunner extends Runner {
4731
.value();
4832
}
4933

34+
cancel() {
35+
this._suiteRunners.forEach((runner) => runner.cancel());
36+
}
37+
5038
_runSuite(suite, stateProcessor) {
5139
const browserAgent = BrowserAgent.create(this._browserId, this._browserPool);
5240
const runner = SuiteRunner.create(suite, browserAgent, this._config);

lib/runner/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ module.exports = class TestsRunner extends Runner {
2424

2525
this._stateProcessor = stateProcessor;
2626

27-
this._browserPool = pool.create(this.config);
27+
this._browserPool = pool.create(this.config, this);
2828

2929
this._suiteMonitor = SuiteMonitor.create(this);
3030
this.passthroughEvent(this._suiteMonitor, Events.END_SUITE);

test/unit/browser-pool/basic-pool.js

Lines changed: 141 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
'use strict';
22

33
const Promise = require('bluebird');
4+
const QEmitter = require('qemitter');
5+
const bluebirdQ = require('bluebird-q');
46
const Browser = require('lib/browser');
57
const BasicPool = require('lib/browser-pool/basic-pool');
68
const CancelledError = require('lib/errors/cancelled-error');
79
const browserWithId = require('test/util').browserWithId;
10+
const Events = require('lib/constants/events');
11+
const _ = require('lodash');
812

913
describe('BasicPool', () => {
1014
const sandbox = sinon.sandbox.create();
1115

12-
const stubBrowser = (id) => {
16+
const stubBrowser_ = (id) => {
1317
id = id || 'id';
1418
const browser = browserWithId(id);
1519
sandbox.stub(browser, 'launch', () => {
@@ -26,66 +30,164 @@ describe('BasicPool', () => {
2630

2731
const stubConfig = () => ({forBrowser: (id) => ({id})});
2832

29-
let pool;
30-
31-
beforeEach(() => {
32-
pool = new BasicPool(stubConfig());
33+
const mkPool_ = (opts) => {
34+
opts = _.defaults(opts || {}, {
35+
config: stubConfig(),
36+
calibrator: 'default-calibrator',
37+
emitter: new QEmitter()
38+
});
3339

34-
sandbox.stub(Browser, 'create');
35-
});
36-
afterEach(() => sandbox.restore());
40+
return new BasicPool(opts.config, opts.calibrator, opts.emitter);
41+
};
3742

38-
const requestBrowser = (browser) => {
39-
browser = browser || stubBrowser();
43+
const requestBrowser_ = (browser, pool) => {
44+
browser = browser || stubBrowser_();
45+
pool = pool || mkPool_();
4046

4147
return pool.getBrowser(browser.id);
4248
};
4349

50+
beforeEach(() => {
51+
sandbox.stub(Browser, 'create');
52+
});
53+
54+
afterEach(() => sandbox.restore());
55+
4456
it('should create new browser when requested', () => {
45-
const browser = stubBrowser('foo');
57+
const browser = stubBrowser_('foo');
4658

47-
return requestBrowser(browser)
59+
return requestBrowser_(browser)
4860
.then(() => assert.calledWith(Browser.create, {id: 'foo'}));
4961
});
5062

5163
it('should launch a browser', () => {
52-
const browser = stubBrowser();
64+
const browser = stubBrowser_();
5365

54-
return requestBrowser(browser)
66+
return requestBrowser_(browser)
5567
.then(() => assert.calledOnce(browser.launch));
5668
});
5769

5870
it('should launch a browser with calibrator', () => {
59-
pool = new BasicPool(stubConfig(), 'calibrator');
71+
const pool = mkPool_({calibrator: 'calibrator'});
6072

61-
const browser = stubBrowser();
73+
const browser = stubBrowser_();
6274

63-
return requestBrowser(browser)
75+
return requestBrowser_(browser, pool)
6476
.then(() => assert.calledWith(browser.launch, 'calibrator'));
6577
});
6678

6779
it('should finalize browser if failed to create it', () => {
68-
const browser = stubBrowser();
80+
const browser = stubBrowser_();
81+
const pool = mkPool_();
6982
const freeBrowser = sinon.spy(pool, 'freeBrowser');
7083
const assertCalled = () => assert.called(freeBrowser);
7184

72-
browser.reset.returns(Promise.reject());
85+
browser.reset.returns(bluebirdQ.reject());
7386

74-
return requestBrowser(browser)
87+
return requestBrowser_(browser, pool)
7588
.then(assertCalled, assertCalled);
7689
});
7790

91+
describe('START_BROWSER event', () => {
92+
it('should be emitted on browser start', () => {
93+
const emitter = new QEmitter();
94+
const onSessionStart = sinon.spy().named('onSessionStart');
95+
emitter.on(Events.START_BROWSER, onSessionStart);
96+
97+
const pool = mkPool_({emitter});
98+
const browser = stubBrowser_();
99+
100+
return requestBrowser_(browser, pool)
101+
.then(() => {
102+
assert.calledOnce(onSessionStart);
103+
assert.calledWith(onSessionStart, browser);
104+
});
105+
});
106+
107+
it('handler should be waited by pool', () => {
108+
const emitter = new QEmitter();
109+
const afterSessionStart = sinon.spy().named('afterSessionStart');
110+
emitter.on(Events.START_BROWSER, () => bluebirdQ.delay(100).then(afterSessionStart));
111+
112+
const pool = mkPool_({emitter});
113+
const browser = stubBrowser_();
114+
115+
return requestBrowser_(browser, pool)
116+
.then(() => assert.callOrder(afterSessionStart, browser.reset));
117+
});
118+
119+
it('handler fail should fail browser request', () => {
120+
const emitter = new QEmitter();
121+
emitter.on(Events.START_BROWSER, () => bluebirdQ.reject('some-error'));
122+
123+
const pool = mkPool_({emitter});
124+
const browser = stubBrowser_();
125+
126+
return assert.isRejected(requestBrowser_(browser, pool), 'some-error');
127+
});
128+
});
129+
78130
it('should quit a browser when freed', () => {
79-
const browser = stubBrowser();
131+
const pool = mkPool_();
132+
const browser = stubBrowser_();
80133

81-
return requestBrowser(browser)
134+
return requestBrowser_(browser, pool)
82135
.then((browser) => pool.freeBrowser(browser))
83136
.then(() => assert.calledOnce(browser.quit));
84137
});
85138

139+
describe('STOP_BROWSER event', () => {
140+
it('should be emitted on browser quit', () => {
141+
const emitter = new QEmitter();
142+
const onSessionEnd = sinon.spy().named('onSessionEnd');
143+
emitter.on(Events.STOP_BROWSER, onSessionEnd);
144+
145+
const pool = mkPool_({emitter});
146+
const browser = stubBrowser_();
147+
148+
return requestBrowser_(browser, pool)
149+
.then((browser) => pool.freeBrowser(browser))
150+
.then(() => {
151+
assert.calledOnce(onSessionEnd);
152+
assert.calledWith(onSessionEnd, browser);
153+
});
154+
});
155+
156+
it('handler should be waited before actual quit', () => {
157+
const emitter = new QEmitter();
158+
const afterSessionEnd = sinon.spy().named('afterSessionEnd');
159+
emitter.on(Events.STOP_BROWSER, () => bluebirdQ.delay(100).then(afterSessionEnd));
160+
161+
const pool = mkPool_({emitter});
162+
const browser = stubBrowser_();
163+
164+
return requestBrowser_(browser, pool)
165+
.then((browser) => pool.freeBrowser(browser))
166+
.then(() => assert.callOrder(afterSessionEnd, browser.quit));
167+
});
168+
169+
it('handler fail should not prevent browser from quit', () => {
170+
const emitter = new QEmitter();
171+
emitter.on(Events.STOP_BROWSER, () => bluebirdQ.reject());
172+
173+
const pool = mkPool_({emitter});
174+
const browser = stubBrowser_();
175+
176+
return requestBrowser_(browser, pool)
177+
.then((browser) => pool.freeBrowser(browser))
178+
.then(() => assert.calledOnce(browser.quit));
179+
});
180+
});
181+
86182
describe('cancel', () => {
87183
it('should quit all browsers on cancel', () => {
88-
return Promise.all([requestBrowser(stubBrowser('id1')), requestBrowser(stubBrowser('id2'))])
184+
const pool = mkPool_();
185+
186+
return Promise
187+
.all([
188+
requestBrowser_(stubBrowser_('id1'), pool),
189+
requestBrowser_(stubBrowser_('id2'), pool)
190+
])
89191
.spread((firstBrowser, secondBrowser) => {
90192
pool.cancel();
91193

@@ -95,7 +197,13 @@ describe('BasicPool', () => {
95197
});
96198

97199
it('should quit all browser with the same id on cancel', () => {
98-
return Promise.all([requestBrowser(stubBrowser('id')), requestBrowser(stubBrowser('id'))])
200+
const pool = mkPool_();
201+
202+
return Promise
203+
.all([
204+
requestBrowser_(stubBrowser_('id'), pool),
205+
requestBrowser_(stubBrowser_('id'), pool)
206+
])
99207
.spread((firstBrowser, secondBrowser) => {
100208
pool.cancel();
101209

@@ -105,24 +213,28 @@ describe('BasicPool', () => {
105213
});
106214

107215
it('should be rejected if getting of a browser was called after cancel', () => {
216+
const pool = mkPool_();
217+
108218
pool.cancel();
109219

110-
return assert.isRejected(requestBrowser(stubBrowser()), CancelledError);
220+
return assert.isRejected(requestBrowser_(stubBrowser_(), pool), CancelledError);
111221
});
112222

113223
it('should quit a browser once if it was launched after cancel', () => {
114-
const browser = stubBrowser();
224+
const pool = mkPool_();
225+
const browser = stubBrowser_();
115226

116227
pool.cancel();
117228

118-
return requestBrowser(browser)
229+
return requestBrowser_(browser, pool)
119230
.catch(() => assert.calledOnce(browser.quit));
120231
});
121232

122233
it('should clean active sessions after cancel', () => {
123-
const browser = stubBrowser();
234+
const pool = mkPool_();
235+
const browser = stubBrowser_();
124236

125-
return requestBrowser(browser)
237+
return requestBrowser_(browser, pool)
126238
.then(() => pool.cancel())
127239
.then(() => pool.cancel())
128240
.then(() => assert.calledOnce(browser.quit));

0 commit comments

Comments
 (0)