UNPKG

skutter

Version:

Skutter: Opinionated BDD Browser based test framework

364 lines (363 loc) 23.7 kB
"use strict"; 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;