UNPKG

@applitools/eyes-storybook

Version:
408 lines (362 loc) 13.2 kB
function __getStories(...args) { var getStories = (function () { 'use strict'; const API_VERSIONS = { v4: 'v4', v5: 'v5', v5_2: 'v5_2', v6_4: 'v6_4', v8: 'v8', }; function getClientAPI() { const frameWindow = getFrameWindow(); const clientAPI = frameWindow.__STORYBOOK_CLIENT_API__; const addons = frameWindow.__STORYBOOK_ADDONS || frameWindow.__STORYBOOK_ADDONS_PREVIEW; return getAPI(getStorybookVersion()); function getStorybookVersion() { const addonsForV4 = frameWindow.__STORYBOOK_ADDONS; if (!clientAPI && frameWindow.__STORYBOOK_PREVIEW__) { return API_VERSIONS.v8; } else if (frameWindow.__STORYBOOK_PREVIEW__) { return API_VERSIONS.v6_4; } else if (frameWindow.__STORYBOOK_STORY_STORE__) { return API_VERSIONS.v5_2; } else if (frameWindow.__STORYBOOK_CLIENT_API__ && frameWindow.__STORYBOOK_CLIENT_API__.raw) { return API_VERSIONS.v5; } else if ( addonsForV4 && addonsForV4.channel && addonsForV4.channel._listeners && addonsForV4.channel._listeners.setCurrentStory && addonsForV4.channel._listeners.setCurrentStory[0] ) { return API_VERSIONS.v4; } else { const storybookVariablesOnWindow = Object.keys(frameWindow).filter(key => key.includes('STORYBOOK'), ); throw new Error( `Cannot get client API: couldn't detect storybook version.${ storybookVariablesOnWindow.length ? ` Found the following storybook variables: ${storybookVariablesOnWindow.join(', ')}` : ' - no STORYBOOK variables found in global scope' }`, ); } } function onStoryRendered(callback) { const channel = addons?.getChannel?.(); if (channel) { channel.once('storyRendered', () => { setTimeout(callback, 0); }); channel.once('playFunctionThrewException', () => { setTimeout(callback, 0); }); channel.once('storyErrored', () => { setTimeout(callback, 0); }); channel.once('storyThrewException', () => { setTimeout(callback, 0); }); } else { callback(); } } function getAPI(version) { if (version) { let api; switch (version) { case API_VERSIONS.v4: { api = { getStories: () => { if (!frameWindow.__APPLITOOLS_STORIES) { frameWindow.__APPLITOOLS_STORIES = Object.values(clientAPI._storyStore._data) .map(({stories, kind}) => Object.values(stories).map(s => ({...s, kind}))) .flat(); } return frameWindow.__APPLITOOLS_STORIES; }, selectStory: i => { const {kind, name: story} = api.getStories()[i]; addons.channel._listeners.setCurrentStory[0]({kind, story}); }, onStoryRendered, }; break; } case API_VERSIONS.v5: { api = { getStories: () => { return clientAPI.raw(); }, selectStory: i => { clientAPI._storyStore.setSelection(clientAPI.raw()[i]); }, onStoryRendered, }; break; } case API_VERSIONS.v5_2: { api = { getStories: () => { return clientAPI.raw(); }, selectStory: i => { frameWindow.__STORYBOOK_STORY_STORE__.setSelection({storyId: clientAPI.raw()[i].id}); }, onStoryRendered, }; break; } case API_VERSIONS.v6_4: { api = { getStories: async () => { if (clientAPI.storyStore.cacheAllCSFFiles) { await clientAPI.storyStore.cacheAllCSFFiles(); } return clientAPI.raw(); }, selectStory: async (i, id) => { let storyId = !clientAPI.storyStore.cacheAllCSFFiles ? clientAPI.raw()[i].id : id; if (!storyId) { await clientAPI.storyStore.cacheAllCSFFiles(); storyId = clientAPI.raw()[i].id; } if (frameWindow.__STORYBOOK_PREVIEW__.urlStore) { frameWindow.__STORYBOOK_PREVIEW__.urlStore.setSelection({ storyId, }); } else { // storybook v7 await clientAPI.storyStore.initializationPromise; frameWindow.__STORYBOOK_PREVIEW__.selectionStore.setSelection({storyId}); } await frameWindow.__STORYBOOK_PREVIEW__.renderSelection(); }, onStoryRendered, }; break; } case API_VERSIONS.v8: { api = { getStories: async () => { await frameWindow.__STORYBOOK_PREVIEW__.ready(); return Object.values(await frameWindow.__STORYBOOK_PREVIEW__.extract()); }, selectStory: async (_i, id) => { await frameWindow.__STORYBOOK_PREVIEW__.ready(); frameWindow.__STORYBOOK_ADDONS_PREVIEW.channel.emit('setCurrentStory', {storyId: id}); }, onStoryRendered, }; } } return {version, ...api}; } } } function getFrameWindow() { if ( (/iframe/.test(window.location.href) && window.__STORYBOOK_PREVIEW__) || /iframe\.html/.test(window.location.href) ) { return window; } const innerFrameWindow = Array.prototype.find.call(window.frames, frame => { try { return ( (/\/iframe/.test(frame.location.href) && frame.__STORYBOOK_PREVIEW__) || /\/iframe\.html/.test(frame.location.href) ); } catch (e) {} }); if (innerFrameWindow) { return innerFrameWindow; } // frame.location.href might not be 'iframe' but still be the correct one if (window.__STORYBOOK_CLIENT_API__ || window.__STORYBOOK_PREVIEW__) { return window; } // the inner frame might have a different url pattern but still be the correct one // this is a fallback in case no other frame matched the usual patterns // and this should be backwards compatible with older storybook versions 🤞 const fallbackFrameWindow = Array.prototype.find.call(window.frames, frame => { try { return frame.__STORYBOOK_PREVIEW__; } catch (e) {} }); if (fallbackFrameWindow) { return fallbackFrameWindow; } throw new Error('Cannot get client API: no frameWindow'); } async function getClientAPIWithRetries({timeout = 5000} = {}) { let error = 'Unknown error'; const RETRY_INTERVAL = 100; const totalAttempts = timeout / RETRY_INTERVAL; for (let attempt = 1; attempt <= totalAttempts; attempt++) { await new Promise(resolve => setTimeout(resolve, RETRY_INTERVAL)); try { return getClientAPI(); } catch (e) { if (!(attempt % 10)) { console.log(`Error in getClientAPI: ${e.message}, attempt ${attempt}/${totalAttempts}`); } error = e; } } throw error; } var getClientAPI_1 = getClientAPIWithRetries; async function getStories() { const Stories = { _getStoriesV2: () => { let categories = getCategoriesV2(); console.log(`got ${categories.length} categories`); let stories = []; for (const anchor of categories) { verifyOpen(anchor); const kind = anchor.textContent; const storyEls = Array.from(anchor.nextElementSibling.querySelectorAll('a')); console.log('found', storyEls.length, 'stories for category', kind); stories = stories.concat( storyEls.map(storyEl => ({ kind, name: storyEl.textContent, })), ); } console.log(stories.map(({kind, name}, i) => `${i + 1}) story kind=${kind}, name=${name}`)); return stories; function verifyOpen(anchor) { if (!anchor.nextElementSibling) { anchor.click(); } } }, _getStoriesV3: () => { let menuItems = getAllMenuItemsV3(); console.log(`got ${menuItems.length} menu items`); let closedMenuItems = getClosedMenus(menuItems); console.log( `got ${closedMenuItems.length} closed menu items:\n${menuItemsToString(closedMenuItems)}`, ); while (closedMenuItems.length) { console.log(`opening ${closedMenuItems.length} closed menu items`); openMenus(closedMenuItems); menuItems = getAllMenuItemsV3(); closedMenuItems = getClosedMenus(menuItems); console.log(`after opening menus, got ${menuItems.length} menu items`); console.log( `after opening menus, got ${ closedMenuItems.length } closed menu items:\n${menuItemsToString(closedMenuItems)}`, ); } const anchors = Array.from( document.querySelectorAll('.Pane.vertical.Pane1 [role="menuitem"] + * a[href]'), ); console.log(`returning ${anchors.length} stories.`); return anchors.map((anchor, i) => { const url = new URL(anchor.href); const kind = url.searchParams.get('selectedKind'); const name = url.searchParams.get('selectedStory'); console.log(`${i + 1}) story kind=${kind}, name=${name}`); return { kind, name, }; }); function getClosedMenus(menuItems) { return menuItems.filter( menuItem => !menuItem.nextElementSibling || !menuItem.nextElementSibling.children[0], ); } function openMenus(menuItems) { menuItems.forEach(menuItem => menuItem.click()); } function menuItemsToString(menuItems) { return menuItems.map(item => item.textContent).join('\n'); } }, }; const clientApi = await getClientAPI_1().catch(() => null); if (clientApi) { console.log(`getting stories from storybook via API. ${clientApi.version}`); return getStoriesThroughClientAPI(clientApi); } else if (isStoryBookLoading()) { return Promise.reject('storybook is loading for too long'); } else { const storybookVersion = getVersion(); if (storybookVersion) { console.log(`getting stories from storybook via scraping. ${storybookVersion}`); return Stories[`_getStories${storybookVersion}`](); } else { return Promise.reject('could not determine storybook version in order to extract stories'); } } async function getStoriesThroughClientAPI(clientApi) { const stories = await clientApi.getStories(); return stories.map((story, index) => { const {name, kind, parameters, id} = story; const hasEyesParameters = parameters && parameters.eyes && typeof parameters.eyes === 'object'; let parametersIfSerialized, error; try { parametersIfSerialized = JSON.parse(JSON.stringify(parameters)); } catch (e) { try { if (hasEyesParameters) { parametersIfSerialized = JSON.parse(JSON.stringify({eyes: parameters.eyes})); } } catch (ex) { error = `Ignoring parameters for story: "${name} ${kind}" since they are not serilizable. Error: "${e.message}"`; } } if (parametersIfSerialized && hasEyesParameters) { for (const prop in parameters.eyes) { if (typeof parameters.eyes[prop] === 'function') { parametersIfSerialized.eyes[prop] = '__func'; } } } const hasPlayFunction = !!story.playFunction; return { isApi: true, hasPlayFunction, index, name, kind, id, parameters: parametersIfSerialized, error, }; }); } function getAllMenuItemsV3() { return Array.from(document.querySelectorAll('.Pane.vertical.Pane1 [role="menuitem"]')); } function getCategoriesV2() { const ul = document.querySelector('.Pane.vertical.Pane1 ul'); return ul && Array.from(ul.children).map(li => li.querySelector('a')); } function getVersion() { if (getAllMenuItemsV3().length !== 0) { return 'V3'; } else if (getCategoriesV2()) { return 'V2'; } } function isStoryBookLoading() { return Array.from(document.querySelectorAll('nav.container span')).some( s => s.innerText === 'loading story', ); } } var getStories_1 = getStories; return getStories_1; }()); return getStories.apply(this, args); } module.exports = __getStories