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

Commit 340286f

Browse files
committed
feat: wait for plugins load
1 parent 3604dcb commit 340286f

File tree

3 files changed

+147
-123
lines changed

3 files changed

+147
-123
lines changed

lib/gemini.js

Lines changed: 87 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const chalk = require('chalk');
55
const _ = require('lodash');
66
const PassthroughEmitter = require('./passthrough-emitter');
77
const Promise = require('bluebird');
8-
const q = require('bluebird-q');
8+
const bluebirdQ = require('bluebird-q');
99
const pluginsLoader = require('plugins-loader');
1010
const gracefulFs = require('graceful-fs');
1111

@@ -37,6 +37,10 @@ module.exports = class Gemini extends PassthroughEmitter {
3737
return new Gemini(config, allowOverrides);
3838
}
3939

40+
static readRawConfig(filePath) {
41+
return Config.readRawConfig(filePath);
42+
}
43+
4044
constructor(config, allowOverrides) {
4145
super();
4246

@@ -46,74 +50,42 @@ module.exports = class Gemini extends PassthroughEmitter {
4650
this.SuiteCollection = SuiteCollection;
4751

4852
setupLog(this.config.system.debug);
49-
this._loadPlugins();
5053
}
5154

52-
static readRawConfig(filePath) {
53-
return Config.readRawConfig(filePath);
55+
getScreenshotPath(suite, stateName, browserId) {
56+
return this.config.forBrowser(browserId).getScreenshotPath(suite, stateName);
5457
}
5558

56-
_run(stateProcessor, paths, options) {
57-
if (!options) {
58-
//if there are only two arguments, they are
59-
//(stateProcessor, options) and paths are
60-
//the default.
61-
options = paths;
62-
paths = undefined;
63-
}
64-
options = options || {};
65-
options.reporters = options.reporters || [];
66-
67-
temp.init(this.config.system.tempDir);
68-
69-
const runner = Runner.create(this.config, stateProcessor);
70-
const envBrowsers = parseBrowsers(process.env.GEMINI_BROWSERS);
71-
const envSkipBrowsers = parseBrowsers(process.env.GEMINI_SKIP_BROWSERS);
72-
73-
options.browsers = options.browsers || envBrowsers;
74-
75-
this._passThroughEvents(runner);
59+
getBrowserCapabilites(browserId) {
60+
return this.config.forBrowser(browserId).desiredCapabilities;
61+
}
7662

77-
// it is important to require signal handler here in order to guarantee subscribing to "INTERRUPT" event
78-
require('./signal-handler').on(Events.INTERRUPT, (data) => {
79-
this.emit(Events.INTERRUPT, data);
63+
getValidBrowsers(browsers) {
64+
return _.intersection(browsers, this.browserIds);
65+
}
8066

81-
runner.cancel();
82-
});
67+
checkUnknownBrowsers(browsers) {
68+
const browsersFromConfig = this.browserIds;
69+
const unknownBrowsers = _.difference(browsers, browsersFromConfig);
8370

84-
if (options.browsers) {
85-
this.checkUnknownBrowsers(options.browsers);
71+
if (unknownBrowsers.length) {
72+
console.warn(
73+
`${chalk.yellow('WARNING:')} Unknown browsers id: ${unknownBrowsers.join(', ')}.\n` +
74+
`Use one of the browser ids specified in config file: ${browsersFromConfig.join(', ')}`
75+
);
8676
}
77+
}
8778

88-
const getTests = (source, options) => {
89-
return source instanceof SuiteCollection
90-
? Promise.resolve(source)
91-
: this.readTests(source, options);
92-
};
93-
94-
return getTests(paths, options)
95-
.then((suiteCollection) => {
96-
this.checkUnknownBrowsers(envSkipBrowsers);
97-
98-
const validSkippedBrowsers = this.getValidBrowsers(envSkipBrowsers);
99-
100-
suiteCollection.skipBrowsers(validSkippedBrowsers);
101-
options.reporters.forEach((reporter) => applyReporter(runner, reporter));
102-
103-
let testsStatistic;
104-
runner.on(Events.END, (stats) => testsStatistic = stats);
105-
106-
return runner.run(suiteCollection)
107-
.then(() => testsStatistic);
108-
});
79+
get browserIds() {
80+
return this.config.getBrowserIds();
10981
}
11082

111-
_passThroughEvents(runner) {
112-
this.passthroughEvent(runner, _.values(Events));
83+
update(paths, options) {
84+
return this._exec(() => this._run(StateProcessor.createScreenUpdater(options), paths, options));
11385
}
11486

115-
_loadPlugins() {
116-
pluginsLoader.load(this, this.config.system.plugins, PREFIX);
87+
test(paths, options) {
88+
return this._exec(() => this._run(StateProcessor.createTester(this.config), paths, options));
11789
}
11890

11991
readTests(paths, options) {
@@ -127,16 +99,25 @@ module.exports = class Gemini extends PassthroughEmitter {
12799
options = options || {};
128100
}
129101

102+
return this._exec(() => this._readTests(paths, options));
103+
}
104+
105+
_exec(fn) {
106+
const plugins = pluginsLoader.load(this, this.config.system.plugins, PREFIX);
107+
return bluebirdQ(Promise.all(plugins).then(() => fn()));
108+
}
109+
110+
_readTests(paths, options) {
130111
options = _.assignIn(options, {paths});
131112

132-
return q(readTests(this, this.config, options)
113+
return readTests(this, this.config, options)
133114
.then((rootSuite) => {
134115
if (options.grep) {
135116
applyGrep_(options.grep, rootSuite);
136117
}
137118

138119
return new SuiteCollection(rootSuite.children);
139-
}));
120+
});
140121

141122
function applyGrep_(grep, suite) {
142123
if (!suite.hasStates) {
@@ -155,40 +136,63 @@ module.exports = class Gemini extends PassthroughEmitter {
155136
}
156137
}
157138

158-
update(paths, options) {
159-
return q(this._run(StateProcessor.createScreenUpdater(options), paths, options));
160-
}
139+
_run(stateProcessor, paths, options) {
140+
if (!options) {
141+
//if there are only two arguments, they are
142+
//(stateProcessor, options) and paths are
143+
//the default.
144+
options = paths;
145+
paths = undefined;
146+
}
147+
options = options || {};
148+
options.reporters = options.reporters || [];
161149

162-
test(paths, options) {
163-
return q(this._run(StateProcessor.createTester(this.config), paths, options));
164-
}
150+
temp.init(this.config.system.tempDir);
165151

166-
getScreenshotPath(suite, stateName, browserId) {
167-
return this.config.forBrowser(browserId).getScreenshotPath(suite, stateName);
168-
}
152+
const runner = Runner.create(this.config, stateProcessor);
153+
const envBrowsers = parseBrowsers(process.env.GEMINI_BROWSERS);
154+
const envSkipBrowsers = parseBrowsers(process.env.GEMINI_SKIP_BROWSERS);
169155

170-
getBrowserCapabilites(browserId) {
171-
return this.config.forBrowser(browserId).desiredCapabilities;
172-
}
156+
options.browsers = options.browsers || envBrowsers;
173157

174-
getValidBrowsers(browsers) {
175-
return _.intersection(browsers, this.browserIds);
176-
}
158+
this._passThroughEvents(runner);
177159

178-
checkUnknownBrowsers(browsers) {
179-
const browsersFromConfig = this.browserIds;
180-
const unknownBrowsers = _.difference(browsers, browsersFromConfig);
160+
// it is important to require signal handler here in order to guarantee subscribing to "INTERRUPT" event
161+
require('./signal-handler').on(Events.INTERRUPT, (data) => {
162+
this.emit(Events.INTERRUPT, data);
181163

182-
if (unknownBrowsers.length) {
183-
console.warn(
184-
`${chalk.yellow('WARNING:')} Unknown browsers id: ${unknownBrowsers.join(', ')}.\n` +
185-
`Use one of the browser ids specified in config file: ${browsersFromConfig.join(', ')}`
186-
);
164+
runner.cancel();
165+
});
166+
167+
if (options.browsers) {
168+
this.checkUnknownBrowsers(options.browsers);
187169
}
170+
171+
const getTests = (source, options) => {
172+
return source instanceof SuiteCollection
173+
? Promise.resolve(source)
174+
: this._readTests(source, options);
175+
};
176+
177+
return getTests(paths, options)
178+
.then((suiteCollection) => {
179+
this.checkUnknownBrowsers(envSkipBrowsers);
180+
181+
const validSkippedBrowsers = this.getValidBrowsers(envSkipBrowsers);
182+
183+
suiteCollection.skipBrowsers(validSkippedBrowsers);
184+
options.reporters.forEach((reporter) => applyReporter(runner, reporter));
185+
186+
let testsStatistic;
187+
runner.on(Events.END, (stats) => testsStatistic = stats);
188+
189+
return runner.run(suiteCollection)
190+
.then(() => testsStatistic);
191+
});
188192
}
189193

190-
get browserIds() {
191-
return this.config.getBrowserIds();
194+
_passThroughEvents(runner) {
195+
this.passthroughEvent(runner, _.values(Events));
192196
}
193197
};
194198

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"looks-same": "^3.0.0",
3131
"micromatch": "^2.3.11",
3232
"node-fetch": "^1.6.3",
33-
"plugins-loader": "^1.0.1",
33+
"plugins-loader": "^1.1.0",
3434
"png-img": "^2.1.0",
3535
"q-promise-utils": "^1.1.0",
3636
"resolve": "^1.1.0",

test/unit/gemini.js

Lines changed: 59 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -67,57 +67,57 @@ describe('gemini', () => {
6767
beforeEach(() => {
6868
sandbox.stub(Runner.prototype, 'cancel').returns(Promise.resolve());
6969
sandbox.stub(console, 'warn');
70-
sandbox.stub(pluginsLoader, 'load');
70+
sandbox.stub(pluginsLoader, 'load').returns([]);
7171
sandbox.stub(temp, 'init');
7272
});
7373

7474
afterEach(() => sandbox.restore());
7575

76-
it('should passthrough runner events', () => {
77-
const runner = new EventEmitter();
78-
sandbox.stub(Runner, 'create').returns(runner);
76+
[
77+
Events.START_RUNNER,
78+
Events.END_RUNNER,
79+
Events.BEGIN,
80+
Events.END,
7981

80-
const gemini = initGemini({});
81-
gemini.test();
82-
83-
[
84-
Events.START_RUNNER,
85-
Events.END_RUNNER,
86-
Events.BEGIN,
87-
Events.END,
82+
Events.BEGIN_SESSION,
83+
Events.END_SESSION,
8884

89-
Events.BEGIN_SESSION,
90-
Events.END_SESSION,
85+
Events.RETRY,
9186

92-
Events.RETRY,
87+
Events.START_BROWSER,
88+
Events.STOP_BROWSER,
9389

94-
Events.START_BROWSER,
95-
Events.STOP_BROWSER,
90+
Events.BEGIN_SUITE,
91+
Events.END_SUITE,
9692

97-
Events.BEGIN_SUITE,
98-
Events.END_SUITE,
93+
Events.SKIP_STATE,
94+
Events.BEGIN_STATE,
95+
Events.END_STATE,
9996

100-
Events.SKIP_STATE,
101-
Events.BEGIN_STATE,
102-
Events.END_STATE,
97+
Events.INFO,
98+
Events.WARNING,
99+
Events.ERROR,
103100

104-
Events.INFO,
105-
Events.WARNING,
106-
Events.ERROR,
101+
Events.END_TEST,
102+
Events.CAPTURE,
107103

108-
Events.END_TEST,
109-
Events.CAPTURE,
104+
Events.TEST_RESULT,
105+
Events.UPDATE_RESULT
106+
].forEach((event) => {
107+
it(`should passthrough '${event}' runner event`, () => {
108+
const runner = new EventEmitter();
109+
runner.run = () => {
110+
runner.emit(event, 'foo');
111+
return Promise.resolve();
112+
};
113+
sandbox.stub(Runner, 'create').returns(runner);
110114

111-
Events.TEST_RESULT,
112-
Events.UPDATE_RESULT
113-
].forEach((event, name) => {
114-
const spy = sinon.spy().named(`${name} handler`);
115+
const gemini = initGemini({});
116+
const spy = sinon.spy();
115117
gemini.on(event, spy);
116118

117-
runner.emit(event, 'value');
118-
119-
assert.calledOnce(spy);
120-
assert.calledWith(spy, 'value');
119+
return gemini.test()
120+
.then(() => assert.calledOnceWith(spy, 'foo'));
121121
});
122122
});
123123

@@ -161,6 +161,28 @@ describe('gemini', () => {
161161
return runGeminiTest()
162162
.then(() => assert.calledWith(pluginsLoader.load, sinon.match.any, sinon.match.any, prefix));
163163
});
164+
165+
it('should wait until plugins loaded', () => {
166+
const afterPluginLoad = sinon.spy();
167+
pluginsLoader.load.callsFake(() => {
168+
return [Promise.delay(20).then(afterPluginLoad)];
169+
});
170+
sandbox.stub(Runner.prototype, 'run').returns(Promise.resolve());
171+
172+
return runGeminiTest()
173+
.then(() => assert.callOrder(afterPluginLoad, Runner.prototype.run));
174+
});
175+
176+
it('should not run tests if plugin failed on load', () => {
177+
const err = new Error('o.O');
178+
pluginsLoader.load.callsFake(() => [Promise.reject(err)]);
179+
sandbox.stub(Runner.prototype, 'run').returns(Promise.resolve());
180+
181+
const result = runGeminiTest();
182+
183+
return assert.isRejected(result, err)
184+
.then(() => assert.notCalled(Runner.prototype.run));
185+
});
164186
});
165187

166188
describe('readTests', () => {
@@ -318,10 +340,8 @@ describe('gemini', () => {
318340
});
319341

320342
it('should initialize temp with specified temp dir', () => {
321-
runGeminiTest({tempDir: '/some/dir'});
322-
323-
assert.calledOnce(temp.init);
324-
assert.calledWith(temp.init, '/some/dir');
343+
return runGeminiTest({tempDir: '/some/dir'})
344+
.then(() => assert.calledOnceWith(temp.init, '/some/dir'));
325345
});
326346

327347
it('should initialize temp before start runner', () => {

0 commit comments

Comments
 (0)