0
votes

I am using the following code, which uses the imagesLoaded package with a callback to tell me when an element with a particular csspath has finished loading all of its images:

imagesLoadedScript = "imagesLoaded( '#{csspath}', { background: true }, function(message) { console.log('PHANTOM CLIENT REPORTING: #{csspath} Images Loaded'); return message; })"
imagesLoadedScript = imagesLoadedScript.strip.gsub(/\s+/,' ')
@session.evaluate_script(imagesLoadedScript)

The timing of the console.log statement, on inspection of PhantomJS logs with debug on, indicates that Capybara/Poltergiest is not waiting for the images to load, as expected, before it moves on to the next statement. I also cannot return a true (or false) value from inside the callback as I would like.

Capybara responds with

{"command_id":"678f1e2e-4820-4631-8cd6-413ce6f4b66f","response":"(cyclic structure)"}

Anyone have any ideas on how to return a value from inside a callback in a function executed via evaluate_script?

Many thanks.

2

2 Answers

1
votes

TLDR; You can't

evaluate_script doesn't support asynchronous functions - you must return the result you want from the function passed in. One way to do what you want would be to execute the imagesLoaded script and have the callback set a global variable, and then loop on an evaluate_script fetching the result of the global until it's what you want - A very basic implementation would be something like

imagesLoadedScript = "window.allImagesLoaded = false; imagesLoaded( '#{csspath}', { background: true }, function() { window.my_images_loaded = true })"
@session.execute_script(imagesLoadedScript)
while [email protected]_script('window.allImagesLoaded')
  sleep 0.05
end

Obviously this could be made more flexible with a timeout ability, etc.

A second option would to write a custom capybara selector type for images with a loaded filter, although with the need for background image checking it would become pretty complicated and probably too slow to be useful.

0
votes

Just in case someone finds this later.

I did roughly what Thomas Walpole suggested in his answer, in a more roundabout fashion, but taking advantage of Poltergeist's inherent waiting capabilities;

#to check that the target has loaded its images, run images loaded
#after a small timeout to allow the page to get the images
#append a marker div to the dom if the images have successfully loaded
imagesLoadedScript = "var item = document.querySelector('#{csspath}');
                      window.scroll(0, item.offsetTop);
                      function imagesDone(path, fn) {
                        imagesLoaded( path, function(instance) {
                           console.log('PHANTOM CLIENT REPORTING: ' + path + ' Images Loaded');
                           fn(true);
                        })
                      }
                      setTimeout(function(){ 
                         imagesDone('#{csspath}', function(done) { 
                           var markerDiv = document.createElement('div'); 
                           markerDiv.id = 'ImagesLoadedMarker'; 
                           document.getElementsByTagName('html')[0].appendChild(markerDiv); 
                         });
                      }, 1000)"

#then we strip the new lines and spaces that we added to make it readable 
imagesLoadedScript = imagesLoadedScript.strip.gsub(/\s+/,' ')
#now we just execute the script as we do not need a return value
@session.execute_script(imagesLoadedScript)
#then we check for the marker, using capybara's inbuilt waiting time
if @session.has_xpath? "//*[@id ='ImagesLoadedMarker']"
   Rails.logger.debug "!!!!! PhantomClient: Images Loaded Reporting: #{csspath} Images Loaded: Check Time #{Time.now} !!!!!"
   @session.save_screenshot(file_path, :selector => csspath)
else
   Rails.logger.debug "!!!!! PhantomClient: Images Loaded Reporting: #{csspath} Images NOT Loaded: Check Time #{Time.now} !!!!!"
   @session.save_screenshot(file_path, :selector => csspath)
end