@applitools/eyes-storybook
Version:
408 lines (362 loc) • 13.2 kB
JavaScript
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