Skip to content

Instantly share code, notes, and snippets.

@senocular
Last active December 1, 2016 17:13
Show Gist options
  • Save senocular/74425c4ab9df1a5462b2874fff5d4f3f to your computer and use it in GitHub Desktop.
Save senocular/74425c4ab9df1a5462b2874fff5d4f3f to your computer and use it in GitHub Desktop.
NW Docs

[DEPRECATED - PLEASE USE THE NIGHTWATCH WIKI VERSION MOVING FORWARD]

https://github.com/nightwatchjs/nightwatch/wiki/Understanding-the-Command-Queue

Understanding the Command Queue

When Nightwatch runs a test, it processes its commands in a list known as the command queue. This list manages the asynchronous execution of the commands defined in that test.

As a queue, the command queue generally follows the rule of first in, first out (FIFO). The first command you call in a test is the first executed when the test runs. The second command is called next, followed by the next command up until the last command added, which becomes the last command executed.

Command Queue Creation

The command API in Nightwatch - accessible via the object passed into test cases, usually called "client" or "browser" - consists of a collection of methods that are used to construct the command queue. When you call a Nightwatch command such as click(), you're not sending the Selenium client the command to immediately click something, at least not right away. That method instead adds a "click" command to the command queue. After the test case function has resolved, something it does synchronously (commands are non-blocking), it traverses the command queue as defined by the Nightwatch commands you called, running through the queue executing each command in it asynchronously.

Consider the following test:

// test with 'click button' test case

module.exports = {
  'click button': function (browser) {
    browser
      .url('example.com')
      .click('button')
      .end();
  }
};

Here, the 'click button' test case calls three methods from the browser object: url(), click(), and end(). When Nightwatch runs this test, it calls the 'click button' function which, in turn, calls each of these methods. These methods run synchronously, one directly and immediately after the other, and populates the command queue without performing any Selenium tasks. The result is a queue that looks something like:

// 'click button' test case command queue

[
  {command: 'url', args: ['example.com']},
  {command: 'click', args: ['button']},
  {command: 'end', args: []}
]

Its only after the test case function resolves that the queue is traversed and the commands executed, either in the context of Selenium, or through whatever other implementation they may have.

Command Queue Execution

Most commands in Nightwatch are Selenium commands, wrapping a single, or two or more Selenium requests. These are handled through the WebDriver API which consists of a series of HTTP endpoints to perfom actions on a WebDriver client, such as a Selenium Server instance. The HTTP requests made by Nightwatch are non-blocking with the results of the requests handled in callbacks. The click command, for example, in the deep internals of Nightwatch code is executed using something like:

// running the Selenium click command

http.request({
    method: 'POST',
    path: '/session/1/element/2/click'
  }, function (response) {
    // request complete
});

The non-blocking, asynchronous nature of Selenium-based commands, or even other asynchronous commands like pause() (effectively a command wrapper for setTimeout()) means that for operations like this to run in sequence, they must wait for the completion of the previous. The susinct, chainable command API of Nightwatch doesn't force this requirement on you, instead handling it internally through the inner workings of the command queue. It acts as a cache saving off the operations you wish to perform until after the test function completes at which point, it runs them each in sequence asynchronously.

// command queue execution pseudo code

commandQueue = []

runTestCaseFunction(browser) // synchronous (adds to commandQueue)

for command of commandQueue {
   await runCommand(command) // asynchronous (commands wait for previous to finish)
}

Command Callbacks and the Dynamic Queue

Just about every native Nightwatch command supports a callback function to be called when the command has been executed within the command queue. These callbacks, as with the execution of the commands themselves within the queue, are called asynchronously.

// click command callback

browser.click('button', function (result) {
  // when the queue has finished executing the click command
  // where `result` is the response from the Selenium click operation
});

