Реализация функции waitFor с phantomjs-узлом

Я пробовал и тестировал-с успехом-пример phantomjs ждать. Тем не менее, у меня возникли трудности с его реализацией через phantomjs-node модуль, в первую очередь, потому что page.evaluate получает оценку в обратного вызова.

PhantomJS реализация

page.open("http://twitter.com/#!/sencha", function () {
    waitFor(function() {

        // This here is easy to do as the evaluate method returns immediately
        return page.evaluate(function() {
            return $("#signin-dropdown").is(":visible");
        });

    }, function() {
       console.log("The sign-in dialog should be visible now.");
       phantom.exit();
    });
  }
});

однако, с phantomjs-node функция evaluate получает возвращенные данные в обратном вызове:

page.evaluate(
    function(){ /* return thing */ },
    function callback(thing) {  /* write code for thing */ }
)

используя phantomjs-node, как я могу запустить функцию на странице только после того, как элемент является видимым?

на всякий случай, если ссылка выше мертва, вот реализация функции waitFor

/**
* Wait until the test condition is true or a timeout occurs. Useful for waiting
* on a server response or for a ui change (fadeIn, etc.) to occur.
*
* @param testFx javascript condition that evaluates to a boolean,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param onReady what to do when testFx condition is fulfilled,
* it can be passed in as a string (e.g.: "1 == 1" or "$('#bar').is(':visible')" or
* as a callback function.
* @param timeOutMillis the max amount of time to wait. If not specified, 3 sec is used.
*/
function waitFor(testFx, onReady, timeOutMillis) {
var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 3000, //< Default Max Timout is 3s
    start = new Date().getTime(),
    condition = false,
    interval = setInterval(function() {
        if ( (new Date().getTime() - start < maxtimeOutMillis) && !condition ) {
            // If not time-out yet and condition not yet fulfilled
            condition = (typeof(testFx) === "string" ? eval(testFx) : testFx()); //<    defensive code
        } else {
            if(!condition) {
                // If condition still not fulfilled (timeout but condition is 'false')
                console.log("'waitFor()' timeout");
                phantom.exit(1);
            } else {
                // Condition fulfilled (timeout and/or condition is 'true')
                console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
                typeof(onReady) === "string" ? eval(onReady) : onReady(); //< Do what it's supposed to do once the condition is fulfilled
                clearInterval(interval); //< Stop this interval
            }
        }
    }, 250); //< repeat check every 250ms
};

спасибо заранее.

3 ответов


столкнулся с этой проблемой сегодня, подумал, что поделюсь своим решением.

  // custom helper function
  function wait(testFx, onReady, maxWait, start) {
    var start = start || new Date().getTime()
    if (new Date().getTime() - start < maxWait) {
      testFx(function(result) {
        if (result) {
          onReady()
        } else {
          setTimeout(function() {
            wait(testFx, onReady, maxWait, start)
          }, 250)
        }
      })
    } else {
      console.error('page timed out')
      ph.exit()
    }
  }

первым шагом является создание нового . Он принимает те же параметры, что и оригинал waitFor функция, но работает немного по-другому. Вместо того, чтобы использовать интервал, мы должны запустить wait функция рекурсивно, после обратного вызова из тестовой функции testFx была активирована. Кроме того, обратите внимание, что на самом деле вам не нужно передавать значение для start, как он устанавливается автоматически.

  wait(function (cb) {
    return page.evaluate(function () 
      // check if something is on the page (should return true/false)
      return something
    }, cb)
  }, function () { // onReady function
    // code
  }, 5000) // maxWait

в этом примере я устанавливаю обратный вызов testFx функция как обратный вызов page.evaluate, который возвращает значение true / false в зависимости от того, удалось ли ему найти какой-либо элемент на странице. Кроме того, вы можете создать обратный вызов для page.evaluate и затем вызвать testFx обратный вызов от него, как показано ниже:

  wait(function (cb) {
    return page.evaluate(function () 
      // check if something is on the page (should return true/false)
      return something
    }, function(result) {
      var newResult = doSomethingCrazy(result)
      cb(newResult)
    }
  }, function () { // onReady function
    // code
  }, 5000) // maxWait

Я написал альтернативу phantomjs-node под названием phridge. Вместо того, чтобы превращать все вызовы и назначения функций в асинхронные операции, он просто выполняет всю функцию внутри PhantomJS.

Я думаю, что ваша проблема может быть выполнено следующим образом:

phridge.spawn()
    .then(function (phantom) {
        return phantom.openPage(url);
    })
    .then(function (page) {
        return page.run(selector, function (selector, resolve, reject) {
            // this function runs inside PhantomJS bound to the webpage instance
            var page = this;
            var intervalId = setInterval(function () {
                var hasBeenFound = page.evaluate(function (selector) {
                    return Boolean(document.querySelector(selector));
                }, selector);

                if (hasBeenFound === false &&
                    /* check if there is still some time left  */) {
                    // wait for next interval
                    return;
                }

                clearInterval(intervalId);

                if (hasBeenFound) {
                    resolve();
                } else {
                    reject(new Error("Wait for " + selector + " timeout"));
                }
            }, 100);
        });
    })
    .then(function () {
        // element has been found
    })
    .catch(function (err) {
        // element has not been found
    });

недавно я создал довольно простой модуль узла для порта waitFor для узла: https://gist.github.com/joseym/1d01edbcc40a7698f55a#file-phantomjs-waitfor-js

var async = require('async');

module.exports = waitFor;

/**
 * waitFor port used with 
 * @see    {@link https://github.com/ariya/phantomjs/blob/master/examples/waitfor.js}
 * @see    {@link https://github.com/sgentle/phantomjs-node}
 * @callback testFx - Test function, will repeat until true or timeout limit is reached
 * @callback onReady - Fires if/when `testFx` passes.
 * @param {(number|boolean|string)} [timeOut=false] - If defined and falsey or string value of`forever` 
 *                                                    then `waitFor` will run until `testFx` passes without 
 *                                                    timing out, otherwise pass a number in miliseconds.
 */
function waitFor(testFx, onReady, timeOut) {

    var maxtimeOutMillis = typeof timeOut !== 'undefined' ? timeOut : 5000 // Default Max Timout is 5s if not defined
        , start = new Date().getTime()
        , isAsync = testFx.length > 0
        , passing = undefined
    ;

    async.until(
        function Test() { 
            return typeof passing !== 'undefined'; 
        },
        function Action(cb) {
            setTimeout(function(){

                if (!maxtimeOutMillis || maxtimeOutMillis == 'forever' || new Date().getTime() - start < maxtimeOutMillis) {

                    // If a callback is passed to `testFx` we'll handle that.
                    function useCallback(){
                        passing = arguments[0]
                        return cb();
                    };                    

                    passing = (function(){
                        return (typeof(testFx) === "string" ? eval(testFx) : testFx).apply(this, arguments);
                    })(isAsync ? useCallback : undefined);

                    if(!isAsync) cb();

                } else {
                    return cb(new Error('`waitFor` timeout'));
                }

            }, 250);
        },
        function Done(err) {
            return (function(){
                return (typeof(onReady) === "string" ? eval(onReady) : onReady).apply(this, arguments);                  
            })(err, passing);
        }
    );

}