我已经尝试并测试了 - 成功 - 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 ,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
};
提前致谢。
答案 0 :(得分:11)
今天遇到这个问题,我想我会分享我的解决方案。
// 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
答案 1 :(得分:4)
我为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
});
答案 2 :(得分:1)
我最近创建了一个相当简单的节点模块来将waitFor over移植到节点: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);
}
);
}