The primary purpose for callbacks is to allow you to capture data from the test while its running, but they also provide an additional opportunity to add commands to the command queue. Unlike the command methods called directly within the test case function body, command methods called in a callback get added to the current location of the command queue within its traversal. Commands added in the click() callback above, for example, would be added directly after the click command's location within the queue rather than being added to the end (the end being the standard FIFO enqueue behavior seen with commands outside of callbacks). Consider a revised sample test:

// test with 'click button' test case

module.exports = {
  'click button': function (browser) {
    browser
      .url('example.com')
      .click('button', function (result) {
        browser.pause(100);
      })
      .end();
  }
};

When run, initially the command queue starts as:

// 'click button' test case command queue after test case called

[
  {command: 'url', args: ['example.com']},
  {command: 'click', args: ['button']},
  {command: 'end', args: []}
]

Note that there is no pause yet, because the pause() command method doesn't get called until after the click command is executed when the queue is being processed. This is the state of the queue immediately after the test case function runs.

When the queue starts, it runs the url command, waits for it to finish, then runs the click command which then calls the callback. When the callback runs, it adds the pause command to the queue, giving us:

// 'click button' test case command queue after click callback called

[
  {command: 'url', args: ['example.com']}, // executed
  {command: 'click', args: ['button']}, // executed
  {command: 'pause', args: [100]}, // <-- just added
  {command: 'end', args: []}
]

When the queue continues to the next step, it then runs the newly added pause command - as that is now the next command in the queue - before eventually running the final end command completing the test case.

One thing to realize is that though the pause command was added out of order, asynchronously, the order in which you read each of the commands in the source code is ultimately the same order they are executed when the command queue is traversed. The added order is url, click, end, pause; while the executed order - and the order read in the source - is url, click, pause, end. This makes it easy to look at source code and immediately get a general idea of the order of commands ultimately getting run.

Working with Data in the Queue

As mentioned earlier, callbacks provide a way to get data from a running test such as values of textfields, attributes, etc. This data isn't available when the test function runs because the actual running of the test case (traversing of the command queue) doesn't start until after the test case function has already run to completion.

// capturing test data from callback

var text;
browser.getValue('#input', function (result) {
  text = result.value; // captured during command queue execution
});

Similar to how commands in callbacks aren't in the command queue right away, values captured this way also aren't available until the test is running. In a callback, all code directly in the test case function body has already resolved, and the only place any other code will run is in other callbacks. This is important to keep in mind because it can be easy to think this might work:

// incorrect usage of a callback value

var text;
browser
  .getValue('#input', function (result) {
    text = result.value;
  })
  .setValue('#output', text); // WRONG: text is undefined

The problem here is that the setValue() call happens in the main test case function call, before the callback is called when text is still undefined. For setValue() to have the correct value for text, it must be called within, or some time after, the getText() callback:

// correct usage of a callback value

var text;
browser.getValue('#input', function (result) {
  text = result.value;
  browser.setValue('#output', text); // RIGHT
});

Any additional dependencies on text may also go in the getValue() callback, and any command there may have their own callbacks which may include additional commands ultimately giving you a mess of nested callbacks.

// nested callbacks

var text, text2, text3; // ...
browser.getValue('#input', function (result) {
  text = result.value;
  browser.getValue('#' + text, function (result) {
    text2 = result.value;
    browser.getValue('#' + text2, function (result) {
      text3 = result.value;
      browser.getValue('#' + text3, function (result) {
        // ...
      });
    });
  });
});

To counter this, you can use the perform() command.

The perform() Command

Nightwatch's perform() command is effectively a no op command that exists only to provide a callback allowing you to run code within the context of the running command queue. Within the callback, you're ensured to have code which will run after any callbacks defined above it, even if nested.

// perform to help combat nested callbacks

var text, text2, text3;
browser
  .getValue('#input', function (result) {
    text = result.value;
    browser.getValue('#' + text, function (result) {
      text2 = result.value;
    });
  })
  .perform(function () {
    browser.getValue('#' + text2, function (result) {
      text3 = result.value;
      browser.getValue('#' + text3, function (result) {
        // ...
      });
    });
  });

