Commit 951eb70b authored by Marcel Hoyer's avatar Marcel Hoyer Committed by Colin Eberhardt

Correcting asynchronous testing

removes unnecessary .then() chaining in test.js as selenium-webdriver already queues calls internally

fixed confusing inverted use of idSelector variable

promisified all tests

promisified test.launchBrowser() and global test hooks

promisified test.closeBrowser()

promisified test.createStandardItems()

🛀 streamlined testOps to always return the promise and flattened out the thens

fixed testOps.assertClearCompleteButtonText() to wait for visible clear-complete button before asserting

fixed testOps.assertClearCompleteButtonIsVisible() to lazy wait for visible clear-complete button

renamed testOps.assertFocussedElementId() to .assertFocussedElement() and streamlined code

fixed page.clickMarkAllCompletedCheckBox() to return inner promise

fixed page.enterItem() to return the promise and flattened out the thens

renamed page.getFocussedElementName() with .getFocussedElementIdOrClass()

fixed page.ensureAppIsVisible() to wait for 'new-todo' element instead of 'main' as some frameworks build the contents after DOM-ready

fixed page.clickClearCompleteButton() to wait for actual button to display before clicking

extracted page.waitForVisibleElement() helper function
parent 4de8afb2
'use strict';
var webdriver = require('selenium-webdriver');
var idSelectors = false;
var idSelectors = true;
module.exports = function Page(browser) {
......@@ -16,35 +16,35 @@ module.exports = function Page(browser) {
};
this.getTodoListXpath = function () {
return !idSelectors ? '//ul[@id="todo-list"]' : '//ul[contains(@class, "todo-list")]';
return idSelectors ? '//ul[@id="todo-list"]' : '//ul[contains(@class, "todo-list")]';
};
this.getMainSectionXpath = function () {
return !idSelectors ? '//section[@id="main"]' : '//section[contains(@class, "main")]';
return idSelectors ? '//section[@id="main"]' : '//section[contains(@class, "main")]';
};
this.getFooterSectionXpath = function () {
return !idSelectors ? '//footer[@id="footer"]' : '//footer[contains(@class, "footer")]';
return idSelectors ? '//footer[@id="footer"]' : '//footer[contains(@class, "footer")]';
};
this.getCompletedButtonXpath = function () {
return !idSelectors ? '//button[@id="clear-completed"]' : '//button[contains(@class, "clear-completed")]';
return idSelectors ? '//button[@id="clear-completed"]' : '//button[contains(@class, "clear-completed")]';
};
this.getNewInputXpath = function () {
return !idSelectors ? '//input[@id="new-todo"]' : '//input[contains(@class,"new-todo")]';
return idSelectors ? '//input[@id="new-todo"]' : '//input[contains(@class,"new-todo")]';
};
this.getToggleAllXpath = function () {
return !idSelectors ? '//input[@id="toggle-all"]' : '//input[contains(@class,"toggle-all")]';
return idSelectors ? '//input[@id="toggle-all"]' : '//input[contains(@class,"toggle-all")]';
};
this.getCountXpath = function () {
return !idSelectors ? '//span[@id="todo-count"]' : '//span[contains(@class, "todo-count")]';
return idSelectors ? '//span[@id="todo-count"]' : '//span[contains(@class, "todo-count")]';
};
this.getFiltersElementXpath = function () {
return !idSelectors ? '//*[@id="filters"]' : '//*[contains(@class, "filters")]';
return idSelectors ? '//*[@id="filters"]' : '//*[contains(@class, "filters")]';
};
this.getFilterXpathByIndex = function (index) {
......@@ -115,9 +115,9 @@ module.exports = function Page(browser) {
return this.getActiveElement().getTagName();
};
this.getFocussedElementName = function () {
this.getFocussedElementIdOrClass = function () {
return this.getActiveElement()
.getAttribute(!idSelectors ? 'id' : 'class');
.getAttribute(idSelectors ? 'id' : 'class');
};
this.getEditInputForItemAtIndex = function (index) {
......@@ -175,9 +175,24 @@ module.exports = function Page(browser) {
});
};
this.waitForVisibleElement = function (getElementFn, timeout) {
var foundVisibleElement;
timeout = timeout || 500;
return browser.wait(function () {
foundVisibleElement = getElementFn();
return foundVisibleElement.isDisplayed();
}, timeout)
.then(function () {
return foundVisibleElement;
})
.thenCatch(function (err) {
return false;
});
}
this.getVisibileLabelIndicies = function () {
var self = this;
var ret;
return this.getItemLabels()
.then(function (elms) {
return elms.map(function (elm, i) {
......@@ -186,18 +201,8 @@ module.exports = function Page(browser) {
})
.then(function (elms) {
return webdriver.promise.filter(elms, function (elmIndex) {
return browser.wait(function () {
return self.tryGetItemLabelAtIndex(elmIndex).isDisplayed()
.then(function (v) {
ret = v;
return true;
})
.thenCatch(function () {
return false;
});
}, 5000)
.then(function () {
return ret;
return self.waitForVisibleElement(function () {
return self.tryGetItemLabelAtIndex(elmIndex);
});
});
});
......@@ -205,58 +210,63 @@ module.exports = function Page(browser) {
// ----------------- page actions
this.ensureAppIsVisible = function () {
return browser.findElements(webdriver.By.css('#todoapp'))
.then(function (elms) {
if (elms.length > 0) {
return true;
} else {
return browser.findElements(webdriver.By.css('.todoapp'));
}
})
.then(function (elms) {
if (elms === true) {
return true;
}
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;
}
if (elms.length) {
idSelectors = true;
return true;
// try to find main element by CSS class
return browser.isElementPresent(webdriver.By.css('#new-todo'));
});
}, 5000)
.then(function (hasFoundNewTodoElement) {
if (!hasFoundNewTodoElement) {
throw new Error('Unable to find application, did you start your local server?');
}
throw new Error('Unable to find application root, did you start your local server?');
});
};
this.clickMarkAllCompletedCheckBox = function () {
return this.getMarkAllCompletedCheckBox().then(function (checkbox) {
checkbox.click();
return checkbox.click();
});
};
this.clickClearCompleteButton = function () {
return this.tryGetClearCompleteButton().click();
var self = this;
return self.waitForVisibleElement(function () {
return self.tryGetClearCompleteButton();
})
.then(function (clearCompleteButton) {
return clearCompleteButton.click();
});
};
this.enterItem = function (itemText) {
var self = this;
browser.wait(function () {
return self.getItemInputField().then(function (textField) {
return textField.sendKeys(itemText, webdriver.Key.ENTER)
.then(function () {
return textField;
});
}).then(function (textField) {
return self.getVisibleLabelText()
.then(function (labels) {
if (labels.indexOf(itemText.trim()) !== -1) {
return true;
}
return textField.clear().then(function () {
return false;
});
});
return browser.wait(function () {
var textField;
return self.getItemInputField().then(function (itemInputField) {
textField = itemInputField;
return textField.sendKeys(itemText, webdriver.Key.ENTER);
})
.then(function () { return self.getVisibleLabelText(); })
.then(function (labels) {
if (labels.indexOf(itemText.trim()) >= 0) {
return true;
}
return textField.clear().then(function () {
return false;
});
});
}, 5000);
};
......
This diff is collapsed.
......@@ -16,15 +16,16 @@ function TestOperations(page) {
function testIsVisible(elements, name) {
assert.equal(elements.length, 1);
elements[0].isDisplayed().then(function (isDisplayed) {
assert(isDisplayed, 'the ' + name + ' element should be displayed');
});
return elements[0].isDisplayed()
.then(function (isDisplayed) {
assert(isDisplayed, 'the ' + name + ' element should be displayed');
});
}
this.assertNewInputNotFocused = function () {
return page.getFocussedElementName()
.then(function (name) {
assert.notEqual(name, 'new-todo');
return page.getFocussedElementIdOrClass()
.then(function (focussedElementIdOrClass) {
assert.equal(focussedElementIdOrClass.indexOf('new-todo'), -1);
});
};
......@@ -35,147 +36,177 @@ function TestOperations(page) {
});
};
this.assertFocussedElementId = function (expectedId) {
page.getFocussedElementName().then(function (id) {
assert.notEqual(id.indexOf(expectedId), -1, 'The focused element did not have the expected id ' + expectedId);
});
this.assertFocussedElement = function (expectedIdentifier) {
return page.getFocussedElementIdOrClass()
.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 () {
page.tryGetClearCompleteButton().then(function (element) {
testIsHidden(element, 'clear completed items button');
}, function (_error) {
assert(_error.code === 7, 'error accessing clear completed items button, error: ' + _error.message);
});
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 () {
page.tryGetClearCompleteButton().then(function (element) {
testIsVisible([element], 'clear completed items button');
});
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) {
page.getItemElements().then(function (toDoItems) {
assert.equal(toDoItems.length, itemCount,
itemCount + ' items expected in the todo list, ' + toDoItems.length + ' items observed');
});
return page.getItemElements()
.then(function (toDoItems) {
assert.equal(toDoItems.length, itemCount,
itemCount + ' items expected in the todo list, ' + toDoItems.length + ' items observed');
});
};
this.assertClearCompleteButtonText = function (buttonText) {
return page.tryGetClearCompleteButton()
.getText().then(function (text) {
assert.equal(text, buttonText);
});
return page.waitForVisibleElement(function () {
return page.tryGetClearCompleteButton();
})
.then(function (clearCompleteButton) {
return clearCompleteButton.getText();
})
.then(function (text) {
return assert.equal(text, buttonText);
});
};
this.assertMainSectionIsHidden = function () {
page.tryGetMainSectionElement().then(function (mainSection) {
testIsHidden(mainSection, 'main');
});
return page.tryGetMainSectionElement()
.then(function (mainSection) {
return testIsHidden(mainSection, 'main');
});
};
this.assertFooterIsHidden = function () {
page.tryGetFooterElement().then(function (footer) {
testIsHidden(footer, 'footer');
});
return page.tryGetFooterElement()
.then(function (footer) {
return testIsHidden(footer, 'footer');
});
};
this.assertMainSectionIsVisible = function () {
page.tryGetMainSectionElement().then(function (mainSection) {
testIsVisible(mainSection, 'main');
});
return page.tryGetMainSectionElement()
.then(function (mainSection) {
return testIsVisible(mainSection, 'main');
});
};
//TODO: fishy!
this.assertItemToggleIsHidden = function (index) {
page.tryGetToggleForItemAtIndex(index).then(function (toggleItem) {
testIsHidden(toggleItem, 'item-toggle');
});
return page.tryGetToggleForItemAtIndex(index)
.then(function (toggleItem) {
return testIsHidden(toggleItem, 'item-toggle');
});
};
this.assertItemLabelIsHidden = function (index) {
page.tryGetItemLabelAtIndex(index).then(function (toggleItem) {
testIsHidden(toggleItem, 'item-label');
});
return page.tryGetItemLabelAtIndex(index)
.then(function (toggleItem) {
return testIsHidden(toggleItem, 'item-label');
});
};
this.assertFooterIsVisible = function () {
page.tryGetFooterElement().then(function (footer) {
testIsVisible(footer, 'footer');
});
return page.tryGetFooterElement()
.then(function (footer) {
return testIsVisible(footer, 'footer');
});
};
this.assertItemInputFieldText = function (text) {
page.getItemInputField().getText().then(function (inputFieldText) {
assert.equal(inputFieldText, text);
});
return page.getItemInputField().getText()
.then(function (inputFieldText) {
assert.equal(inputFieldText, text);
});
};
this.assertItemText = function (itemIndex, textToAssert) {
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');
});
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) {
return page.getVisibleLabelText()
.then(function (visibleText) {
assert.deepEqual(visibleText.sort(), textArray.sort());
});
.then(function (visibleText) {
assert.deepEqual(visibleText.sort(), textArray.sort());
});
};
this.assertItemCountText = function (textToAssert) {
page.getItemsCountElement().getText().then(function (text) {
assert.equal(text.trim(), textToAssert, 'the item count text was incorrect');
});
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.assertItemAtIndexIsCompleted = function (index) {
page.getItemElements().then(function (toDoItems) {
toDoItems[index].getAttribute('class').then(function (cssClass) {
assert(cssClass.indexOf('completed') !== -1,
'the item at index ' + index + ' should have been marked as completed');
return page.getItemElements()
.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) {
page.getItemElements().then(function (toDoItems) {
toDoItems[index].getAttribute('class').then(function (cssClass) {
return page.getItemElements()
.then(function (toDoItems) {
return toDoItems[index].getAttribute('class');
})
.then(function (cssClass) {
// the maria implementation uses an 'incompleted' CSS class which is redundant
// TODO: this should really be moved into the pageLaxMode
assert(cssClass.indexOf('completed') === -1 || cssClass.indexOf('incompleted') !== -1,
'the item at index ' + index + ' should not have been marked as completed');
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) {
page.findByXpath(page.getSelectedFilterXPathByIndex(selectedIndex + 1))
.then(function (elm) {
assert.notEqual(elm, undefined, 'the filter / route at index ' + selectedIndex + ' should have been selected');
});
return page.findByXpath(page.getSelectedFilterXPathByIndex(selectedIndex + 1))
.then(function (elm) {
var failMsg = 'the filter / route at index ' + selectedIndex + ' should have been selected';
assert.notEqual(elm, undefined, failMsg);
});
};
this.assertCompleteAllIsClear = function () {
page.getMarkAllCompletedCheckBox().then(function (markAllCompleted) {
markAllCompleted.isSelected().then(function (isSelected) {
return page.getMarkAllCompletedCheckBox()
.then(function (markAllCompleted) {
return markAllCompleted.isSelected();
})
.then(function (isSelected) {
assert(!isSelected, 'the mark-all-completed checkbox should be clear');
});
});
};
this.assertCompleteAllIsChecked = function () {
page.getMarkAllCompletedCheckBox().then(function (markAllCompleted) {
markAllCompleted.isSelected().then(function (isSelected) {
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