Intern JS - how can I use Promise.all() within cha

2019-05-10 02:24发布

问题:

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;
});

回答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(//...


回答2:

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