Let's rundown of how this set of commands would play out in a test case, starting from when the test case is first called:

// initial test case queue

[
  {command: 'getValue', args: ['#input', callback]},
  {command: 'perform', args: [callback]},
]

// queue after first getValue ('#input') callback

[
  {command: 'getValue', args: ['#input', callback]}, // executed, text defined
  {command: 'getValue', args: ['#text', callback]}, // <-- added
  {command: 'perform', args: [callback]},
]

// queue after second getValue ('#text') callback

[
  {command: 'getValue', args: ['#input', callback]}, // executed, text defined
  {command: 'getValue', args: ['#text', callback]}, // executed, text2 defined
  {command: 'perform', args: [callback]},
]

// second getValue callback adds no commands, perform is next

[
  {command: 'getValue', args: ['#input', callback]}, // executed, text defined
  {command: 'getValue', args: ['#text', callback]}, // executed, text2 defined
  {command: 'perform', args: [callback]}, // executed
  {command: 'getValue', args: ['#text2', callback]}, // <-- added
]

// final queue after third getValue ('#text2') callback

[
  {command: 'getValue', args: ['#input', callback]}, // executed, text defined
  {command: 'getValue', args: ['#text', callback]}, // executed, text2 defined
  {command: 'perform', args: [callback]}, // executed
  {command: 'getValue', args: ['#text2', callback]}, // executed, text3 defined
  {command: 'getValue', args: ['#text3', callback]}, // <-- added
]

Callbacks (inline), like the commands themselves, are executed in the same order they're read in the code. So even though the perform() command itself is added to the queue before the text2 defining getValue() call is, its callback doesn't execute until after text2 has been defined in that getValue()'s callback.

[DEPRECATED - PLEASE USE THE NIGHTWATCH WIKI VERSION MOVING FORWARD]

https://github.com/nightwatchjs/nightwatch/wiki/Page-Object-API

Page Object API

Nightwatch v0.9.9

Page objects provide an additional layer of abstraction for test case creation. Page objects are defined in modules and parsed into factory functions that create page object instances. These factories are accessible through the page reference within the command API (accessible through the "client" or "browser" object) using the name of the module that defines them.

var myPageObject = browser.page.MyPage(); // defined in MyPage.js module

For more information on general page object creation and usage, see: http://nightwatchjs.org/guide#page-objects

There are two flavors of page objects: enhanced and legacy. The current and recommended approach is through the enhanced model (available since Nightwatch 0.7). The legacy model continues to work, but can be considered deprecated and should be avoided when possible.

Enhanced Page Objects Modules

Unlike legacy page objects which are mostly simple abstractions of functionality, enhanced page objects benefit from being able to use a subset of the Nightwatch commands API. These commands supports element selectors which are named references to selectors defined within the page object module. Additionally, sections can be used to further divide page objects into smaller, dedicated parts.

Enhanced page object modules are identified through object exports having one or both of either the elements and sections properties. While legacy page objects modules can export a function or an object, enhanced page objects must export an object.

Enhanced Module Members

Name Type Description
commands Array [Optional] A list of objects containing functions to represent methods added to the page object instance. Also see Enhanced Page Object Commands.
elements Object [Required if not using sections] An object of named element definitions (see Enhanced Module Element Members) to be used as element selectors within element commands called from the page object.
props Object|Function [Optional] An object or a function returning an object representing a container for user variables. Props objects are copied directly into the props property of the page object instance. Values stored within the props object don't have to worry about possible collisions with command functions.
sections Object [Required if not using elements] An object of named sections definitions (see Enhanced Module Section Members) defining the sections within the page object.
url string|Function [Optional] A url or function returning a url to be used in a url() command when the page's navigate() method is called.

Enhanced Module Element Members

Element definitions are in the elements object of the module root object. Keys in the user-defined properties in the elements object represent the name of the Element object and its value the definition.

elements: {
  <element-name>: <element-definition>
}
Name Type Description
locateStrategy string [Optional] The locate strategy to be used with selector when finding the element within the DOM. This can be any of the following: 'class name', 'css selector', 'id', 'name', 'link text', 'partial link text', 'tag name', 'xpath'. The default is 'css selector'.
selector string [Required] The selector string used to find the element in the DOM. For elements, this string can be assigned directly to the element property as <element-definition> as shorthand for an element definition object only containing a selector value.

Enhanced Module Section Members

Section definitions are in the sections objects of the root module object and other sections (they can be nested). Keys in the user-defined properties in the sections object represent the name of the Section object and its value the definition.

sections: {
  <section-name>: <section-definition>
}
Name Type Description
commands Array [Optional] Same as module root.
elements Object [Optional] Same as module root.
locateStrategy string [Optional] Same as Element.
props Object|Function [Optional] Same as module root.
sections Object [Optional] Same as module root.
selector string [Required] Same as Element.

Enhanced Page Object Example

// enhanced page object

module.exports = {

  // can be string or function
  url: function () {
    return this.api.launchUrl;
  },
  
  elements: {
  
    // shorthand
    mySubmitButton: 'input[type=submit]'
    
    // full
    myTextInput: {
      selector: 'input[type=text]',
      locateStrategy: 'css selector'
    }
  },
  
  commands: [
    {
      myCustomPause: function () {
        this.api.pause(this.props.myPauseTime);
      }
    }
  ],
  
  // object version
  props: {
    myPauseTime: 1000
  },
  
  sections: {
  
    myFooterSection: {
    
      selector: '#my-footer',
      locateStrategy: 'css selector',
      
      elements: {
        myLogo: {
          selector: '.my-logo',
          locateStrategy: 'css selector'
        }
      },
      
      commands: [
        {
          myMoveToLogo: function () {
            this.moveToElement('@myLogo', this.props.myLogoX, this.props.myLogoY);
          }
        }
      ],
      
      // function version
      props: function () {
        return {
          myLogoX: 10,
          myLogoY: 10
        };
      },
      
      sections: {
        // additional, nested sections
      }
    }
  }
};

Enhanced Page Object Instances

Page object module definitions are used to define page object instances when their respective factory functions within the page reference of the standard command API is called.

var myPageObject = browser.page.MyPage(); // defined in MyPage.js module

Every time a factory function like MyPage above is called, a new instance of the page object is instantiated.

Enhanced Page Object Instance Properties

Name Type Description
api Object A reference providing access to the full Nightwatch command API, usually known as "client" or "browser" in test cases. This is used to access those commands that are not part of the subset of commands within the page object API.
client Object [Internal] A referenece to the Nightwatch instance (not to be confused with the "client" or "browser" object which contains the command API).
commandLoader Function [Internal] Used during page object construction to add commands to the page object API.
elements Object A map of Element objects (see Enhanced Element Instances) used by element selectors.
name string The name of the page object as defined by its module name (not including the extension). This is the same name used to access the page object factory from the page reference in the command API.
props Object A reference to props object assigned from the module definition. Note: this will be the same props object for all instances of the page object if defined as an object instance within the page object module. If you wish for each props object to be unique, define props in the module as a function that would return a new props object for each new page object instance.
section Object A map of Sections objects (see Enhanced Section Instances) defined for the page object. This will only contain sections within the page object module's root sections definition. Nested sections are accessible through their parent section's own section reference.
url string|Function The url value from the page object module, either a string or a function depending on how it was defined there.

Enhanced Page Object Instance Methods

.navigate()

Navigates to the resolved url defined for the page object using the command API's url() command. This command is generally used in place of the command API's url() when working with page objects because the url member of the page object is the user-defined url string or function and not the call used to navigate to a url.

Parameters:

Name Type Description
(none) - -

.getUrl()

[Internal] Navigates to the resolved url defined for the page object using the command api's url() command.

Parameters:

