PhantomJS click on an image and wait for load - javascript

I'm trying to do page automation with PhantomJS. My goal is to be able to go to a website, click an image, and continue with other code once the page has loaded from the click. To test this I'm trying to write a script that will go to the url of the quick start guide on the PhantomJS website and then click on the PhantomJS logo bringing the page to the PhantomJS homepage. Also to render a picture of the website before and after the click to make sure the click worked. This is my current code:
var page = require('webpage').create();
page.open('http://phantomjs.org/quick-start.html', function(status) {
console.log(status);
page.render('websiteBeforeClick.png');
console.log(page.frameUrl); //check url before click
var element = page.evaluate(function() {
return document.querySelector('img[alt="PhantomJS"]');
});
page.sendEvent('click', element.offsetLeft, element.offsetTop, 'left');
window.setTimeout(function () {
console.log(page.frameUrl); //check url after click
}, 3000);
console.log('element is ' + element); //check that querySelector() is returning an element
page.render('websiteAfterClick.png');
phantom.exit();
});
Problem is my before and after pictures are the same. This is my output when I run it.
success
element is [object Object]
Im using their sendEvent method from here "http://phantomjs.org/api/webpage/method/send-event.html" but I'm not sure if its working.
Also why doesnt the console.log(page.frameUrl) in my window.setTimeout() get executed?
I was looking at their page automation examples on the PhantomJS website. Particularly this one "https://github.com/ariya/phantomjs/blob/master/examples/imagebin.js".
I noticed their examples used
document.querySelector('input[name=disclaimer_agree]').click()
But when I tried it with my code I got an error.
document.querySelector('img[alt="PhantomJS"]').click();
TypeError: 'undefined' is not a function
EDIT#1:
I changed the end section of my code to this:
page.sendEvent('click', element.offsetLeft, element.offsetTop, 'left');
window.setTimeout(function () {
console.log(page.frameUrl);
page.render('websiteAfterClick.png');
phantom.exit();
}, 3000);
console.log('element is ' + element);
});
Now my after image is correct. But now my question is, If I want to continue on with my code i.e. click on another element on the site, will my new code have to be all nested inside of the timeout function?

There is an example function phantom.waitFor(callback) that I explain on the following post, it goes as follows:
phantom.waitFor = function(callback) {
do {
// Clear the event queue while waiting.
// This can be accomplished using page.sendEvent()
this.page.sendEvent('mousemove');
} while (!callback());
}
This can help streamline your code and avoid nested calls to window.setTimeout(), which are not very reliable anyway as you are waiting for a pre-set amount of time instead of waiting for the element to become visible. An example would be as follows:
// Step 1: Open and wait to finish loading
page.open('http://localhost/');
phantom.waitFor(function() {return !page.loading;});
// Step 2: Click on first panel and wait for it to show
page.evaluate(function() { $("#activate-panel1").click(); });
phantom.waitFor(function() {
return page.evaluate(function() {return $("#panel1").is(":visible");})
});
// Step 3: Click on second panel and wait for it to show
page.evaluate(function() { $("#activate-panel2").click(); });
phantom.waitFor(function() {
return page.evaluate(function() {return $("#panel2").is(":visible");})
});
console.log('READY!');
phantom.exit();
This will load each panel in succession (ie synchronously) while keeping your code simple and avoiding nested callbacks.
Hope it makes sense. You could also use CasperJS as an alternative, its aimed at making this stuff simpler.

Yes, your new code will be called from inside of the setTimeout callback. You can nest the code directly or write a function which capsules the code for you and call that function inside setTimeout.
function anotherClick(){
// something
}
page.sendEvent('click', element.offsetLeft, element.offsetTop, 'left');
window.setTimeout(function () {
console.log(page.frameUrl);
page.render('websiteAfterClick.png');
anotherClick();
phantom.exit();
}, 3000);
There is another way. You can also write it completely with multiple setTimeout, but then you cannot react to sudden conditions in previous calls.
page.sendEvent('click', element.offsetLeft, element.offsetTop, 'left');
window.setTimeout(function () {
console.log(page.frameUrl);
page.render('websiteAfterClick.png');
}, 3000);
window.setTimeout(function () {
// some more actions
}, 6000); // you cannot know if this delay is sufficient
window.setTimeout(function () {
phantom.exit();
}, 9000); // you cannot know if this delay is sufficient
I would suggest using CasperJS, if you want to do many actions/navigation steps.

Related

How to automatically read a web page when refreshed [duplicate]

