269 lines
9.4 KiB
JavaScript
269 lines
9.4 KiB
JavaScript
'use strict';
|
|
|
|
var webdriver = require('selenium-webdriver');
|
|
var until = require('selenium-webdriver/lib/until');
|
|
|
|
var idSelectors = true;
|
|
var classOrId = idSelectors ? '#' : '.';
|
|
|
|
var DEFAULT_TIMEOUT = 3000;
|
|
var REMOVED_TIMEOUT = 100;
|
|
|
|
var REMOVE_TEXT_KEY_SEQ = Array(51).join(webdriver.Key.BACK_SPACE + webdriver.Key.DELETE);
|
|
|
|
// Unique symbols
|
|
var ELEMENT_MISSING = Object.freeze({});
|
|
var ITEM_HIDDEN_OR_REMOVED = Object.freeze({});
|
|
|
|
module.exports = function Page(browser) {
|
|
// CSS ELEMENT SELECTORS
|
|
|
|
this.getMainSectionCss = function () { return classOrId + 'main'; };
|
|
|
|
this.getFooterSectionCss = function () { return 'footer' + classOrId + 'footer'; };
|
|
|
|
this.getClearCompletedButtonCss = function () { return 'button' + classOrId + 'clear-completed'; };
|
|
|
|
this.getNewInputCss = function () { return 'input' + classOrId + 'new-todo'; };
|
|
|
|
this.getToggleAllCss = function () { return 'input' + classOrId + 'toggle-all'; };
|
|
|
|
this.getItemCountCss = function () { return 'span' + classOrId + 'todo-count'; };
|
|
|
|
this.getFilterCss = function (index) {
|
|
return classOrId + 'filters li:nth-of-type(' + (index + 1) + ') a, ' +
|
|
classOrId + 'filters a:nth-of-type(' + (index + 1) + ')';
|
|
};
|
|
|
|
this.getSelectedFilterCss = function (index) { return this.getFilterCss(index) + '.selected'; };
|
|
|
|
this.getFilterAllCss = function () { return this.getFilterCss(0); };
|
|
|
|
this.getFilterActiveCss = function () { return this.getFilterCss(1); };
|
|
|
|
this.getFilterCompletedCss = function () { return this.getFilterCss(2); };
|
|
|
|
this.getListCss = function (suffixCss) { return 'ul' + classOrId + 'todo-list' + (suffixCss || ''); };
|
|
|
|
this.getListItemCss = function (index, suffixCss, excludeParentSelector) {
|
|
suffixCss = (index === undefined ? '' : ':nth-of-type(' + (index + 1) + ')') + (suffixCss || '');
|
|
return excludeParentSelector ? 'li' + suffixCss : this.getListCss(' li' + suffixCss);
|
|
};
|
|
|
|
this.getListItemToggleCss = function (index) { return this.getListItemCss(index, ' input.toggle'); };
|
|
|
|
this.getListItemLabelCss = function (index) { return this.getListItemCss(index, ' label'); };
|
|
|
|
this.getLastListItemLabelCss = function (index) { return this.getListItemCss(index, ':last-of-type label'); };
|
|
|
|
this.getListItemInputCss = function (index) { return this.getListItemCss(index, ' input.edit'); };
|
|
|
|
this.getEditingListItemInputCss = function () { return this.getListItemCss(undefined, '.editing input.edit'); };
|
|
|
|
// This CSS selector returns the _last_ element of a list that exactly matches the provided list of completed states
|
|
// 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);
|
|
};
|
|
|
|
// PUBLIC SYMBOLS
|
|
|
|
this.ITEM_HIDDEN_OR_REMOVED = ITEM_HIDDEN_OR_REMOVED;
|
|
|
|
// NAVIGATION
|
|
|
|
this.back = function () {
|
|
return browser.navigate().back();
|
|
};
|
|
|
|
// ELEMENT RETREIVAL
|
|
// wait* methods guarantees to return an element, or throw an exception
|
|
// 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.waitForElement = function (css, failMsg, timeout) {
|
|
return browser.wait(until.elementLocated(webdriver.By.css(css)), timeout || DEFAULT_TIMEOUT, failMsg);
|
|
};
|
|
|
|
this.waitForFocusedElement = function (css, failMsg) {
|
|
return this.waitForElement(css + ':focus', failMsg);
|
|
};
|
|
|
|
this.waitForBlurredElement = function (css, failMsg) {
|
|
return this.waitForElement(css + ':not(:focus)', failMsg);
|
|
};
|
|
|
|
this.waitForListItemCount = function (count) {
|
|
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.waitForClearCompleteButton = function () {
|
|
return this.waitForElement(this.getClearCompletedButtonCss());
|
|
};
|
|
|
|
this.waitForToggleForItem = function (index) {
|
|
return this.waitForElement(this.getListItemToggleCss(index));
|
|
};
|
|
|
|
this.waitForItemLabel = function (index) {
|
|
return this.waitForElement(this.getListItemLabelCss(index));
|
|
};
|
|
|
|
this.waitForNewItemInputField = function () {
|
|
return this.waitForElement(this.getNewInputCss());
|
|
};
|
|
|
|
this.waitForMarkAllCompletedCheckBox = function () {
|
|
return this.waitForElement(this.getToggleAllCss());
|
|
};
|
|
|
|
this.getListItems = function () {
|
|
return this.getElements(this.getListItemCss());
|
|
};
|
|
|
|
this.waitForVisibility = function (shouldBeVisible, css, failMsg) {
|
|
if (shouldBeVisible) {
|
|
return this.waitForElement(css, failMsg)
|
|
.then(function (element) {
|
|
return browser.wait(until.elementIsVisible(element), DEFAULT_TIMEOUT, failMsg);
|
|
});
|
|
} 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.waitForMainSectionRemovedOrEmpty = function () {
|
|
return this.waitForElement(this.getMainSectionCss(), undefined, REMOVED_TIMEOUT)
|
|
.catch(function () { return ELEMENT_MISSING; })
|
|
.then(function (elementOrError) {
|
|
return elementOrError === ELEMENT_MISSING ? ELEMENT_MISSING : this.waitForListItemCount(0);
|
|
}.bind(this));
|
|
};
|
|
|
|
this.waitForCheckedStatus = function (shouldBeChecked, failMsg, element) {
|
|
var condition = shouldBeChecked ? 'elementIsSelected' : 'elementIsNotSelected';
|
|
return browser.wait(until[condition](element), DEFAULT_TIMEOUT, failMsg);
|
|
};
|
|
|
|
this.waitForTextContent = function (text, failMsg, element) {
|
|
return browser.wait(until.elementTextIs(element, text), DEFAULT_TIMEOUT, failMsg);
|
|
};
|
|
|
|
// PAGE ACTIONS
|
|
|
|
this.ensureAppIsVisibleAndLoaded = function () {
|
|
return this.waitForVisibility(false, this.getFooterSectionCss(), 'Footer is not hidden')
|
|
.then(this.waitForElement.bind(
|
|
this, '.new-todo, #new-todo', 'Could not find new todo input field', undefined))
|
|
.then(function (newTodoElement) {
|
|
return newTodoElement.getAttribute('id');
|
|
})
|
|
.then(function (newTodoElementId) {
|
|
if (newTodoElementId === 'new-todo') { return; }
|
|
idSelectors = false;
|
|
classOrId = idSelectors ? '#' : '.';
|
|
});
|
|
};
|
|
|
|
this.clickMarkAllCompletedCheckBox = function () {
|
|
return this.waitForMarkAllCompletedCheckBox().click();
|
|
};
|
|
|
|
this.clickClearCompleteButton = function () {
|
|
return this.waitForVisibility(true, this.getClearCompletedButtonCss(),
|
|
'Expected clear completed items button to be visible')
|
|
.then(function (clearCompleteButton) {
|
|
clearCompleteButton.click();
|
|
});
|
|
};
|
|
|
|
this.enterItem = function (itemText) {
|
|
var self = this;
|
|
var nItems;
|
|
return self.getListItems()
|
|
.then(function (items) {
|
|
nItems = items.length;
|
|
})
|
|
.then(this.waitForNewItemInputField.bind(this))
|
|
.then(function (newItemInput) {
|
|
return newItemInput.sendKeys(itemText).then(function () { return newItemInput; });
|
|
})
|
|
.then(function (newItemInput) {
|
|
return browser.wait(function () {
|
|
// Hit Enter repeatedly until the text goes away
|
|
return newItemInput.sendKeys(webdriver.Key.ENTER)
|
|
.then(newItemInput.getAttribute.bind(newItemInput, 'value'))
|
|
.then(function (newItemInputValue) {
|
|
return newItemInputValue.length === 0;
|
|
});
|
|
}, 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) {
|
|
return this.waitForToggleForItem(index).click();
|
|
};
|
|
|
|
this.editItemAtIndex = function (index, itemText) {
|
|
return this.waitForElement(this.getListItemInputCss(index))
|
|
.then(function (itemEditField) {
|
|
return itemEditField.sendKeys(REMOVE_TEXT_KEY_SEQ, itemText);
|
|
});
|
|
};
|
|
|
|
this.doubleClickItemAtIndex = function (index) {
|
|
return this.waitForItemLabel(index).then(function (itemLabel) {
|
|
// double click is not 'natively' supported, so we need to send the event direct to the element, see:
|
|
// jscs:disable
|
|
// http://stackoverflow.com/questions/3982442/selenium-2-webdriver-how-to-double-click-a-table-row-which-opens-a-new-window
|
|
// jscs:enable
|
|
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);' +
|
|
'arguments[0].dispatchEvent(evt);', itemLabel);
|
|
});
|
|
};
|
|
|
|
this.filterBy = function (filterCss) {
|
|
return this.waitForElement(filterCss)
|
|
.click()
|
|
.then(this.waitForElement.bind(this, filterCss + '.selected', undefined, undefined));
|
|
};
|
|
|
|
this.filterByActiveItems = function () {
|
|
return this.filterBy(this.getFilterActiveCss());
|
|
};
|
|
|
|
this.filterByCompletedItems = function () {
|
|
return this.filterBy(this.getFilterCompletedCss());
|
|
};
|
|
|
|
this.filterByAllItems = function () {
|
|
return this.filterBy(this.getFilterAllCss());
|
|
};
|
|
};
|