From a1f900876a2cb9d3cc80f749a0e56e9adbf814dc Mon Sep 17 00:00:00 2001 From: Bassem Ibrahim Date: Wed, 30 Jul 2025 13:23:11 +0200 Subject: [PATCH 1/2] Handle storybook V8 fetch call --- packages/browser/src/handle-stories-fetch.js | 39 +++++++++++++++++++ packages/browser/src/index.js | 2 + packages/core/src/get-stories-from-index.js | 24 ++++++++++++ packages/core/src/index.js | 2 + .../src/create-chrome-target.js | 15 +++++++ 5 files changed, 82 insertions(+) create mode 100644 packages/browser/src/handle-stories-fetch.js create mode 100644 packages/core/src/get-stories-from-index.js diff --git a/packages/browser/src/handle-stories-fetch.js b/packages/browser/src/handle-stories-fetch.js new file mode 100644 index 00000000..9fc21191 --- /dev/null +++ b/packages/browser/src/handle-stories-fetch.js @@ -0,0 +1,39 @@ +/** + * Storybook V8 initilization includes a fetch call to the index.json file. The index.json file includes a json object + * with all the stories from the storybook build. + * + * Since Loki is accessing the storybook build using file-based urls (file://tmp/storybook-static/iframe.html), + * Chromium will not be able to fetch the index.json file. (Fetch calls are blocked for security reasons.) + * + * This function handles the fetch call to the index.json file and returns the content of the file based on the + * storiesJson parameter. + * + * If this function doesn't do it, we would get 'failed to fetch' errors instead of the stories being rendered. + * + * @param {Window} window - The window object. + * @param {string} storiesJson - The json object with all the stories from the storybook build. + */ +const handleStoriesFetch = function (window, storiesJson) { + const originalFetch = window.fetch; + + const mockedFetch = (url, options) => { + if (url.endsWith('index.json')) { + return Promise.resolve( + // eslint-disable-next-line no-undef + new Response(storiesJson, { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }) + ); + } + + return originalFetch(url, options); + }; + + if (window && window.fetch) { + // eslint-disable-next-line no-param-reassign + window.fetch = mockedFetch; + } +}; + +module.exports = handleStoriesFetch; diff --git a/packages/browser/src/index.js b/packages/browser/src/index.js index 39b48842..9073844d 100644 --- a/packages/browser/src/index.js +++ b/packages/browser/src/index.js @@ -6,6 +6,7 @@ const disableInputCaret = require('./disable-input-caret'); const disablePointerEvents = require('./disable-pointer-events'); const getSelectorBoxSize = require('./get-selector-box-size'); const getStorybookError = require('./get-storybook-error'); +const handleStoriesFetch = require('./handle-stories-fetch'); const getStories = require('./get-stories'); const populateLokiHelpers = require('./populate-loki-helpers'); const setLokiIsRunning = require('./set-loki-is-running'); @@ -20,6 +21,7 @@ module.exports = { disablePointerEvents, getSelectorBoxSize, getStorybookError, + handleStoriesFetch, getStories, populateLokiHelpers, setLokiIsRunning, diff --git a/packages/core/src/get-stories-from-index.js b/packages/core/src/get-stories-from-index.js new file mode 100644 index 00000000..458716be --- /dev/null +++ b/packages/core/src/get-stories-from-index.js @@ -0,0 +1,24 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * Storybook V8 builds includes an index.json file that contains all the stories for the project. + * This function reads the index.json file and returns the content of the file. + * + * @param {string} baseUrl - The base url of the storybook build. + * @returns {string} - The content of the index.json file. + */ +function getStoriesFromIndex(baseUrl) { + const absoluteUrl = baseUrl.startsWith('file:') + ? baseUrl.replace('file:', '') + : baseUrl; + + const indexJsonContent = fs.readFileSync( + path.join(absoluteUrl, 'index.json'), + 'utf8' + ); + + return JSON.stringify(JSON.parse(indexJsonContent)); +} + +module.exports = { getStoriesFromIndex }; diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 8e99698a..10e794cd 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -4,12 +4,14 @@ const dependencyDetection = require('./dependency-detection'); const getAbsoluteURL = require('./get-absolute-url'); const { getLocalIPAddress } = require('./get-local-ip-address'); const { createStaticServer } = require('./create-static-server'); +const { getStoriesFromIndex } = require('./get-stories-from-index'); module.exports = Object.assign( { getAbsoluteURL, getLocalIPAddress, createStaticServer, + getStoriesFromIndex, }, errors, failureHandling, diff --git a/packages/target-chrome-core/src/create-chrome-target.js b/packages/target-chrome-core/src/create-chrome-target.js index 69878eff..743a1c76 100644 --- a/packages/target-chrome-core/src/create-chrome-target.js +++ b/packages/target-chrome-core/src/create-chrome-target.js @@ -11,7 +11,9 @@ const { setLokiIsRunning, setLokiTestAttribute, populateLokiHelpers, + handleStoriesFetch, } = require('@loki/browser'); +const { getStoriesFromIndex } = require('@loki/core'); const { createReadyStateManager } = require('@loki/integration-core'); const { @@ -43,6 +45,7 @@ function createChromeTarget( prepare ) { const resolvedBaseUrl = getAbsoluteURL(baseUrl); + const storiesJson = getStoriesFromIndex(baseUrl); function getDeviceMetrics(options) { return { @@ -65,6 +68,15 @@ function createChromeTarget( await Network.enable(); await DOM.enable(); await Page.enable(); + + if (debug.enabled) { + const { Console } = client; + await Console.enable(); + Console.messageAdded(({ message }) => { + debug('Console message:', message); + }); + } + if (options.userAgent) { await Network.setUserAgentOverride({ userAgent: options.userAgent, @@ -201,6 +213,9 @@ function createChromeTarget( await evaluateOnNewDocument(`(${disablePointerEvents})(window);`); await evaluateOnNewDocument(`(${disableInputCaret})(window);`); await evaluateOnNewDocument(`(${setLokiIsRunning})(window);`); + await evaluateOnNewDocument( + `(${handleStoriesFetch})(window, ${JSON.stringify(storiesJson)});` + ); debug(`Navigating to ${url}`); startObservingRequests(); From a839b3d46cce1a400b30851bc532983be60e90be Mon Sep 17 00:00:00 2001 From: Bassem Ibrahim Date: Wed, 30 Jul 2025 17:37:12 +0200 Subject: [PATCH 2/2] Do not read index.json if not found --- packages/core/src/get-stories-from-index.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/core/src/get-stories-from-index.js b/packages/core/src/get-stories-from-index.js index 458716be..3c4f8ce8 100644 --- a/packages/core/src/get-stories-from-index.js +++ b/packages/core/src/get-stories-from-index.js @@ -12,11 +12,15 @@ function getStoriesFromIndex(baseUrl) { const absoluteUrl = baseUrl.startsWith('file:') ? baseUrl.replace('file:', '') : baseUrl; + const indexJsonPath = path.join(absoluteUrl, 'index.json'); - const indexJsonContent = fs.readFileSync( - path.join(absoluteUrl, 'index.json'), - 'utf8' - ); + // If no index.json file is found, return an empty string. + // This is only required for a storybook V7 build. + if (!fs.existsSync(indexJsonPath)) { + return ''; + } + + const indexJsonContent = fs.readFileSync(indexJsonPath, 'utf8'); return JSON.stringify(JSON.parse(indexJsonContent)); }