This question already has answers here:
How to make JavaScript execute after page load?
(25 answers)
Closed 1 year ago.
I am using following code to execute some statements after page load.
<script type="text/javascript">
window.onload = function () {
newInvite();
document.ag.src="b.jpg";
}
</script>
But this code does not work properly. The function is called even if some images or elements are loading. What I want is to call the function the the page is loaded completely.
this may work for you :
document.addEventListener('DOMContentLoaded', function() {
// your code here
}, false);
or
if your comfort with jquery,
$(document).ready(function(){
// your code
});
$(document).ready() fires on DOMContentLoaded, but this event is not being fired consistently among browsers. This is why jQuery will most probably implement some heavy workarounds to support all the browsers. And this will make it very difficult to "exactly" simulate the behavior using plain Javascript (but not impossible of course).
as Jeffrey Sweeney and J Torres suggested, i think its better to have a setTimeout function, before firing the function like below :
setTimeout(function(){
//your code here
}, 3000);
JavaScript
document.addEventListener('readystatechange', event => {
// When HTML/DOM elements are ready:
if (event.target.readyState === "interactive") { //does same as: ..addEventListener("DOMContentLoaded"..
alert("hi 1");
}
// When window loaded ( external resources are loaded too- `css`,`src`, etc...)
if (event.target.readyState === "complete") {
alert("hi 2");
}
});
same for jQuery:
$(document).ready(function() { //same as: $(function() {
alert("hi 1");
});
$(window).load(function() {
alert("hi 2");
});
NOTE: - Don't use the below markup ( because it overwrites other same-kind declarations ) :
document.onreadystatechange = ...
I'm little bit confuse that what you means by page load completed, "DOM Load" or "Content Load" as well? In a html page load can fire event after two type event.
DOM load: Which ensure the entire DOM tree loaded start to end. But not ensure load the reference content. Suppose you added images by the img tags, so this event ensure that all the img loaded but no the images properly loaded or not. To get this event you should write following way:
document.addEventListener('DOMContentLoaded', function() {
// your code here
}, false);
Or using jQuery:
$(document).ready(function(){
// your code
});
After DOM and Content Load: Which indicate the the DOM and Content load as well. It will ensure not only img tag it will ensure also all images or other relative content loaded. To get this event you should write following way:
window.addEventListener('load', function() {...})
Or using jQuery:
$(window).on('load', function() {
console.log('All assets are loaded')
})
If you can use jQuery, look at load. You could then set your function to run after your element finishes loading.
For example, consider a page with a simple image:
<img src="book.png" alt="Book" id="book" />
The event handler can be bound to the image:
$('#book').load(function() {
// Handler for .load() called.
});
If you need all elements on the current window to load, you can use
$(window).load(function () {
// run code
});
If you cannot use jQuery, the plain Javascript code is essentially the same amount of (if not less) code:
window.onload = function() {
// run code
};
If you wanna call a js function in your html page use onload event. The onload event occurs when the user agent finishes loading a window or all frames within a FRAMESET. This attribute may be used with BODY and FRAMESET elements.
<body onload="callFunction();">
....
</body>
You're best bet as far as I know is to use
window.addEventListener('load', function() {
console.log('All assets loaded')
});
The #1 answer of using the DOMContentLoaded event is a step backwards since the DOM will load before all assets load.
Other answers recommend setTimeout which I would strongly oppose since it is completely subjective to the client's device performance and network connection speed. If someone is on a slow network and/or has a slow cpu, a page could take several to dozens of seconds to load, thus you could not predict how much time setTimeout will need.
As for readystatechange, it fires whenever readyState changes which according to MDN will still be before the load event.
Complete
The state indicates that the load event is about to fire.
This way you can handle the both cases - if the page is already loaded or not:
document.onreadystatechange = function(){
if (document.readyState === "complete") {
myFunction();
}
else {
window.onload = function () {
myFunction();
};
};
}
you can try like this without using jquery
window.addEventListener("load", afterLoaded,false);
function afterLoaded(){
alert("after load")
}
Alternatively you can try below.
$(window).bind("load", function() {
// code here });
This works in all the case. This will trigger only when the entire page is loaded.
window.onload = () => {
// run in onload
setTimeout(() => {
// onload finished.
// and execute some code here like stat performance.
}, 10)
}
If you're already using jQuery, you could try this:
$(window).bind("load", function() {
// code here
});
I can tell you that the best answer I found is to put a "driver" script just after the </body> command. It is the easiest and, probably, more universal than some of the solutions, above.
The plan: On my page is a table. I write the page with the table out to the browser, then sort it with JS. The user can resort it by clicking column headers.
After the table is ended a </tbody> command, and the body is ended, I use the following line to invoke the sorting JS to sort the table by column 3. I got the sorting script off of the web so it is not reproduced here. For at least the next year, you can see this in operation, including the JS, at static29.ILikeTheInternet.com. Click "here" at the bottom of the page. That will bring up another page with the table and scripts. You can see it put up the data then quickly sort it. I need to speed it up a little but the basics are there now.
</tbody></body><script type='text/javascript'>sortNum(3);</script></html>
MakerMikey
I tend to use the following pattern to check for the document to complete loading. The function returns a Promise (if you need to support IE, include the polyfill) that resolves once the document completes loading. It uses setInterval underneath because a similar implementation with setTimeout could result in a very deep stack.
function getDocReadyPromise()
{
function promiseDocReady(resolve)
{
function checkDocReady()
{
if (document.readyState === "complete")
{
clearInterval(intervalDocReady);
resolve();
}
}
var intervalDocReady = setInterval(checkDocReady, 10);
}
return new Promise(promiseDocReady);
}
Of course, if you don't have to support IE:
const getDocReadyPromise = () =>
{
const promiseDocReady = (resolve) =>
{
const checkDocReady = () =>
((document.readyState === "complete") && (clearInterval(intervalDocReady) || resolve()));
let intervalDocReady = setInterval(checkDocReady, 10);
}
return new Promise(promiseDocReady);
}
With that function, you can do the following:
getDocReadyPromise().then(whatIveBeenWaitingToDo);
call a function after complete page load set time out
setTimeout(function() {
var val = $('.GridStyle tr:nth-child(2) td:nth-child(4)').text();
for(var i, j = 0; i = ddl2.options[j]; j++) {
if(i.text == val) {
ddl2.selectedIndex = i.index;
break;
}
}
}, 1000);
Try this jQuery:
$(function() {
// Handler for .ready() called.
});
Put your script after the completion of body tag...it works...

Cannot get link to be clicked and switch to next page using PhantomJS

I am having an issue getting phantomJS to click the login button on a website.
I can see in my second screenshot that it is trying to select the login button, but I cannot get it to wait and take the screenshot on the next page.
Here is my JS file:
var page = require('webpage').create();
page.viewportSize = {width: 1920,height: 1080};
page.open('http://clubs.bluesombrero.com/default.aspx?portalid=1809', function (status) {
console.log("Status: " + status);
if (status === "success") {
var url = page.url;
console.log('URL: ' + url);
console.log("TC0001: Pass");
page.render('TC0001.png');
var a = page.evaluate(function() {
return document.querySelector('#dnn_dnnLOGIN_cmdLogin');
});
page.sendEvent('click', a.offsetLeft, a.offsetTop);
page.render('TC0002.png');
} else {
console.log("TC0001: Failed, Page did not load.");
}
phantom.exit();
});
I have tried a few ways to get it to wait to take the screenshot after the page has loaded, but I have not had any luck.
page.sendEvent() is a synchronous function that finishes as soon as its action is done. The next call (page.render()) is executed even before the request which was triggered by the click is answered.
1. setTimeout
JavaScript provides two functions to wait a static amount of time: setTimeout and setInterval:
page.sendEvent('click', a.offsetLeft, a.offsetTop);
setTimeout(function(){
page.render('TC0002.png');
phantom.exit();
}, 5000);
(don't forget to remove the other phantom.exit() since you don't want to exit too early)
Of course the problem is now that on one hand the page still might not be ready after 5 seconds or on the other hand the page was loaded extremely fast and just sits there doing nothing.
2. waitFor
A better approach would be to use the waitFor() function that is provided in the examples folder of PhantomJS. You can wait for a specific condition of the page like the existence of a specific element:
page.sendEvent('click', a.offsetLeft, a.offsetTop);
waitFor(function _testFx(){
return page.evaluate(function(){
return !!document.querySelector("#someID");
});
}, function _done(){
page.render('TC0002.png');
phantom.exit();
}, 10000);
3. page.onLoadFinished
Another approach would be to listen to the page.onLoadFinished event which will be called when the next page is loaded, but you should register to it before you click:
page.onLoadFinished = function(){
page.render('TC0002.png');
phantom.exit();
};
page.sendEvent('click', a.offsetLeft, a.offsetTop);
4. page.onPageCreated
Whenever a new window/tab would be opened in a desktop browser, the page.onPageCreated would be triggered in PhantomJS. It provides a reference to the newly created page, because the previous page is not overwritten.
page.onPageCreated = function(newPage){
newPage.render('TC0002.png');
newPage.close();
phantom.exit();
};
page.sendEvent('click', a.offsetLeft, a.offsetTop);
In all the other cases, the page instance is overwritten by the new page.
5. "Full" page load
That might still not be sufficient, because PhantomJS doesn't specify what it means when a page is loaded and the JavaScript of the page may still make further requests to build up the page. This Q&A has some good suggestions to wait for a "full" page load: phantomjs not waiting for “full” page load

How to test for my Javascript callback with Jasmine?

I need to test if specific methods are called when user scrolls the window to a certain point. In my source code I have windows listener attached, something like:
$(window).on("scroll.singleJob",function(e)
{
// code here, check if the window is scrolled past certain point etc. and then I need to call this method
LozengesPanel.makeFixed();
}
Now, in my Jasmine test I'm trying to confirm that the method is being called when the window is scrolled to a certain point. So I set up the test:
describe("SingleJob page", function() {
beforeEach(function() {
loadFixtures('my_fixture.html');
});
it("panel sticks to top when page scrolled down", function() {
spyOn(mycompany.singleJobTestable.LozengesPanel, "makeFixed");
window.scroll(0,1000);
expect(mycompany.singleJobTestable.LozengesPanel.makeFixed).toHaveBeenCalled();
});
});
But the test fails, all I get is Expected spy makeFixed to have been called.
How can I trigger window scroll so I can test methods inside of this callback?
EDIT:
Finally it all makes sense.. It seems that scroll event was put in a tasks queue only to be executed after the current thread finishes. Adding $(window).trigger("scroll"); did the trick. I posted short blog post about it that explains the issue http://spirytoos.blogspot.com.au/2014/02/testing-windowscroll-with-qunitjasmine.html
EDIT: This answer does not satisfy the question. See the comments for the reason.
Actually, it looks like you are triggering the scroll event from your Jasmine spec. I tried very similar code, which I include below. However, my expect still fails, like yours (I'm still getting familiar with Jasmine, so I can't explain with certainty why that is).
var fun = {
scrollEventCallback: function() {
console.log('scroll event triggered');
}
};
$(window).on('scroll', fun.scrollEventCallback);
describe("A test suite", function() {
it("should trigger f", function() {
spyOn(fun, "scrollEventCallback");
$(window).trigger('scroll'); // my callback function is executed because it logs to the console
expect(fun.scrollEventCallback).toHaveBeenCalled(); // this fails anyway
});
});
Maybe your window.scroll(0, 1000) is not actually pushing the viewport low enough to trigger your Lozenges.makeFixed() call. That would be the case if the page (your fixture, I think) wasn't long and it didn't actually have anywhere to scroll.
Also, I got code similar to your provided code to work. The expect(...) succeeds. It is pasted below.
var LozengesPanel = {
makeFixed: function() {
console.log('makeFixed was called with its original function definition');
}
};
$(window).on("scroll.singleJob",function(e) {
LozengesPanel.makeFixed();
});
describe("A test suite", function() {
it("should trigger callback", function() {
spyOn(LozengesPanel, "makeFixed");
$(window).trigger('scroll'); // nothing is logged to the console
expect(LozengesPanel.makeFixed).toHaveBeenCalled(); // succeeds
});
});

Removing setTimeout on click

I'm getting some odd behavior in a part of my js code.
I have some notifications which appear in a bar on top of the page and then disappear after a certain amount of time. I have used a simple setTimeout() to acheive this.
Sometimes, a notification will appear as a result of a particular url query string when the page loads but then a new one would need to be displayed when the user clicks on a button. I want the old one to disappear and the new one to appear. I'm using a variable to keep a reference to the setTimeout() in order to cancel it. However, when I try to do this I manage to create a loop that eventually crashes my chrome tab.
I have put together a jsfiddle illustrating my problem - http://jsfiddle.net/5Nm4c/
Clicking on show notification while another is visible will crash the browser tab. If you click on it when nothing is shown, it is fine.
Here is my js:
var Notification = {
// close main notification bar
close: function (callback) {
$('#notification-bar').fadeOut(250, function () {
// reset its position and fade it back in so it is ready to go again
$(this).css('top', -100).fadeIn(1);
// check if a callback function has been passed in
if (typeof callback === 'function') {
callback();
}
});
},
// open notification bar with the appropriate css class and message
open: function (message) {
// if the notification bar is already visisble
if (verge.inViewport($('#notification-bar'))) {
// hide and then show it with the new message
window.clearTimeout(Notification.timeout);
Notification.close(Notification.open(message));
return false;
}
$('#notification-bar').html(message);
$('#notification-bar').animate({
'top': 0
}, 250, function () {
Notification.timeout = window.setTimeout(function () { Notification.close() }, 1500);
});
},
timeout: null
}
Notification.open('hello');
$('#button').click(function(e){
e.preventDefault();
Notification.open('link clicked');
});
I'm using https://github.com/ryanve/verge/ as it has some nice methods to check if elements are visible in the viewport.
Could someone please tell me where my error is?
I think the error Uncaught RangeError: Maximum call stack size exceededcomes from jsfiddle itself, so I am not able to test it.
I see what you did there:
var Notification = {
open: function (message) {
Notification.close(Notification.open(message)); //Here you create the loop!!
}
}
Another problem I see in your code is, that when Notification.open is called while a animation is running Notification.timeout is not actuell. Try a $('#notification-bar').stop(true, true); to stop the actuell animation befor you call window.clearTimeout(Notification.timeout);. Maybe it would be even better to use $('#notification-bar').stop(true, false);, so the "old" setTimeout will not even be called.

Delaying click event

I'm wondering whether there's a simple way to delay the click event from being processed for a specified period of time. For example we could have
$('#someElement').on('click', 'a', function(event) {
var duration = 1000;
someAsynchronousFunction(); // Start as soon as click occurs
... // Code to delay page transition from taking place for duration specified
});
So in this case the asynchronous function would be guaranteed some amount of time to run. If it hasn't completed it's work in this time I wouldn't care and would just like to continue with the page transition. I know that it's possible to accomplish something close with
event.preventDefault();
...
setTimeout(function(){
window.location = $(this).attr('href');
}, duration);
But this only works when the link being clicked goes to a full page. I want to be able to deal with links that are used for ajax calls (which don't change the url) as well.
I noticed that the mixpanel library has a function track_links which seems to accomplish the delay on the page transition, though that function doesn't seem to work well with the support for ajax links that I mentioned.
Any help would be great! Thanks.
Edit: So I suppose my question wasn't exactly clear, so I'll try to provide some more details below.
I don't care if the async function finishes running! I only want to give it the guarantee that it has some set amount of time to execute, after which I don't care if it finishes, and would prefer to go ahead with the page transition.
i.e. I want to delay not the start of the async function, but the start of the page transition. The async function would start running as soon as the click occured.
Hopefully this is a bit more clear!
I figured out a way to solve the problem:
var secondClick = false;
var duration = 1000;
$('#someElement').on('click', 'a', function(event) {
var that = $(this);
if(!secondClick) {
event.stopPropagation();
setTimeout(function(){
secondClick = true;
that.click();
}, duration);
someAsynchronousFunction();
} else {
secondClick = false;
}
});
When the user clicks the link, it internally prevents that click from actually having any effect, and gives the asynchronous function a set amount of time to do it's work before doing a second click on the link which behaves normally.
setTimeout allows you to delay running code by however many ms you want
setTimeout(function(){
console.log('Stuff be done'); //This will be delayed for one second
}, 1000);
In reality, if you're dealing with ajax you want to respond when the ajax call is complete. It may take more or less than 1000ms. $.ajax allows you do this with the .done() method. This is an example from the docs:
$.ajax({
url: "test.html",
context: document.body
}).done(function() {
$(this).addClass("done");
});
window.setTimeout will execute any given function after a specified delay.
You'd call it like this:
$('yourElement').click(function (event) {
setTimeout(function () { console.log('hi'); }, 1000);
});
But I have to wonder why you need to do this. What's the problem you're trying to solve? Usually delaying stuff doesn't really solve anything.
jQuery's ajax functionality provides exactly what you are looking for. You can define a callback function to run after your ajax request.
Something like this:
$('#someElement').click(function(event){
event.preventDefault();
var loc = $(this).attr('href');
$.ajax({
url: "test.html",
complete: function(){
// Handle the complete event
loc = $(this).attr('href');
window.location.href = loc;
}
});
});
You may want to use ajaxStop instead of complete, it seems like your motivation for delaying navigation is because you have a bunch of asynchronous stuff going on and you want to make sure all your ajax stuff is complete before you navigate to that page.
Regardless I would recommend looking at http://api.jquery.com/Ajax_Events/ (a very useful page of documentation).

Categories