Element addCommand implementation #1796

Open
Filipoliko opened this Issue Jan 3, 2017 · 11 comments

Projects

None yet

5 participants

@Filipoliko

Hello, i was trying to add custom commands to my tests, but got stucked on how to use these commands on an element, instead of browser. The only solution i came with was passing the element as an argument, which is quite ugly solution.

Would be greate to have possibility to add custom commands not just for browser (as described in here), but also for elements.

Environment

  • WebdriverIO version: 4.5.2
  • Node.js version: 6.9.2
  • Standalone mode or wdio testrunner: testrunner
  • if wdio testrunner, running synchronous or asynchronous tests: synchronous

Example

Currently, the way to create a lazyClick command is this way.

browser.addCommand("lazyClick", function(element) {
    element.leftClick().pause(1000);
    return element;
}

Usage:

describe('MyPage', function() {
    it('can click submit button', function() {
        browser.lazyClick(browser.element('#submit'));
    }
});

But the solution i would like to see implemented would be like this.

element.addCommand("lazyClick", function() {
    this.leftClick().pause(1000);
    return this;
}

Usage:

describe('MyPage', function() {
    it('can click submit button', function() {
        browser.element('#submit').lazyClick();
    }
});

Why?

I think it is very limiting to have custom commands only for browser, when most commands i could think of are originating from element. And this way, it would be possible to implement many project specific behavior in an elegant way. You could also save a lot of redundant code for actions that are repeating themselves and are not very fitting to be specified in page objects.

Thanks for reading and feel free to comment.

@rahulmr
rahulmr commented Jan 3, 2017

👍 Till now I thought it is the way you @Filipoliko have mentioned. Also, thanks for your example, I was just looking for something like this.

@monolithed
Contributor
monolithed commented Jan 4, 2017 edited
element.addCommand("lazyClick", function (selector, timeout) {
    return this.leftClick(selector).pause(timeout);
}

browser.lazyClick('#submit');
browser.element('#submit').lazyClick();

Or am I wrong?

@Filipoliko, why do you need this command? Using commands like pause is not a good practice. It can be replaced with a simple wrapper like:

.moveTo.leftClick...

or

.waitForVisible.click...

or

.waitUntil(() => .click...)

And so on

PS: @christian-bromann, it would be nice to have a sandbox like jsbin.com for such tickets (if it possible) 😉

@Filipoliko

Thanks for your reply, @monolithed
The selector use case can be used as you say. I was avoiding it, since i have defined page objects, which returns elements and I just rewrote the use case without the page objects. I believe, that i am not only one, using it like this, so this might be another reason to have this implemented.

The pause is there, since we run the tests against virtual machines, which have really low configuration and were crashing/freezing, when they were clicked on so fast. The pause is not there to wait for some element, but just because of the virtual machine stability. Because of this, i was looking for some solution to add the pause to all clicks in the current project and make it easily removable if not neccessary anymore. This solution seemed like the best, it just did not exist 😄

@monolithed
Contributor
monolithed commented Jan 5, 2017 edited

The pause is there, since we run the tests against virtual machines, which have really low configuration and were crashing/freezing, when they were clicked on so fast.

You'd better define an anchor element in your page object and wait for one. Also, it would nice to have some timeouts:

// config.js

const DEFAULT_TIMEOUT = 30 * 1000;

{

   waitForSelector: DEFAULT_TIMEOUT,
   waitForPageLoad: DEFAULT_TIMEOUT

   before () {
         browser.timeouts('implicit', this.waitForSelector); // wait for each command 30s
   }
}
// PageObject.js
class PageObject {
   constructor () {
        this.wait();
   }
   
   page () {
        return browser;
   }
   
   open (location) {
      this.page.timeouts('page load', this.page.options.waitForPageLoad);
      this.page.url(this.location || location);
   }

   wait (container, ...options) {
      this.page.waitForExist(container || this.locators.container, ...options);
   }
}
// PageLogin.js
class PageLogin extends PageObject {
   get location () {
        return '/login';
    }

    get locators () {
          return {
              container: 'body',
              button: '.button'
          }
    }

    clickButton () {
        this.page.click(this.locators.button);
    }
}
// TestLogin.js

let login = new PageLogin;

login.open(); 
login.clickButton();

As you see, your problem is not so unique.
Read more info about timeouts here

@Filipoliko

The problem is, that the page is loaded and all buttons are available, but if i click multiple times in few seconds, the webpage goes down. The problem is being solved by other developers, but I had to come up with temporarily solution so that the developers can run tests against their virtual machines.

Thank you for your comments, but this is not something i wanted to solve in this topic, i would rather see discussion about implementing addCommands for element. (If I missunderstood your comments, feel free to sent me PM, thanks)

@monolithed
Contributor
monolithed commented Jan 5, 2017 edited

The problem is, that the page is loaded and all buttons are available, but if i click multiple times in few seconds, the webpage goes down

Have you tried to set implicit timeouts?

An implicit timeout is to tell WebDriver to poll the DOM for a certain amount of time when trying to find an element or elements if they are not immediately available. The default setting is 0 (your situation).

browser.element(locator).click(); // If your locator does not exist, WebDriver will wait.
@Filipoliko

No, this is not my problem, all elements are available and tests are clicking on them as fast as they can, but the website crashes after some time, if i hit it so fast, so i need to make some pauses so the website does not crash. I don't think it's a problem with tests, but with the website.

@rahulmr
rahulmr commented Jan 5, 2017 edited

@monolithed as you have given good example of Page Object, I wanted to ask you a question. Is it possible to create a PageObject which extends the browser object in such way that every browser method and property is available to this Page say LoginScreen ( I am working on Mobile Automation)
Here is what I have done

var LoginScreen = Object.create(browser, {
  username: {
    get: function() {
         return browser.element('username');
    },
    ...
    login: {
      value: function(user,pwd) {
         this.username.setValue('user');
         this.password.setValue('pwd');
         return this.login.click();
     }
  },
   signUp: {
      value: function() {
      ..
      }
   }

  }
...
...
}
module.exports = LoginScreen;

Actually, I wanted LoginScreento have all browser methods like click, element, pause, etc. and its own method should also be chainable like LoginScreen.signUp().login().pause(2000);
Is it possible to implement something like this?

I was also thinking whether it is also possible to add custom command like browser.elementAndroidResIdClick('resId'); which can be used like LoginScreen.elementAndroidResIdClick('ResId');

@monolithed
Contributor
monolithed commented Jan 5, 2017 edited

You should't do that in accordance with The Open Closed Principle. You'd better add a custom command or method.

// config.js
import WebDriverAPI from '@qa/wdio-api'; // My private package for custom commands

{
   before () {
        new WebDriverAPI.import();
   }
}
@kevinmcdonnell
Contributor
kevinmcdonnell commented Jan 13, 2017 edited

@Filipoliko did you have any success with this?

i want to do a similar thing, and am having the same issue

@ResolverJay
ResolverJay commented Jan 14, 2017 edited

Anyone has an answer or solution to the original question?

@monolithed did you try this code yourself?

element.addCommand("lazyClick", function (selector, timeout) {
    return this.leftClick(selector).pause(timeout);
}

browser.lazyClick('#submit');
browser.element('#submit').lazyClick();

It doesn't click the element with browser.element('#submit').lazyClick(); (though it doesn't give error).

If do the same for click() or setValue() (instead of leftClick()), it gives me error like selector needs to be typeof 'string'.

In addition, if you check this in the custom command, it is always browser, when you do browser.element('#submit').lazyClick();.

So, I don't think custom commands written in this way work for element. And I'm looking forward to a solution/fix for this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment