3

I am new to writing tests with Intern JS and have been following their documentation to use the Object interface and Page objects, particularly. According to the docs, the idea behind page objects is to encapsulate the DOM for a particular page, in case of the markup changing.

In attempting to follow the page object pattern, my goal is to have a method on myPage which uses Promise.all() to find several DOM elements, and then return these to an actual test function.

I'm roughly basing this on the Promise.all() example near the top of the documentation for leadfood/Command. I'm suspecting I'm getting the chaining wrong to return what I want to go back to the test 1 function when it calls testOneStuff() ... but since I don't see a way to include the Promise.all() in the chain, I'm finding myself at a loss.

When I try running this test, it seems to work up to starting the function (unitWrapper) (I have a console.log in there), then after several seconds it fails with CancelError: Timeout reached....

Is this idea even possible? I also realize I might be approaching this in an unusual way since I am not familiarized with typical patterns in Intern JS beyond a few basic examples in their docs.

Here are the relevant parts of what I'm doing:

In file defining the tests:

define([
  'intern!object',
  'intern/chai!assert',
  '../support/pages/MyPage'
], function (registerSuite, assert, MyPage) {
  registerSuite(function () {
    var myPage;

    return {
      name: 'my_test',

      setup: function () {
        myPage = new MyPage(this.remote);
      },

      'test 1': function () {
        return myPage
          .testOneStuff()
          .then(function (elements) {
            // here I would like 'elements' to be the object I'm
            // returning in function passed into Promise.all().then().
          });
      }
    };
  });
});

In file defining MyPage:

define(function (require) {

  // Constructor to exec at runtime
  function MyPage(remote) {
    this.remote = remote;
  }

  MyPage.prototype = {
    constructor: MyPage,

    testOneStuff: function () {
      return this.remote
        .findByCssSelector('.unit-question')
        .then(function (unitWrapper) {

          // Note that 'this' is the command object.
          return Promise.all([
            // Note we've left the 'find' context with .unit-question as parent
            this.findByCssSelector('ul.question-numbers li.current').getVisibleText(),
            this.findAllByCssSelector('ul.answers li a.answer'),
            this.findByCssSelector('a.action-submit-answer'),
            this.findByCssSelector('a.action-advance-assessment')
          ]).then(function (results) {
            return {
              currentQuestionNumber: results[0],
              answerLinks: results[1],
              btnSubmit: results[2],
              btnNext: results[3]
            };
          });
        });
    }
  };

  return MyPage;
});
teatime
  • 201
  • 2
  • 5
  • Don't know exactly what you can use or not (don't know Intern JS), but can't you return a deferred and resolve it when you want it to be done? –  Dec 09 '15 at 19:29

2 Answers2

1

The hang is probably due to the use of this to start Command chains in the then callback. Returning this, or a Command chain started from this, from a Command then callback will deadlock the chain. Instead, use this.parent in the callback, like:

return Promise.all([
    this.parent.findByCssSelector('...'),
    this.parent.findAllByCssSelector('...'),
    this.parent.findByCssSelector('...'),
    this.parent.findByCssSelector('...')
]).then(//...
jason0x43
  • 3,363
  • 1
  • 16
  • 15
  • Thanks - I didn't get a chance to try this as I figured out a rather different approach that turned out not to use `Promise.all()` at all, and things went from there. May come back to this idea. – teatime Jan 06 '16 at 23:45
  • Is using the `this.parent` pattern equivalent to saving a reference to `this.remote` at the beginning of the test function? Are there pros/cons? – Seth Holladay Feb 16 '16 at 16:09
  • `this.parent` is a new chain that is initialized with the state of `this.remote`, so it will behave similarly, but it's not the *same* as `this.remote`. You should generally not use `this.remote` in `then` callbacks because using a chain in its own callback (like, using `this.remote` in a chain started from `this.remote`) will cause deadlocks. – jason0x43 Feb 16 '16 at 16:16
  • Actually...mostly just disregard my previous comment. `this.parent` is the parent Command of the current Command. It's not _new_, but it is distinct from the current Command. And it's returning promises derived from `this`, not `this.remote`, that will lead to deadlocks. Using a reference to the original `this.remote` in callbacks is fine. The benefit you get from `this.parent` is that you're working with the current context of the parent chain at the point of the callback, whereas with `this.remote` you're working with the initial state of the remote. Must think before writing. /sigh – jason0x43 Feb 17 '16 at 01:01
0

I ended up back in a very similar situation just now, inadvertently.

I have a bunch of things structured differently now that my tests have developed much further. So this time, instead of wanting to return the result of a Promise.all().then() from my page object's method, I simply wanted to grab the results of two Command chains and use them together in an assertion, then continue the chain.

Here's an excerpt showing the pattern that seems to work. All the console logs print out in the order they're called (visibly in the chain here), so I assume all chained calls are waiting for the prior ones to complete, as is desired. This chain goes within a function defining one of my tests (i.e. the test 1 function in my question's example code).

// Store the instance of leadfoot/Command object in clearly named
// variable to avoid issues due to different contexts of `this`.
var command = this.remote;

... // other Command function calls
.end()
.then(function () {
  console.log('=== going to Promise.all');
  return Promise.all([
    command
      .findByCssSelector('li.question.current')
      .getAttribute('data-question-id'),
    command
      .findByCssSelector('h3.question')
      .getVisibleText()
  ]);
})
.then(function (results) {
  console.log('=== hopefully, then() for Promise.all?');
  var qid = results[0];
  var questionText = results[1];
  console.log(qid, questionText);
  console.log('=== then() is completed');

  // This allows continuing to chain Command methods
  return command;
})
.findByCssSelector('h3.question')
  .getVisibleText()
  .then(function (questionText) {
    console.log('=== I\'m the next then()!');
  })
.end()
... // chain continues as usual
teatime
  • 201
  • 2
  • 5