Commit 26ceac57 authored by Oskar Gustafsson's avatar Oskar Gustafsson Committed by Sam Saccone

Improve test suite robustness. (#1646)

..by waiting for DOM states instead of checking them only once.
parent aa9b051b
'use strict'; 'use strict';
var webdriver = require('selenium-webdriver'); var webdriver = require('selenium-webdriver');
var until = require('selenium-webdriver/lib/until');
var idSelectors = true; var idSelectors = true;
var classOrId = idSelectors ? '#' : '.';
module.exports = function Page(browser) { var DEFAULT_TIMEOUT = 3000;
var REMOVED_TIMEOUT = 100;
// ----------------- utility methods var REMOVE_TEXT_KEY_SEQ = Array(51).join(webdriver.Key.BACK_SPACE + webdriver.Key.DELETE);
this.tryFindByXpath = function (xpath) { // Unique symbols
return browser.findElements(webdriver.By.xpath(xpath)); var ELEMENT_MISSING = Object.freeze({});
}; var ITEM_HIDDEN_OR_REMOVED = Object.freeze({});
this.findByXpath = function (xpath) { module.exports = function Page(browser) {
return browser.findElement(webdriver.By.xpath(xpath));
};
this.getTodoListXpath = function () { // CSS ELEMENT SELECTORS
return idSelectors ? '//ul[@id="todo-list"]' : '//ul[contains(@class, "todo-list")]';
};
this.getMainSectionXpath = function () { this.getMainSectionCss = function () { return classOrId + 'main'; };
return idSelectors ? '//section[@id="main"]' : '//section[contains(@class, "main")]';
};
this.getFooterSectionXpath = function () { this.getFooterSectionCss = function () { return 'footer' + classOrId + 'footer'; };
return idSelectors ? '//footer[@id="footer"]' : '//footer[contains(@class, "footer")]';
};
this.getCompletedButtonXpath = function () { this.getClearCompletedButtonCss = function () { return 'button' + classOrId + 'clear-completed'; };
return idSelectors ? '//button[@id="clear-completed"]' : '//button[contains(@class, "clear-completed")]';
};
this.getNewInputXpath = function () { this.getNewInputCss = function () { return 'input' + classOrId + 'new-todo'; };
return idSelectors ? '//input[@id="new-todo"]' : '//input[contains(@class,"new-todo")]';
};
this.getToggleAllXpath = function () { this.getToggleAllCss = function () { return 'input' + classOrId + 'toggle-all'; };
return idSelectors ? '//input[@id="toggle-all"]' : '//input[contains(@class,"toggle-all")]';
};
this.getCountXpath = function () { this.getItemCountCss = function () { return 'span' + classOrId + 'todo-count'; };
return idSelectors ? '//span[@id="todo-count"]' : '//span[contains(@class, "todo-count")]';
};
this.getFiltersElementXpath = function () { this.getFilterCss = function (index) { return classOrId + 'filters li:nth-of-type(' + (index + 1) + ') a'; };
return idSelectors ? '//*[@id="filters"]' : '//*[contains(@class, "filters")]';
};
this.getFilterXpathByIndex = function (index) { this.getSelectedFilterCss = function (index) { return this.getFilterCss(index) + '.selected'; };
return this.getFiltersElementXpath() + '/li[' + index + ']/a';
};
this.getSelectedFilterXPathByIndex = function (index) { this.getFilterAllCss = function () { return this.getFilterCss(0); };
return this.getFilterXpathByIndex(index) + '[contains(@class, "selected")]';
};
this.getFilterAllXpath = function () { this.getFilterActiveCss = function () { return this.getFilterCss(1); };
return this.getFilterXpathByIndex(1);
};
this.getFilterActiveXpath = function () { this.getFilterCompletedCss = function () { return this.getFilterCss(2); };
return this.getFilterXpathByIndex(2);
};
this.getFilterCompletedXpath = function () { this.getListCss = function (suffixCss) { return 'ul' + classOrId + 'todo-list' + (suffixCss || ''); };
return this.getFilterXpathByIndex(3);
};
this.xPathForItemAtIndex = function (index) { this.getListItemCss = function (index, suffixCss, excludeParentSelector) {
// why is XPath the only language silly enough to be 1-indexed? suffixCss = (index === undefined ? '' : ':nth-of-type(' + (index + 1) + ')') + (suffixCss || '');
return this.getTodoListXpath() + '/li[' + (index + 1) + ']'; return excludeParentSelector ? 'li' + suffixCss : this.getListCss(' li' + suffixCss);
}; };
// ----------------- navigation methods this.getListItemToggleCss = function (index) { return this.getListItemCss(index, ' input.toggle'); };
this.back = function () { this.getListItemLabelCss = function (index) { return this.getListItemCss(index, ' label'); };
return browser.navigate().back();
};
// ----------------- try / get methods this.getLastListItemLabelCss = function (index) { return this.getListItemCss(index, ':last-of-type label'); };
// unfortunately webdriver does not have a decent API for determining if an this.getListItemInputCss = function (index) { return this.getListItemCss(index, ' input.edit'); };
// element exists. The standard approach is to obtain an array of elements
// and test that the length is zero. These methods are used to obtain
// elements which *might* be present in the DOM, hence the try/get name.
this.tryGetMainSectionElement = function () { this.getEditingListItemInputCss = function () { return this.getListItemCss(undefined, '.editing input.edit'); };
return this.tryFindByXpath(this.getMainSectionXpath());
};
this.tryGetFooterElement = function () { // This CSS selector returns the _last_ element of a list that exactly matches the provided list of completed states
return this.tryFindByXpath(this.getFooterSectionXpath()); // It is used as a boolean test of the item states
this.getListItemsWithCompletedStatesCss = function (completedStates) {
var suffixCss = ' ' + completedStates.map(function (completed, i) {
return this.getListItemCss(i, completed ? '.completed' : ':not(.completed)', true);
}, this).join(' + ');
return this.getListCss(suffixCss);
}; };
this.tryGetClearCompleteButton = function () { // PUBLIC SYMBOLS
return this.findByXpath(this.getCompletedButtonXpath());
};
this.tryGetToggleForItemAtIndex = function (index) { this.ITEM_HIDDEN_OR_REMOVED = ITEM_HIDDEN_OR_REMOVED;
var xpath = this.xPathForItemAtIndex(index) + '//input[contains(@class,"toggle")]';
return this.findByXpath(xpath);
};
this.tryGetItemLabelAtIndex = function (index) { // NAVIGATION
return this.findByXpath(this.xPathForItemAtIndex(index) + '//label');
this.back = function () {
return browser.navigate().back();
}; };
// ----------------- DOM element access methods // ELEMENT RETREIVAL
this.getActiveElement = function () { // wait* methods guarantees to return an element, or throw an exception
return browser.switchTo().activeElement(); // get* methods may return nothing at all, or in the case of element lists, an older version of the list
this.getElements = function (css) {
return browser.findElements(webdriver.By.css(css));
}; };
this.getFocussedTagName = function () { this.waitForElement = function (css, failMsg, timeout) {
return this.getActiveElement().getTagName(); return browser.wait(until.elementLocated(webdriver.By.css(css)), timeout || DEFAULT_TIMEOUT, failMsg);
}; };
this.getFocussedElementIdOrClass = function () { this.waitForFocusedElement = function (css, failMsg) {
return this.getActiveElement() return this.waitForElement(css + ':focus', failMsg);
.getAttribute(idSelectors ? 'id' : 'class');
}; };
this.getEditInputForItemAtIndex = function (index) { this.waitForBlurredElement = function (css, failMsg) {
var xpath = this.xPathForItemAtIndex(index) + '//input[contains(@class,"edit")]'; return this.waitForElement(css + ':not(:focus)', failMsg);
return this.findByXpath(xpath);
}; };
this.getItemInputField = function () { this.waitForListItemCount = function (count) {
return this.findByXpath(this.getNewInputXpath()); var self = this;
return browser.wait(function () {
return self.waitForElement(self.getListCss())
.then(function (listElement) {
return listElement.findElements(webdriver.By.css(self.getListItemCss(undefined, undefined, true)));
})
.then(function (listItems) {
return listItems.length === count;
});
}, DEFAULT_TIMEOUT, 'Expected item list to contain ' + count + ' item' + (count === 1 ? '' : 's'));
}; };
this.getMarkAllCompletedCheckBox = function () { this.waitForClearCompleteButton = function () {
return this.findByXpath(this.getToggleAllXpath()); return this.waitForElement(this.getClearCompletedButtonCss());
}; };
this.getItemElements = function () { this.waitForToggleForItem = function (index) {
return this.tryFindByXpath(this.getTodoListXpath() + '/li'); return this.waitForElement(this.getListItemToggleCss(index));
}; };
this.getNonCompletedItemElements = function () { this.waitForItemLabel = function (index) {
return this.tryFindByXpath(this.getTodoListXpath() + '/li[not(contains(@class,"completed"))]'); return this.waitForElement(this.getListItemLabelCss(index));
}; };
this.getItemsCountElement = function () { this.waitForNewItemInputField = function () {
return this.findByXpath(this.getCountXpath()); return this.waitForElement(this.getNewInputCss());
}; };
this.getItemLabelAtIndex = function (index) { this.waitForMarkAllCompletedCheckBox = function () {
return this.findByXpath(this.xPathForItemAtIndex(index) + '//label'); return this.waitForElement(this.getToggleAllCss());
}; };
this.getItemLabels = function () { this.getListItems = function () {
var xpath = this.getTodoListXpath() + '/li//label'; return this.getElements(this.getListItemCss());
return this.tryFindByXpath(xpath);
}; };
this.getVisibleLabelText = function () { this.waitForVisibility = function (shouldBeVisible, css, failMsg) {
var self = this; if (shouldBeVisible) {
return this.getVisibileLabelIndicies() return this.waitForElement(css, failMsg)
.then(function (indicies) { .then(function (element) {
return webdriver.promise.map(indicies, function (elmIndex) { return browser.wait(until.elementIsVisible(element), DEFAULT_TIMEOUT, failMsg);
var ret;
return browser.wait(function () {
return self.tryGetItemLabelAtIndex(elmIndex).getText()
.then(function (v) {
ret = v;
return true;
})
.thenCatch(function () { return false; });
}, 5000)
.then(function () {
return ret;
});
}); });
} else {
return this.waitForElement(css, undefined, REMOVED_TIMEOUT)
.catch(function () { return ELEMENT_MISSING; })
.then(function (elementOrError) {
return elementOrError === ELEMENT_MISSING ?
ELEMENT_MISSING : // Returning a value will resolve the promise
browser.wait(until.elementIsNotVisible(elementOrError), DEFAULT_TIMEOUT, failMsg);
}); });
}
}; };
this.waitForVisibleElement = function (getElementFn, timeout) { this.waitForMainSectionRemovedOrEmpty = function () {
var foundVisibleElement; return this.waitForElement(this.getMainSectionCss(), undefined, REMOVED_TIMEOUT)
timeout = timeout || 500; .catch(function () { return ELEMENT_MISSING; })
.then(function (elementOrError) {
return elementOrError === ELEMENT_MISSING ? ELEMENT_MISSING : this.waitForListItemCount(0);
}.bind(this));
};
return browser.wait(function () { this.waitForCheckedStatus = function (shouldBeChecked, failMsg, element) {
foundVisibleElement = getElementFn(); var condition = shouldBeChecked ? 'elementIsSelected' : 'elementIsNotSelected';
return foundVisibleElement.isDisplayed(); return browser.wait(until[condition](element), DEFAULT_TIMEOUT, failMsg);
}, timeout) };
.then(function () {
return foundVisibleElement;
})
.thenCatch(function (err) {
return false;
});
}
this.getVisibileLabelIndicies = function () { this.waitForTextContent = function (text, failMsg, element) {
var self = this; return browser.wait(until.elementTextIs(element, text), DEFAULT_TIMEOUT, failMsg);
return this.getItemLabels()
.then(function (elms) {
return elms.map(function (elm, i) {
return i;
});
})
.then(function (elms) {
return webdriver.promise.filter(elms, function (elmIndex) {
return self.waitForVisibleElement(function () {
return self.tryGetItemLabelAtIndex(elmIndex);
});
});
});
}; };
// ----------------- page actions // PAGE ACTIONS
this.ensureAppIsVisible = function () {
var self = this;
return browser.wait(function () {
// try to find main element by ID
return browser.isElementPresent(webdriver.By.css('.new-todo'))
.then(function (foundByClass) {
if (foundByClass) {
idSelectors = false;
return true;
}
// try to find main element by CSS class this.ensureAppIsVisibleAndLoaded = function () {
return browser.isElementPresent(webdriver.By.css('#new-todo')); return this.waitForVisibility(false, this.getFooterSectionCss(), 'Footer is not hidden') // Footer hidden -> app is active
}); .then(this.waitForElement.bind(this, '.new-todo, #new-todo', 'Could not find new todo input field', undefined))
}, 5000) .then(function (newTodoElement) {
.then(function (hasFoundNewTodoElement) { return newTodoElement.getAttribute('id');
if (!hasFoundNewTodoElement) { })
throw new Error('Unable to find application, did you start your local server?'); .then(function (newTodoElementId) {
} if (newTodoElementId === 'new-todo') { return; }
idSelectors = false;
classOrId = idSelectors ? '#' : '.';
}); });
}; };
this.clickMarkAllCompletedCheckBox = function () { this.clickMarkAllCompletedCheckBox = function () {
return this.getMarkAllCompletedCheckBox().then(function (checkbox) { return this.waitForMarkAllCompletedCheckBox().click();
return checkbox.click();
});
}; };
this.clickClearCompleteButton = function () { this.clickClearCompleteButton = function () {
var self = this; return this.waitForVisibility(true, this.getClearCompletedButtonCss(), 'Expected clear completed items button to be visible')
return self.waitForVisibleElement(function () {
return self.tryGetClearCompleteButton();
})
.then(function (clearCompleteButton) { .then(function (clearCompleteButton) {
return clearCompleteButton.click(); clearCompleteButton.click();
}); });
}; };
this.enterItem = function (itemText) { this.enterItem = function (itemText) {
var self = this; var self = this;
var nItems;
return browser.wait(function () { return self.getListItems()
var textField; .then(function (items) {
nItems = items.length;
return self.getItemInputField().then(function (itemInputField) {
textField = itemInputField;
return textField.sendKeys(itemText, webdriver.Key.ENTER);
}) })
.then(function () { return self.getVisibleLabelText(); }) .then(this.waitForNewItemInputField.bind(this))
.then(function (labels) { .then(function (newItemInput) {
if (labels.indexOf(itemText.trim()) >= 0) { return newItemInput.sendKeys(itemText).then(function () { return newItemInput; });
return true; })
} .then(function (newItemInput) {
return browser.wait(function () {
return textField.clear().then(function () { // Hit Enter repeatedly until the text goes away
return false; return newItemInput.sendKeys(webdriver.Key.ENTER)
}); .then(newItemInput.getAttribute.bind(newItemInput, 'value'))
.then(function (newItemInputValue) {
return newItemInputValue.length === 0;
}); });
}, 5000); }, DEFAULT_TIMEOUT);
})
.then(function () {
return self.waitForElement(self.getLastListItemLabelCss(nItems));
})
.then(this.waitForTextContent.bind(this, itemText.trim(), 'Expected new item label to read ' + itemText.trim()));
}; };
this.toggleItemAtIndex = function (index) { this.toggleItemAtIndex = function (index) {
return this.tryGetToggleForItemAtIndex(index).click(); return this.waitForToggleForItem(index).click();
}; };
this.editItemAtIndex = function (index, itemText) { this.editItemAtIndex = function (index, itemText) {
return this.getEditInputForItemAtIndex(index) return this.waitForElement(this.getListItemInputCss(index))
.then(function (itemEditField) { .then(function (itemEditField) {
// send 50 delete keypresses, just to be sure the item text is deleted return itemEditField.sendKeys(REMOVE_TEXT_KEY_SEQ, itemText);
var deleteKeyPresses = '';
for (var i = 0; i < 50; i++) {
deleteKeyPresses += webdriver.Key.BACK_SPACE;
deleteKeyPresses += webdriver.Key.DELETE;
}
itemEditField.sendKeys(deleteKeyPresses);
// update the item with the new text.
itemEditField.sendKeys(itemText);
}); });
}; };
this.doubleClickItemAtIndex = function (index) { this.doubleClickItemAtIndex = function (index) {
return this.getItemLabelAtIndex(index).then(function (itemLabel) { return this.waitForItemLabel(index).then(function (itemLabel) {
// double click is not 'natively' supported, so we need to send the // double click is not 'natively' supported, so we need to send the event direct to the element, see:
// event direct to the element see:
// jscs:disable // jscs:disable
// http://stackoverflow.com/questions/3982442/selenium-2-webdriver-how-to-double-click-a-table-row-which-opens-a-new-window // http://stackoverflow.com/questions/3982442/selenium-2-webdriver-how-to-double-click-a-table-row-which-opens-a-new-window
// jscs:enable // jscs:enable
browser.executeScript('var evt = document.createEvent("MouseEvents");' + return browser.executeScript('var evt = document.createEvent("MouseEvents");' +
'evt.initMouseEvent("dblclick",true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0,null);' + 'evt.initMouseEvent("dblclick",true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0,null);' +
'arguments[0].dispatchEvent(evt);', itemLabel); 'arguments[0].dispatchEvent(evt);', itemLabel);
}); });
}; };
this.filterBy = function (selectorFn) { this.filterBy = function (filterCss) {
var self = this; return this.waitForElement(filterCss)
.click()
return browser.wait(function () { .then(this.waitForElement.bind(this, filterCss + '.selected', undefined, undefined));
return self.findByXpath(selectorFn()).click()
.then(function () {
return self.findByXpath(selectorFn()).getAttribute('class');
})
.then(function (klass) {
return klass.indexOf('selected') !== -1;
});
}, 5000);
}; };
this.filterByActiveItems = function () { this.filterByActiveItems = function () {
return this.filterBy(this.getFilterActiveXpath.bind(this)); return this.filterBy(this.getFilterActiveCss());
}; };
this.filterByCompletedItems = function () { this.filterByCompletedItems = function () {
return this.filterBy(this.getFilterCompletedXpath.bind(this)); return this.filterBy(this.getFilterCompletedCss());
}; };
this.filterByAllItems = function () { this.filterByAllItems = function () {
return this.filterBy(this.getFilterAllXpath.bind(this)); return this.filterBy(this.getFilterAllCss());
}; };
}; };
'use strict'; 'use strict';
var webdriver = require('selenium-webdriver');
var Page = require('./page'); var Page = require('./page');
module.exports = function PageLaxMode(browser) { module.exports = function PageLaxMode() {
Page.apply(this, [browser]);
Page.apply(this, arguments);
this.tryGetMainSectionElement = function () { this.getMainSectionCss = function () {
return this.tryFindByXpath('//section//section'); return 'section section';
}; };
this.tryGetFooterElement = function () { this.getFooterSectionCss = function () {
return this.tryFindByXpath('//section//footer'); return 'section footer';
}; };
this.getTodoListXpath = function () { this.getListCss = function (suffixCss) {
return '(//section/ul | //section/div/ul | //ul[@id="todo-list"])'; return [
'section > ul',
'section > div > ul',
'ul#todo-list',
].map(function (listCss) {
return listCss + (suffixCss || '');
}).join(', ');
}; };
this.getMarkAllCompletedCheckBox = function () { this.getToggleAllCss = function () {
var xpath = '(//section/input[@type="checkbox"] | //section/*/input[@type="checkbox"] | //input[@id="toggle-all"])'; return [
return browser.findElement(webdriver.By.xpath(xpath)); 'section > input[type="checkbox"]',
'section > * > input[type="checkbox"]',
'input#toggle-all',
].join(', ');
}; };
this.tryGetClearCompleteButton = function () { this.getClearCompletedButtonCss = function () {
var xpath = '(//footer/button | //footer/*/button | //button[@id="clear-completed"])'; return [
return browser.findElements(webdriver.By.xpath(xpath)); 'footer > button',
'footer > * > button',
'button#clear-completed',
].join(', ');
}; };
this.getItemsCountElement = function () { this.getItemCountCss = function () {
var xpath = '(//footer/span | //footer/*/span)'; return [
return browser.findElement(webdriver.By.xpath(xpath)); 'footer > span',
'footer > * > span',
].join(', ');
}; };
this.getFilterElements = function () { this.getFilterCss = function (index) {
return browser.findElements(webdriver.By.xpath('//footer//ul//a')); return 'footer ul li:nth-child(' + (index + 1) + ') a';
}; };
this.getNewInputCss = function () {
this.getItemInputField = function () { return [
// allow a more generic method for locating the text getItemInputField 'header > input',
var xpath = '(//header/input | //header/*/input | //input[@id="new-todo"])'; 'header > * > input',
return browser.findElement(webdriver.By.xpath(xpath)); 'input#new-todo',
].join(', ');
}; };
this.tryGetToggleForItemAtIndex = function (index) { this.getListItemToggleCss = function (index) {
// the specification dictates that the checkbox should have the 'toggle' CSS class. Some implementations deviate from return this.getListItemCss(index, ' input[type="checkbox"]');
// this, hence in lax mode we simply look for any checkboxes within the specified 'li'.
var xpath = this.xPathForItemAtIndex(index) + '//input[@type="checkbox"]';
return browser.findElements(webdriver.By.xpath(xpath));
}; };
this.getEditInputForItemAtIndex = function (index) { this.getListItemInputCss = function (index) {
// the specification dictates that the input element that allows the user to edit a todo item should have a CSS return [
// class of 'edit'. In lax mode, we also look for an input of type 'text'. this.getListItemCss(index, ' input.edit'),
var xpath = '(' + this.xPathForItemAtIndex(index) + '//input[@type="text"]' + '|' + this.getListItemCss(index, ' input[type="text"]'),
this.xPathForItemAtIndex(index) + '//input[contains(@class,"edit")]' + ')'; ].join(', ');
return browser.findElement(webdriver.By.xpath(xpath));
}; };
}; };
...@@ -8,7 +8,9 @@ var PageLaxMode = require('./pageLaxMode'); ...@@ -8,7 +8,9 @@ var PageLaxMode = require('./pageLaxMode');
var TestOperations = require('./testOperations'); var TestOperations = require('./testOperations');
module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMode, browserName) { module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMode, browserName) {
test.describe('TodoMVC - ' + frameworkName, function () { test.describe('TodoMVC - ' + frameworkName, function () {
var TODO_ITEM_ONE = 'buy some cheese'; var TODO_ITEM_ONE = 'buy some cheese';
var TODO_ITEM_TWO = 'feed the cat'; var TODO_ITEM_TWO = 'feed the cat';
var TODO_ITEM_THREE = 'book a doctors appointment'; var TODO_ITEM_THREE = 'book a doctors appointment';
...@@ -22,7 +24,7 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -22,7 +24,7 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
.then(function () { .then(function () {
if (done instanceof Function) { if (done instanceof Function) {
done(); done();
}; }
}); });
} }
...@@ -44,11 +46,11 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -44,11 +46,11 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
page = laxMode ? new PageLaxMode(browser) : new Page(browser); page = laxMode ? new PageLaxMode(browser) : new Page(browser);
testOps = new TestOperations(page); testOps = new TestOperations(page);
return page.ensureAppIsVisible() return page.ensureAppIsVisibleAndLoaded()
.then(function () { .then(function () {
if (done instanceof Function) { if (done instanceof Function) {
done(); done();
}; }
}); });
} }
...@@ -67,30 +69,15 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -67,30 +69,15 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
return browser return browser
.quit() .quit()
.then(function () { .then(function () {
if (done instanceof Function) { if (done instanceof Function) { done(); }
done();
};
}); });
} }
if (speedMode) { if (speedMode) {
test.before(launchBrowser); test.before(launchBrowser);
test.after(closeBrowser); test.after(closeBrowser);
test.beforeEach(function (done) { test.afterEach(function (done) {
return page.getItemElements() return browser.executeScript('window.localStorage && localStorage.clear(); location.reload(true);')
.then(function (items) {
if (items.length == 0) { return; }
// find any items that are not complete
page.getNonCompletedItemElements()
.then(function (nonCompleteItems) {
if (nonCompleteItems.length > 0) {
return page.clickMarkAllCompletedCheckBox();
}
})
return page.clickClearCompleteButton();
})
.then(function () { done(); }); .then(function () { done(); });
}); });
} else { } else {
...@@ -99,38 +86,43 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -99,38 +86,43 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
printCapturedLogs() printCapturedLogs()
.then(function () { .then(function () {
return closeBrowser(done); return closeBrowser(done);
}) });
}); });
} }
test.describe('When page is initially opened', function () { test.describe('When page is initially opened', function () {
test.it('should focus on the todo input field', function (done) { test.it('should focus on the todo input field', function (done) {
testOps.assertFocussedElement('new-todo') testOps.assertNewInputFocused()
.then(function () { done(); }); .then(function () { done(); });
}); });
}); });
test.describe('No Todos', function () { test.describe('No Todos', function () {
test.it('should hide #main and #footer', function (done) { test.it('should hide #main and #footer', function (done) {
testOps.assertItemCount(0); testOps.assertItemCount(0);
testOps.assertMainSectionIsHidden(); testOps.assertMainSectionVisibility(false);
testOps.assertFooterIsHidden() testOps.assertFooterVisibility(false)
.then(function () { done(); }); .then(function () { done(); });
}); });
}); });
test.describe('New Todo', function () { test.describe('New Todo', function () {
test.it('should allow me to add todo items', function (done) { test.it('should allow me to add todo items', function (done) {
page.enterItem(TODO_ITEM_ONE); page.enterItem(TODO_ITEM_ONE);
testOps.assertItems([TODO_ITEM_ONE]); testOps.assertItems([ TODO_ITEM_ONE ]);
page.enterItem(TODO_ITEM_TWO); page.enterItem(TODO_ITEM_TWO);
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_TWO]) testOps.assertItems([ TODO_ITEM_ONE, TODO_ITEM_TWO ])
.then(function () { done(); }); .then(function () { done(); });
}); });
test.it('should clear text input field when an item is added', function (done) { test.it('should clear text input field when an item is added', function (done) {
page.enterItem(TODO_ITEM_ONE); page.enterItem(TODO_ITEM_ONE);
testOps.assertItemInputFieldText('') testOps.assertNewItemInputFieldText('')
.then(function () { done(); }); .then(function () { done(); });
}); });
...@@ -151,20 +143,20 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -151,20 +143,20 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
test.it('should show #main and #footer when items added', function (done) { test.it('should show #main and #footer when items added', function (done) {
page.enterItem(TODO_ITEM_ONE); page.enterItem(TODO_ITEM_ONE);
testOps.assertMainSectionIsVisible(); testOps.assertMainSectionVisibility(true);
testOps.assertFooterIsVisible() testOps.assertFooterVisibility(true)
.then(function () { done(); }); .then(function () { done(); });
}); });
}); });
test.describe('Mark all as completed', function () { test.describe('Mark all as completed', function () {
test.beforeEach(createStandardItems); test.beforeEach(createStandardItems);
test.it('should allow me to mark all items as completed', function (done) { test.it('should allow me to mark all items as completed', function (done) {
page.clickMarkAllCompletedCheckBox(); page.clickMarkAllCompletedCheckBox();
testOps.assertItemAtIndexIsCompleted(0); testOps.assertItemCompletedStates([ true, true, true ])
testOps.assertItemAtIndexIsCompleted(1);
testOps.assertItemAtIndexIsCompleted(2)
.then(function () { done(); }); .then(function () { done(); });
}); });
...@@ -175,7 +167,7 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -175,7 +167,7 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
page.toggleItemAtIndex(2); page.toggleItemAtIndex(2);
// ensure checkall is in the correct state // ensure checkall is in the correct state
testOps.assertCompleteAllIsChecked() testOps.assertCompleteAllCheckedStatus(true)
.then(function () { done(); }); .then(function () { done(); });
}); });
...@@ -183,39 +175,37 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -183,39 +175,37 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
page.clickMarkAllCompletedCheckBox(); page.clickMarkAllCompletedCheckBox();
page.clickMarkAllCompletedCheckBox(); page.clickMarkAllCompletedCheckBox();
testOps.assertItemAtIndexIsNotCompleted(0); testOps.assertItemCompletedStates([ false, false, false ])
testOps.assertItemAtIndexIsNotCompleted(1);
testOps.assertItemAtIndexIsNotCompleted(2)
.then(function () { done(); }); .then(function () { done(); });
}); });
test.it('complete all checkbox should update state when items are completed / cleared', function (done) { test.it('complete all checkbox should update state when items are completed / cleared', function (done) {
page.clickMarkAllCompletedCheckBox(); page.clickMarkAllCompletedCheckBox();
testOps.assertCompleteAllIsChecked(); testOps.assertCompleteAllCheckedStatus(true);
// all items are complete, now mark one as not-complete // all items are complete, now mark one as not-complete
page.toggleItemAtIndex(0); page.toggleItemAtIndex(0);
testOps.assertCompleteAllIsClear(); testOps.assertCompleteAllCheckedStatus(false);
// now mark as complete, so that once again all items are completed // now mark as complete, so that once again all items are completed
page.toggleItemAtIndex(0); page.toggleItemAtIndex(0);
testOps.assertCompleteAllIsChecked() testOps.assertCompleteAllCheckedStatus(true)
.then(function () { done(); }); .then(function () { done(); });
}); });
}); });
test.describe('Item', function () { test.describe('Item', function () {
test.it('should allow me to mark items as complete', function (done) { test.it('should allow me to mark items as complete', function (done) {
page.enterItem(TODO_ITEM_ONE); page.enterItem(TODO_ITEM_ONE);
page.enterItem(TODO_ITEM_TWO); page.enterItem(TODO_ITEM_TWO);
page.toggleItemAtIndex(0); page.toggleItemAtIndex(0);
testOps.assertItemAtIndexIsCompleted(0); testOps.assertItemCompletedStates([ true, false ]);
testOps.assertItemAtIndexIsNotCompleted(1);
page.toggleItemAtIndex(1); page.toggleItemAtIndex(1);
testOps.assertItemAtIndexIsCompleted(0); testOps.assertItemCompletedStates([ true, true ])
testOps.assertItemAtIndexIsCompleted(1)
.then(function () { done(); }); .then(function () { done(); });
}); });
...@@ -224,17 +214,17 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -224,17 +214,17 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
page.enterItem(TODO_ITEM_TWO); page.enterItem(TODO_ITEM_TWO);
page.toggleItemAtIndex(0); page.toggleItemAtIndex(0);
testOps.assertItemAtIndexIsCompleted(0); testOps.assertItemCompletedStates([ true, false ]);
testOps.assertItemAtIndexIsNotCompleted(1);
page.toggleItemAtIndex(0); page.toggleItemAtIndex(0);
testOps.assertItemAtIndexIsNotCompleted(0); testOps.assertItemCompletedStates([ false, false ])
testOps.assertItemAtIndexIsNotCompleted(1)
.then(function () { done(); }); .then(function () { done(); });
}); });
}); });
test.describe('Editing', function (done) { test.describe('Editing', function () {
test.beforeEach(function (done) { test.beforeEach(function (done) {
createStandardItems(); createStandardItems();
page.doubleClickItemAtIndex(1) page.doubleClickItemAtIndex(1)
...@@ -242,8 +232,8 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -242,8 +232,8 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
}); });
test.it('should focus the input', function (done) { test.it('should focus the input', function (done) {
testOps.assertInputFocused(); testOps.assertItemInputFocused();
testOps.assertNewInputNotFocused() testOps.assertNewInputBlurred() // Unnecessary? The HTML spec dictates that only one element can be focused.
.then(function () { done(); }); .then(function () { done(); });
}); });
...@@ -255,7 +245,7 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -255,7 +245,7 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
test.it('should save edits on enter', function (done) { test.it('should save edits on enter', function (done) {
page.editItemAtIndex(1, 'buy some sausages' + webdriver.Key.ENTER); page.editItemAtIndex(1, 'buy some sausages' + webdriver.Key.ENTER);
testOps.assertItems([TODO_ITEM_ONE, 'buy some sausages', TODO_ITEM_THREE]) testOps.assertItems([ TODO_ITEM_ONE, 'buy some sausages', TODO_ITEM_THREE ])
.then(function () { done(); }); .then(function () { done(); });
}); });
...@@ -263,30 +253,32 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -263,30 +253,32 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
page.editItemAtIndex(1, 'buy some sausages'); page.editItemAtIndex(1, 'buy some sausages');
// click a toggle button so that the blur() event is fired // click a toggle button so that the blur() event is fired
page.toggleItemAtIndex(0); page.toggleItemAtIndex(0);
testOps.assertItems([TODO_ITEM_ONE, 'buy some sausages', TODO_ITEM_THREE]) testOps.assertItems([ TODO_ITEM_ONE, 'buy some sausages', TODO_ITEM_THREE ])
.then(function () { done(); }); .then(function () { done(); });
}); });
test.it('should trim entered text', function (done) { test.it('should trim entered text', function (done) {
page.editItemAtIndex(1, ' buy some sausages ' + webdriver.Key.ENTER); page.editItemAtIndex(1, ' buy some sausages ' + webdriver.Key.ENTER);
testOps.assertItems([TODO_ITEM_ONE, 'buy some sausages', TODO_ITEM_THREE]) testOps.assertItems([ TODO_ITEM_ONE, 'buy some sausages', TODO_ITEM_THREE ])
.then(function () { done(); }); .then(function () { done(); });
}); });
test.it('should remove the item if an empty text string was entered', function (done) { test.it('should remove the item if an empty text string was entered', function (done) {
page.editItemAtIndex(1, webdriver.Key.ENTER); page.editItemAtIndex(1, webdriver.Key.ENTER);
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_THREE]) testOps.assertItems([ TODO_ITEM_ONE, TODO_ITEM_THREE ])
.then(function () { done(); }); .then(function () { done(); });
}); });
test.it('should cancel edits on escape', function (done) { test.it('should cancel edits on escape', function (done) {
page.editItemAtIndex(1, 'foo' + webdriver.Key.ESCAPE); page.editItemAtIndex(1, 'foo' + webdriver.Key.ESCAPE);
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_TWO, TODO_ITEM_THREE]) testOps.assertItems([ TODO_ITEM_ONE, TODO_ITEM_TWO, TODO_ITEM_THREE ])
.then(function () { done(); }); .then(function () { done(); });
}); });
}); });
test.describe('Counter', function () { test.describe('Counter', function () {
test.it('should display the current number of todo items', function (done) { test.it('should display the current number of todo items', function (done) {
page.enterItem(TODO_ITEM_ONE); page.enterItem(TODO_ITEM_ONE);
testOps.assertItemCountText('1 item left'); testOps.assertItemCountText('1 item left');
...@@ -294,9 +286,11 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -294,9 +286,11 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
testOps.assertItemCountText('2 items left') testOps.assertItemCountText('2 items left')
.then(function () { done(); }); .then(function () { done(); });
}); });
}); });
test.describe('Clear completed button', function () { test.describe('Clear completed button', function () {
test.beforeEach(createStandardItems); test.beforeEach(createStandardItems);
test.it('should display the correct text', function (done) { test.it('should display the correct text', function (done) {
...@@ -309,32 +303,27 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -309,32 +303,27 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
page.toggleItemAtIndex(1); page.toggleItemAtIndex(1);
page.clickClearCompleteButton(); page.clickClearCompleteButton();
testOps.assertItemCount(2); testOps.assertItemCount(2);
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_THREE]) testOps.assertItems([ TODO_ITEM_ONE, TODO_ITEM_THREE ])
.then(function () { done(); }); .then(function () { done(); });
}); });
test.it('should be hidden when there are no items that are completed', function (done) { test.it('should be hidden when there are no items that are completed', function (done) {
page.toggleItemAtIndex(1); page.toggleItemAtIndex(1);
testOps.assertClearCompleteButtonIsVisible(); testOps.assertClearCompleteButtonVisibility(true);
page.clickClearCompleteButton(); page.clickClearCompleteButton();
testOps.assertClearCompleteButtonIsHidden() testOps.assertClearCompleteButtonVisibility(false)
.then(function () { done(); }); .then(function () { done(); });
}); });
}); });
test.describe('Persistence', function () { test.describe('Persistence', function () {
test.it('should persist its data', function (done) { test.it('should persist its data', function (done) {
function stateTest() { function stateTest() {
// wait until things are visible testOps.assertItemCount(2);
browser.wait(function () { testOps.assertItems([ TODO_ITEM_ONE, TODO_ITEM_TWO ]);
return page.getVisibleLabelText().then(function (labels) { return testOps.assertItemCompletedStates([ false, true ]);
return labels.length > 0;
});
}, 5000);
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_TWO]);
testOps.assertItemAtIndexIsCompleted(1);
return testOps.assertItemAtIndexIsNotCompleted(0);
} }
// set up state // set up state
...@@ -351,15 +340,17 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -351,15 +340,17 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
stateTest() stateTest()
.then(function () { done(); }); .then(function () { done(); });
}); });
}); });
test.describe('Routing', function () { test.describe('Routing', function () {
test.beforeEach(createStandardItems); test.beforeEach(createStandardItems);
test.it('should allow me to display active items', function (done) { test.it('should allow me to display active items', function (done) {
page.toggleItemAtIndex(1); page.toggleItemAtIndex(1);
page.filterByActiveItems(); page.filterByActiveItems();
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_THREE]) testOps.assertItems([ TODO_ITEM_ONE, page.ITEM_HIDDEN_OR_REMOVED, TODO_ITEM_THREE ])
.then(function () { return done(); }); .then(function () { return done(); });
}); });
...@@ -367,18 +358,18 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -367,18 +358,18 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
page.toggleItemAtIndex(1); page.toggleItemAtIndex(1);
page.filterByActiveItems(); page.filterByActiveItems();
page.filterByCompletedItems(); page.filterByCompletedItems();
testOps.assertItems([TODO_ITEM_TWO]);// should show completed items testOps.assertItems([ page.ITEM_HIDDEN_OR_REMOVED, TODO_ITEM_TWO ]); // should show completed items
page.back(); // then active items page.back(); // then active items
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_THREE]); testOps.assertItems([ TODO_ITEM_ONE, page.ITEM_HIDDEN_OR_REMOVED, TODO_ITEM_THREE ]);
page.back(); // then all items page.back(); // then all items
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_TWO, TODO_ITEM_THREE]) testOps.assertItems([ TODO_ITEM_ONE, TODO_ITEM_TWO, TODO_ITEM_THREE ])
.then(function () { done(); }); .then(function () { done(); });
}); });
test.it('should allow me to display completed items', function (done) { test.it('should allow me to display completed items', function (done) {
page.toggleItemAtIndex(1); page.toggleItemAtIndex(1);
page.filterByCompletedItems(); page.filterByCompletedItems();
testOps.assertItems([TODO_ITEM_TWO]); testOps.assertItems([ page.ITEM_HIDDEN_OR_REMOVED, TODO_ITEM_TWO ]);
page.filterByAllItems() // TODO: why page.filterByAllItems() // TODO: why
.then(function () { done(); }); .then(function () { done(); });
}); });
...@@ -390,7 +381,7 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -390,7 +381,7 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
page.filterByActiveItems(); page.filterByActiveItems();
page.filterByCompletedItems(); page.filterByCompletedItems();
page.filterByAllItems(); page.filterByAllItems();
testOps.assertItems([TODO_ITEM_ONE, TODO_ITEM_TWO, TODO_ITEM_THREE]) testOps.assertItems([ TODO_ITEM_ONE, TODO_ITEM_TWO, TODO_ITEM_THREE ])
.then(function () { done(); }); .then(function () { done(); });
}); });
...@@ -403,6 +394,9 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod ...@@ -403,6 +394,9 @@ module.exports.todoMVCTest = function (frameworkName, baseUrl, speedMode, laxMod
testOps.assertFilterAtIndexIsSelected(2) testOps.assertFilterAtIndexIsSelected(2)
.then(function () { done(); }); .then(function () { done(); });
}); });
}); });
}); });
}; };
'use strict'; 'use strict';
var assert = require('assert');
function TestOperations(page) { function TestOperations(page) {
// unfortunately webdriver does not have a decent API for determining if an
// element exists. The standard approach is to obtain an array of elements
// and test that the length is zero. In this case the item is hidden if
// it is either not in the DOM, or is in the DOM but not visible.
function testIsHidden(elements, name) {
if (elements.length === 1) {
elements[0].isDisplayed().then(function (isDisplayed) {
assert(!isDisplayed, 'the ' + name + ' element should be hidden');
});
}
}
function testIsVisible(elements, name) {
assert.equal(elements.length, 1);
return elements[0].isDisplayed()
.then(function (isDisplayed) {
assert(isDisplayed, 'the ' + name + ' element should be displayed');
});
}
this.assertNewInputNotFocused = function () { this.assertItemInputFocused = function () {
return page.getFocussedElementIdOrClass() return page.waitForFocusedElement(page.getEditingListItemInputCss(), 'Expected the item input to be focused');
.then(function (focussedElementIdOrClass) {
assert.equal(focussedElementIdOrClass.indexOf('new-todo'), -1);
});
}; };
this.assertInputFocused = function () { this.assertNewInputFocused = function () {
return page.getFocussedTagName() return page.waitForFocusedElement(page.getNewInputCss());
.then(function (name) {
assert.equal(name, 'input', 'input does not have focus');
});
}; };
this.assertFocussedElement = function (expectedIdentifier) { this.assertNewInputBlurred = function () {
return page.getFocussedElementIdOrClass() return page.waitForBlurredElement(page.getNewInputCss());
.then(function (focusedElementIdentifier) {
var failMsg = 'The focused element did not have the expected class or id "' + expectedIdentifier + '"';
assert.notEqual(focusedElementIdentifier.indexOf(expectedIdentifier), -1, failMsg);
});
};
this.assertClearCompleteButtonIsHidden = function () {
return page.tryGetClearCompleteButton()
.then(function (element) {
return testIsHidden(element, 'clear completed items button');
}, function (_error) {
assert(_error.code === 7, 'error accessing clear completed items button, error: ' + _error.message);
});
};
this.assertClearCompleteButtonIsVisible = function () {
return page.waitForVisibleElement(function () {
return page.tryGetClearCompleteButton();
})
.then(function (clearCompleteButton) {
assert(clearCompleteButton, 'the clear completed items button element should be displayed');
});
}; };
this.assertItemCount = function (itemCount) { this.assertItemCount = function (itemCount) {
return page.getItemElements() return itemCount === 0 ?
.then(function (toDoItems) { page.waitForMainSectionRemovedOrEmpty() :
assert.equal(toDoItems.length, itemCount, page.waitForListItemCount(itemCount);
itemCount + ' items expected in the todo list, ' + toDoItems.length + ' items observed');
});
}; };
this.assertClearCompleteButtonText = function (buttonText) { this.assertClearCompleteButtonText = function (buttonText) {
return page.waitForVisibleElement(function () { return page.waitForClearCompleteButton()
return page.tryGetClearCompleteButton(); .then(page.waitForTextContent.bind(page, buttonText, 'Expected clear button text to be ' + buttonText));
})
.then(function (clearCompleteButton) {
return clearCompleteButton.getText();
})
.then(function (text) {
return assert.equal(text, buttonText);
});
}; };
this.assertMainSectionIsHidden = function () { this.assertClearCompleteButtonVisibility = function (shouldBeVisible) {
return page.tryGetMainSectionElement() var failMsg = 'Expected the clear completed items button to be ' + (shouldBeVisible ? 'visible' : 'hidden');
.then(function (mainSection) { return page.waitForVisibility(shouldBeVisible, page.getClearCompletedButtonCss(), failMsg);
return testIsHidden(mainSection, 'main');
});
}; };
this.assertFooterIsHidden = function () { this.assertMainSectionVisibility = function (shouldBeVisible) {
return page.tryGetFooterElement() var failMsg = 'Expected main section to be ' + (shouldBeVisible ? 'visible' : 'hidden');
.then(function (footer) { return page.waitForVisibility(shouldBeVisible, page.getMainSectionCss(), failMsg);
return testIsHidden(footer, 'footer');
});
}; };
this.assertMainSectionIsVisible = function () { this.assertFooterVisibility = function (shouldBeVisible) {
return page.tryGetMainSectionElement() var failMsg = 'Expected footer to be ' + (shouldBeVisible ? 'visible' : 'hidden');
.then(function (mainSection) { return page.waitForVisibility(shouldBeVisible, page.getFooterSectionCss(), failMsg);
return testIsVisible(mainSection, 'main');
});
}; };
//TODO: fishy!
this.assertItemToggleIsHidden = function (index) { this.assertItemToggleIsHidden = function (index) {
return page.tryGetToggleForItemAtIndex(index) return page.waitForVisibility(false, page.getListItemToggleCss(index), 'Expected the item toggle button to be hidden');
.then(function (toggleItem) {
return testIsHidden(toggleItem, 'item-toggle');
});
}; };
this.assertItemLabelIsHidden = function (index) { this.assertItemLabelIsHidden = function (index) {
return page.tryGetItemLabelAtIndex(index) return page.waitForVisibility(false, page.getListItemLabelCss(index), 'Expected the item label to be hidden');
.then(function (toggleItem) {
return testIsHidden(toggleItem, 'item-label');
});
}; };
this.assertFooterIsVisible = function () { this.assertNewItemInputFieldText = function (text) {
return page.tryGetFooterElement() return page.waitForNewItemInputField()
.then(function (footer) { .then(page.waitForTextContent.bind(page, text, 'Expected the new item input text field contents to be ' + text));
return testIsVisible(footer, 'footer');
});
}; };
this.assertItemInputFieldText = function (text) { this.assertItemText = function (itemIndex, text) {
return page.getItemInputField().getText() return page.waitForItemLabel(itemIndex)
.then(function (inputFieldText) { .then(page.waitForTextContent.bind(page, text, 'Expected the item label to be ' + text));
assert.equal(inputFieldText, text);
});
}; };
this.assertItemText = function (itemIndex, textToAssert) {
return page.getItemLabelAtIndex(itemIndex).getText()
.then(function (text) {
assert.equal(text, textToAssert,
'A todo item with text \'' + textToAssert + '\' was expected at index ' +
itemIndex + ', the text \'' + text + '\' was observed');
});
};
// tests that the list contains the following items, independant of order
this.assertItems = function (textArray) { this.assertItems = function (textArray) {
return page.getVisibleLabelText() return page.getListItems().then(function (items) {
.then(function (visibleText) { if (items.length < textArray.length) {
assert.deepEqual(visibleText.sort(), textArray.sort()); // This means that the framework removes rather than hides list items
textArray = textArray.filter(function (item) { return item !== page.ITEM_HIDDEN_OR_REMOVED; });
}
var ret;
textArray.forEach(function (text, i) {
if (text === page.ITEM_HIDDEN_OR_REMOVED) { return; }
var promise = page.waitForTextContent(text, 'Expected item text to be ' + text, items[i]);
ret = ret ? ret.then(promise) : promise;
}); });
}; return ret;
this.assertItemCountText = function (textToAssert) {
return page.getItemsCountElement().getText()
.then(function (text) {
assert.equal(text.trim(), textToAssert, 'the item count text was incorrect');
}); });
}; };
// tests for the presence of the 'completed' CSS class for the item at the given index this.assertItemCountText = function (text) {
this.assertItemAtIndexIsCompleted = function (index) { return page.waitForElement(page.getItemCountCss())
return page.getItemElements() .then(page.waitForTextContent.bind(page, text, 'Expected item count text to be ' + text));
.then(function (toDoItems) {
return toDoItems[index].getAttribute('class');
})
.then(function (cssClass) {
var failMsg = 'the item at index ' + index + ' should have been marked as completed';
assert(cssClass.indexOf('completed') !== -1, failMsg);
});
}; };
this.assertItemAtIndexIsNotCompleted = function (index) { this.assertItemCompletedStates = function (completedStates) {
return page.getItemElements() return page.waitForElement(
.then(function (toDoItems) { page.getListItemsWithCompletedStatesCss(completedStates),
return toDoItems[index].getAttribute('class'); 'Item completed states were incorrect');
})
.then(function (cssClass) {
// the maria implementation uses an 'incompleted' CSS class which is redundant
// TODO: this should really be moved into the pageLaxMode
var failMsg = 'the item at index ' + index + ' should not have been marked as completed';
assert(cssClass.indexOf('completed') === -1 || cssClass.indexOf('incompleted') !== -1, failMsg);
});
}; };
this.assertFilterAtIndexIsSelected = function (selectedIndex) { this.assertFilterAtIndexIsSelected = function (selectedIndex) {
return page.findByXpath(page.getSelectedFilterXPathByIndex(selectedIndex + 1)) return page.waitForElement(
.then(function (elm) { page.getSelectedFilterCss(selectedIndex),
var failMsg = 'the filter / route at index ' + selectedIndex + ' should have been selected'; 'Expexted the filter / route at index ' + selectedIndex + ' to be selected');
assert.notEqual(elm, undefined, failMsg);
});
}; };
this.assertCompleteAllIsClear = function () { this.assertCompleteAllCheckedStatus = function (shouldBeChecked) {
return page.getMarkAllCompletedCheckBox() var failMsg = 'Expected the mark-all-completed checkbox to be ' + shouldBeChecked ? 'checked' : 'unchecked';
.then(function (markAllCompleted) { return page.waitForMarkAllCompletedCheckBox()
return markAllCompleted.isSelected(); .then(page.waitForCheckedStatus.bind(page, shouldBeChecked, failMsg));
})
.then(function (isSelected) {
assert(!isSelected, 'the mark-all-completed checkbox should be clear');
});
};
this.assertCompleteAllIsChecked = function () {
return page.getMarkAllCompletedCheckBox()
.then(function (markAllCompleted) {
return markAllCompleted.isSelected();
})
.then(function (isSelected) {
assert(isSelected, 'the mark-all-completed checkbox should be checked');
});
}; };
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment