Skip to content

Instantly share code, notes, and snippets.

@munro
Last active June 28, 2024 21:57
Show Gist options
  • Save munro/7f81bd1657499866f7c2 to your computer and use it in GitHub Desktop.
Save munro/7f81bd1657499866f7c2 to your computer and use it in GitHub Desktop.
Selenium wait for all images to load, including background images.
from textwrap import dedent
def wait_until_images_loaded(driver, timeout=30):
"""Waits for all images & background images to load."""
driver.set_script_timeout(timeout)
driver.execute_async_script(dedent('''
// Function to extract URL from CSS 'url()' function
function extractCSSURL(text) {
var url_str = text.replace(/.*url\((.*)\).*/, '$1');
// If the URL is enclosed with double quotes
if (url_str[0] === '"') {
return JSON.parse(url_str);
}
// If the URL is enclosed with single quotes
if (url_str[0] === "'") {
return JSON.parse(
url_str
.replace(/'/g, '__DOUBLE__QUOTE__HERE__')
.replace(/"/g, "'")
.replace(/__DOUBLE__QUOTE__HERE__/g, '"')
);
}
// Return the URL as is
return url_str;
}
// Function to create a promise that resolves when the image is loaded
function imageResolved(url) {
return new Promise(function (resolve) {
var img = new Image();
img.onload = function () {
resolve(url);
};
img.src = url;
// If the image is already loaded, resolve the promise immediately
if (img.complete) {
resolve(url);
}
});
}
// The last argument is expected to be a callback function
var callback = arguments[arguments.length - 1];
Promise.all([
// Get all img tags, create a promise for each one
...Array.from(document.querySelectorAll('img[src]'), img => imageResolved(img.src)),
// Get all inline styles with 'url()' and create a promise for each one
...Array.from(document.querySelectorAll('[style*="url("]'), elem => imageResolved(extractCSSURL(elem.style.cssText)))
])
.then(function () {
// After all images are loaded, wait for all fonts to load
return document.fonts.ready;
})
.then(function() {
// After all fonts are loaded, check for images in CSS stylesheets
let cssImagesPromises = [];
for(let i = 0; i < document.styleSheets.length; i++) {
let rules = document.styleSheets[i].cssRules;
for (let j = 0; j < rules.length; j++) {
let style = rules[j].style;
// Check if a background image is found and if it's a URL
if (style && String(style.backgroundImage).startsWith('url(')) {
let url = extractCSSURL(style.backgroundImage);
cssImagesPromises.push(imageResolved(url));
}
}
}
return Promise.all(cssImagesPromises);
})
.then(function () {
// Call the callback function when all promises are resolved
callback(arguments);
});
return undefined;
'''))
@dmitridb
Copy link

dmitridb commented Jun 9, 2024

I haven't fully tested this (which is why I haven't updated the gist) but here's code that works without jQuery dependency:

Let me know if this works and I'll update the gist!!

// Function to extract URL from CSS 'url()' function
function extractCSSURL(text) {
    var url_str = text.replace(/.*url\((.*)\).*/, '$1');
    // If the URL is enclosed with double quotes
    if (url_str[0] === '"') {
        return JSON.parse(url_str);
    }
    // If the URL is enclosed with single quotes
    if (url_str[0] === "'") {
        return JSON.parse(
            url_str
                .replace(/'/g, '__DOUBLE__QUOTE__HERE__')
                .replace(/"/g, "'")
                .replace(/__DOUBLE__QUOTE__HERE__/g, '"')
        );
    }
    // Return the URL as is
    return url_str;
}

// Function to create a promise that resolves when the image is loaded
function imageResolved(url) {
    return new Promise(function (resolve) {
        var img = new Image();
        img.onload = function () {
            resolve(url);
        };
        img.src = url;
        // If the image is already loaded, resolve the promise immediately
        if (img.complete) {
            resolve(url);
        }
    });
}

// The last argument is expected to be a callback function
var callback = arguments[arguments.length - 1];

Promise.all([
    // Get all img tags, create a promise for each one
    ...Array.from(document.querySelectorAll('img[src]'), img => imageResolved(img.src)),
    // Get all inline styles with 'url()' and create a promise for each one
    ...Array.from(document.querySelectorAll('[style*="url("]'), elem => imageResolved(extractCSSURL(elem.style.cssText)))
])
.then(function () {
    // After all images are loaded, wait for all fonts to load
    return document.fonts.ready;
})
.then(function() {
    // After all fonts are loaded, check for images in CSS stylesheets
    let cssImagesPromises = [];
    for(let i = 0; i < document.styleSheets.length; i++) {
        let rules = document.styleSheets[i].cssRules;
        for (let j = 0; j < rules.length; j++) {
            let style = rules[j].style;
            // Check if a background image is found and if it's a URL
            if (style && String(style.backgroundImage).startsWith('url(')) {
                let url = extractCSSURL(style.backgroundImage);
                cssImagesPromises.push(imageResolved(url));
            }
        }
    }
    return Promise.all(cssImagesPromises);
})
.then(function () { 
    // Call the callback function when all promises are resolved
    callback(arguments); 
});

return undefined;

I can confirm this script works great thanks!

@munro
Copy link
Author

munro commented Jun 15, 2024

awesome updated 😄

@dmitridb
Copy link

dmitridb commented Jun 15, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment