Skip to content

Commit

Permalink
Merge branch 'devel'
Browse files Browse the repository at this point in the history
  • Loading branch information
apendua committed Dec 24, 2014
2 parents 1c3cebd + c0f2889 commit e2c8d46
Show file tree
Hide file tree
Showing 17 changed files with 236 additions and 73 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ describe('You can also use browser in your tests', function () {
});
```

## Testing with Selenium WebDriver

Lets assume that you have a copy of `selenium-server-standalone-*.jar` available at `/path/to/selenium.jar`. First start a selenium "hub" with the following command:
```
java -jar /path/to/selenium.jar -role hub
```
Selenium server should be listening on port `4444` by default. Then start a selenium "node" with
```
java -jar /path/to/selenium.jar -role node -hub http://localhost:4444
```
Finally run your Gagarin tests providing `--webdriver` option
```
gagarin --webdriver http://localhost:4444/wd/hub
```
We've been testing Gagarin with `chrome` (38) and `firefox` (34). At this moment we cannot guarantee it will work with other browsers.

# Examples

Since we don't have a comprehensive documentation yet, please consider the following set of simple examples as a current API reference. Note that this document will evolve in the nearest future.
Expand Down
1 change: 0 additions & 1 deletion backdoor.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
"use strict";

var vm = Npm.require('vm');
var Fiber = Npm.require('fibers');
Expand Down
16 changes: 9 additions & 7 deletions lib/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function Browser (options) {

var self = this;
var closure = null;
var driverLocation = options.webdriver || "127.0.0.1:9515";
var driverLocation = options.webdriver || "http://localhost:9515";
var browser = wd.remote(driverLocation); // default to chromedriver
var myLocation = options.location || "http://localhost:3000"; // default to meteor
var dontWaitForMeteor = options.dontWaitForMeteor !== undefined ? !!options.dontWaitForMeteor : false;
Expand Down Expand Up @@ -39,10 +39,13 @@ function Browser (options) {
}
reject(err);
}

var driverLocationParsed = URL.parse(driverLocation);
portscanner.checkPortStatus(driverLocationParsed.port, driverLocationParsed.hostname, function(error, status) {
if(status != 'open')

portscanner.checkPortStatus(driverLocationParsed.port, driverLocationParsed.hostname, function (error, status) {
if (status !== 'open') {
_reject('webdriver not found on ' + driverLocation);
}
browser.init(capabilities, function (err) {
if (err) {
return _reject(err);
Expand Down Expand Up @@ -88,10 +91,9 @@ function Browser (options) {
});
});
});
}

});
});
} // after resize
}); // init
}); // checkPortStatus
}); // Promise
}

Expand Down
28 changes: 17 additions & 11 deletions lib/browserPromiseChain.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ function BrowserPromiseChain (operand, helpers) {
this._promise = operand;
this._helpers = helpers;

// TODO: check for conflicts
// install helpers
Object.keys(helpers).forEach(function (key) {
if (self[key] !== undefined) {
console.warn('helper ' + key + ' conflicts with some BrowserPromiseChain method');
}
self[key] = helpers[key];
});

}

[ 'then', 'catch' ].forEach(function (name) {
Expand Down Expand Up @@ -71,26 +72,31 @@ webdriverMethods.forEach(function (name) {
"use strict";

BrowserPromiseChain.prototype[name] = function () {
return this.applyWebDriver(name, Array.prototype.slice.call(arguments, 0));
return this._applyWebDriver(name, Array.prototype.slice.call(arguments, 0));
};

});

BrowserPromiseChain.methods = webdriverMethods.concat([
'applyWebDriver',
'callWebDriver',
'_applyWebDriver',
'_callWebDriver',
'catch',
'then',
'always',
'sleep',
'expectError',
'noWait',
'yet',
'execute',
'timeout',
'promise',
'wait',
]);

/**
* Update the current promise and return this to allow chaining.
*/
BrowserPromiseChain.prototype.applyWebDriver = function (name, args, custom) {
BrowserPromiseChain.prototype._applyWebDriver = function (name, args, custom) {
"use strict";

args = Array.prototype.slice.call(args, 0); // shallow copy the arguments
Expand All @@ -117,10 +123,10 @@ BrowserPromiseChain.prototype.applyWebDriver = function (name, args, custom) {
return self;
}

BrowserPromiseChain.prototype.callWebDriver = function (name) {
BrowserPromiseChain.prototype._callWebDriver = function (name) {
"use strict";

return this.applyWebDriver(name, Array.prototype.slice.call(arguments, 1));
return this._applyWebDriver(name, Array.prototype.slice.call(arguments, 1));
}

BrowserPromiseChain.prototype.always = function (callback) {
Expand Down Expand Up @@ -185,7 +191,7 @@ BrowserPromiseChain.prototype.execute = function (code, args) {
throw new Error('`args` has to be an array');
}

return self.applyWebDriver('execute', arguments, function (operand, name, myArgs) {
return self._applyWebDriver('execute', arguments, function (operand, name, myArgs) {
var closure = operand.closure ? operand.closure() : {};
var cb = myArgs[myArgs.length-1];

Expand Down Expand Up @@ -219,7 +225,7 @@ BrowserPromiseChain.prototype.promise = function (code, args) {
}

// we could set this 5000 globally, right?
return self.callWebDriver('setAsyncScriptTimeout', self._timeout || 5000).applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) {
return self._callWebDriver('setAsyncScriptTimeout', self._timeout || 5000)._applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) {

var closure = operand.closure ? operand.closure() : {};
var cb = myArgs[myArgs.length-1]; // the last argument is callback
Expand Down Expand Up @@ -298,7 +304,7 @@ BrowserPromiseChain.prototype.wait = function (timeout, message, code, args) {

var self = this;

return self.callWebDriver('setAsyncScriptTimeout', 2 * timeout).applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) {
return self._callWebDriver('setAsyncScriptTimeout', 2 * timeout)._applyWebDriver('executeAsync', arguments, function (operand, name, myArgs) {

var closure = operand.closure ? operand.closure() : {};
var cb = myArgs[myArgs.length-1]; // callback is always the last one
Expand Down
50 changes: 32 additions & 18 deletions lib/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ module.exports = function BuildAsPromise (options) {
var timeout = options.timeout || 60000;
var verbose = options.verbose !== undefined ? !!options.verbose : false;

var pathToVersions = path.join(pathToApp, '.meteor', 'versions');
var pathToSmartJson = path.join(pathToApp, 'smart.json');
var pathToMain = path.join(pathToApp, '.meteor', 'local', 'build', 'main.js');
var skipBuild = !!options.skipBuild;
Expand All @@ -32,19 +31,6 @@ module.exports = function BuildAsPromise (options) {
}
}

var versions = fs.readFileSync(pathToVersions, 'utf-8');
var versionMatch = versions.match(/anti:gagarin@(.*)/);

if (!versionMatch) {
return Promise.reject(new Error('Please add anti:gagarin to your app before running tests.'));
} else if (versionMatch[1] !== version) {
return Promise.reject(new Error(
'Versions of node package (' + version +
') and meteor packages (' + versionMatch[1] +
') are not compatible; please update.'
));
}

if (myBuildPromises[pathToApp]) return myBuildPromises[pathToApp];

if (fs.existsSync(pathToSmartJson)) {
Expand Down Expand Up @@ -116,11 +102,17 @@ function BuildPromise(options) {
regExp: /App running at:/,
action: function () {
meteor.once('exit', function () {
if (fs.existsSync(pathToMain)) {
resolve(pathToMain);
} else {
reject(new Error('Meteor build failed.'));
if (!fs.existsSync(pathToMain)) {
reject(new Error('File ' + pathToMain + ' does not exist.'));
return;
}
try {
checkIfVersionsMatches(pathToApp);
} catch (err) {
reject(err);
return;
}
resolve(pathToMain);
});
meteor.kill('SIGINT');
}
Expand Down Expand Up @@ -213,3 +205,25 @@ function isLocked(pathToApp) {
return fs.existsSync(pathToMongoLock) && fs.readFileSync(pathToMongoLock).toString('utf8');
}

/**
* Verify if Gagarin is instaled and if the version is compatible.
*/
function checkIfVersionsMatches(pathToApp) {

var pathToVersions = path.join(pathToApp, '.meteor', 'versions');
var versions = fs.readFileSync(pathToVersions, 'utf-8');
var versionMatch = versions.match(/anti:gagarin@(.*)/);

if (!versionMatch) { // looks like gagarin is not even instaled
throw new Error('Please add anti:gagarin to your app before running tests.');
} else if (versionMatch[1] !== version) { // versions of gagarin are not compatible
throw new Error(
'Versions of node package (' + version +
') and meteor package (' + versionMatch[1] +
') are not compatible; please update.'
);
}

}


31 changes: 25 additions & 6 deletions lib/ddp.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,31 @@ module.exports = function makeDDPClientFactory (ddpSetupProvider) {
ddpVersion : '1'
});

ddpClient.connect(function (err, wasReconnected) {
if (err) {
return reject(err);
}
resolve(ddpClient);
});
//ddpClient.connect(function (err, wasReconnected) {
// if (err) {
// return reject(err);
// }
// resolve(ddpClient);
//});

var retryCount = 5;

// XXX we need this because the WebApp.httpServer may start with some delay;
// in fact, this should be handled within the app itself
(function tryConnect() {
ddpClient.connect(function (err, wasReconnected) {
if (err) {
if (retryCount <= 0) {
reject(typeof err === 'string' ? new Error(err) : err);
} else if (!wasReconnected) {
retryCount -= 1;
setTimeout(tryConnect, 500);
}
} else {
resolve(ddpClient);
}
});
})();

// TODO: re-enable this feature when we make timeout configurable
//setTimeout(function () {
Expand Down
4 changes: 2 additions & 2 deletions lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ exports.client = {
},

click: function (selector, keys) {
return this.callWebDriver('elementByCssSelectorOrNull', selector)
return this._callWebDriver('elementByCssSelectorOrNull', selector)
.then(function (element) {
if (!element) {
throw new Error('element ' + selector + ' does not exists');
Expand All @@ -109,7 +109,7 @@ exports.client = {
//},

sendKeys: function (selector, keys) {
return this.callWebDriver('elementByCssSelectorOrNull', selector)
return this._callWebDriver('elementByCssSelectorOrNull', selector)
.then(function (element) {
if (!element) {
throw new Error('element ' + selector + ' does not exists');
Expand Down
25 changes: 19 additions & 6 deletions lib/meteor.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ module.exports.BuildAsPromise = BuildAsPromise;
function Meteor (options) {
"use strict";

var self = this;

options = options || {};

if (typeof options === 'string') {
Expand All @@ -41,7 +43,8 @@ function Meteor (options) {
remoteServer = url.parse(remoteServer);
}

var databasePromise = new DatabaseAsPromise({ pathToApp: pathToApp });
//XXX when using a remote server, we don't really need database promise here
var databasePromise = remoteServer ? Promise.resolve({}) : new DatabaseAsPromise({ pathToApp: pathToApp });
var ddpClientAsPromise = makeDDPClientFactory(ddpSetupProvider);

var instance = null;
Expand Down Expand Up @@ -94,14 +97,24 @@ function Meteor (options) {
},
};

this.location = meteorLocation;
this.helpers = options.helpers || {};
self.location = meteorLocation;
self.helpers = {};

// set-up helpers
self.helpers = {};

Object.keys(options.helpers || {}).forEach(function (key) {
if (self[key] !== undefined) {
console.warn('helper ' + key + ' conflicts with some Meteor method');
}
self[key] = self.helpers[key] = options.helpers[key];
});

this.useClosure = function (objectOrGetter) {
self.useClosure = function (objectOrGetter) {
remote.useClosure(objectOrGetter);
};

this.meteorRemoteAsPromise = Promise.resolve(remote);
self.meteorRemoteAsPromise = Promise.resolve(remote);

function cleanUpThen(callback) {
meteorPromise = null;
Expand Down Expand Up @@ -265,7 +278,7 @@ MeteorPromiseChain.methods.forEach(function (name) {
"use strict";

Meteor.prototype[name] = function () {
var chain = new MeteorPromiseChain(this.meteorRemoteAsPromise, this.helpers);
var chain = new MeteorPromiseChain(this.meteorRemoteAsPromise, this._helpers);
return chain[name].apply(chain, arguments);
};

Expand Down
Loading

0 comments on commit e2c8d46

Please sign in to comment.