From 6f290154229b1fa638dd5fdc1152f7e66e5e8036 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Mon, 31 Mar 2025 00:42:01 -0700 Subject: [PATCH] Set up a mostly-default project with BackstopJS Signed-off-by: Danila Fedorin --- backstop.js | 59 + backstop_data/engine_scripts/cookies.json | 14 + backstop_data/engine_scripts/imageStub.jpg | Bin 0 -> 2900 bytes .../playwright/clickAndHoverHelper.js | 43 + .../playwright/interceptImages.js | 31 + .../engine_scripts/playwright/loadCookies.js | 16 + .../engine_scripts/playwright/onBefore.js | 3 + .../engine_scripts/playwright/onReady.js | 6 + .../engine_scripts/playwright/overrideCSS.js | 27 + .../puppet/clickAndHoverHelper.js | 41 + .../engine_scripts/puppet/ignoreCSP.js | 65 + .../engine_scripts/puppet/interceptImages.js | 37 + .../engine_scripts/puppet/loadCookies.js | 33 + .../engine_scripts/puppet/onBefore.js | 3 + .../engine_scripts/puppet/onReady.js | 6 + .../engine_scripts/puppet/overrideCSS.js | 15 + chatgpt-fix-root-URLs.py | 74 + functions.bash | 3 + package-lock.json | 2613 +++++++++++++++++ package.json | 6 + required.txt | 4 + 21 files changed, 3099 insertions(+) create mode 100644 backstop.js create mode 100644 backstop_data/engine_scripts/cookies.json create mode 100644 backstop_data/engine_scripts/imageStub.jpg create mode 100644 backstop_data/engine_scripts/playwright/clickAndHoverHelper.js create mode 100644 backstop_data/engine_scripts/playwright/interceptImages.js create mode 100644 backstop_data/engine_scripts/playwright/loadCookies.js create mode 100644 backstop_data/engine_scripts/playwright/onBefore.js create mode 100644 backstop_data/engine_scripts/playwright/onReady.js create mode 100644 backstop_data/engine_scripts/playwright/overrideCSS.js create mode 100644 backstop_data/engine_scripts/puppet/clickAndHoverHelper.js create mode 100644 backstop_data/engine_scripts/puppet/ignoreCSP.js create mode 100644 backstop_data/engine_scripts/puppet/interceptImages.js create mode 100644 backstop_data/engine_scripts/puppet/loadCookies.js create mode 100644 backstop_data/engine_scripts/puppet/onBefore.js create mode 100644 backstop_data/engine_scripts/puppet/onReady.js create mode 100644 backstop_data/engine_scripts/puppet/overrideCSS.js create mode 100644 chatgpt-fix-root-URLs.py create mode 100644 functions.bash create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 required.txt diff --git a/backstop.js b/backstop.js new file mode 100644 index 0000000..f364a77 --- /dev/null +++ b/backstop.js @@ -0,0 +1,59 @@ +const fs = require('node:fs'); +const list = fs.readFileSync('./pages.json', 'utf8'); + +function scenarioForFile(file) { + return { + "label": file, + "cookiePath": "backstop_data/engine_scripts/cookies.json", + "url": file, + "referenceUrl": "", + "readyEvent": "", + "readySelector": "", + "delay": 0, + "hideSelectors": [], + "removeSelectors": [], + "hoverSelector": "", + "clickSelector": "", + "postInteractionWait": 0, + "selectors": [], + "selectorExpansion": true, + "expect": 0, + "misMatchThreshold" : 0.1, + "requireSameDimensions": true + }; +} + +module.exports = { + "id": "blog_regression", + "viewports": [ + { + "label": "phone", + "width": 320, + "height": 480 + }, + { + "label": "tablet", + "width": 1024, + "height": 768 + } + ], + "onBeforeScript": "puppet/onBefore.js", + "onReadyScript": "puppet/onReady.js", + "scenarios": list.map(scenarioForFile), + "paths": { + "bitmaps_reference": "backstop_data/bitmaps_reference", + "bitmaps_test": "backstop_data/bitmaps_test", + "engine_scripts": "backstop_data/engine_scripts", + "html_report": "backstop_data/html_report", + "ci_report": "backstop_data/ci_report" + }, + "report": ["browser"], + "engine": "puppeteer", + "engineOptions": { + "args": ["--no-sandbox"] + }, + "asyncCaptureLimit": 5, + "asyncCompareLimit": 50, + "debug": false, + "debugWindow": false +} diff --git a/backstop_data/engine_scripts/cookies.json b/backstop_data/engine_scripts/cookies.json new file mode 100644 index 0000000..4123a5d --- /dev/null +++ b/backstop_data/engine_scripts/cookies.json @@ -0,0 +1,14 @@ +[ + { + "domain": ".www.yourdomain.com", + "path": "/", + "name": "yourCookieName", + "value": "yourCookieValue", + "expirationDate": 1798790400, + "hostOnly": false, + "httpOnly": false, + "secure": false, + "session": false, + "sameSite": "Lax" + } +] diff --git a/backstop_data/engine_scripts/imageStub.jpg b/backstop_data/engine_scripts/imageStub.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3e526b4bbbc74ef5125cb58f7c8849497ef4fa83 GIT binary patch literal 2900 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 literal 0 HcmV?d00001 diff --git a/backstop_data/engine_scripts/playwright/clickAndHoverHelper.js b/backstop_data/engine_scripts/playwright/clickAndHoverHelper.js new file mode 100644 index 0000000..8a0e8f0 --- /dev/null +++ b/backstop_data/engine_scripts/playwright/clickAndHoverHelper.js @@ -0,0 +1,43 @@ +module.exports = async (page, scenario) => { + const hoverSelector = scenario.hoverSelectors || scenario.hoverSelector; + const clickSelector = scenario.clickSelectors || scenario.clickSelector; + const keyPressSelector = scenario.keyPressSelectors || scenario.keyPressSelector; + const scrollToSelector = scenario.scrollToSelector; + const postInteractionWait = scenario.postInteractionWait; // selector [str] | ms [int] + + if (keyPressSelector) { + for (const keyPressSelectorItem of [].concat(keyPressSelector)) { + await page.waitForSelector(keyPressSelectorItem.selector); + await page.type(keyPressSelectorItem.selector, keyPressSelectorItem.keyPress); + } + } + + if (hoverSelector) { + for (const hoverSelectorIndex of [].concat(hoverSelector)) { + await page.waitForSelector(hoverSelectorIndex); + await page.hover(hoverSelectorIndex); + } + } + + if (clickSelector) { + for (const clickSelectorIndex of [].concat(clickSelector)) { + await page.waitForSelector(clickSelectorIndex); + await page.click(clickSelectorIndex); + } + } + + if (postInteractionWait) { + if (parseInt(postInteractionWait) > 0) { + await page.waitForTimeout(postInteractionWait); + } else { + await page.waitForSelector(postInteractionWait); + } + } + + if (scrollToSelector) { + await page.waitForSelector(scrollToSelector); + await page.evaluate(scrollToSelector => { + document.querySelector(scrollToSelector).scrollIntoView(); + }, scrollToSelector); + } +}; diff --git a/backstop_data/engine_scripts/playwright/interceptImages.js b/backstop_data/engine_scripts/playwright/interceptImages.js new file mode 100644 index 0000000..4077e76 --- /dev/null +++ b/backstop_data/engine_scripts/playwright/interceptImages.js @@ -0,0 +1,31 @@ +/** + * INTERCEPT IMAGES + * Listen to all requests. If a request matches IMAGE_URL_RE + * then stub the image with data from IMAGE_STUB_URL + * + * Use this in an onBefore script E.G. + ``` + module.exports = async function(page, scenario) { + require('./interceptImages')(page, scenario); + } + ``` + * + */ + +const fs = require('fs'); +const path = require('path'); + +const IMAGE_URL_RE = /\.gif|\.jpg|\.png/i; +const IMAGE_STUB_URL = path.resolve(__dirname, '../../imageStub.jpg'); +const IMAGE_DATA_BUFFER = fs.readFileSync(IMAGE_STUB_URL); +const HEADERS_STUB = {}; + +module.exports = async function (page, scenario) { + page.route(IMAGE_URL_RE, route => { + route.fulfill({ + body: IMAGE_DATA_BUFFER, + headers: HEADERS_STUB, + status: 200 + }); + }); +}; diff --git a/backstop_data/engine_scripts/playwright/loadCookies.js b/backstop_data/engine_scripts/playwright/loadCookies.js new file mode 100644 index 0000000..6a9044e --- /dev/null +++ b/backstop_data/engine_scripts/playwright/loadCookies.js @@ -0,0 +1,16 @@ +const fs = require('fs'); + +module.exports = async (browserContext, scenario) => { + let cookies = []; + const cookiePath = scenario.cookiePath; + + // Read Cookies from File, if exists + if (fs.existsSync(cookiePath)) { + cookies = JSON.parse(fs.readFileSync(cookiePath)); + } + + // Add cookies to browser + browserContext.addCookies(cookies); + + console.log('Cookie state restored with:', JSON.stringify(cookies, null, 2)); +}; diff --git a/backstop_data/engine_scripts/playwright/onBefore.js b/backstop_data/engine_scripts/playwright/onBefore.js new file mode 100644 index 0000000..f163c2d --- /dev/null +++ b/backstop_data/engine_scripts/playwright/onBefore.js @@ -0,0 +1,3 @@ +module.exports = async (page, scenario, viewport, isReference, browserContext) => { + await require('./loadCookies')(browserContext, scenario); +}; diff --git a/backstop_data/engine_scripts/playwright/onReady.js b/backstop_data/engine_scripts/playwright/onReady.js new file mode 100644 index 0000000..a944d91 --- /dev/null +++ b/backstop_data/engine_scripts/playwright/onReady.js @@ -0,0 +1,6 @@ +module.exports = async (page, scenario, viewport, isReference, browserContext) => { + console.log('SCENARIO > ' + scenario.label); + await require('./clickAndHoverHelper')(page, scenario); + + // add more ready handlers here... +}; diff --git a/backstop_data/engine_scripts/playwright/overrideCSS.js b/backstop_data/engine_scripts/playwright/overrideCSS.js new file mode 100644 index 0000000..a61cbef --- /dev/null +++ b/backstop_data/engine_scripts/playwright/overrideCSS.js @@ -0,0 +1,27 @@ +/** + * OVERRIDE CSS + * Apply this CSS to the loaded page, as a way to override styles. + * + * Use this in an onReady script E.G. + ``` + module.exports = async function(page, scenario) { + await require('./overrideCSS')(page, scenario); + } + ``` + * + */ + +const BACKSTOP_TEST_CSS_OVERRIDE = ` + html { + background-image: none; + } +`; + +module.exports = async (page, scenario) => { + // inject arbitrary css to override styles + await page.addStyleTag({ + content: BACKSTOP_TEST_CSS_OVERRIDE + }); + + console.log('BACKSTOP_TEST_CSS_OVERRIDE injected for: ' + scenario.label); +}; diff --git a/backstop_data/engine_scripts/puppet/clickAndHoverHelper.js b/backstop_data/engine_scripts/puppet/clickAndHoverHelper.js new file mode 100644 index 0000000..703d3b8 --- /dev/null +++ b/backstop_data/engine_scripts/puppet/clickAndHoverHelper.js @@ -0,0 +1,41 @@ +module.exports = async (page, scenario) => { + const hoverSelector = scenario.hoverSelectors || scenario.hoverSelector; + const clickSelector = scenario.clickSelectors || scenario.clickSelector; + const keyPressSelector = scenario.keyPressSelectors || scenario.keyPressSelector; + const scrollToSelector = scenario.scrollToSelector; + const postInteractionWait = scenario.postInteractionWait; // selector [str] | ms [int] + + if (keyPressSelector) { + for (const keyPressSelectorItem of [].concat(keyPressSelector)) { + await page.waitForSelector(keyPressSelectorItem.selector); + await page.type(keyPressSelectorItem.selector, keyPressSelectorItem.keyPress); + } + } + + if (hoverSelector) { + for (const hoverSelectorIndex of [].concat(hoverSelector)) { + await page.waitForSelector(hoverSelectorIndex); + await page.hover(hoverSelectorIndex); + } + } + + if (clickSelector) { + for (const clickSelectorIndex of [].concat(clickSelector)) { + await page.waitForSelector(clickSelectorIndex); + await page.click(clickSelectorIndex); + } + } + + if (postInteractionWait) { + await new Promise(resolve => { + setTimeout(resolve, postInteractionWait); + }); + } + + if (scrollToSelector) { + await page.waitForSelector(scrollToSelector); + await page.evaluate(scrollToSelector => { + document.querySelector(scrollToSelector).scrollIntoView(); + }, scrollToSelector); + } +}; diff --git a/backstop_data/engine_scripts/puppet/ignoreCSP.js b/backstop_data/engine_scripts/puppet/ignoreCSP.js new file mode 100644 index 0000000..1dea285 --- /dev/null +++ b/backstop_data/engine_scripts/puppet/ignoreCSP.js @@ -0,0 +1,65 @@ +/** + * IGNORE CSP HEADERS + * Listen to all requests. If a request matches scenario.url + * then fetch the request again manually, strip out CSP headers + * and respond to the original request without CSP headers. + * Allows `ignoreHTTPSErrors: true` BUT... requires `debugWindow: true` + * + * see https://github.com/GoogleChrome/puppeteer/issues/1229#issuecomment-380133332 + * this is the workaround until Page.setBypassCSP lands... https://github.com/GoogleChrome/puppeteer/pull/2324 + * + * @param {REQUEST} request + * @return {VOID} + * + * Use this in an onBefore script E.G. + ``` + module.exports = async function(page, scenario) { + require('./removeCSP')(page, scenario); + } + ``` + * + */ + +const fetch = require('node-fetch'); +const https = require('https'); +const agent = new https.Agent({ + rejectUnauthorized: false +}); + +module.exports = async function (page, scenario) { + const intercept = async (request, targetUrl) => { + const requestUrl = request.url(); + + // FIND TARGET URL REQUEST + if (requestUrl === targetUrl) { + const cookiesList = await page.cookies(requestUrl); + const cookies = cookiesList.map(cookie => `${cookie.name}=${cookie.value}`).join('; '); + const headers = Object.assign(request.headers(), { cookie: cookies }); + const options = { + headers, + body: request.postData(), + method: request.method(), + follow: 20, + agent + }; + + const result = await fetch(requestUrl, options); + + const buffer = await result.buffer(); + const cleanedHeaders = result.headers._headers || {}; + cleanedHeaders['content-security-policy'] = ''; + await request.respond({ + body: buffer, + headers: cleanedHeaders, + status: result.status + }); + } else { + request.continue(); + } + }; + + await page.setRequestInterception(true); + page.on('request', req => { + intercept(req, scenario.url); + }); +}; diff --git a/backstop_data/engine_scripts/puppet/interceptImages.js b/backstop_data/engine_scripts/puppet/interceptImages.js new file mode 100644 index 0000000..2b02be9 --- /dev/null +++ b/backstop_data/engine_scripts/puppet/interceptImages.js @@ -0,0 +1,37 @@ +/** + * INTERCEPT IMAGES + * Listen to all requests. If a request matches IMAGE_URL_RE + * then stub the image with data from IMAGE_STUB_URL + * + * Use this in an onBefore script E.G. + ``` + module.exports = async function(page, scenario) { + require('./interceptImages')(page, scenario); + } + ``` + * + */ + +const fs = require('fs'); +const path = require('path'); + +const IMAGE_URL_RE = /\.gif|\.jpg|\.png/i; +const IMAGE_STUB_URL = path.resolve(__dirname, '../imageStub.jpg'); +const IMAGE_DATA_BUFFER = fs.readFileSync(IMAGE_STUB_URL); +const HEADERS_STUB = {}; + +module.exports = async function (page, scenario) { + const intercept = async (request, targetUrl) => { + if (IMAGE_URL_RE.test(request.url())) { + await request.respond({ + body: IMAGE_DATA_BUFFER, + headers: HEADERS_STUB, + status: 200 + }); + } else { + request.continue(); + } + }; + await page.setRequestInterception(true); + page.on('request', intercept); +}; diff --git a/backstop_data/engine_scripts/puppet/loadCookies.js b/backstop_data/engine_scripts/puppet/loadCookies.js new file mode 100644 index 0000000..85b90cd --- /dev/null +++ b/backstop_data/engine_scripts/puppet/loadCookies.js @@ -0,0 +1,33 @@ +const fs = require('fs'); + +module.exports = async (page, scenario) => { + let cookies = []; + const cookiePath = scenario.cookiePath; + + // READ COOKIES FROM FILE IF EXISTS + if (fs.existsSync(cookiePath)) { + cookies = JSON.parse(fs.readFileSync(cookiePath)); + } + + // MUNGE COOKIE DOMAIN + cookies = cookies.map(cookie => { + if (cookie.domain.startsWith('http://') || cookie.domain.startsWith('https://')) { + cookie.url = cookie.domain; + } else { + cookie.url = 'https://' + cookie.domain; + } + delete cookie.domain; + return cookie; + }); + + // SET COOKIES + const setCookies = async () => { + return Promise.all( + cookies.map(async (cookie) => { + await page.setCookie(cookie); + }) + ); + }; + await setCookies(); + console.log('Cookie state restored with:', JSON.stringify(cookies, null, 2)); +}; diff --git a/backstop_data/engine_scripts/puppet/onBefore.js b/backstop_data/engine_scripts/puppet/onBefore.js new file mode 100644 index 0000000..a1c374c --- /dev/null +++ b/backstop_data/engine_scripts/puppet/onBefore.js @@ -0,0 +1,3 @@ +module.exports = async (page, scenario, vp) => { + await require('./loadCookies')(page, scenario); +}; diff --git a/backstop_data/engine_scripts/puppet/onReady.js b/backstop_data/engine_scripts/puppet/onReady.js new file mode 100644 index 0000000..517c0e4 --- /dev/null +++ b/backstop_data/engine_scripts/puppet/onReady.js @@ -0,0 +1,6 @@ +module.exports = async (page, scenario, vp) => { + console.log('SCENARIO > ' + scenario.label); + await require('./clickAndHoverHelper')(page, scenario); + + // add more ready handlers here... +}; diff --git a/backstop_data/engine_scripts/puppet/overrideCSS.js b/backstop_data/engine_scripts/puppet/overrideCSS.js new file mode 100644 index 0000000..d568205 --- /dev/null +++ b/backstop_data/engine_scripts/puppet/overrideCSS.js @@ -0,0 +1,15 @@ +const BACKSTOP_TEST_CSS_OVERRIDE = 'html {background-image: none;}'; + +module.exports = async (page, scenario) => { + // inject arbitrary css to override styles + await page.evaluate(`window._styleData = '${BACKSTOP_TEST_CSS_OVERRIDE}'`); + await page.evaluate(() => { + const style = document.createElement('style'); + style.type = 'text/css'; + const styleNode = document.createTextNode(window._styleData); + style.appendChild(styleNode); + document.head.appendChild(style); + }); + + console.log('BACKSTOP_TEST_CSS_OVERRIDE injected for: ' + scenario.label); +}; diff --git a/chatgpt-fix-root-URLs.py b/chatgpt-fix-root-URLs.py new file mode 100644 index 0000000..a95f3b2 --- /dev/null +++ b/chatgpt-fix-root-URLs.py @@ -0,0 +1,74 @@ +import os +from bs4 import BeautifulSoup +from urllib.parse import urlparse + +# Domains considered part of your site. +SITE_ROOT_URLS = ["https://danilafe.com/", "http://danilafe.com/"] +# The project root is the current working directory. +PROJECT_ROOT = os.getcwd() +HTML_EXTENSIONS = {".html", ".htm"} + +def convert_to_relative(url, base_filepath): + """ + Convert an absolute URL (including domain-relative URLs) to a relative path + appropriate for the HTML file at base_filepath. + """ + parsed = urlparse(url) + # If the URL is already relative, return it unchanged. + if not (url.startswith("/") or any(url.startswith(root) for root in SITE_ROOT_URLS)): + return url + + # If it's an absolute URL on danilafe.com, strip the domain. + for root_url in SITE_ROOT_URLS: + if url.startswith(root_url): + url = url[len(root_url):] + break + + # For domain-relative URLs (starting with "/"), remove the leading slash. + if url.startswith("/"): + url = url.lstrip("/") + + # Build the full filesystem path for the target resource. + target_path = os.path.normpath(os.path.join(PROJECT_ROOT, url)) + base_dir = os.path.dirname(base_filepath) + # Compute the relative path from the HTML file's directory to the target. + relative_path = os.path.relpath(target_path, start=base_dir) + return relative_path.replace(os.path.sep, "/") + +def process_html_file(filepath): + """Process a single HTML file to rewrite links, unwrap