UNPKG

qiprofile

Version:

Quantitative Imaging Profile ( QiPr) web application

379 lines (349 loc) 12.3 kB
_ = require 'lodash' webdriver = require 'selenium-webdriver' expect = require('./expect')() require './object' # The WebElement extension for finding subelts. class Findable # Finds the elt with the given search selection chain. # Each selector is either a Protractor By object or a search # condition string. The search condition string can be CSS, # an id or an xpath, determined as follows: # # * If the string contains a '/' or equals '..', then the # string is assumed to be an xpath. # # * Otherwise, if the string starts with '#', then the search # is on the id following the hash. # # * Otherwise, if the search argument starts with '.', then # the search is by CSS. # # * Otherwise, the search is by tag name. # # For example, # # page.find('.qi-billboard', 'h3', '..') # # returns the parent of the h3 elt within the .qi-billboard # elt on the page. # # The ElementFinder result is extended with a find function # for chaining, so the above example is equivalent to: # # page.element(By.css('.qi-billboard')) # .then (bb) -> # bb.element(By.tagName('h3')) # .then (h3) -> # h3.element(By.xpath('..')) # # @param selectors the search conditions # @returns a promise which resolves to the target # Findable, or null if no such WebElement exists find: (selectors...) -> @_find(selectors...).then (elt) -> if elt? then addFindableMixin(elt) else elt # @param selectors the search conditions # @returns a promise which resolves to the target # ElementFinder, or null if no such elt exists _find: (selectors...) -> # @param current the Findable to search from, or this # Page to search from the document root # @param locator the protractor By object # @returns the result of calling *elt* on the current # object with the given locator next = (current, locator) -> current.element(locator) # Convert the selectors to locators. locators = (@_locatorFor(selector) for selector in selectors) # Chain the selects and return a promise which adds the # Findable methods to the result, if it exists. target = locators.reduce(next, this) target.isPresent().then (exists) -> if exists then target else null # Finds the elts with the given search selection. # The selectors are described in the find function. # Unlike the find function, findAll only accepts one # selector argument. # # Each Findable in the array result is extended # with a find function for chaining. # # @param selectors the search condition # @returns a promise which resolves to the target # Findable array findAll: (selectors...) -> @_findAll(selectors...).then (target) -> target.map(addFindableMixin) # @param selectors the search condition # @returns a promise which resolves to the target # WebElement array _findAll: (selectors...) -> # @param parent the Findable to search from, or # this Page to search from the document root # @param locator the protractor By object # @returns the search result WebElement array next = (current, locator) -> current.all(locator) # Convert the selectors to locators. locators = (@_locatorFor(selector) for selector in selectors) # Chain the search. locators.reduce(next, this) # @param selectors the search condition # @returns a promise which resolves to this WebElement's visible # text, or null if either: # * the search result does not exist # * the element is not displayed # * the text content is empty text: (selectors...) -> @find(selectors...) .then (target) -> if target? target.isDisplayed().then (shown) -> if shown then target else null else null .then (target) -> if target? then target.getText() else null .then (text) -> if text? and text.length then text else null # Clicks on this element, captures the visited URL, # and returns. # # @returns the visited URL visit: -> # Navigates to the previous page, if necessary. # # @param prev_url the previous location # @returns the URL of the page navigated from restore = (prev_url) -> # The current URL. browser.getLocationAbsUrl().then (curr_url) -> # If the location changed, then navigate back to # the previous page. if curr_url == prev_url curr_url else browser.navigate().back().then -> # Resolve to the page navigated from. curr_url # Capture the current location. browser.getLocationAbsUrl().then (url) => # Click the button and resolve to the home page. @click().then -> restore(url) # Finds a nested hyperlink. The hyperlink is the href # attribute of an anchor element (<a href=...></a>) contained # within this Findable's HTML. # # @param selectors the search condition # @returns a promise which resolves to the hyperlink URL hyperlink: (selectors...) -> selectors.push('a') @_find(selectors...).then (anchor) -> if anchor then anchor.getAttribute('href') else null # Finds all nested hyperlinks. # See the hyperlink method. # # @param selectors the search condition # @returns a promise which resolves to the hyperlink URLs hyperlinks: (selectors...) -> selectors.push('a') @_findAll(selectors...).then (anchors) -> anchors.map (anchor) -> anchor.getAttribute('href') # Finds the table WebElement for the given find() selector. # The return value is a promisr resolving to a Table object. # Each table promise resolves to an object {header, body}, # where: # * header is a promise which resolves to an array of table # heading text value promises # * body is a promise which resolves to an array of row promises # # Each row promise resolves to an array of column text promises. # # If the function argument is provided, then it is applied to # each table cell text value promise. # # @param bindings the table {field: ng-bind expression} # bindings # @param selectors the table elt find selectors # @returns a promise resolving to the table WebElement # with the field properties, or null if the table # does not exist findTable: (selectors...) -> finder = @_find(selectors...).then (table) -> # Add the Table methods to the elt. if table? then addTableMixin(table) else table # Finds the table WebElements for the given Page.findAll() # selector. The return value is an array of table promises. # Each table promise resolves to an object {header, body}, # where: # * header is a promise which resolves to an array of table # heading text value promises # * body is a promise which resolves to an array of row promises # # Each row promise resolves to an array of column text promises. # # If the function argument is provided, then it is applied to # each table cell text value promise. # # @param selector the Protractor CSS locator argument # @returns a promise resolving to the Table array findTables: (selectors...) -> @_findAll(selectors...).then (tables) -> # Add the Table prototype to each WebElement in the result. tables.map(addTableMixin) # @param selector the search condition # @returns the By locator object _locatorFor: (selector) -> # If the selector is a string, then make a By locator. # Otherwise, the selector is assumed to already be a # By locator. if _.isString(selector) # An xpath selector string has a slash or equals. # An id selector starts with # and doesn't have a space. # Anything else is assumed to be CSS. if selector == '..' or '/' in selector By.xpath(selector) else if selector[0] == '#' and not ' ' in selector By.id(selector[1..-1]) else if selector.match('in ') By.repeater(selector) else if selector.match(/^\w[\w-]*$/) By.tagName(selector) else By.css(selector) else if selector? # Pass through a non-string selector. selector else throw new Error("The selector is not defined") # # Note - The Table class defined below is not defined # in it's own file because there is a cyclical Findable # dependency. # # The Table class represents a table WebElement with the # following properties: # # * header - a promise resolving to the array of <th/> # WebElement text values # # * rows - a promise resolving to the two-dimensional # <tr/> x <td/> cell value array # # The header values can be either in a separate *thead* # row or as the leading *th* element in each *tbody* row, # e.g.: # # <table id="t01"> # <thead> # <tr> # <th>Name</th> # <th>Gender</th> # </tr> # </thead> # <tbody> # <tr> # <td>John</td> # <td>Male</td> # </tr> # </tbody> # </table> # # or: # # <table id="t02"> # <tbody> # <tr> # <th>Name</th> # <td>John</td> # </tr> # <tr> # <th>Gender</th> # <td>Male</td> # </tr> # </tbody> # </table> # # In the both cases, the Table *heading* resolves to # ['Name', 'Gender']. In the first case, the Table # *rows* resolves to [['John', 'Male]]. In the second # case, the Table *rows* resolves to [['John'], ['Male]]. # # If this table contains a <tbody/> subelement, then this # property includes the rows in the first such body # subelement. Otherwise, this methods returns all <tr/> # rows contained in the table. # # Note - If there are multiple <tbody/> elements in the # table, only the rows of the first <tbody> are returned. # # Note - The rows of nested <table/> elements are returned # along with the parent rows. # # TODO - address the above two notes by adding a Table # subTables property for subtables and include only the # direct rows in a parent Table. # class Table extends Findable # Adds *field* properties to this table which resolve to the # the value given by the corresponding AngularJS binding # expression. # # @param bindings the {field: binding} object, where *binding* # is the AngularJS expression to which the field element is # bound # @returns this table WebElement addBindings: (bindings) -> for field, binding of bindings locator = By.binding(binding) this[field] = @find(locator).then (elt) -> if elt? then elt.text() else elt this # @returns a promise which resolves to the [[cell, ...], ...] # row x column Finder array @property rows: -> # Chain the finder to preferably look for rows in a tbody tag, # otherwise look in the table itself. @find(By.tagName('tbody')) .then (body) -> # The parent element is the table child tbody, if it exists, # otherwise the table itself. if body? then body else this .then (parent) -> # Find the rows... parent.findAll(By.tagName('tr')) .then (rows) -> rows.map (row) -> # ...then find the cells... row.findAll(By.tagName('td')) .then (rows) -> webdriver.promise.all(rows) # @returns a promise which resolves to a header text array @property header: -> @findAll(By.tagName('th')) .then (headings) -> headings.map (heading) -> heading.text() .then (headings) -> webdriver.promise.all(headings) # # Findable utility functions. # # The following mix-ins return a new mix-in instance which # delegates to the mixin argument, e.g.: # extended = addFindableMixin(result) # expect(extended).to.be.an.instanceOf(Findable) # expect(_.functionsIn(extended).to.include.members(_.functionsIn(result)) # # @param mixin the mix-in class # @param obj the source object # @return an empty new mixin object which delegates to # the given object addMixin = (mixin, obj) -> _.extend(new mixin(), obj) # Adds Findable methods to the given WebElement. addFindableMixin = _.partial(addMixin, Findable) # Adds Table methods to the given WebElement. addTableMixin = _.partial(addMixin, Table) module.exports = Findable