garris / BackstopJS

Catch CSS curve balls.
http://backstopjs.org
MIT License
6.71k stars 603 forks source link

Unreliable font loading causes difference between test and reference #1564

Open giorgiosironi opened 5 months ago

giorgiosironi commented 5 months ago

We are experiencing intermittent test failures due to font not loading for some scenarios.

Using BackstopJS 6.2.2, running in Docker with:

"dockerCommandTemplate": "docker run --rm -i --user $(id -u):$(id -g) --network sciety-test_default --mount type=bind,source=\"{cwd}\",target=/src backstopjs/backstopjs:{version} {backstopCommand} {args}",

Examples: failed_diff_backstop_default_Sciety_feed_0_main_2_desktop backstop_default_Sciety_feed_0_main_2_desktop_2 backstop_default_Sciety_feed_0_main_2_desktop

Researching previous issues lead us to code to wait for font loading: https://github.com/garris/BackstopJS/issues/1298#issuecomment-936130845 https://github.com/garris/BackstopJS/issues/1303#issuecomment-828225823 but due to the age of these issues we don't know how to implement this in onReady with the Puppeteer API, can anyone advise?

weseze commented 5 months ago

We are also experiencing this issue on 100+ websites with large testscanario's. We are testing local websites vs production websites to find visual regressions between updates.

Anything from 50-95% of tests fail. The fails are random. Every time we run the tests, the result is different.

These tests used to run fine until we updated our entire npm stack. Mainly because the chromium we were using didn't support modern CSS (things like grid) and those tests failed.

Tried almost every suggestion I could find here in this issue and related issues. Nothing has worked so far.

1/ Tried reverting to older version of backstopJS (mainly 5.0/5.1): this didn't work because of a dependency hell on old libraries that I couldn't get resolved. I was able to install a 5.2 release, but that didn't help.

2/ Tried updating everything to latest versions, remaking our backstop configs from the latest versions examples: did not work

3/ Tried adding JS to check if fonts are fully loaded before creating the screenshots: did not work

4/ Added delays up to 10 seconds before taking the screenshots: did not work

5/ Limit the async capture limit to 1: did not work

6/ Tried al sort of CSS injections for font smoothing, kerning, aliasing: nothing worked. Fonts just started looking ugly, but still the same small differences

7/ Tried to use 2 testscenario's with only the testUrl, not the referenceUrl and comparing those: did not work

I have run out of things to try... As of right now, backstopJS testing has become unusable for us and we are investigating other methods. Any help to get it working again would be greatly appreciated, because switching our entire testsuite to a new system is a big project...

giorgiosironi commented 5 months ago

For reference, we tried (from https://github.com/dgrebb/BackstopJS/commit/bf413fbad9694704ef382df9a540977d8a3f00a2):

await page.waitForFunction(() => document.fonts.ready);

added to onReady. Still see intermittent failures of the same kind.

dgrebb commented 5 months ago

Thanks @giorgiosironi - this adds the same into the runner itself, and I've seen some very minimal improvements.

Out of curiosity, and it sounds like the answer is no, could this be a CSP issue? The fonts are there sometimes but not always, correct?

randombitsUK commented 1 month ago

I experienced font intermittent issues a few years ago. It was very frustrating at the time. I resolved it by having the font files locally.

In onBefore.js I added a call to await require('./interceptFonts')(page, scenario);

interceptFonts.js contains:


const fs = require('fs');
const path = require('path');

const FONT_URL_RE = /fonts\/.../i;

module.exports = async function (page, scenario) {
    page.route(FONT_URL_RE, route => {
        const fontURL = route.request().url();
        const fontReference = fontURL.replace('https://{FONT_URL_PATTERN}/', '');
        const fontPath = path.resolve(__dirname, `../../fonts/${fontReference}`);

        if (!fs.existsSync(fontPath)) {
            console.log(`Download: ${fontURL} to ${fontPath}`)
        }

        route.fulfill({
            body: fs.readFileSync(fontPath),
            headers: {
                "content-type": "font/woff2",
                "accept-ranges": "bytes",
                "access-control-allow-credentials": false,
                "access-control-allow-headers": "*",
                "access-control-allow-methods": "HEAD,GET",
                "access-control-allow-origin": "*",
                "access-control-max-age": 300,
                "cache-control": "public, max-age=31536000, immutable",
                "timing-allow-origin": "https://www.{AAA}.co.uk, https://www.{AAA}.com",
            },
            status: 200
        });
    });
};

So basically I look for font related URL requests and then highlight the fonts that need to be downloaded. I am manually downloading them but yo could automate this.

This resolved any font inconsistencies that I experienced.