skutter
Version:
Skutter: Opinionated BDD Browser based test framework
364 lines (363 loc) • 23.7 kB
JavaScript
;
const Lazy = require("lazy.js");
const iskutter_library_1 = require("../framework/iskutter-library");
const iskutter_dictionary_1 = require("../framework/iskutter-dictionary");
const qualifier_validator_1 = require("./qualifier-validator");
const skutter_library_1 = require("../framework/skutter-library");
const page_element_searcher_1 = require("../framework/page-structure/page-element-searcher");
const html_actions_1 = require("../actions/html-actions");
const stack_1 = require("../framework/stack");
const list_library_1 = require("./list-library");
const stack_item_1 = require("../framework/stack-item");
const container_1 = require("../framework/page-structure/container");
function assertListItemCount(context, areIs, numericalQualifier, expectedCount, itemItems, next) {
let expectedCountInt = parseInt(expectedCount);
if (isNaN(expectedCountInt)) {
throw new Error("expectedCount is not a number");
}
let listInspection = list_library_1.ListLibrary.findCurrentListContainer(context.inspectionStack, false);
while (!listInspection && context.inspectionStack.topItem !== null) {
context.inspectionStack.pop();
listInspection = list_library_1.ListLibrary.findCurrentListContainer(context.inspectionStack, false);
}
if (listInspection === null) {
throw new Error("Unable to find list to inspect in the current stack.");
}
if (listInspection.type !== "ListContainer") {
throw new Error("Current inspection item is not a listContainer.");
}
html_actions_1.HtmlActions.getChildElements(listInspection.element, listInspection.definition.itemSelector)
.then((itemElements) => {
console.log(`Testing: List items count is ${numericalQualifier} ${expectedCount}`);
next(qualifier_validator_1.QualifierValidator.qualifierNumericalValidate(itemElements.length, numericalQualifier, expectedCountInt));
}, error => {
next(error || new Error("Promise resolution failed with no error details."));
});
}
class ListSteps {
constructor() {
let dictionary = new iskutter_dictionary_1.SkutterDictionary()
.define("quotedWordOrWords", /"([^"]+)"/)
.define("nonQuotedWord", /([^\s"]+)/)
.define("qualifier", qualifier_validator_1.QualifierValidator.qualifierRegex)
.define("qualifierPlural", qualifier_validator_1.QualifierValidator.qualifierPluralRegex)
.define("qualifierNumerical", qualifier_validator_1.QualifierValidator.qualifierNumericalRegex)
.define("visibilityQualifier", qualifier_validator_1.QualifierValidator.qualifierVisibilityRegex)
.define("editableQualifier", /(is not editable|is editable|is not readonly|is readonly)/)
.define("disabledQualifier", /(is not disabled|is disabled|is not enabled|is enabled)/);
this.lib = new skutter_library_1.SkutterLibrary(dictionary);
}
createLibrary() {
let lib = this.lib;
lib.when(new iskutter_library_1.WhenDefinition(["I am inspecting the $quotedWordOrWords list",
"I am inspecting the $nonQuotedWord list"]), function (listName, next) {
let inspectionStack = this.context.inspectionStack;
let stackItem = null;
let searchStackItem = false;
if (inspectionStack) {
page_element_searcher_1.PageElementSearcher.rewindStack(inspectionStack, listName, false);
let topItem = inspectionStack.topItem;
if (topItem) {
stackItem = topItem;
if (!stackItem.definition) {
throw new Error("definition is undefined for a valid stack item");
}
if (stackItem.definition.name !== listName) {
console.log(`[ListSteps] Current inspection list is not the desired. "${stackItem.definition.name}" vs "${listName}".`);
searchStackItem = true;
}
}
}
if (stackItem !== null && searchStackItem === false) {
if (stackItem.type !== "ListContainer") {
throw new Error(`[ListSteps] structural element matching "${listName}" is not a ListContainer.`);
}
console.log(`Inspection context is already set to "${listName}".`);
next();
return;
}
let listContainerElementPromise = null;
let listSelectorDefinition = null;
if (searchStackItem === false || !inspectionStack || stackItem === null) {
listSelectorDefinition = this.context.page.structure.find(listName, true);
listContainerElementPromise = html_actions_1.HtmlActions.getElement(this.context, listSelectorDefinition.selector, true);
}
else {
listSelectorDefinition = stackItem.definition.find(listName, true);
listContainerElementPromise = html_actions_1.HtmlActions.getElementDirect(stackItem.element, listSelectorDefinition.selector, true);
}
return listContainerElementPromise
.then(listContainerElement => {
if (!inspectionStack) {
inspectionStack = new stack_1.Stack();
}
inspectionStack.push(new stack_item_1.ListStackItem(listSelectorDefinition, listContainerElement, "ListContainer"));
next();
}, error => { next(error || new Error("Promise resolution failed with no error details.")); });
});
lib.when(new iskutter_library_1.WhenDefinition(["I am inspecting the $positional item (?:of|in) the list"]), function (position, next) {
let listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
while (!listInspection && this.context.inspectionStack.topItem !== null) {
this.context.inspectionStack.pop();
listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
}
if (listInspection === null) {
throw new Error("Unable to find list to inspect in the current stack.");
}
if (listInspection.type !== "ListContainer") {
throw new Error("Current inspection item is not a listContainer.");
}
return html_actions_1.HtmlActions.getChildElements(listInspection.element, listInspection.definition.itemSelector)
.then(itemElements => {
console.log(`Discovered ${itemElements.length} items.`);
let item = null;
try {
item = list_library_1.ListLibrary.findItemByPosition({ items: itemElements }, position);
}
catch (exception) {
next(exception.message);
return;
}
let itemDefinition = new container_1.Container("ListItem", listInspection.definition.itemSelector, listInspection.definition.item.contents);
if (!this.context.inspectionStack) {
this.context.inspectionStack = new stack_1.Stack();
}
console.log(`Inspecting ${position} row.`);
this.context.inspectionStack.push(new stack_item_1.ListStackItem(itemDefinition, item, "ListItem"));
next();
}, error => { next(error || new Error("Promise resolution failed with no error details.")); });
});
lib.then(new iskutter_library_1.ThenDefinition(["the $quotedWordOrWords in the item $qualifier $quotedWordOrWords",
"the $nonQuotedWord in the item $qualifier $quotedWordOrWords"]), function (selectorIdentifier, qualifier, expectedValue, next) {
let itemInspection = list_library_1.ListLibrary.findCurrentListItem(this.context.inspectionStack, true);
let itemDefinition = itemInspection.definition;
let targetElementDefinition = itemDefinition.find(selectorIdentifier, true);
return html_actions_1.HtmlActions.getElementDirect(itemInspection.element, targetElementDefinition.selector, true)
.then(htmlElement => htmlElement.getText())
.then(actualText => { next(qualifier_validator_1.QualifierValidator.qualifierValidate(qualifier, actualText, expectedValue)); }, error => { next(error || new Error("Promise resolution failed with no error details.")); });
});
lib.then(new iskutter_library_1.ThenDefinition(["the $positional item $qualifier $quotedWordOrWords"]), function (position, qualifier, expectedValue, next) {
let listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
while (!listInspection && this.context.inspectionStack.topItem !== null) {
this.context.inspectionStack.pop();
listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
}
return html_actions_1.HtmlActions.getChildElements(listInspection.element, listInspection.definition.itemSelector)
.then(itemElements => {
console.log(`Discovered ${itemElements.length} items.`);
let item = list_library_1.ListLibrary.findItemByPosition({ items: itemElements }, position);
item.getText()
.then(actualText => { next(qualifier_validator_1.QualifierValidator.qualifierValidate(qualifier, actualText, expectedValue)); }, error => { next(error || new Error("Promise resolution failed with no error details.")); });
}, error => { next(error || new Error("Promise resolution failed with no error details.")); });
});
lib.then(new iskutter_library_1.ThenDefinition(["an item $qualifier $quotedWordOrWords"]), function (qualifier, inspectionValue, next) {
let listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
while (!listInspection && this.context.inspectionStack.topItem !== null) {
this.context.inspectionStack.pop();
listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
}
if (listInspection === null) {
throw new Error("Unable to find list to inspect in the current stack.");
}
if (listInspection.type !== "ListContainer") {
throw new Error("Current inspection item is not a listContainer.");
}
return html_actions_1.HtmlActions.getChildElements(listInspection.element, listInspection.definition.itemSelector)
.then(itemElements => {
console.log(`Discovered ${itemElements.length} items.`);
let items = itemElements;
if (!items || items.length === 0) {
next("List items are undefined or there are no items.");
return;
}
let getTextPromises = [];
Lazy(items).each((item) => {
getTextPromises.push(Promise.resolve(item.getText()));
});
Promise.all(getTextPromises).then((getTextArray) => {
console.log(`Testing: Any item in list ${qualifier} "${inspectionValue}".`);
let all = Lazy(getTextArray).some((text) => {
return qualifier_validator_1.QualifierValidator.qualifierValidate(qualifier, text, inspectionValue) === null;
});
next(all ? null : `Expected Any item in list ${qualifier} "${inspectionValue}".`);
}, (error) => { next(error || new Error("Promise resolution failed with no error details.")); });
}, (error) => { next(error || new Error("Promise resolution failed with no error details.")); });
});
lib.then(new iskutter_library_1.ThenDefinition(["all items $qualifierPlural $quotedWordOrWords"]), function (qualifierPlural, inspectionValue, next) {
let listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
while (!listInspection && this.context.inspectionStack.topItem !== null) {
this.context.inspectionStack.pop();
listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
}
if (listInspection === null) {
throw new Error("Unable to find list to inspect in the current stack.");
}
if (listInspection.type !== "ListContainer") {
throw new Error("Current inspection item is not a listContainer.");
}
return html_actions_1.HtmlActions.getChildElements(listInspection.element, listInspection.definition.itemSelector)
.then(itemElements => {
console.log(`Discovered ${itemElements.length} items.`);
let items = itemElements;
if (!items || items.length === 0) {
next("List items are undefined or there are no items.");
return;
}
let getTextPromises = [];
Lazy(items).each(function (item) {
getTextPromises.push(Promise.resolve(item.getText()));
});
Promise.all(getTextPromises)
.then((getTextArray) => {
console.log(`Testing: All items in list ${qualifierPlural} "${inspectionValue}".`);
let allErrors = Lazy(getTextArray)
.map(text => qualifier_validator_1.QualifierValidator.pluralQualifierValidate(qualifierPlural, text, inspectionValue));
let error = allErrors.compact().first();
next(error);
}, error => { next(error || new Error("Promise resolution failed with no error details.")); });
}, error => { next(error || new Error("Promise resolution failed with no error details.")); });
});
lib.then(new iskutter_library_1.ThenDefinition(["the item has a class containing $quotedWordOrWords"]), function (expectedClass, next) {
let inspected = list_library_1.ListLibrary.findCurrentListItem(this.context.inspectionStack, true);
let item = inspected.element;
if (!item) {
throw new Error("Item is undefined or null.");
}
item.getAttribute("class")
.then(classValue => {
let error = null;
if (classValue.indexOf(expectedClass) === -1) {
error = `Expected "${classValue}" to contain "${expectedClass}".`;
}
next(error);
}, error => { next(error || new Error("Promise resolution failed with no error details.")); });
});
lib.then(new iskutter_library_1.ThenDefinition(["the $positional item $qualifier unicode $quotedWordOrWords"]), function (position, qualifier, expectedValue, next) {
let listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
while (!listInspection && this.context.inspectionStack.topItem !== null) {
this.context.inspectionStack.pop();
listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
}
if (listInspection === null) {
throw new Error("Unable to find list to inspect in the current stack.");
}
if (listInspection.type !== "ListContainer") {
throw new Error("Current inspection item is not a listContainer.");
}
html_actions_1.HtmlActions.getChildElements(listInspection.element, listInspection.definition.itemSelector)
.then(itemElements => {
console.log(`Discovered ${itemElements.length} items.`);
let item = list_library_1.ListLibrary.findItemByPosition({ items: itemElements }, position);
item.getText()
.then(actualValue => {
let expectedNumericValue = parseInt(expectedValue, 16);
console.log(`Expected: ${expectedValue} unicode integer ${expectedNumericValue}.`);
let actualInt = actualValue.charCodeAt(0);
console.log(`Actual: ${actualValue} unicode integer ${actualInt}.`);
next(qualifier_validator_1.QualifierValidator.qualifierNumericalValidate(actualInt, qualifier, expectedNumericValue));
}, error => { next(error || new Error("Promise resolution failed with no error details.")); });
}, error => { next(error || new Error("Promise resolution failed with no error details.")); });
});
lib.when(new iskutter_library_1.WhenDefinition(["I am inspecting the item where the $quotedWordOrWords $qualifier $quotedWordOrWords",
"I am inspecting the item where the $nonQuotedWord $qualifier $quotedWordOrWords"]), function (selectorIdentifier, qualifier, expectedValue, next) {
let listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
while (!listInspection && this.context.inspectionStack.topItem !== null) {
this.context.inspectionStack.pop();
listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
}
if (listInspection === null) {
throw new Error("Unable to find list to inspect in the current stack.");
}
if (listInspection.type !== "ListContainer") {
throw new Error("Current inspection item is not a listContainer.");
}
return html_actions_1.HtmlActions.getChildElements(listInspection.element, listInspection.definition.itemSelector)
.then(itemElements => {
console.log("Discovered items: " + itemElements.length);
let promises = [];
Lazy(itemElements).each((item) => {
let targetElementDefinition = listInspection.definition.item.find(selectorIdentifier, true);
let getTextPromise = html_actions_1.HtmlActions.getElementDirect(item, targetElementDefinition.selector, true)
.then((htmlElement) => { return Promise.resolve(htmlElement.getText()); });
promises.push(getTextPromise);
});
Promise.all(promises)
.then((textValues) => {
let index = 0;
let firstMatch = Lazy(textValues).map(textValue => {
return {
index: index++,
result: qualifier_validator_1.QualifierValidator.qualifierValidate(qualifier, textValue, expectedValue)
};
}).pluck("result").indexOf(null);
let matched = itemElements[firstMatch];
if (firstMatch === -1) {
next(`No item found in list where "${selectorIdentifier}" ${qualifier} "${expectedValue}".`);
return;
}
else {
console.log(`Matched value at position ${firstMatch}`);
}
let itemDefinition = listInspection.definition.item;
if (!this.context.inspectionStack) {
this.context.inspectionStack = new stack_1.Stack();
}
this.context.inspectionStack.push(new stack_item_1.ListStackItem(itemDefinition, matched, "ListItem"));
next();
});
}, error => { next(error || new Error("Promise resolution failed with no error details.")); });
});
lib.then(new iskutter_library_1.ThenDefinition(["the item $qualifier $quotedWordOrWords"]), function (qualifier, expectedValue, next) {
let itemInspection = list_library_1.ListLibrary.findCurrentListItem(this.context.inspectionStack, true);
let itemElement = itemInspection.element;
itemElement.getText()
.then(actualText => {
next(qualifier_validator_1.QualifierValidator.qualifierValidate(qualifier, actualText, expectedValue));
}, error => { next(error || new Error("Promise resolution failed with no error details.")); });
});
lib.when(new iskutter_library_1.WhenDefinition(["I click the item"]), function (next) {
let itemInspection = list_library_1.ListLibrary.findCurrentListItem(this.context.inspectionStack, true);
if (!itemInspection) {
throw new Error("Not inspecting an item.");
}
let itemElement = itemInspection.element;
if (!itemElement) {
throw new Error("Item doesn't resolve to anything.");
}
console.log("Clicking inspected list row.");
itemElement.click().then(() => { next(); }, error => { next(error || new Error("Promise resolution failed with no error details.")); });
});
lib.when(new iskutter_library_1.WhenDefinition(["I click the $positional item (?:of|in) the list"]), function (position, next) {
let listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
while (!listInspection && this.context.inspectionStack.topItem !== null) {
this.context.inspectionStack.pop();
listInspection = list_library_1.ListLibrary.findCurrentListContainer(this.context.inspectionStack, false);
}
if (listInspection === null) {
throw new Error("Unable to find list to inspect in the current stack.");
}
if (listInspection.type !== "ListContainer") {
throw new Error("Current inspection item is not a listContainer.");
}
html_actions_1.HtmlActions.getChildElements(listInspection.element, listInspection.definition.itemSelector)
.then(itemElements => {
console.log(`Discovered ${itemElements.length} items.`);
let item = null;
try {
item = list_library_1.ListLibrary.findItemByPosition({ items: itemElements }, position);
}
catch (exception) {
next(exception.message);
return;
}
item.click()
.then(() => next(), error => { next(error || new Error("Promise resolution failed with no error details.")); });
}, error => { next(error || new Error("Promise resolution failed with no error details.")); });
});
lib.then(new iskutter_library_1.ThenDefinition(["there (are|is) $NUM (items|item) in the list"]), function (areIs, expectedCount, itemItems, next) {
assertListItemCount(this.context, areIs, "exactly", expectedCount, itemItems, next);
});
return lib;
}
}
exports.default = ListSteps;