browser-monkey
Advanced tools
Comparing version 1.2.0 to 1.3.0
37
index.js
@@ -77,6 +77,14 @@ var retry = require('trytryagain'); | ||
if (text) { | ||
var elementText = els.text(); | ||
if (text instanceof Array) { | ||
var actualTexts = els.toArray().map(function (item) { | ||
return $(item).text(); | ||
}); | ||
if (elementText.indexOf(text) < 0) { | ||
throw new Error(message || ('expected element to have text ' + JSON.stringify(text) + ' but contained ' + JSON.stringify(elementText))); | ||
expect(actualTexts).to.eql(text); | ||
} else { | ||
var elementText = els.text(); | ||
if (elementText.indexOf(text) < 0) { | ||
throw new Error(message || ('expected element to have text ' + JSON.stringify(text) + ' but contained ' + JSON.stringify(elementText))); | ||
} | ||
} | ||
@@ -209,7 +217,19 @@ } | ||
Selector.prototype.exists = function (options) { | ||
return this.shouldExist(options); | ||
}; | ||
Selector.prototype.shouldExist = function (options) { | ||
return this.resolve(options); | ||
}; | ||
Selector.prototype.elements = function (options) { | ||
return this.resolve({allowMultiple: true}); | ||
}; | ||
Selector.prototype.element = function (options) { | ||
return this.resolve(); | ||
}; | ||
Selector.prototype.has = function(options) { | ||
return this.addFinder(elementTester(options)).exists({allowMultiple: true}); | ||
return this.addFinder(elementTester(options)).shouldExist({allowMultiple: true}); | ||
}; | ||
@@ -239,11 +259,2 @@ | ||
Selector.prototype.expect = function(assertion) { | ||
return this.addFinder({ | ||
find: function(element) { | ||
assertion(element); | ||
return element; | ||
} | ||
}).exists(); | ||
}; | ||
module.exports = new Selector(); | ||
@@ -250,0 +261,0 @@ |
{ | ||
"name": "browser-monkey", | ||
"version": "1.2.0", | ||
"version": "1.3.0", | ||
"description": "reliable dom testing", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -9,13 +9,96 @@ # browser monkey | ||
Browser Monkey is a DOM assertion library. It helps you write framework agnostic browser tests that are reliable in the face of asynchronous behaviours like animations, AJAX and delayed rendering. It also helps you to write tests that exhibit the semantic meaning of the page, as opposed to a jumble of CSS selectors. | ||
* timing resistant | ||
* create rich DSLs for your page structure | ||
* framework agnostic: works with React, Angular, jQuery, [Plastiq](https://github.com/featurist/plastiq) and many many more. | ||
* can simulate text entry and clicks. (please let us know if you need more!) | ||
* returns promises that resolve when the elements are found. | ||
# example | ||
```js | ||
var browser = require('browser-monkey'); | ||
describe('admin', function () { | ||
// describes the admin panel, with a search box, results and a user editor | ||
var adminPanel = browser.extend({ | ||
searchUsers: function () { | ||
return this.find('.search'); | ||
}, | ||
userResult: function (name) { | ||
// find a user in the results by their name | ||
return this.find('.results .user', {text: name}); | ||
} | ||
userEditor: function () { | ||
// return the user editor, scoped to the .user-editor div. | ||
return userEditor.scope(this.find('.user-editor')); | ||
} | ||
}); | ||
describe('admin page', function () { | ||
it('has users', function () { | ||
... | ||
return browser.find('.user-name', {text: 'Bob'}).exists(); | ||
// describes the user editor, with inputs for name and email, and a save button. | ||
var userEditor = browser.extend({ | ||
name: function () { this.find('.name'); }, | ||
email: function () { this.find('.email'); }, | ||
save: function () { this.find('.save'); }, | ||
}); | ||
it('can search for, edit and save a user', function () { | ||
return adminPanel.searchUsers().typeIn('bar').then(function () { | ||
return adminPanel.userResult('Barry').click(); | ||
}).then(function () { | ||
var userEditor = adminPanel.userEditor(); | ||
return Promise.all([ | ||
userEditor.name().typeIn('Barry Jones'), | ||
userEditor.email().typeIn('barryjones@example.com') | ||
]).then(function () { | ||
return userEditor.save().click(); | ||
}); | ||
}).then(function () { | ||
// verify that the user was saved | ||
// use mockjax-router! | ||
}); | ||
}); | ||
}); | ||
``` | ||
# api | ||
The API is made up of three concepts: scopes, actions and assertions. | ||
* scopes are chains of queries, such as `find(css)` and `containing(css)`, that progressively narrow the scope of elements to be searched for. These queries return new scopes. | ||
* actions such as `click()` and `typeIn(text)` wait for the scope to be found before simulating a UI event. These return promises that resolve when the event has been dispatched. | ||
* assertions such as `shouldExist()` and `shouldHave(properties)` can be made on scopes to ensure that they exist or contain text, classes or other properties. | ||
All scope chains are immutable, so you can reuse portions of a scope chain to build new chains: | ||
```js | ||
var details = browser.find('.details'); // finds .details | ||
var name = details.find('.name'); // finds .details .name | ||
var email = details.find('.email'); // finds .details .email | ||
... | ||
``` | ||
The API starts with the browser scope, which contains everything on the page. | ||
You can also create DSLs for components on the page using `scope.extend(methods)`. By extending a scope, you can add methods that represent elements of the component at a higher level than mere CSS selectors. It's probably worth noting that these methods should normally just return scopes and not perform actions or assertions. | ||
## find | ||
```js | ||
var innerScope = scope.find(css, [options]); | ||
``` | ||
Returns a new scope that matches `css`. | ||
* `css` - css to find in the scope | ||
* `options.text` - text to find in the scope. | ||
## containing | ||
```js | ||
var scope = scope.containing(css, [options]); | ||
``` | ||
Ensures that the scope contains the `css` and `options.text`, the scope returned still refers to the outer scope. | ||
* `css` - css to find in the scope | ||
* `options.text` - text to find in the scope. |
@@ -17,7 +17,13 @@ var browser = require('..'); | ||
it('should eventually find an element', function () { | ||
var promise = browser.find('.element').exists(); | ||
function eventuallyInsertHtml(html) { | ||
setTimeout(function () { | ||
$('<div class="element"></div>').appendTo(div); | ||
$(html).appendTo(div); | ||
}, 200); | ||
} | ||
it('should eventually find an element', function () { | ||
var promise = browser.find('.element').shouldExist(); | ||
eventuallyInsertHtml('<div class="element"></div>'); | ||
return promise; | ||
@@ -30,7 +36,7 @@ }); | ||
setTimeout(function () { | ||
eventuallyInsertHtml( | ||
$('<div class="element"></div>').click(function () { | ||
clicked = true; | ||
}).appendTo(div); | ||
}, 200); | ||
}) | ||
); | ||
@@ -46,5 +52,3 @@ return promise.then(function () { | ||
setTimeout(function () { | ||
$('<input type="text" class="element"></input>').appendTo(div); | ||
}, 200); | ||
eventuallyInsertHtml('<input type="text" class="element"></input>'); | ||
@@ -57,6 +61,4 @@ return promise.then(function () { | ||
it('eventually finds an element containing text', function () { | ||
var promise = browser.find('.element', {text: 'some t'}).exists(); | ||
setTimeout(function () { | ||
$('<div class="element"><div>some text</div></div>').appendTo(div); | ||
}, 200); | ||
var promise = browser.find('.element', {text: 'some t'}).shouldExist(); | ||
eventuallyInsertHtml('<div class="element"><div>some text</div></div>'); | ||
return promise; | ||
@@ -70,5 +72,3 @@ }); | ||
setTimeout(function () { | ||
$('<div class="element"><div>some text</div></div>').appendTo(div); | ||
}, 200); | ||
eventuallyInsertHtml('<div class="element"><div>some text</div></div>'); | ||
@@ -81,2 +81,16 @@ return Promise.all([ | ||
it('eventually finds elements and asserts that they each have text', function () { | ||
var good = browser.find('.element div').shouldHave({text: ['one', 'two']}); | ||
var bad1 = browser.find('.element div').shouldHave({text: ['one']}); | ||
var bad2 = browser.find('.element div').shouldHave({text: ['one', 'three']}); | ||
eventuallyInsertHtml('<div class="element"><div>one</div><div>two</div></div>'); | ||
return Promise.all([ | ||
good, | ||
expect(bad1).to.be.rejected, | ||
expect(bad2).to.be.rejected | ||
]); | ||
}); | ||
it('eventually finds an element and asserts that it has css', function () { | ||
@@ -87,5 +101,3 @@ var good = browser.find('.element').shouldHave({css: '.the-class'}); | ||
setTimeout(function () { | ||
$('<div class="element the-class"><div class="not-the-class">some text</div></div>').appendTo(div); | ||
}, 200); | ||
eventuallyInsertHtml('<div class="element the-class"><div class="not-the-class">some text</div></div>'); | ||
@@ -103,5 +115,3 @@ return Promise.all([ | ||
setTimeout(function () { | ||
$('<div class="element"></div><div class="element"></div>').appendTo(div); | ||
}, 200); | ||
eventuallyInsertHtml('<div class="element"></div><div class="element"></div>'); | ||
@@ -125,5 +135,3 @@ return Promise.all([ | ||
setTimeout(function () { | ||
$('<div class="element"></div><div class="element">a</div>').appendTo(div); | ||
}, 200); | ||
eventuallyInsertHtml('<div class="element"></div><div class="element">a</div>'); | ||
@@ -138,38 +146,89 @@ return Promise.all([ | ||
it('eventually finds an element containing another element', function () { | ||
var promise = browser.find('.outer').containing('.inner').exists(); | ||
setTimeout(function () { | ||
$('<div class="outer"><div>bad</div></div>').appendTo(div); | ||
$('<div class="outer"><div class="inner">good</div></div>').appendTo(div); | ||
}, 200); | ||
describe('containing', function () { | ||
it('eventually finds an element containing another element', function () { | ||
var promise = browser.find('.outer').containing('.inner').shouldExist(); | ||
return promise.then(function (element) { | ||
expect($(element).text()).to.equal('good'); | ||
setTimeout(function () { | ||
$('<div class="outer"><div>bad</div></div>').appendTo(div); | ||
$('<div class="outer"><div class="inner">good</div></div>').appendTo(div); | ||
}, 200); | ||
return promise; | ||
}); | ||
it('element returns the outer element', function () { | ||
var promise = browser.find('.outer').containing('.inner').element(); | ||
setTimeout(function () { | ||
$('<div class="outer"><div>bad</div></div>').appendTo(div); | ||
$('<div class="outer"><div class="inner">good</div></div>').appendTo(div); | ||
}, 200); | ||
return promise.then(function (element) { | ||
expect(element.is('.outer')).to.be.true; | ||
}); | ||
}); | ||
it("fails if it can't find an element containing another", function () { | ||
var promise = browser.find('.outer').containing('.inner').shouldExist(); | ||
setTimeout(function () { | ||
$('<div class="outer"><div>bad</div></div>').appendTo(div); | ||
}, 200); | ||
return expect(promise).to.be.rejected; | ||
}); | ||
}); | ||
it('can scope with an element', function () { | ||
var red = $('<div><div class="element">red</div></div>').appendTo(div); | ||
var blue = $('<div><div class="element">blue</div></div>').appendTo(div); | ||
describe('chains', function () { | ||
it('eventually finds the inner element, even if the outer element exists', function () { | ||
var promise = browser.find('.outer').find('.inner').shouldExist(); | ||
return browser.scope(red).find('.element').exists().then(function (element) { | ||
expect($(element).text()).to.equal('red'); | ||
}).then(function () { | ||
return browser.scope(blue).find('.element').exists(); | ||
}).then(function (element) { | ||
expect($(element).text()).to.equal('blue'); | ||
setTimeout(function () { | ||
var outer = $('<div class="outer"></div>').appendTo(div); | ||
setTimeout(function () { | ||
$('<div class="inner">good</div>').appendTo(outer); | ||
}, 200); | ||
}, 200); | ||
return promise; | ||
}); | ||
it('fails to find the inner element if it never arrives', function () { | ||
var promise = browser.find('.outer').find('.inner').shouldExist(); | ||
setTimeout(function () { | ||
var outer = $('<div class="outer"></div>').appendTo(div); | ||
}, 200); | ||
return expect(promise).to.be.rejected; | ||
}); | ||
}); | ||
it('can scope with another finder', function () { | ||
var red = $('<div class="red"><div class="element">red</div></div>').appendTo(div); | ||
var blue = $('<div class="blue"><div class="element">blue</div></div>').appendTo(div); | ||
describe('scope', function () { | ||
it('can scope with an element', function () { | ||
var red = $('<div><div class="element">red</div></div>').appendTo(div); | ||
var blue = $('<div><div class="element">blue</div></div>').appendTo(div); | ||
return browser.scope(browser.find('.red')).find('.element').exists().then(function (element) { | ||
expect($(element).text()).to.equal('red'); | ||
}).then(function () { | ||
return browser.scope(browser.find('.blue')).find('.element').exists(); | ||
}).then(function (element) { | ||
expect($(element).text()).to.equal('blue'); | ||
return browser.scope(red).find('.element').shouldExist().then(function (element) { | ||
expect($(element).text()).to.equal('red'); | ||
}).then(function () { | ||
return browser.scope(blue).find('.element').shouldExist(); | ||
}).then(function (element) { | ||
expect($(element).text()).to.equal('blue'); | ||
}); | ||
}); | ||
it('can scope with another finder', function () { | ||
var red = $('<div class="red"><div class="element">red</div></div>').appendTo(div); | ||
var blue = $('<div class="blue"><div class="element">blue</div></div>').appendTo(div); | ||
return browser.scope(browser.find('.red')).find('.element').shouldExist().then(function (element) { | ||
expect($(element).text()).to.equal('red'); | ||
}).then(function () { | ||
return browser.scope(browser.find('.blue')).find('.element').shouldExist(); | ||
}).then(function (element) { | ||
expect($(element).text()).to.equal('blue'); | ||
}); | ||
}); | ||
}); | ||
@@ -189,7 +248,5 @@ | ||
var promise = user.name().exists(); | ||
var promise = user.name().shouldExist(); | ||
setTimeout(function () { | ||
$('<div class="user"><div class="user-name">bob</div><div class="user-address">bob\'s address</div></div>').appendTo(div); | ||
}, 50); | ||
eventuallyInsertHtml('<div class="user"><div class="user-name">bob</div><div class="user-address">bob\'s address</div></div>'); | ||
@@ -216,7 +273,5 @@ return promise; | ||
var promise = admin.user().name().exists(); | ||
var promise = admin.user().name().shouldExist(); | ||
setTimeout(function () { | ||
$('<div class="user"><div class="user-name">bob</div><div class="user-address">bob\'s address</div></div>').appendTo(div); | ||
}, 50); | ||
eventuallyInsertHtml('<div class="user"><div class="user-name">bob</div><div class="user-address">bob\'s address</div></div>'); | ||
@@ -223,0 +278,0 @@ return promise; |
24048
545
104