npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

protractor-element-extend

v2.4.1

Published

Module, that helps you to extend ElementFinder and ElementArrayFinder from protractor by your own custom classes

Downloads

9,321

Readme

No Maintenance Intended

This library is deprecated and won't be updated in future, since protractor support is ending as well: https://github.com/angular/protractor/issues/5502

Build Status

Protractor Page Fragments

Simple module, that helps you build your own page fragments, that are inherited from ElementFinder/ElementArrayFinder objects, that brings awesome posibilities to your ProtractorJS tests.

You might heard other names for this pattern: Page Components, Page Composition, HTML Elements, Custom WebElements, WebElement inheritance, Page Elements. This is all about the same.

Installing

As any other NPM package:

npm install protractor-element-extend --save-dev

Importing

JS:

let BaseFragment = require('protractor-element-extend').BaseFragment

let BaseArrayFragment = require('protractor-element-extend').BaseArrayFragment

TS: import {BaseFragment, BaseArrayFragment} from 'protractor-element-extend'

Creating own single fragment

To declare your own fragment, declare your class, extend it from BaseFragment, and pass ElementFinder(WebElement in protractorJS) that represents this fragment to super constructor.

Here is example how Checkbox fragment can be declared:

import {BaseFragment} from 'protractor-element-extend'
import {browser, ExpectedConditions as EC} from 'protractor'

class Checkbox extends BaseFragment {
  constructor(element) {
    super(element) //that's it! now your 'this' reference, is reference to element passed to super constructor
  }
  
  //You can extend it with any methods that you what, and use internal reference to your element
  select() {
    this.isSelected().then(selected => {
      if(!selected) {
        this.click()
        // Notice that because your element is valid ElementFinder - you can pass it as parameter to ExpectedConditions.
        browser.wait(EC.elementToBeSelected(this), 5000, `Checkbox ${this.locator()} must became selected after click, but it wasn't`)
      } else {
          console.warn(`Checkbox ${this.locator()} was already selected, skipping select`)
      }
    })
  }
  
  unselect() {
    this.isSelected().then(selected => {
      if(selected) {
        this.click()
        // Notice that because your element is valid ElementFinder - you can pass it as parameter to ExpectedConditions!
        browser.wait(EC.not(EC.elementToBeSelected(this)), 5000, `Checkbox ${this.locator()} must became unselected after click, but it wasn't`)
      } else {
          console.warn(`Checkbox ${this.locator()} was already unselected, skipping unselect`)
      }
    })
  }

}

Creating own collection of custom fragments

Often needed to work with own custom collection of own fragments, not only single fragment. For this purpose BaseArrayFragment is added. This object extends ElementArrayFragment, and overrides methods that return single elements, to return your custom fragments. .map() .filter() .reduce() .each() and other will receive your custom fragment as parameter as well.

Here is example how SearchResultsCollection fragment can be declared:

import { BaseArrayFragment, BaseFragment } from 'protractor-element-extend'
import { browser, ExpectedConditions as EC, $$ } from 'protractor'

// Describing single search result on our page. Notice that constructor declaration could be skipped, in this case constructor from BaseFragment will be used
class SearchResult extends BaseFragment {
    name() {
      return $('.name').getText()
    }

    isDiscounted() {
        return this.$('.discount-label').isDisplayed()
    }

    open() {
        this.$('button.open').click()
    }
}

// Generics - <SearchResults> are needs to be defined to provide typings support.
class SearchResultsCollection extends BaseArrayFragment<SearchResult> {
    constructor(elementsToExtend: ElementArrayFinder) {
        // You should pass ElementArrayFinder into super constructor, and constructor(class) that will be used to wrap each element in your collection
        super(elementsToExtend, SearchResult);
    }

    findResultsWithDiscount() {
        // This will return new SearchResults object with only those SearchResult objects that has isDiscounted == true
        return this.filter(searchRes => searchRes.isDiscounted())
    }
}

// Initializing is the same as BaseFragment, but you need to pass ElementArrayFinder now.
let searchResults = new SearchResultsCollection($$('.search-result'))
// Awesome readability for your tests
searchResults.findResultsWithDiscount().first().open()

BaseArrayFragment also supports additional array methods that does not exist in ElementArrayFinder in ProtractorJS: .every() .some() .find()

More tricks

You can wrap any ElementFinder into your fragment:

let checkbox = new Checkbox($$('.checkbox').last())

You can use your fragments everywhere where ElementFinder is expected. For example - inside browser.wait

browser.wait(EC.elementToBeClickable(checkbox), 5000, 'Checkbox should be clickable')

Or inside executeScript:

let checkbox = new Checkbox($('div.checkbox'))
var tag = browser.executeScript('return arguments[0].tagName', checkbox);
expect(tag).toEqual('div');

You can override ElementFinder or ElementArrayFinder methods, to get even more powerful functionality

...
import {promise} from 'protractor'

class Checkbox extends BaseFragment {
  ...
  // Ovveriding isDisplayed function that is used in ExpectedCondition.visibilityOf()
  isDisplayed() {
    let fragmentDisplayed = super.isDisplayed()
    let loaderDisplayed = $('.loader').isDispayed()
    // This will return promise, that will be resolved to true, if element is displayed, but loader is not displayed.
    return promise.all(fragmentDisplayed, loaderDisplayed).then(displArray=> displArray[0] && !displArray[1])
  }
}

...
let checkbox = new Checkbox($('.checkbox'))
browser.wait(EC.visibilityOf(checkbox), 3000, 'Checkbox should be visible, but loader should not be visible')

Try to use fragments by placing fragments one into another:

class LoginForm extends BaseFragment {
    loginField:TextField
    passwordField:TextField
    rememberMe:Checkbox

    constructor() {
        super($('.loginform'))
        // Notice that we are searching only inside 'this' element, this brings additional stability to tests
        this.loginField = new TextField(this.$('.loginfield'))
        this.passwordField = new TextField(this.$('.passwordfield'))
        this.rememberMe = new Checkbox(this.$('.rememberme'))
    }

    login(username='test', password='test', rememberme=true) {
        this.loginField.type(username)
        this.passwordField.type(password)
        rememberme && this.rememberMe.select()
        this.$('button.login').click()
    }
}

Supported versions

Currently tested on NodeJS:

  • 6
  • 7
  • 8
  • 9
  • 10

ProtractorJS:

  • 5.x

This lib should work on protractor 4.x without modifications (but this is untested). Protractor 3.x will require browser_ reference rename. PRs are welcome!

Typings for TypeScript are included.

Future

  • Better logging for fragments. Provide possibility to set name attribute, and if it is not set - try to generate best we can with locator()
  • Want some feature? Feel free to create issue!

Something to read

Source code for ElementFinder and ElementArrayFinder -

https://github.com/angular/protractor/blob/master/lib/element.ts

Generics in TypeScript -

https://www.typescriptlang.org/docs/handbook/generics.html