Name Type Description
url string|Function The url value to resolve into a url string. If this value is a string, it is returned. If a function, it is called and its value returned. Otherwise null is returned.

Enhanced Page Object Command API Methods

A subset of commands from the standard Nightwatch command API is inherited by page objects. The general rule for identifying which of the native commands are available to Page instances is: Does the command accept a selector for the first argument? If it does, it should also be a page object command as well. Currently, this list includes (what are known as "Element commands"):

  • clearValue
  • click
  • getAttribute
  • getCssProperty
  • getElementSize
  • getLocation
  • getLocationInView
  • getTagName
  • getText
  • getValue
  • isVisible
  • moveToElement
  • sendKeys
  • setValue
  • submitForm
  • waitForElementNotPresent
  • waitForElementNotVisible
  • waitForElementPresent
  • waitForElementVisible

As element commands - commands that find and operate on elements - they're added to the page object command api to allow support for element selectors (using @). Other native commands that would not make use of element selectors would have to be called through the page object's api reference.

Enhanced Element Instances

Element instances encapsulate the definition used to handle element selectors. Generally you won't need to access them directly, instead refering to them using their @-prefixed names for selector arguments, but they are available through a page object or section's elements property.

Enhanced Page Object Instance Properties

Name Type Description
name string The name of the element as defined by its key in the parent section or the page object's elements definition. This is the same name used with the @ prefix in selector arguments for page object commands that refer to the element.
locateStrategy string The locate strategy to be used with selector when finding the element within the DOM.
parent Object [Internal] A reference to the parent object instance. This is the parent section or the page object that contained the definition for this object.
selector string The selector string used to find the element in the DOM.

Enhanced Section Instances

Page object section instances are accessed from the section property of a page object instance (note that this is the singular version of "section" whereas the plural version, "sections", was used in the module definition). Sections are created automatically through the page object factory and are available directly as properties from the section reference.

var myPageObject = browser.page.MyPage();
var mySection = myPageObject.section.MySection; // from a `sections: {}` block in page object

Enhanced Section Instance Properties

Name Type Description
api Object Same as Page instances.
client Object [Internal] Same as Page instances.
commandLoader Function [Internal] Same as Page instances.
elements Object Same as Page instances.
locateStrategy string Same as Element instances.
name string The name of the section as defined by its key in the parent section or the page object's sections definition. This is the same name used to access the section instance from the section reference from the parent page object or section.
props Object Same as Page instances.
section Object Same as Page instances.
selector string Same as Element instances.
parent Object [Internal] Same as Element instances.

Enhanced Page Object Commands

Page object commands are implemented differently from your standard, custom Nightwatch commands. Key differences include:

  • Page object commands are defined within page object modules. They can be in the module root object within the commands list or within section definitions (also in a commands), but only exist for the definition they're within. Page object commands in the module root commands are not available in child sections and section commands are not available in parent sections or the root page object. Custom Nightwatch commands are available in the standard Nightwatch command API (the "client" or "browser" object), every page object, and every page object section.
  • Page object command context (the value of this) is the page object (for sections its the section object). The page object command API is limited to the page object API (see Enhanced Page Object Instances). To access other commands, you must do so through this.api, though those methods do not have access to the page object's element selectors (using @) as they are called outside the context of the page object.
  • Page object commands are not called from within the command queue. Code in a page object command is executed immediately when the function is called. Custom Nightwatch command code is run when the command is executed in the command queue (while the test is running). Page object commands can be considered "methods" more than "commands".
  • Page object commands must return a value for chaining. This can be whatever you want, but it's recommended you stick to this to allow your commands to be chained in the context of the page object instance. Custom Nightwatch commands inherently always return the current command context (you don't need to explicitly return anything yourself).
    • Nightwatch commands called from the standard command API returns the "browser" or "client" api object
    • Nightwatch commands called from a page object returns the page object
    • Nightwatch commands called from a page object section returns the section

Legacy Page Object Modules

TODO

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