1

The question is related to general js programming, but I'll use nightwatch.js as an example to elaborate my query.

NightWatch JS provides various chaining methods for its browser components, like: -

 browser
    .setValue('input[name='email']','example@mail.com')
    .setValue('input[name='password']', '123456')
    .click('#submitButton')

But if I'm writing method to select an option from dropdown, it requires multiple steps, and if there are multiple dropdowns in a form, it gets really confusing, like: -

 browser
    .click(`#country`)
    .waitForElementVisible(`#india`)
    .click(`#india`)
    .click(`#state`)
    .waitForElementVisible(`#delhi`)
    .click(`#delhi`)

Is it possible to create a custom chaining method to group these already defined methods? For example something like:

/* custom method */
const dropdownSelector = (id, value) {
    return this
        .click(`${id}`).
        .waitForElementVisible(`${value}`)
        .click(`${value}`)
} 

/* So it can be used as a chaining method */
browser
    .dropdownSelector('country', 'india')
    .dropdownSelector('state', 'delhi')

Or is there any other way I can solve my problem of increasing reusability and readability of my code?

2 Answers2

0

This is a great place to use a Proxy. Given some class:

function Apple () 
{
  this.eat = function () 
  {
    console.log("I was eaten!");
    return this;
  }

  this.nomnom = function () 
  {
    console.log("Nom nom!");
    return this;
  }
}

And a set of "extension methods":

const appleExtensions = 
{
  eatAndNomnom () 
  {
    this.eat().nomnom();
    return this;
  }
}

We can create function which returns a Proxy to select which properties are retrieved from the extension object and which are retrieved from the originating object:

function makeExtendedTarget(target, extensions) 
{
  return new Proxy(target, 
  {
    get (obj, prop) 
    {
      if (prop in extensions)
      {
        return extensions[prop];
      }

      return obj[prop];
    }
  });
}

And we can use it like so:

let apple = makeExtendedTarget(new Apple(), appleExtensions);
apple
  .eatAndNomnom()
  .eat();
// => "I was eaten!"
//    "Nom nom!"
//    "I was eaten!"

Of course, this requires you to call makeExtendedTarget whenever you want to create a new Apple. However, I would consider this a plus, as it makes it abundantly clear you are created an extended object, and to expect to be able to call methods not normally available on the class API.

Of course, whether or not you should be doing this is an entirely different discussion!

RecyclingBen
  • 310
  • 3
  • 9
0

I'm somewhat new to JS so couldn't tell you an ideal code solution, would have to admit I don't know what a proxy is in this context. But in the world of Nightwatch and test-automation i'd normally wrap multiple steps I plan on reusing into a page object. Create a new file in a pageObject folder and fill it with the method you want to reuse

So your test...

browser
.click(`#country`)
.waitForElementVisible(`#india`)
.click(`#india`)
.click(`#state`)
.waitForElementVisible(`#delhi`)
.click(`#delhi`)

becomes a page object method in another file called 'myObject' like...

selectLocation(browser, country, state, city) {
  browser
    .click(`#country`) <== assume this never changes?
    .waitForElementVisible(country)
    .click(country)
    .click(state)
    .waitForElementVisible(city)
    .click(city);
}

and then each of your tests inherit the method and define those values themselves, however you chose to manage that...

const myObject = require ('<path to the new pageObject file>')

module.exports = {
  'someTest': function (browser) {
    const country = 'something' 
    const state = 'something'
    const city = 'something'

    myObject.selectLocation(browser);

You can also set your country / state / city as variables in a globals file and set them as same for everything but I don't know how granular you want to be.

Hope that made some sense :)

Asoc
  • 51
  • 2