Реализация waitFor функциональность с phantomjs- node
Я пробовал и тестировал - с успехом - пример phantomjs waitFor. Тем не менее, мне трудно реализовать его с помощью 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 функция оценки возвращает возвращенные данные в обратном вызове:
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 supposed to do once the condition is fulfilled
clearInterval(interval); //< Stop this interval
}
}
}, 250); //< repeat check every 250ms
};
Спасибо заранее.
Ответы
Ответ 1
Сегодня в этой проблеме возникла мысль, что я поделюсь своим решением.
// 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()
}
}
Первым шагом является создание новой функции wait
. Он принимает те же параметры, что и исходная функция 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
Ответ 2
Я написал альтернативу для 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
});
Ответ 3
Недавно я создал довольно простой модуль node для переноса waitFor до node: 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);
}
);
}