PageSourceSearch

https://meticulous.ai/assets/index-Cy4eGKU4.js

js meticulous.ai collected 2026-10-03 19:03:05 UTC 1,387,417 bytes, 10,254 lines download raw bytes

vendor: 1,444 bytes, lines 1-2
1const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index-DhAS6qGo.js","assets/MarketingPage-BdNh-Pd-.js","assets/ResponsiveImage-Cqlthg7n.js","assets/Tabs-DiFX9v5f.js","assets/about-us-F_ftiKXA.js","assets/book-a-demo-CkLIhBy1.js","assets/case-studies-Dlw1ASa7.js","assets/how-it-works-D8jH6MjO.js","assets/privacy-policy-4HSC5mFr.js","assets/terms-conditions-BmRqwKjQ.js","assets/_slug-DnIFOLoz.js","assets/BackToBlogLink-B0dyEwlf.js","assets/BlogPostGrid-CnmZTE23.js","assets/BlogPostCard-CqY6YI2I.js","assets/BlogCategoryLink-CXBXI3Dh.js","assets/_slug-D6sFLFDh.js","assets/index-DF16s_l1.js","assets/_slug-BEa5Vw5q.js","assets/cases-tabs-element-BDLKGJFq.js","assets/engine-CixdYneU.js","assets/useVimeoHeroPlayButton-CtkagkH7.js","assets/launchdarkly-B0rD7a2c.js","assets/notion-Bp0okpt1.js"])))=>i.map(i=>d[i]);
2function Xy(a){return a&&a.__esModule&&Object.prototype.hasOwnProperty.call(a,"default")?a.default:a}var _d={exports:{}},Os={};var Rp;function Ky(){if(Rp)return Os;Rp=1;var a=Symbol.for("react.transitional.element"),i=Symbol.for("react.fragment");function s(r,c,u){var h=null;if(u!==void 0&&(h=""+u),c.key!==void 0&&(h=""+c.key),"key"in c){u={};for(var f in c)f!=="key"&&(u[f]=c[f])}else u=c;return c=u.ref,{$$typeof:a,type:r,key:h,ref:c!==void 0?c:null,props:u}}return Os.Fragment=i,Os.jsx=s,Os.jsxs=s,Os}var Op;function Zy(){return Op||(Op=1,_d.exports=Ky()),_d.exports}var Q=Zy(),kd={exports:{}},ue={};var Mp;
vendor: 16,384 bytes, line 2
2function e6(){if(Mp)return ue;Mp=1;var a=Symbol.for("react.transitional.element"),i=Symbol.for("react.portal"),s=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),c=Symbol.for("react.profiler"),u=Symbol.for("react.consumer"),h=Symbol.for("react.context"),f=Symbol.for("react.forward_ref"),m=Symbol.for("react.suspense"),g=Symbol.for("react.memo"),w=Symbol.for("react.lazy"),b=Symbol.for("react.activity"),v=Symbol.iterator;function _(A){return A===null||typeof A!="object"?null:(A=v&&A[v]||A["@@iterator"],typeof A=="function"?A:null)}var T={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},x=Object.assign,I={};function U(A,z,Y){this.props=A,this.context=z,this.refs=I,this.updater=Y||T}U.prototype.isReactComponent={},U.prototype.setState=function(A,z){if(typeof A!="object"&&typeof A!="function"&&A!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,A,z,"setState")},U.prototype.forceUpdate=function(A){this.updater.enqueueForceUpdate(this,A,"forceUpdate")};function N(){}N.prototype=U.prototype;function L(A,z,Y){this.props=A,this.context=z,this.refs=I,this.updater=Y||T}var G=L.prototype=new N;G.constructor=L,x(G,U.prototype),G.isPureReactComponent=!0;var Z=Array.isArray;function J(){}var M={H:null,A:null,T:null,S:null},V=Object.prototype.hasOwnProperty;function ae(A,z,Y){var K=Y.ref;return{$$typeof:a,type:A,key:z,ref:K!==void 0?K:null,props:Y}}function le(A,z){return ae(A.type,z,A.props)}function F(A){return typeof A=="object"&&A!==null&&A.$$typeof===a}function P(A){var z={"=":"=0",":":"=2"};return"$"+A.replace(/[=:]/g,function(Y){return z[Y]})}var $=/\/+/g;function ce(A,z){return typeof A=="object"&&A!==null&&A.key!=null?P(""+A.key):z.toString(36)}function te(A){switch(A.status){case"fulfilled":return A.value;case"rejected":throw A.reason;default:switch(typeof A.status=="string"?A.then(J,J):(A.status="pending",A.then(function(z){A.status==="pending"&&(A.status="fulfilled",A.value=z)},function(z){A.status==="pending"&&(A.status="rejected",A.reason=z)})),A.status){case"fulfilled":return A.value;case"rejected":throw A.reason}}throw A}function O(A,z,Y,K,de){var ge=typeof A;(ge==="undefined"||ge==="boolean")&&(A=null);var oe=!1;if(A===null)oe=!0;else switch(ge){case"bigint":case"string":case"number":oe=!0;break;case"object":switch(A.$$typeof){case a:case i:oe=!0;break;case w:return oe=A._init,O(oe(A._payload),z,Y,K,de)}}if(oe)return de=de(A),oe=K===""?"."+ce(A,0):K,Z(de)?(Y="",oe!=null&&(Y=oe.replace($,"$&/")+"/"),O(de,z,Y,"",function(Kt){return Kt})):de!=null&&(F(de)&&(de=le(de,Y+(de.key==null||A&&A.key===de.key?"":(""+de.key).replace($,"$&/")+"/")+oe)),z.push(de)),1;oe=0;var Se=K===""?".":K+":";if(Z(A))for(var De=0;De<A.length;De++)K=A[De],ge=Se+ce(K,De),oe+=O(K,z,Y,ge,de);else if(De=_(A),typeof De=="function")for(A=De.call(A),De=0;!(K=A.next()).done;)K=K.value,ge=Se+ce(K,De++),oe+=O(K,z,Y,ge,de);else if(ge==="object"){if(typeof A.then=="function")return O(te(A),z,Y,K,de);throw z=String(A),Error("Objects are not valid as a React child (found: "+(z==="[object Object]"?"object with keys {"+Object.keys(A).join(", ")+"}":z)+"). If you meant to render a collection of children, use an array instead.")}return oe}function W(A,z,Y){if(A==null)return A;var K=[],de=0;return O(A,K,"","",function(ge){return z.call(Y,ge,de++)}),K}function ie(A){if(A._status===-1){var z=A._result;z=z(),z.then(function(Y){(A._status===0||A._status===-1)&&(A._status=1,A._result=Y)},function(Y){(A._status===0||A._status===-1)&&(A._status=2,A._result=Y)}),A._status===-1&&(A._status=0,A._result=z)}if(A._status===1)return A._result.default;throw A._result}var Te=typeof reportError=="function"?reportError:function(A){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var z=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof A=="object"&&A!==null&&typeof A.message=="string"?String(A.message):String(A),error:A});if(!window.dispatchEvent(z))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",A);return}console.error(A)},be={map:W,forEach:function(A,z,Y){W(A,function(){z.apply(this,arguments)},Y)},count:function(A){var z=0;return W(A,function(){z++}),z},toArray:function(A){return W(A,function(z){return z})||[]},only:function(A){if(!F(A))throw Error("React.Children.only expected to receive a single React element child.");return A}};return ue.Activity=b,ue.Children=be,ue.Component=U,ue.Fragment=s,ue.Profiler=c,ue.PureComponent=L,ue.StrictMode=r,ue.Suspense=m,ue.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=M,ue.__COMPILER_RUNTIME={__proto__:null,c:function(A){return M.H.useMemoCache(A)}},ue.cache=function(A){return function(){return A.apply(null,arguments)}},ue.cacheSignal=function(){return null},ue.cloneElement=function(A,z,Y){if(A==null)throw Error("The argument must be a React element, but you passed "+A+".");var K=x({},A.props),de=A.key;if(z!=null)for(ge in z.key!==void 0&&(de=""+z.key),z)!V.call(z,ge)||ge==="key"||ge==="__self"||ge==="__source"||ge==="ref"&&z.ref===void 0||(K[ge]=z[ge]);var ge=arguments.length-2;if(ge===1)K.children=Y;else if(1<ge){for(var oe=Array(ge),Se=0;Se<ge;Se++)oe[Se]=arguments[Se+2];K.children=oe}return ae(A.type,de,K)},ue.createContext=function(A){return A={$$typeof:h,_currentValue:A,_currentValue2:A,_threadCount:0,Provider:null,Consumer:null},A.Provider=A,A.Consumer={$$typeof:u,_context:A},A},ue.createElement=function(A,z,Y){var K,de={},ge=null;if(z!=null)for(K in z.key!==void 0&&(ge=""+z.key),z)V.call(z,K)&&K!=="key"&&K!=="__self"&&K!=="__source"&&(de[K]=z[K]);var oe=arguments.length-2;if(oe===1)de.children=Y;else if(1<oe){for(var Se=Array(oe),De=0;De<oe;De++)Se[De]=arguments[De+2];de.children=Se}if(A&&A.defaultProps)for(K in oe=A.defaultProps,oe)de[K]===void 0&&(de[K]=oe[K]);return ae(A,ge,de)},ue.createRef=function(){return{current:null}},ue.forwardRef=function(A){return{$$typeof:f,render:A}},ue.isValidElement=F,ue.lazy=function(A){return{$$typeof:w,_payload:{_status:-1,_result:A},_init:ie}},ue.memo=function(A,z){return{$$typeof:g,type:A,compare:z===void 0?null:z}},ue.startTransition=function(A){var z=M.T,Y={};M.T=Y;try{var K=A(),de=M.S;de!==null&&de(Y,K),typeof K=="object"&&K!==null&&typeof K.then=="function"&&K.then(J,Te)}catch(ge){Te(ge)}finally{z!==null&&Y.types!==null&&(z.types=Y.types),M.T=z}},ue.unstable_useCacheRefresh=function(){return M.H.useCacheRefresh()},ue.use=function(A){return M.H.use(A)},ue.useActionState=function(A,z,Y){return M.H.useActionState(A,z,Y)},ue.useCallback=function(A,z){return M.H.useCallback(A,z)},ue.useContext=function(A){return M.H.useContext(A)},ue.useDebugValue=function(){},ue.useDeferredValue=function(A,z){return M.H.useDeferredValue(A,z)},ue.useEffect=function(A,z){return M.H.useEffect(A,z)},ue.useEffectEvent=function(A){return M.H.useEffectEvent(A)},ue.useId=function(){return M.H.useId()},ue.useImperativeHandle=function(A,z,Y){return M.H.useImperativeHandle(A,z,Y)},ue.useInsertionEffect=function(A,z){return M.H.useInsertionEffect(A,z)},ue.useLayoutEffect=function(A,z){return M.H.useLayoutEffect(A,z)},ue.useMemo=function(A,z){return M.H.useMemo(A,z)},ue.useOptimistic=function(A,z){return M.H.useOptimistic(A,z)},ue.useReducer=function(A,z,Y){return M.H.useReducer(A,z,Y)},ue.useRef=function(A){return M.H.useRef(A)},ue.useState=function(A){return M.H.useState(A)},ue.useSyncExternalStore=function(A,z,Y){return M.H.useSyncExternalStore(A,z,Y)},ue.useTransition=function(){return M.H.useTransition()},ue.version="19.2.5",ue}var Up;function Ws(){return Up||(Up=1,kd.exports=e6()),kd.exports}var se=Ws();const Jr=Xy(se);var xd={exports:{}},Ms={},Td={exports:{}},Sd={};var Dp;function t6(){return Dp||(Dp=1,(function(a){function i(O,W){var ie=O.length;O.push(W);e:for(;0<ie;){var Te=ie-1>>>1,be=O[Te];if(0<c(be,W))O[Te]=W,O[ie]=be,ie=Te;else break e}}function s(O){return O.length===0?null:O[0]}function r(O){if(O.length===0)return null;var W=O[0],ie=O.pop();if(ie!==W){O[0]=ie;e:for(var Te=0,be=O.length,A=be>>>1;Te<A;){var z=2*(Te+1)-1,Y=O[z],K=z+1,de=O[K];if(0>c(Y,ie))K<be&&0>c(de,Y)?(O[Te]=de,O[K]=ie,Te=K):(O[Te]=Y,O[z]=ie,Te=z);else if(K<be&&0>c(de,ie))O[Te]=de,O[K]=ie,Te=K;else break e}}return W}function c(O,W){var ie=O.sortIndex-W.sortIndex;return ie!==0?ie:O.id-W.id}if(a.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var u=performance;a.unstable_now=function(){return u.now()}}else{var h=Date,f=h.now();a.unstable_now=function(){return h.now()-f}}var m=[],g=[],w=1,b=null,v=3,_=!1,T=!1,x=!1,I=!1,U=typeof setTimeout=="function"?setTimeout:null,N=typeof clearTimeout=="function"?clearTimeout:null,L=typeof setImmediate<"u"?setImmediate:null;function G(O){for(var W=s(g);W!==null;){if(W.callback===null)r(g);else if(W.startTime<=O)r(g),W.sortIndex=W.expirationTime,i(m,W);else break;W=s(g)}}function Z(O){if(x=!1,G(O),!T)if(s(m)!==null)T=!0,J||(J=!0,P());else{var W=s(g);W!==null&&te(Z,W.startTime-O)}}var J=!1,M=-1,V=5,ae=-1;function le(){return I?!0:!(a.unstable_now()-ae<V)}function F(){if(I=!1,J){var O=a.unstable_now();ae=O;var W=!0;try{e:{T=!1,x&&(x=!1,N(M),M=-1),_=!0;var ie=v;try{t:{for(G(O),b=s(m);b!==null&&!(b.expirationTime>O&&le());){var Te=b.callback;if(typeof Te=="function"){b.callback=null,v=b.priorityLevel;var be=Te(b.expirationTime<=O);if(O=a.unstable_now(),typeof be=="function"){b.callback=be,G(O),W=!0;break t}b===s(m)&&r(m),G(O)}else r(m);b=s(m)}if(b!==null)W=!0;else{var A=s(g);A!==null&&te(Z,A.startTime-O),W=!1}}break e}finally{b=null,v=ie,_=!1}W=void 0}}finally{W?P():J=!1}}}var P;if(typeof L=="function")P=function(){L(F)};else if(typeof MessageChannel<"u"){var $=new MessageChannel,ce=$.port2;$.port1.onmessage=F,P=function(){ce.postMessage(null)}}else P=function(){U(F,0)};function te(O,W){M=U(function(){O(a.unstable_now())},W)}a.unstable_IdlePriority=5,a.unstable_ImmediatePriority=1,a.unstable_LowPriority=4,a.unstable_NormalPriority=3,a.unstable_Profiling=null,a.unstable_UserBlockingPriority=2,a.unstable_cancelCallback=function(O){O.callback=null},a.unstable_forceFrameRate=function(O){0>O||125<O?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):V=0<O?Math.floor(1e3/O):5},a.unstable_getCurrentPriorityLevel=function(){return v},a.unstable_next=function(O){switch(v){case 1:case 2:case 3:var W=3;break;default:W=v}var ie=v;v=W;try{return O()}finally{v=ie}},a.unstable_requestPaint=function(){I=!0},a.unstable_runWithPriority=function(O,W){switch(O){case 1:case 2:case 3:case 4:case 5:break;default:O=3}var ie=v;v=O;try{return W()}finally{v=ie}},a.unstable_scheduleCallback=function(O,W,ie){var Te=a.unstable_now();switch(typeof ie=="object"&&ie!==null?(ie=ie.delay,ie=typeof ie=="number"&&0<ie?Te+ie:Te):ie=Te,O){case 1:var be=-1;break;case 2:be=250;break;case 5:be=1073741823;break;case 4:be=1e4;break;default:be=5e3}return be=ie+be,O={id:w++,callback:W,priorityLevel:O,startTime:ie,expirationTime:be,sortIndex:-1},ie>Te?(O.sortIndex=ie,i(g,O),s(m)===null&&O===s(g)&&(x?(N(M),M=-1):x=!0,te(Z,ie-Te))):(O.sortIndex=be,i(m,O),T||_||(T=!0,J||(J=!0,P()))),O},a.unstable_shouldYield=le,a.unstable_wrapCallback=function(O){var W=v;return function(){var ie=v;v=W;try{return O.apply(this,arguments)}finally{v=ie}}}})(Sd)),Sd}var Bp;function n6(){return Bp||(Bp=1,Td.exports=t6()),Td.exports}var Ad={exports:{}},ct={};var Lp;function a6(){if(Lp)return ct;Lp=1;var a=Ws();function i(m){var g="https://react.dev/errors/"+m;if(1<arguments.length){g+="?args[]="+encodeURIComponent(arguments[1]);for(var w=2;w<arguments.length;w++)g+="&args[]="+encodeURIComponent(arguments[w])}return"Minified React error #"+m+"; visit "+g+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function s(){}var r={d:{f:s,r:function(){throw Error(i(522))},D:s,C:s,L:s,m:s,X:s,S:s,M:s},p:0,findDOMNode:null},c=Symbol.for("react.portal");function u(m,g,w){var b=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:c,key:b==null?null:""+b,children:m,containerInfo:g,implementation:w}}var h=a.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function f(m,g){if(m==="font")return"";if(typeof g=="string")return g==="use-credentials"?g:""}return ct.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=r,ct.createPortal=function(m,g){var w=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!g||g.nodeType!==1&&g.nodeType!==9&&g.nodeType!==11)throw Error(i(299));return u(m,g,null,w)},ct.flushSync=function(m){var g=h.T,w=r.p;try{if(h.T=null,r.p=2,m)return m()}finally{h.T=g,r.p=w,r.d.f()}},ct.preconnect=function(m,g){typeof m=="string"&&(g?(g=g.crossOrigin,g=typeof g=="string"?g==="use-credentials"?g:"":void 0):g=null,r.d.C(m,g))},ct.prefetchDNS=function(m){typeof m=="string"&&r.d.D(m)},ct.preinit=function(m,g){if(typeof m=="string"&&g&&typeof g.as=="string"){var w=g.as,b=f(w,g.crossOrigin),v=typeof g.integrity=="string"?g.integrity:void 0,_=typeof g.fetchPriority=="string"?g.fetchPriority:void 0;w==="style"?r.d.S(m,typeof g.precedence=="string"?g.precedence:void 0,{crossOrigin:b,integrity:v,fetchPriority:_}):w==="script"&&r.d.X(m,{crossOrigin:b,integrity:v,fetchPriority:_,nonce:typeof g.nonce=="string"?g.nonce:void 0})}},ct.preinitModule=function(m,g){if(typeof m=="string")if(typeof g=="object"&&g!==null){if(g.as==null||g.as==="script"){var w=f(g.as,g.crossOrigin);r.d.M(m,{crossOrigin:w,integrity:typeof g.integrity=="string"?g.integrity:void 0,nonce:typeof g.nonce=="string"?g.nonce:void 0})}}else g==null&&r.d.M(m)},ct.preload=function(m,g){if(typeof m=="string"&&typeof g=="object"&&g!==null&&typeof g.as=="string"){var w=g.as,b=f(w,g.crossOrigin);r.d.L(m,w,{crossOrigin:b,integrity:typeof g.integrity=="string"?g.integrity:void 0,nonce:typeof g.nonce=="string"?g.nonce:void 0,type:typeof g.type=="string"?g.type:void 0,fetchPriority:typeof g.fetchPriority=="string"?g.fetchPriority:void 0,referrerPolicy:typeof g.referrerPolicy=="string"?g.referrerPolicy:void 0,imageSrcSet:typeof g.imageSrcSet=="string"?g.imageSrcSet:void 0,imageSizes:typeof g.imageSizes=="string"?g.imageSizes:void 0,media:typeof g.media=="string"?g.media:void 0})}},ct.preloadModule=function(m,g){if(typeof m=="string")if(g){var w=f(g.as,g.crossOrigin);r.d.m(m,{as:typeof g.as=="string"&&g.as!=="script"?g.as:void 0,crossOrigin:w,integrity:typeof g.integrity=="string"?g.integrity:void 0})}else r.d.m(m)},ct.requestFormReset=function(m){r.d.r(m)},ct.unstable_batchedUpdates=function(m,g){return m(g)},ct.useFormState=function(m,g,w){return h.H.useFormState(m,g,w)},ct.useFormStatus=function(){return h.H.useHostTransitionStatus()},ct.version="19.2.5",ct}var qp;function i6(){if(qp)return Ad.exports;qp=1;function a(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(a)}catch(i){console.error(i)}}return a(),Ad.exports=a6(),Ad.exports}var zp;function s6(){if(zp)return Ms;zp=1;var a=n6(),i=Ws(),s=i6();function r(e){var t="https://react.dev/errors/"+e;if(1<arguments.length){t+="?args[]="+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n])}return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function c(e){return!(!e||e.nodeType!==1&&e.nodeType!==9&&e.nodeType!==11)}function u(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do t=e,(t.flags&4098)!==0&&(n=t.return),e=t.return;while(e)}return t.tag===3?n:null}function h(e){if(e.tag===13){var t=e.memoizedState;if(t===null&&(e=e.alternate,e!==null&&(t=e.memoizedState)),t!==null)return t.dehydrated}return null}function f(e){if(e.tag===31){var t=e.memoizedState;if(t===null&&(e=e.alternate,e!==null&&(t=e.memoizedState)),t!==null)return t.dehydrated}return null}function m(e){if(u(e)!==e)throw Error(r(188))}function g(e){var t=e.alternate;if(!t){if(t=u(e),t===null)throw Error(r(188));return t!==e?null:e}for(var n=e,o=t;;){var l=n.return;if(l===null)break;var d=l.alternate;if(d===null){if(o=l.return,o!==null){n=o;continue}break}if(l.child===d.child){
vendor: 16,009 bytes, lines 2-9
2for(d=l.child;d;){if(d===n)return m(l),e;if(d===o)return m(l),t;d=d.sibling}throw Error(r(188))}if(n.return!==o.return)n=l,o=d;else{for(var p=!1,y=l.child;y;){if(y===n){p=!0,n=l,o=d;break}if(y===o){p=!0,o=l,n=d;break}y=y.sibling}if(!p){for(y=d.child;y;){if(y===n){p=!0,n=d,o=l;break}if(y===o){p=!0,o=d,n=l;break}y=y.sibling}if(!p)throw Error(r(189))}}if(n.alternate!==o)throw Error(r(190))}if(n.tag!==3)throw Error(r(188));return n.stateNode.current===n?e:t}function w(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e;for(e=e.child;e!==null;){if(t=w(e),t!==null)return t;e=e.sibling}return null}var b=Object.assign,v=Symbol.for("react.element"),_=Symbol.for("react.transitional.element"),T=Symbol.for("react.portal"),x=Symbol.for("react.fragment"),I=Symbol.for("react.strict_mode"),U=Symbol.for("react.profiler"),N=Symbol.for("react.consumer"),L=Symbol.for("react.context"),G=Symbol.for("react.forward_ref"),Z=Symbol.for("react.suspense"),J=Symbol.for("react.suspense_list"),M=Symbol.for("react.memo"),V=Symbol.for("react.lazy"),ae=Symbol.for("react.activity"),le=Symbol.for("react.memo_cache_sentinel"),F=Symbol.iterator;function P(e){return e===null||typeof e!="object"?null:(e=F&&e[F]||e["@@iterator"],typeof e=="function"?e:null)}var $=Symbol.for("react.client.reference");function ce(e){if(e==null)return null;if(typeof e=="function")return e.$$typeof===$?null:e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case x:return"Fragment";case U:return"Profiler";case I:return"StrictMode";case Z:return"Suspense";case J:return"SuspenseList";case ae:return"Activity"}if(typeof e=="object")switch(e.$$typeof){case T:return"Portal";case L:return e.displayName||"Context";case N:return(e._context.displayName||"Context")+".Consumer";case G:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case M:return t=e.displayName||null,t!==null?t:ce(e.type)||"Memo";case V:t=e._payload,e=e._init;try{return ce(e(t))}catch{}}return null}var te=Array.isArray,O=i.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,W=s.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,ie={pending:!1,data:null,method:null,action:null},Te=[],be=-1;function A(e){return{current:e}}function z(e){0>be||(e.current=Te[be],Te[be]=null,be--)}function Y(e,t){be++,Te[be]=e.current,e.current=t}var K=A(null),de=A(null),ge=A(null),oe=A(null);function Se(e,t){switch(Y(ge,t),Y(de,e),Y(K,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?ep(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=ep(t),e=tp(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}z(K),Y(K,e)}function De(){z(K),z(de),z(ge)}function Kt(e){e.memoizedState!==null&&Y(oe,e);var t=K.current,n=tp(t,e.type);t!==n&&(Y(de,e),Y(K,n))}function ma(e){de.current===e&&(z(K),z(de)),oe.current===e&&(z(oe),js._currentValue=ie)}var cn,Cu;function ba(e){if(cn===void 0)try{throw Error()}catch(n){var t=n.stack.trim().match(/\n( *(at )?)/);cn=t&&t[1]||"",Cu=-1<n.stack.indexOf(`
3    at`)?" (<anonymous>)":-1<n.stack.indexOf("@")?"@unknown:0:0":""}return`
4`+cn+e+Cu}var il=!1;function sl(e,t){if(!e||il)return"";il=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var o={DetermineComponentFrameRoot:function(){try{if(t){var H=function(){throw Error()};if(Object.defineProperty(H.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(H,[])}catch(D){var R=D}Reflect.construct(e,[],H)}else{try{H.call()}catch(D){R=D}e.call(H.prototype)}}else{try{throw Error()}catch(D){R=D}(H=e())&&typeof H.catch=="function"&&H.catch(function(){})}}catch(D){if(D&&R&&typeof D.stack=="string")return[D.stack,R.stack]}return[null,null]}};o.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var l=Object.getOwnPropertyDescriptor(o.DetermineComponentFrameRoot,"name");l&&l.configurable&&Object.defineProperty(o.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var d=o.DetermineComponentFrameRoot(),p=d[0],y=d[1];if(p&&y){var k=p.split(`
5`),C=y.split(`
6`);for(l=o=0;o<k.length&&!k[o].includes("DetermineComponentFrameRoot");)o++;for(;l<C.length&&!C[l].includes("DetermineComponentFrameRoot");)l++;if(o===k.length||l===C.length)for(o=k.length-1,l=C.length-1;1<=o&&0<=l&&k[o]!==C[l];)l--;for(;1<=o&&0<=l;o--,l--)if(k[o]!==C[l]){if(o!==1||l!==1)do if(o--,l--,0>l||k[o]!==C[l]){var B=`
7`+k[o].replace(" at new "," at ");return e.displayName&&B.includes("<anonymous>")&&(B=B.replace("<anonymous>",e.displayName)),B}while(1<=o&&0<=l);break}}}finally{il=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:"")?ba(n):""}function j0(e,t){switch(e.tag){case 26:case 27:case 5:return ba(e.type);case 16:return ba("Lazy");case 13:return e.child!==t&&t!==null?ba("Suspense Fallback"):ba("Suspense");case 19:return ba("SuspenseList");case 0:case 15:return sl(e.type,!1);case 11:return sl(e.type.render,!1);case 1:return sl(e.type,!0);case 31:return ba("Activity");default:return""}}function Iu(e){try{var t="",n=null;do t+=j0(e,n),n=e,e=e.return;while(e);return t}catch(o){return`
8Error generating stack: `+o.message+`
9`+o.stack}}var ol=Object.prototype.hasOwnProperty,rl=a.unstable_scheduleCallback,ll=a.unstable_cancelCallback,C0=a.unstable_shouldYield,I0=a.unstable_requestPaint,kt=a.unstable_now,R0=a.unstable_getCurrentPriorityLevel,Ru=a.unstable_ImmediatePriority,Ou=a.unstable_UserBlockingPriority,eo=a.unstable_NormalPriority,O0=a.unstable_LowPriority,Mu=a.unstable_IdlePriority,M0=a.log,U0=a.unstable_setDisableYieldValue,Ni=null,xt=null;function Bn(e){if(typeof M0=="function"&&U0(e),xt&&typeof xt.setStrictMode=="function")try{xt.setStrictMode(Ni,e)}catch{}}var Tt=Math.clz32?Math.clz32:L0,D0=Math.log,B0=Math.LN2;function L0(e){return e>>>=0,e===0?32:31-(D0(e)/B0|0)|0}var to=256,no=262144,ao=4194304;function wa(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function io(e,t,n){var o=e.pendingLanes;if(o===0)return 0;var l=0,d=e.suspendedLanes,p=e.pingedLanes;e=e.warmLanes;var y=o&134217727;return y!==0?(o=y&~d,o!==0?l=wa(o):(p&=y,p!==0?l=wa(p):n||(n=y&~e,n!==0&&(l=wa(n))))):(y=o&~d,y!==0?l=wa(y):p!==0?l=wa(p):n||(n=o&~e,n!==0&&(l=wa(n)))),l===0?0:t!==0&&t!==l&&(t&d)===0&&(d=l&-l,n=t&-t,d>=n||d===32&&(n&4194048)!==0)?t:l}function Hi(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function q0(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Uu(){var e=ao;return ao<<=1,(ao&62914560)===0&&(ao=4194304),e}function cl(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function Pi(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function z0(e,t,n,o,l,d){var p=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var y=e.entanglements,k=e.expirationTimes,C=e.hiddenUpdates;for(n=p&~n;0<n;){var B=31-Tt(n),H=1<<B;y[B]=0,k[B]=-1;var R=C[B];if(R!==null)for(C[B]=null,B=0;B<R.length;B++){var D=R[B];D!==null&&(D.lane&=-536870913)}n&=~H}o!==0&&Du(e,o,0),d!==0&&l===0&&e.tag!==0&&(e.suspendedLanes|=d&~(p&~t))}function Du(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var o=31-Tt(t);e.entangledLanes|=t,e.entanglements[o]=e.entanglements[o]|1073741824|n&261930}function Bu(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var o=31-Tt(n),l=1<<o;l&t|e[o]&t&&(e[o]|=t),n&=~l}}function Lu(e,t){var n=t&-t;return n=(n&42)!==0?1:dl(n),(n&(e.suspendedLanes|t))!==0?0:n}function dl(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function ul(e){return e&=-e,2<e?8<e?(e&134217727)!==0?32:268435456:8:2}function qu(){var e=W.p;return e!==0?e:(e=window.event,e===void 0?32:Tp(e.type))}function zu(e,t){var n=W.p;try{return W.p=e,t()}finally{W.p=n}}var Ln=Math.random().toString(36).slice(2),tt="__reactFiber$"+Ln,pt="__reactProps$"+Ln,Va="__reactContainer$"+Ln,hl="__reactEvents$"+Ln,N0="__reactListeners$"+Ln,H0="__reactHandles$"+Ln,Nu="__reactResources$"+Ln,Fi="__reactMarker$"+Ln;function fl(e){delete e[tt],delete e[pt],delete e[hl],delete e[N0],delete e[H0]}function Wa(e){var t=e[tt];if(t)return t;for(var n=e.parentNode;n;){if(t=n[Va]||n[tt]){if(n=t.alternate,t.child!==null||n!==null&&n.child!==null)for(e=lp(e);e!==null;){if(n=e[tt])return n;e=lp(e)}return t}e=n,n=e.parentNode}return null}function Qa(e){if(e=e[tt]||e[Va]){var t=e.tag;if(t===5||t===6||t===13||t===31||t===26||t===27||t===3)return e}return null}function Ji(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e.stateNode;throw Error(r(33))}function $a(e){var t=e[Nu];return t||(t=e[Nu]={hoistableStyles:new Map,hoistableScripts:new Map}),t}function Ke(e){e[Fi]=!0}var Hu=new Set,Pu={};function ya(e,t){Xa(e,t),Xa(e+"Capture",t)}function Xa(e,t){for(Pu[e]=t,e=0;e<t.length;e++)Hu.add(t[e])}var P0=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD][:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$"),Fu={},Ju={};function F0(e){return ol.call(Ju,e)?!0:ol.call(Fu,e)?!1:P0.test(e)?Ju[e]=!0:(Fu[e]=!0,!1)}function so(e,t,n){if(F0(t))if(n===null)e.removeAttribute(t);else{switch(typeof n){case"undefined":case"function":case"symbol":e.removeAttribute(t);return;case"boolean":var o=t.toLowerCase().slice(0,5);if(o!=="data-"&&o!=="aria-"){e.removeAttribute(t);return}}e.setAttribute(t,""+n)}}function oo(e,t,n){if(n===null)e.removeAttribute(t);else{switch(typeof n){case"undefined":case"function":case"symbol":case"boolean":e.removeAttribute(t);return}e.setAttribute(t,""+n)}}function dn(e,t,n,o){if(o===null)e.removeAttribute(n);else{switch(typeof o){case"undefined":case"function":case"symbol":case"boolean":e.removeAttribute(n);return}e.setAttributeNS(t,n,""+o)}}function Ut(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Gu(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function J0(e,t,n){var o=Object.getOwnPropertyDescriptor(e.constructor.prototype,t);if(!e.hasOwnProperty(t)&&typeof o<"u"&&typeof o.get=="function"&&typeof o.set=="function"){var l=o.get,d=o.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(p){n=""+p,d.call(this,p)}}),Object.defineProperty(e,t,{enumerable:o.enumerable}),{getValue:function(){return n},setValue:function(p){n=""+p},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function gl(e){if(!e._valueTracker){var t=Gu(e)?"checked":"value";e._valueTracker=J0(e,t,""+e[t])}}function Yu(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),o="";return e&&(o=Gu(e)?e.checked?"true":"false":e.value),e=o,e!==n?(t.setValue(e),!0):!1}function ro(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var G0=/[\n"\\]/g;function Dt(e){return e.replace(G0,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function pl(e,t,n,o,l,d,p,y){e.name="",p!=null&&typeof p!="function"&&typeof p!="symbol"&&typeof p!="boolean"?e.type=p:e.removeAttribute("type"),t!=null?p==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+Ut(t)):e.value!==""+Ut(t)&&(e.value=""+Ut(t)):p!=="submit"&&p!=="reset"||e.removeAttribute("value"),t!=null?ml(e,p,Ut(t)):n!=null?ml(e,p,Ut(n)):o!=null&&e.removeAttribute("value"),l==null&&d!=null&&(e.defaultChecked=!!d),l!=null&&(e.checked=l&&typeof l!="function"&&typeof l!="symbol"),y!=null&&typeof y!="function"&&typeof y!="symbol"&&typeof y!="boolean"?e.name=""+Ut(y):e.removeAttribute("name")}function Vu(e,t,n,o,l,d,p,y){if(d!=null&&typeof d!="function"&&typeof d!="symbol"&&typeof d!="boolean"&&(e.type=d),t!=null||n!=null){if(!(d!=="submit"&&d!=="reset"||t!=null)){gl(e);return}n=n!=null?""+Ut(n):"",t=t!=null?""+Ut(t):n,y||t===e.value||(e.value=t),e.defaultValue=t}o=o??l,o=typeof o!="function"&&typeof o!="symbol"&&!!o,e.checked=y?e.checked:!!o,e.defaultChecked=!!o,p!=null&&typeof p!="function"&&typeof p!="symbol"&&typeof p!="boolean"&&(e.name=p),gl(e)}function ml(e,t,n){t==="number"&&ro(e.ownerDocument)===e||e.defaultValue===""+n||(e.defaultValue=""+n)}function Ka(e,t,n,o){if(e=e.options,t){t={};for(var l=0;l<n.length;l++)t["$"+n[l]]=!0;for(n=0;n<e.length;n++)l=t.hasOwnProperty("$"+e[n].value),e[n].selected!==l&&(e[n].selected=l),l&&o&&(e[n].defaultSelected=!0)}else{for(n=""+Ut(n),t=null,l=0;l<e.length;l++){if(e[l].value===n){e[l].selected=!0,o&&(e[l].defaultSelected=!0);return}t!==null||e[l].disabled||(t=e[l])}t!==null&&(t.selected=!0)}}function Wu(e,t,n){if(t!=null&&(t=""+Ut(t),t!==e.value&&(e.value=t),n==null)){e.defaultValue!==t&&(e.defaultValue=t);return}e.defaultValue=n!=null?""+Ut(n):""}function Qu(e,t,n,o){if(t==null){if(o!=null){if(n!=null)throw Error(r(92));if(te(o)){if(1<o.length)throw Error(r(93));o=o[0]}n=o}n==null&&(n=""),t=n}n=Ut(t),e.defaultValue=n,o=e.textContent,o===n&&o!==""&&o!==null&&(e.value=o),gl(e)}function Za(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Y0=new Set("animationIterationCount aspectRatio borderImageOutset borderImageSlice borderImageWidth boxFlex boxFlexGroup boxOrdinalGroup columnCount columns flex flexGrow flexPositive flexShrink flexNegative flexOrder gridArea gridRow gridRowEnd gridRowSpan gridRowStart gridColumn gridColumnEnd gridColumnSpan gridColumnStart fontWeight lineClamp lineHeight opacity order orphans scale tabSize widows zIndex zoom fillOpacity floodOpacity stopOpacity strokeDasharray strokeDashoffset strokeMiterlimit strokeOpacity strokeWidth MozAnimationIterationCount MozBoxFlex MozBoxFlexGroup MozLineClamp msAnimationIterationCount msFlex msZoom msFlexGrow msFlexNegative msFlexOrder msFlexPositive msFlexShrink msGridColumn msGridColumnSpan msGridRow msGridRowSpan WebkitAnimationIterationCount WebkitBoxFlex WebKitBoxFlexGroup WebkitBoxOrdinalGroup WebkitColumnCount WebkitColumns WebkitFlex WebkitFlexGrow WebkitFlexPositive WebkitFlexShrink WebkitLineClamp".split(" "));function $u(e,t,n){var o=t.indexOf("--")===0;n==null||typeof n=="boolean"||n===""?o?e.setProperty(t,""):t==="float"?e.cssFloat="":e[t]="":o?e.setProperty(t,n):typeof n!="number"||n===0||Y0.has(t)?t==="float"?e.cssFloat=n:e[t]=(""+n).trim():e[t]=n+"px"}function Xu(e,t,n){if(t!=null&&typeof t!="object")throw Error(r(62));if(e=e.style,n!=null){for(var o in n)!n.hasOwnProperty(o)||t!=null&&t.hasOwnProperty(o)||(o.indexOf("--")===0?e.
vendor: 13,302 bytes, line 9
9setProperty(o,""):o==="float"?e.cssFloat="":e[o]="");for(var l in t)o=t[l],t.hasOwnProperty(l)&&n[l]!==o&&$u(e,l,o)}else for(var d in t)t.hasOwnProperty(d)&&$u(e,d,t[d])}function bl(e){if(e.indexOf("-")===-1)return!1;switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var V0=new Map([["acceptCharset","accept-charset"],["htmlFor","for"],["httpEquiv","http-equiv"],["crossOrigin","crossorigin"],["accentHeight","accent-height"],["alignmentBaseline","alignment-baseline"],["arabicForm","arabic-form"],["baselineShift","baseline-shift"],["capHeight","cap-height"],["clipPath","clip-path"],["clipRule","clip-rule"],["colorInterpolation","color-interpolation"],["colorInterpolationFilters","color-interpolation-filters"],["colorProfile","color-profile"],["colorRendering","color-rendering"],["dominantBaseline","dominant-baseline"],["enableBackground","enable-background"],["fillOpacity","fill-opacity"],["fillRule","fill-rule"],["floodColor","flood-color"],["floodOpacity","flood-opacity"],["fontFamily","font-family"],["fontSize","font-size"],["fontSizeAdjust","font-size-adjust"],["fontStretch","font-stretch"],["fontStyle","font-style"],["fontVariant","font-variant"],["fontWeight","font-weight"],["glyphName","glyph-name"],["glyphOrientationHorizontal","glyph-orientation-horizontal"],["glyphOrientationVertical","glyph-orientation-vertical"],["horizAdvX","horiz-adv-x"],["horizOriginX","horiz-origin-x"],["imageRendering","image-rendering"],["letterSpacing","letter-spacing"],["lightingColor","lighting-color"],["markerEnd","marker-end"],["markerMid","marker-mid"],["markerStart","marker-start"],["overlinePosition","overline-position"],["overlineThickness","overline-thickness"],["paintOrder","paint-order"],["panose-1","panose-1"],["pointerEvents","pointer-events"],["renderingIntent","rendering-intent"],["shapeRendering","shape-rendering"],["stopColor","stop-color"],["stopOpacity","stop-opacity"],["strikethroughPosition","strikethrough-position"],["strikethroughThickness","strikethrough-thickness"],["strokeDasharray","stroke-dasharray"],["strokeDashoffset","stroke-dashoffset"],["strokeLinecap","stroke-linecap"],["strokeLinejoin","stroke-linejoin"],["strokeMiterlimit","stroke-miterlimit"],["strokeOpacity","stroke-opacity"],["strokeWidth","stroke-width"],["textAnchor","text-anchor"],["textDecoration","text-decoration"],["textRendering","text-rendering"],["transformOrigin","transform-origin"],["underlinePosition","underline-position"],["underlineThickness","underline-thickness"],["unicodeBidi","unicode-bidi"],["unicodeRange","unicode-range"],["unitsPerEm","units-per-em"],["vAlphabetic","v-alphabetic"],["vHanging","v-hanging"],["vIdeographic","v-ideographic"],["vMathematical","v-mathematical"],["vectorEffect","vector-effect"],["vertAdvY","vert-adv-y"],["vertOriginX","vert-origin-x"],["vertOriginY","vert-origin-y"],["wordSpacing","word-spacing"],["writingMode","writing-mode"],["xmlnsXlink","xmlns:xlink"],["xHeight","x-height"]]),W0=/^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;function lo(e){return W0.test(""+e)?"javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')":e}function un(){}var wl=null;function yl(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var ei=null,ti=null;function Ku(e){var t=Qa(e);if(t&&(e=t.stateNode)){var n=e[pt]||null;e:switch(e=t.stateNode,t.type){case"input":if(pl(e,n.value,n.defaultValue,n.defaultValue,n.checked,n.defaultChecked,n.type,n.name),t=n.name,n.type==="radio"&&t!=null){for(n=e;n.parentNode;)n=n.parentNode;for(n=n.querySelectorAll('input[name="'+Dt(""+t)+'"][type="radio"]'),t=0;t<n.length;t++){var o=n[t];if(o!==e&&o.form===e.form){var l=o[pt]||null;if(!l)throw Error(r(90));pl(o,l.value,l.defaultValue,l.defaultValue,l.checked,l.defaultChecked,l.type,l.name)}}for(t=0;t<n.length;t++)o=n[t],o.form===e.form&&Yu(o)}break e;case"textarea":Wu(e,n.value,n.defaultValue);break e;case"select":t=n.value,t!=null&&Ka(e,!!n.multiple,t,!1)}}}var vl=!1;function Zu(e,t,n){if(vl)return e(t,n);vl=!0;try{var o=e(t);return o}finally{if(vl=!1,(ei!==null||ti!==null)&&($o(),ei&&(t=ei,e=ti,ti=ei=null,Ku(t),e)))for(t=0;t<e.length;t++)Ku(e[t])}}function Gi(e,t){var n=e.stateNode;if(n===null)return null;var o=n[pt]||null;if(o===null)return null;n=o[t];e:switch(t){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":case"onMouseEnter":(o=!o.disabled)||(e=e.type,o=!(e==="button"||e==="input"||e==="select"||e==="textarea")),e=!o;break e;default:e=!1}if(e)return null;if(n&&typeof n!="function")throw Error(r(231,t,typeof n));return n}var hn=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),_l=!1;if(hn)try{var Yi={};Object.defineProperty(Yi,"passive",{get:function(){_l=!0}}),window.addEventListener("test",Yi,Yi),window.removeEventListener("test",Yi,Yi)}catch{_l=!1}var qn=null,kl=null,co=null;function eh(){if(co)return co;var e,t=kl,n=t.length,o,l="value"in qn?qn.value:qn.textContent,d=l.length;for(e=0;e<n&&t[e]===l[e];e++);var p=n-e;for(o=1;o<=p&&t[n-o]===l[d-o];o++);return co=l.slice(e,1<o?1-o:void 0)}function uo(e){var t=e.keyCode;return"charCode"in e?(e=e.charCode,e===0&&t===13&&(e=13)):e=t,e===10&&(e=13),32<=e||e===13?e:0}function ho(){return!0}function th(){return!1}function mt(e){function t(n,o,l,d,p){this._reactName=n,this._targetInst=l,this.type=o,this.nativeEvent=d,this.target=p,this.currentTarget=null;for(var y in e)e.hasOwnProperty(y)&&(n=e[y],this[y]=n?n(d):d[y]);return this.isDefaultPrevented=(d.defaultPrevented!=null?d.defaultPrevented:d.returnValue===!1)?ho:th,this.isPropagationStopped=th,this}return b(t.prototype,{preventDefault:function(){this.defaultPrevented=!0;var n=this.nativeEvent;n&&(n.preventDefault?n.preventDefault():typeof n.returnValue!="unknown"&&(n.returnValue=!1),this.isDefaultPrevented=ho)},stopPropagation:function(){var n=this.nativeEvent;n&&(n.stopPropagation?n.stopPropagation():typeof n.cancelBubble!="unknown"&&(n.cancelBubble=!0),this.isPropagationStopped=ho)},persist:function(){},isPersistent:ho}),t}var va={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},fo=mt(va),Vi=b({},va,{view:0,detail:0}),Q0=mt(Vi),xl,Tl,Wi,go=b({},Vi,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:Al,button:0,buttons:0,relatedTarget:function(e){return e.relatedTarget===void 0?e.fromElement===e.srcElement?e.toElement:e.fromElement:e.relatedTarget},movementX:function(e){return"movementX"in e?e.movementX:(e!==Wi&&(Wi&&e.type==="mousemove"?(xl=e.screenX-Wi.screenX,Tl=e.screenY-Wi.screenY):Tl=xl=0,Wi=e),xl)},movementY:function(e){return"movementY"in e?e.movementY:Tl}}),nh=mt(go),$0=b({},go,{dataTransfer:0}),X0=mt($0),K0=b({},Vi,{relatedTarget:0}),Sl=mt(K0),Z0=b({},va,{animationName:0,elapsedTime:0,pseudoElement:0}),ew=mt(Z0),tw=b({},va,{clipboardData:function(e){return"clipboardData"in e?e.clipboardData:window.clipboardData}}),nw=mt(tw),aw=b({},va,{data:0}),ah=mt(aw),iw={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},sw={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},ow={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function rw(e){var t=this.nativeEvent;return t.getModifierState?t.getModifierState(e):(e=ow[e])?!!t[e]:!1}function Al(){return rw}var lw=b({},Vi,{key:function(e){if(e.key){var t=iw[e.key]||e.key;if(t!=="Unidentified")return t}return e.type==="keypress"?(e=uo(e),e===13?"Enter":String.fromCharCode(e)):e.type==="keydown"||e.type==="keyup"?sw[e.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,repeat:0,locale:0,getModifierState:Al,charCode:function(e){return e.type==="keypress"?uo(e):0},keyCode:function(e){return e.type==="keydown"||e.type==="keyup"?e.keyCode:0},which:function(e){return e.type==="keypress"?uo(e):e.type==="keydown"||e.type==="keyup"?e.keyCode:0}}),cw=mt(lw),dw=b({},go,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0}),ih=mt(dw),uw=b({},Vi,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,ctrlKey:0,shiftKey:0,getModifierState:Al}),hw=mt(uw),fw=b({},va,{propertyName:0,elapsedTime:0,pseudoElement:0}),gw=mt(fw),pw=b({},go,{deltaX:function(e){return"deltaX"in e?e.deltaX:"wheelDeltaX"in e?-e.wheelDeltaX:0},deltaY:function(e){return"deltaY"in e?e.deltaY:"wheelDeltaY"in e?-e.wheelDeltaY:"wheelDelta"in e?-e.wheelDelta:0},deltaZ:0,deltaMode:0}),mw=mt(pw),bw=b({},va,{newState:0,oldState:0}),ww=mt(bw),yw=[9,13,27,32],El=hn&&"CompositionEvent"in window,Qi=null;hn&&"documentMode"in document&&(Qi=document.documentMode);var vw=hn&&"TextEvent"in window&&!Qi,sh=hn&&(!El||Qi&&8<Qi&&11>=Qi),oh=" ",rh=!1;function lh(e,t){switch(e){case"keyup":return yw.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function ch(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ni=!1;function _w(e,t){switch(e){case"compositionend":return ch(t);case"keypress":return t.which!==32?null:(rh=!0,oh);case"textInput":return e=t.data,e===oh&&rh?null:e;default:return null}}function kw(e,t){if(ni)return e==="compositionend"||!El&&lh(e,t)?(e=eh(),co=kl=qn=null,ni=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1<t.char.length)return t.char;if(t.which)return String.fromCharCode(t.which)}return null;case"compositionend":return sh&&t.locale!=="ko"?null:t.data;default:return null}}var xw={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function dh(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t==="input"?!!xw[e.type]:t==="textarea"}function uh(e,t,n,o){ei?ti?ti.push(o):ti=[o]:ei=o,t=ar(t,"onChange"),0<t.length&&(n=new fo("onChange","change",null,n,o),e.push({event:n,listeners:t}))}var $i=null,Xi=null;function Tw(e){Wg(e,0)}function po(e){var t=Ji(e);if(Yu(t))return e}function hh(e,t){if(e==="change")return t}var fh=!1;if(hn){var jl;if(hn){var Cl="oninput"in document;if(!Cl){var gh=document.createElement("div");gh.setAttribute("oninput","return;"),Cl=typeof gh.oninput=="function"}jl=Cl}else jl=!1;fh=jl&&(!document.documentMode||9<document.documentMode)}function ph(){$i&&($i.detachEvent("onpropertychange",mh),Xi=$i=null)}function mh(e){if(e.propertyName==="value"&&po(Xi)){var t=[];uh(t,Xi,e,yl(e)),Zu(Tw,t)}}function Sw(e,t,n){e==="focusin"?(ph(),$i=t,Xi=n,$i.attachEvent("onpropertychange",mh)):e==="focusout"&&ph()}function Aw(e){if(e==="selectionchange"||e==="keyup"||e==="keydown")return po(Xi)}function Ew(e,t){if(e==="click")return po(t)}function jw(e,t){if(e==="input"||e==="change")return po(t)}function Cw(e,t){return e===t&&(e!==0||1/e===1/t)||e!==e&&t!==t}var St=typeof Object.is=="function"?Object.is:Cw;function Ki(e,t){if(St(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;var n=Object.keys(e),o=Object.keys(t);if(n.length!==o.length)return!1;for(o=0;o<n.length;o++){var l=n[o];if(!ol.call(t,l)||!St(e[l],t[l]))return!1}return!0}function bh(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function wh(e,t){var n=bh(e);e=0;for(var o;n;){if(n.nodeType===3){if(o=e+n.textContent.length,e<=t&&o>=t)return{node:n,offset:t-e};e=o}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=bh(n)}}function yh(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?yh(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function vh(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=ro(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=ro(e.document)}return t}function Il(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var Iw=hn&&"documentMode"in document&&11>=document.documentMode,ai=null,Rl=null,Zi=null,Ol=!1;function _h(e,t,n){var o=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;
vendor: 18,223 bytes, line 9
9Ol||ai==null||ai!==ro(o)||(o=ai,"selectionStart"in o&&Il(o)?o={start:o.selectionStart,end:o.selectionEnd}:(o=(o.ownerDocument&&o.ownerDocument.defaultView||window).getSelection(),o={anchorNode:o.anchorNode,anchorOffset:o.anchorOffset,focusNode:o.focusNode,focusOffset:o.focusOffset}),Zi&&Ki(Zi,o)||(Zi=o,o=ar(Rl,"onSelect"),0<o.length&&(t=new fo("onSelect","select",null,t,n),e.push({event:t,listeners:o}),t.target=ai)))}function _a(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n}var ii={animationend:_a("Animation","AnimationEnd"),animationiteration:_a("Animation","AnimationIteration"),animationstart:_a("Animation","AnimationStart"),transitionrun:_a("Transition","TransitionRun"),transitionstart:_a("Transition","TransitionStart"),transitioncancel:_a("Transition","TransitionCancel"),transitionend:_a("Transition","TransitionEnd")},Ml={},kh={};hn&&(kh=document.createElement("div").style,"AnimationEvent"in window||(delete ii.animationend.animation,delete ii.animationiteration.animation,delete ii.animationstart.animation),"TransitionEvent"in window||delete ii.transitionend.transition);function ka(e){if(Ml[e])return Ml[e];if(!ii[e])return e;var t=ii[e],n;for(n in t)if(t.hasOwnProperty(n)&&n in kh)return Ml[e]=t[n];return e}var xh=ka("animationend"),Th=ka("animationiteration"),Sh=ka("animationstart"),Rw=ka("transitionrun"),Ow=ka("transitionstart"),Mw=ka("transitioncancel"),Ah=ka("transitionend"),Eh=new Map,Ul="abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");Ul.push("scrollEnd");function Jt(e,t){Eh.set(e,t),ya(t,[e])}var mo=typeof reportError=="function"?reportError:function(e){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof e=="object"&&e!==null&&typeof e.message=="string"?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",e);return}console.error(e)},Bt=[],si=0,Dl=0;function bo(){for(var e=si,t=Dl=si=0;t<e;){var n=Bt[t];Bt[t++]=null;var o=Bt[t];Bt[t++]=null;var l=Bt[t];Bt[t++]=null;var d=Bt[t];if(Bt[t++]=null,o!==null&&l!==null){var p=o.pending;p===null?l.next=l:(l.next=p.next,p.next=l),o.pending=l}d!==0&&jh(n,l,d)}}function wo(e,t,n,o){Bt[si++]=e,Bt[si++]=t,Bt[si++]=n,Bt[si++]=o,Dl|=o,e.lanes|=o,e=e.alternate,e!==null&&(e.lanes|=o)}function Bl(e,t,n,o){return wo(e,t,n,o),yo(e)}function xa(e,t){return wo(e,null,null,t),yo(e)}function jh(e,t,n){e.lanes|=n;var o=e.alternate;o!==null&&(o.lanes|=n);for(var l=!1,d=e.return;d!==null;)d.childLanes|=n,o=d.alternate,o!==null&&(o.childLanes|=n),d.tag===22&&(e=d.stateNode,e===null||e._visibility&1||(l=!0)),e=d,d=d.return;return e.tag===3?(d=e.stateNode,l&&t!==null&&(l=31-Tt(n),e=d.hiddenUpdates,o=e[l],o===null?e[l]=[t]:o.push(t),t.lane=n|536870912),d):null}function yo(e){if(50<_s)throw _s=0,Gc=null,Error(r(185));for(var t=e.return;t!==null;)e=t,t=e.return;return e.tag===3?e.stateNode:null}var oi={};function Uw(e,t,n,o){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.refCleanup=this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=o,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function At(e,t,n,o){return new Uw(e,t,n,o)}function Ll(e){return e=e.prototype,!(!e||!e.isReactComponent)}function fn(e,t){var n=e.alternate;return n===null?(n=At(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&65011712,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n.refCleanup=e.refCleanup,n}function Ch(e,t){e.flags&=65011714;var n=e.alternate;return n===null?(e.childLanes=0,e.lanes=t,e.child=null,e.subtreeFlags=0,e.memoizedProps=null,e.memoizedState=null,e.updateQueue=null,e.dependencies=null,e.stateNode=null):(e.childLanes=n.childLanes,e.lanes=n.lanes,e.child=n.child,e.subtreeFlags=0,e.deletions=null,e.memoizedProps=n.memoizedProps,e.memoizedState=n.memoizedState,e.updateQueue=n.updateQueue,e.type=n.type,t=n.dependencies,e.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext}),e}function vo(e,t,n,o,l,d){var p=0;if(o=e,typeof e=="function")Ll(e)&&(p=1);else if(typeof e=="string")p=zy(e,n,K.current)?26:e==="html"||e==="head"||e==="body"?27:5;else e:switch(e){case ae:return e=At(31,n,t,l),e.elementType=ae,e.lanes=d,e;case x:return Ta(n.children,l,d,t);case I:p=8,l|=24;break;case U:return e=At(12,n,t,l|2),e.elementType=U,e.lanes=d,e;case Z:return e=At(13,n,t,l),e.elementType=Z,e.lanes=d,e;case J:return e=At(19,n,t,l),e.elementType=J,e.lanes=d,e;default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case L:p=10;break e;case N:p=9;break e;case G:p=11;break e;case M:p=14;break e;case V:p=16,o=null;break e}p=29,n=Error(r(130,e===null?"null":typeof e,"")),o=null}return t=At(p,n,t,l),t.elementType=e,t.type=o,t.lanes=d,t}function Ta(e,t,n,o){return e=At(7,e,o,t),e.lanes=n,e}function ql(e,t,n){return e=At(6,e,null,t),e.lanes=n,e}function Ih(e){var t=At(18,null,null,0);return t.stateNode=e,t}function zl(e,t,n){return t=At(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}var Rh=new WeakMap;function Lt(e,t){if(typeof e=="object"&&e!==null){var n=Rh.get(e);return n!==void 0?n:(t={value:e,source:t,stack:Iu(t)},Rh.set(e,t),t)}return{value:e,source:t,stack:Iu(t)}}var ri=[],li=0,_o=null,es=0,qt=[],zt=0,zn=null,Zt=1,en="";function gn(e,t){ri[li++]=es,ri[li++]=_o,_o=e,es=t}function Oh(e,t,n){qt[zt++]=Zt,qt[zt++]=en,qt[zt++]=zn,zn=e;var o=Zt;e=en;var l=32-Tt(o)-1;o&=~(1<<l),n+=1;var d=32-Tt(t)+l;if(30<d){var p=l-l%5;d=(o&(1<<p)-1).toString(32),o>>=p,l-=p,Zt=1<<32-Tt(t)+l|n<<l|o,en=d+e}else Zt=1<<d|n<<l|o,en=e}function Nl(e){e.return!==null&&(gn(e,1),Oh(e,1,0))}function Hl(e){for(;e===_o;)_o=ri[--li],ri[li]=null,es=ri[--li],ri[li]=null;for(;e===zn;)zn=qt[--zt],qt[zt]=null,en=qt[--zt],qt[zt]=null,Zt=qt[--zt],qt[zt]=null}function Mh(e,t){qt[zt++]=Zt,qt[zt++]=en,qt[zt++]=zn,Zt=t.id,en=t.overflow,zn=e}var nt=null,Le=null,ve=!1,Nn=null,Nt=!1,Pl=Error(r(519));function Hn(e){var t=Error(r(418,1<arguments.length&&arguments[1]!==void 0&&arguments[1]?"text":"HTML",""));throw ts(Lt(t,e)),Pl}function Uh(e){var t=e.stateNode,n=e.type,o=e.memoizedProps;switch(t[tt]=e,t[pt]=o,n){case"dialog":me("cancel",t),me("close",t);break;case"iframe":case"object":case"embed":me("load",t);break;case"video":case"audio":for(n=0;n<xs.length;n++)me(xs[n],t);break;case"source":me("error",t);break;case"img":case"image":case"link":me("error",t),me("load",t);break;case"details":me("toggle",t);break;case"input":me("invalid",t),Vu(t,o.value,o.defaultValue,o.checked,o.defaultChecked,o.type,o.name,!0);break;case"select":me("invalid",t);break;case"textarea":me("invalid",t),Qu(t,o.value,o.defaultValue,o.children)}n=o.children,typeof n!="string"&&typeof n!="number"&&typeof n!="bigint"||t.textContent===""+n||o.suppressHydrationWarning===!0||Kg(t.textContent,n)?(o.popover!=null&&(me("beforetoggle",t),me("toggle",t)),o.onScroll!=null&&me("scroll",t),o.onScrollEnd!=null&&me("scrollend",t),o.onClick!=null&&(t.onclick=un),t=!0):t=!1,t||Hn(e,!0)}function Dh(e){for(nt=e.return;nt;)switch(nt.tag){case 5:case 31:case 13:Nt=!1;return;case 27:case 3:Nt=!0;return;default:nt=nt.return}}function ci(e){if(e!==nt)return!1;if(!ve)return Dh(e),ve=!0,!1;var t=e.tag,n;if((n=t!==3&&t!==27)&&((n=t===5)&&(n=e.type,n=!(n!=="form"&&n!=="button")||od(e.type,e.memoizedProps)),n=!n),n&&Le&&Hn(e),Dh(e),t===13){if(e=e.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(r(317));Le=rp(e)}else if(t===31){if(e=e.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(r(317));Le=rp(e)}else t===27?(t=Le,ta(e.type)?(e=ud,ud=null,Le=e):Le=t):Le=nt?Pt(e.stateNode.nextSibling):null;return!0}function Sa(){Le=nt=null,ve=!1}function Fl(){var e=Nn;return e!==null&&(vt===null?vt=e:vt.push.apply(vt,e),Nn=null),e}function ts(e){Nn===null?Nn=[e]:Nn.push(e)}var Jl=A(null),Aa=null,pn=null;function Pn(e,t,n){Y(Jl,t._currentValue),t._currentValue=n}function mn(e){e._currentValue=Jl.current,z(Jl)}function Gl(e,t,n){for(;e!==null;){var o=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,o!==null&&(o.childLanes|=t)):o!==null&&(o.childLanes&t)!==t&&(o.childLanes|=t),e===n)break;e=e.return}}function Yl(e,t,n,o){var l=e.child;for(l!==null&&(l.return=e);l!==null;){var d=l.dependencies;if(d!==null){var p=l.child;d=d.firstContext;e:for(;d!==null;){var y=d;d=l;for(var k=0;k<t.length;k++)if(y.context===t[k]){d.lanes|=n,y=d.alternate,y!==null&&(y.lanes|=n),Gl(d.return,n,e),o||(p=null);break e}d=y.next}}else if(l.tag===18){if(p=l.return,p===null)throw Error(r(341));p.lanes|=n,d=p.alternate,d!==null&&(d.lanes|=n),Gl(p,n,e),p=null}else p=l.child;if(p!==null)p.return=l;else for(p=l;p!==null;){if(p===e){p=null;break}if(l=p.sibling,l!==null){l.return=p.return,p=l;break}p=p.return}l=p}}function di(e,t,n,o){e=null;for(var l=t,d=!1;l!==null;){if(!d){if((l.flags&524288)!==0)d=!0;else if((l.flags&262144)!==0)break}if(l.tag===10){var p=l.alternate;if(p===null)throw Error(r(387));if(p=p.memoizedProps,p!==null){var y=l.type;St(l.pendingProps.value,p.value)||(e!==null?e.push(y):e=[y])}}else if(l===oe.current){if(p=l.alternate,p===null)throw Error(r(387));p.memoizedState.memoizedState!==l.memoizedState.memoizedState&&(e!==null?e.push(js):e=[js])}l=l.return}e!==null&&Yl(t,e,n,o),t.flags|=262144}function ko(e){for(e=e.firstContext;e!==null;){if(!St(e.context._currentValue,e.memoizedValue))return!0;e=e.next}return!1}function Ea(e){Aa=e,pn=null,e=e.dependencies,e!==null&&(e.firstContext=null)}function at(e){return Bh(Aa,e)}function xo(e,t){return Aa===null&&Ea(e),Bh(e,t)}function Bh(e,t){var n=t._currentValue;if(t={context:t,memoizedValue:n,next:null},pn===null){if(e===null)throw Error(r(308));pn=t,e.dependencies={lanes:0,firstContext:t},e.flags|=524288}else pn=pn.next=t;return n}var Dw=typeof AbortController<"u"?AbortController:function(){var e=[],t=this.signal={aborted:!1,addEventListener:function(n,o){e.push(o)}};this.abort=function(){t.aborted=!0,e.forEach(function(n){return n()})}},Bw=a.unstable_scheduleCallback,Lw=a.unstable_NormalPriority,Je={$$typeof:L,Consumer:null,Provider:null,_currentValue:null,_currentValue2:null,_threadCount:0};function Vl(){return{controller:new Dw,data:new Map,refCount:0}}function ns(e){e.refCount--,e.refCount===0&&Bw(Lw,function(){e.controller.abort()})}var as=null,Wl=0,ui=0,hi=null;function qw(e,t){if(as===null){var n=as=[];Wl=0,ui=Xc(),hi={status:"pending",value:void 0,then:function(o){n.push(o)}}}return Wl++,t.then(Lh,Lh),t}function Lh(){if(--Wl===0&&as!==null){hi!==null&&(hi.status="fulfilled");var e=as;as=null,ui=0,hi=null;for(var t=0;t<e.length;t++)(0,e[t])()}}function zw(e,t){var n=[],o={status:"pending",value:null,reason:null,then:function(l){n.push(l)}};return e.then(function(){o.status="fulfilled",o.value=t;for(var l=0;l<n.length;l++)(0,n[l])(t)},function(l){for(o.status="rejected",o.reason=l,l=0;l<n.length;l++)(0,n[l])(void 0)}),o}var qh=O.S;O.S=function(e,t){kg=kt(),typeof t=="object"&&t!==null&&typeof t.then=="function"&&qw(e,t),qh!==null&&qh(e,t)};var ja=A(null);function Ql(){var e=ja.current;return e!==null?e:Ue.pooledCache}function To(e,t){t===null?Y(ja,ja.current):Y(ja,t.pool)}function zh(){var e=Ql();return e===null?null:{parent:Je._currentValue,pool:e}}var fi=Error(r(460)),$l=Error(r(474)),So=Error(r(542)),Ao={then:function(){}};function Nh(e){return e=e.status,e==="fulfilled"||e==="rejected"}function Hh(e,t,n){switch(n=e[n],n===void 0?e.push(t):n!==t&&(t.then(un,un),t=n),t.status){case"fulfilled":return t.value;case"rejected":throw e=t.reason,Fh(e),e;default:if(typeof t.status=="string")t.then(un,un);else{if(e=Ue,e!==null&&100<e.shellSuspendCounter)throw Error(r(482));e=t,e.status="pending",e.then(function(o){if(t.status==="pending"){var l=t;l.status="fulfilled",l.value=o}},function(o){if(t.status==="pending"){var l=t;l.status="rejected",l.reason=o}})}switch(t.status){case"fulfilled":return t.value;case"rejected":throw e=t.reason,Fh(e),e}throw Ia=t,fi}}function Ca(e){try{var t=e._init;return t(e._payload)}catch(n){throw n!==null&&typeof n=="object"&&typeof n.then=="function"?(Ia=n,fi):n}}var Ia=null;function Ph(){if(Ia===null)throw Error(r(459));var e=Ia;return Ia=null,e}function Fh(e){if(e===fi||e===So)throw Error(r(483))}var gi=null,is=0;function Eo(e){var t=is;return is+=1,gi===null&&(gi=[]),Hh(gi,e,t)}function ss(e,t){t=t.props.ref,e.ref=t!==void 0?t:null}function jo(e,t){throw t.$$typeof===v?Error(r(525)):(e=Object.prototype.toString.call(t),Error(r(31,e==="[object Object]"?"object with keys {"+Object.keys(t).join(", ")+"}":e)))}function Jh(e){function t(E,S){if(e){var j=E.deletions;j===null?(E.deletions=[S],E.flags|=16):j.push(S)}}function n(E,S){if(!e)return null;for(;S!==null;)t(E,S),S=S.sibling;return null}function o(E){for(var S=new Map;E!==null;)E.key!==null?S.set(E.key,E):S.set(E.index,E),E=E.sibling;return S}function l(E,S){return E=fn(E,S),E.index=0,E.sibling=null,E}function d(E,S,j){return E.index=j,e?(j=E.alternate,j!==null?(j=j.index,j<S?(E.flags|=67108866,S):j):(E.flags|=67108866,S)):(E.flags|=1048576,S)}function p(E){return e&&E.alternate===null&&(E.flags|=67108866),E}function y(E,S,j,q){return S===null||S.tag!==6?(S=ql(j,E.mode,q),S.return=E,S):(S=l(S,j),S.return=E,S)}function k(E,S,j,q){var ne=j.type;return ne===x?B(E,S,j.props.children,q,j.key):S!==null&&(S.elementType===ne||typeof ne=="object"&&ne!==null&&ne.$$typeof===V&&Ca(ne)===S.type)?(S=l(S,j.props),ss(S,j),S.return=E,S):(S=vo(j.type,j.key,j.props,null,E.mode,q),ss(S,j),S.return=E,S)}function C(E,S,j,q){return S===null||S.tag!==4||S.stateNode.containerInfo!==j.containerInfo||S.stateNode.implementation!==j.implementation?(S=zl(j,E.mode,q),S.return=E,S):(S=l(S,j.children||[]),S.return=E,S)}function B(E,S,j,q,ne){return S===null||S.tag!==7?(S=Ta(j,E.mode,q,ne),S.return=E,S):(S=l(S,j),S.return=E,S)}function H(E,S,j){if(typeof S=="string"&&S!==""||typeof S=="number"||typeof S=="bigint")return S=ql(""+S,E.mode,j),S.return=E,S;if(typeof S=="object"&&S!==null){switch(S.$$typeof){case _:return j=vo(S.type,S.key,S.props,null,E.mode,j),ss(j,S),j.return=E,j;case T:return S=zl(S,E.mode,j),S.return=E,S;case V:return S=Ca(S),H(E,S,j)}if(te(S)||P(S))return S=Ta(S,E.mode,j,null),S.return=E,S;if(typeof S.then=="function")return H(E,Eo(S),j);if(S.$$typeof===L)return H(E,xo(E,S),j);jo(E,S)}return null}function R(E,S,j,q){var ne=S!==null?S.key:null;if(typeof j=="string"&&j!==""||typeof j=="number"||typeof j=="bigint")return ne!==null?null:y(E,S,""+j,q);if(typeof j=="object"&&j!==null){switch(j.$$typeof){case _:return j.key===ne?k(E,S,j,q):null;case T:return j.key===ne?C(E,S,j,q):null;case V:return j=Ca(j),R(E,S,j,q)}if(te(j)||P(j))return ne!==null?null:B(E,S,j,q,null);if(typeof j.then=="function")return R(E,S,Eo(j),q);if(j.$$typeof===L)return R(E,S,xo(E,j),q);jo(E,j)}return null}function D(E,S,j,q,ne){if(typeof q=="string"&&q!==""||typeof q=="number"||typeof q=="bigint")return E=E.get(j)||null,y(S,E,""+q,ne);if(typeof q=="object"&&q!==null){switch(q.$$typeof){case _:return E=E.get(q.key===null?j:q.key)||null,k(S,E,q,ne);case T:return E=E.get(q.key===null?j:q.key)||null,C(S,E,q,ne);case V:return q=Ca(q),D(E,S,j,q,ne)}if(te(q)||P(q))return E=E.get(j)||null,B(S,E,q,ne,null);if(typeof q.then=="function")return D(E,S,j,Eo(q),ne);if(q.$$typeof===L)return D(E,S,j,xo(S,q),ne);jo(S,q)}return null}function X(E,S,j,q){for(var ne=null,ke=null,ee=S,fe=S=0,ye=null;ee!==null&&fe<j.length;fe++){ee.index>fe?(ye=ee,ee=null):ye=ee.sibling;var xe=R(E,ee,j[fe],q);if(xe===null){ee===null&&(ee=ye);break}e&&ee&&xe.alternate===null&&t(E,ee),S=d(xe,S,fe),ke===null?ne=xe:ke.sibling=xe,ke=xe,ee=ye}if(fe===j.length)return n(E,ee),ve&&gn(E,fe),ne;if(ee===null){for(;fe<j.length;fe++)ee=H(E,j[fe],q),ee!==null&&(S=d(ee,S,fe),ke===null?ne=ee:ke.sibling=ee,ke=ee);return ve&&gn(E,fe),ne}for(ee=o(ee);fe<j.length;fe++)ye=D(ee,E,fe,j[fe],q),ye!==null&&(e&&ye.alternate!==null&&ee.delete(ye.key===null?fe:ye.key),S=d(ye,S,fe),ke===null?ne=ye:ke.sibling=ye,ke=ye);return e&&ee.forEach(function(oa){return t(E,oa)}),ve&&gn(E,fe),ne}function re(E,S,j,q){if(j==null)throw Error(r(151));for(var ne=null,ke=null,ee=S,fe=S=0,ye=null,xe=j.next();ee!==null&&!xe.done;fe++,xe=j.next()){ee.index>fe?(ye=ee,ee=null):ye=ee.sibling;var oa=R(E,ee,xe.value,q);if(oa===null){ee===null&&(ee=ye);break}e&&ee&&oa.alternate===null&&t(E,ee),S=d(oa,S,fe),ke===null?ne=oa:ke.sibling=oa,ke=oa,ee=ye}if(xe.done)return n(E,ee),ve&&gn(E,fe),ne;if(ee===null){for(;!xe.done;fe++,xe=j.next())xe=H(E,xe.value,q),xe!==null&&(S=d(xe,S,fe),ke===null?ne=xe:ke.sibling=xe,ke=xe);return ve&&gn(E,fe),ne}for(ee=o(ee);!xe.done;fe++,xe=j.next())xe=D(ee,E,fe,xe.value,q),xe!==null&&(e&&xe.alternate!==null&&ee.delete(xe.key===null?fe:xe.key),S=d(xe,S,fe),ke===null?ne=xe:ke.sibling=xe,ke=xe);return e&&ee.forEach(function($y){return t(E,$y)}),ve&&gn(E,fe),ne}function Re(E,S,j,q){if(typeof j=="object"&&j!==null&&j.type===x&&j.key===null&&(j=j.props.children),typeof j=="object"&&j!==null){switch(j.$$typeof){case _:e:{for(var ne=j.key;S!==null;
vendor: 4,171 bytes, line 9
9){if(S.key===ne){if(ne=j.type,ne===x){if(S.tag===7){n(E,S.sibling),q=l(S,j.props.children),q.return=E,E=q;break e}}else if(S.elementType===ne||typeof ne=="object"&&ne!==null&&ne.$$typeof===V&&Ca(ne)===S.type){n(E,S.sibling),q=l(S,j.props),ss(q,j),q.return=E,E=q;break e}n(E,S);break}else t(E,S);S=S.sibling}j.type===x?(q=Ta(j.props.children,E.mode,q,j.key),q.return=E,E=q):(q=vo(j.type,j.key,j.props,null,E.mode,q),ss(q,j),q.return=E,E=q)}return p(E);case T:e:{for(ne=j.key;S!==null;){if(S.key===ne)if(S.tag===4&&S.stateNode.containerInfo===j.containerInfo&&S.stateNode.implementation===j.implementation){n(E,S.sibling),q=l(S,j.children||[]),q.return=E,E=q;break e}else{n(E,S);break}else t(E,S);S=S.sibling}q=zl(j,E.mode,q),q.return=E,E=q}return p(E);case V:return j=Ca(j),Re(E,S,j,q)}if(te(j))return X(E,S,j,q);if(P(j)){if(ne=P(j),typeof ne!="function")throw Error(r(150));return j=ne.call(j),re(E,S,j,q)}if(typeof j.then=="function")return Re(E,S,Eo(j),q);if(j.$$typeof===L)return Re(E,S,xo(E,j),q);jo(E,j)}return typeof j=="string"&&j!==""||typeof j=="number"||typeof j=="bigint"?(j=""+j,S!==null&&S.tag===6?(n(E,S.sibling),q=l(S,j),q.return=E,E=q):(n(E,S),q=ql(j,E.mode,q),q.return=E,E=q),p(E)):n(E,S)}return function(E,S,j,q){try{is=0;var ne=Re(E,S,j,q);return gi=null,ne}catch(ee){if(ee===fi||ee===So)throw ee;var ke=At(29,ee,null,E.mode);return ke.lanes=q,ke.return=E,ke}}}var Ra=Jh(!0),Gh=Jh(!1),Fn=!1;function Xl(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Kl(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Jn(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Gn(e,t,n){var o=e.updateQueue;if(o===null)return null;if(o=o.shared,(Ae&2)!==0){var l=o.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),o.pending=t,t=yo(e),jh(e,null,n),t}return wo(e,o,t,n),yo(e)}function os(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194048)!==0)){var o=t.lanes;o&=e.pendingLanes,n|=o,t.lanes=n,Bu(e,n)}}function Zl(e,t){var n=e.updateQueue,o=e.alternate;if(o!==null&&(o=o.updateQueue,n===o)){var l=null,d=null;if(n=n.firstBaseUpdate,n!==null){do{var p={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};d===null?l=d=p:d=d.next=p,n=n.next}while(n!==null);d===null?l=d=t:d=d.next=t}else l=d=t;n={baseState:o.baseState,firstBaseUpdate:l,lastBaseUpdate:d,shared:o.shared,callbacks:o.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var ec=!1;function rs(){if(ec){var e=hi;if(e!==null)throw e}}function ls(e,t,n,o){ec=!1;var l=e.updateQueue;Fn=!1;var d=l.firstBaseUpdate,p=l.lastBaseUpdate,y=l.shared.pending;if(y!==null){l.shared.pending=null;var k=y,C=k.next;k.next=null,p===null?d=C:p.next=C,p=k;var B=e.alternate;B!==null&&(B=B.updateQueue,y=B.lastBaseUpdate,y!==p&&(y===null?B.firstBaseUpdate=C:y.next=C,B.lastBaseUpdate=k))}if(d!==null){var H=l.baseState;p=0,B=C=k=null,y=d;do{var R=y.lane&-536870913,D=R!==y.lane;if(D?(we&R)===R:(o&R)===R){R!==0&&R===ui&&(ec=!0),B!==null&&(B=B.next={lane:0,tag:y.tag,payload:y.payload,callback:null,next:null});e:{var X=e,re=y;R=t;var Re=n;switch(re.tag){case 1:if(X=re.payload,typeof X=="function"){H=X.call(Re,H,R);break e}H=X;break e;case 3:X.flags=X.flags&-65537|128;case 0:if(X=re.payload,R=typeof X=="function"?X.call(Re,H,R):X,R==null)break e;H=b({},H,R);break e;case 2:Fn=!0}}R=y.callback,R!==null&&(e.flags|=64,D&&(e.flags|=8192),D=l.callbacks,D===null?l.callbacks=[R]:D.push(R))}else D={lane:R,tag:y.tag,payload:y.payload,callback:y.callback,next:null},B===null?(C=B=D,k=H):B=B.next=D,p|=R;if(y=y.next,y===null){if(y=l.shared.pending,y===null)break;D=y,y=D.next,D.next=null,l.lastBaseUpdate=D,l.shared.pending=null}}while(!0);B===null&&(k=H),l.baseState=k,l.firstBaseUpdate=C,l.lastBaseUpdate=B,d===null&&(l.shared.lanes=0),$n|=p,e.lanes=p,e.memoizedState=H}}function Yh(e,t){if(typeof e!="function")throw Error(r(191,e));e.call(t)}function Vh(e,t){var n=e.callbacks;
vendor: 4,310 bytes, line 9
9if(n!==null)for(e.callbacks=null,e=0;e<n.length;e++)Yh(n[e],t)}var pi=A(null),Co=A(0);function Wh(e,t){e=Sn,Y(Co,e),Y(pi,t),Sn=e|t.baseLanes}function tc(){Y(Co,Sn),Y(pi,pi.current)}function nc(){Sn=Co.current,z(pi),z(Co)}var Et=A(null),Ht=null;function Yn(e){var t=e.alternate;Y(Pe,Pe.current&1),Y(Et,e),Ht===null&&(t===null||pi.current!==null||t.memoizedState!==null)&&(Ht=e)}function ac(e){Y(Pe,Pe.current),Y(Et,e),Ht===null&&(Ht=e)}function Qh(e){e.tag===22?(Y(Pe,Pe.current),Y(Et,e),Ht===null&&(Ht=e)):Vn()}function Vn(){Y(Pe,Pe.current),Y(Et,Et.current)}function jt(e){z(Et),Ht===e&&(Ht=null),z(Pe)}var Pe=A(0);function Io(e){for(var t=e;t!==null;){if(t.tag===13){var n=t.memoizedState;if(n!==null&&(n=n.dehydrated,n===null||cd(n)||dd(n)))return t}else if(t.tag===19&&(t.memoizedProps.revealOrder==="forwards"||t.memoizedProps.revealOrder==="backwards"||t.memoizedProps.revealOrder==="unstable_legacy-backwards"||t.memoizedProps.revealOrder==="together")){if((t.flags&128)!==0)return t}else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}var bn=0,he=null,Ce=null,Ge=null,Ro=!1,mi=!1,Oa=!1,Oo=0,cs=0,bi=null,Nw=0;function Ne(){throw Error(r(321))}function ic(e,t){if(t===null)return!1;for(var n=0;n<t.length&&n<e.length;n++)if(!St(e[n],t[n]))return!1;return!0}function sc(e,t,n,o,l,d){return bn=d,he=t,t.memoizedState=null,t.updateQueue=null,t.lanes=0,O.H=e===null||e.memoizedState===null?Mf:vc,Oa=!1,d=n(o,l),Oa=!1,mi&&(d=Xh(t,n,o,l)),$h(e),d}function $h(e){O.H=hs;var t=Ce!==null&&Ce.next!==null;if(bn=0,Ge=Ce=he=null,Ro=!1,cs=0,bi=null,t)throw Error(r(300));e===null||Ye||(e=e.dependencies,e!==null&&ko(e)&&(Ye=!0))}function Xh(e,t,n,o){he=e;var l=0;do{if(mi&&(bi=null),cs=0,mi=!1,25<=l)throw Error(r(301));if(l+=1,Ge=Ce=null,e.updateQueue!=null){var d=e.updateQueue;d.lastEffect=null,d.events=null,d.stores=null,d.memoCache!=null&&(d.memoCache.index=0)}O.H=Uf,d=t(n,o)}while(mi);return d}function Hw(){var e=O.H,t=e.useState()[0];return t=typeof t.then=="function"?ds(t):t,e=e.useState()[0],(Ce!==null?Ce.memoizedState:null)!==e&&(he.flags|=1024),t}function oc(){var e=Oo!==0;return Oo=0,e}function rc(e,t,n){t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~n}function lc(e){if(Ro){for(e=e.memoizedState;e!==null;){var t=e.queue;t!==null&&(t.pending=null),e=e.next}Ro=!1}bn=0,Ge=Ce=he=null,mi=!1,cs=Oo=0,bi=null}function ht(){var e={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return Ge===null?he.memoizedState=Ge=e:Ge=Ge.next=e,Ge}function Fe(){if(Ce===null){var e=he.alternate;e=e!==null?e.memoizedState:null}else e=Ce.next;var t=Ge===null?he.memoizedState:Ge.next;if(t!==null)Ge=t,Ce=e;else{if(e===null)throw he.alternate===null?Error(r(467)):Error(r(310));Ce=e,e={memoizedState:Ce.memoizedState,baseState:Ce.baseState,baseQueue:Ce.baseQueue,queue:Ce.queue,next:null},Ge===null?he.memoizedState=Ge=e:Ge=Ge.next=e}return Ge}function Mo(){return{lastEffect:null,events:null,stores:null,memoCache:null}}function ds(e){var t=cs;return cs+=1,bi===null&&(bi=[]),e=Hh(bi,e,t),t=he,(Ge===null?t.memoizedState:Ge.next)===null&&(t=t.alternate,O.H=t===null||t.memoizedState===null?Mf:vc),e}function Uo(e){if(e!==null&&typeof e=="object"){if(typeof e.then=="function")return ds(e);if(e.$$typeof===L)return at(e)}throw Error(r(438,String(e)))}function cc(e){var t=null,n=he.updateQueue;if(n!==null&&(t=n.memoCache),t==null){var o=he.alternate;o!==null&&(o=o.updateQueue,o!==null&&(o=o.memoCache,o!=null&&(t={data:o.data.map(function(l){return l.slice()}),index:0})))}if(t==null&&(t={data:[],index:0}),n===null&&(n=Mo(),he.updateQueue=n),n.memoCache=t,n=t.data[t.index],n===void 0)for(n=t.data[t.index]=Array(e),o=0;o<e;o++)n[o]=le;return t.index++,n}function wn(e,t){return typeof t=="function"?t(e):t}function Do(e){var t=Fe();return dc(t,Ce,e)}function dc(e,t,n){var o=e.queue;if(o===null)throw Error(r(311));o.lastRenderedReducer=n;var l=e.baseQueue,d=o.pending;if(d!==null){if(l!==null){var p=l.next;l.next=d.next,d.next=p}t.baseQueue=l=d,o.pending=null}if(d=e.baseState,l===null)e.memoizedState=d;else{t=l.next;var y=p=null,k=null,C=t,B=!1;do{var H=C.lane&-536870913;if(H!==C.lane?(we&H)===H:(bn&H)===H){var R=C.revertLane;
vendor: 4,993 bytes, line 9
9if(R===0)k!==null&&(k=k.next={lane:0,revertLane:0,gesture:null,action:C.action,hasEagerState:C.hasEagerState,eagerState:C.eagerState,next:null}),H===ui&&(B=!0);else if((bn&R)===R){C=C.next,R===ui&&(B=!0);continue}else H={lane:0,revertLane:C.revertLane,gesture:null,action:C.action,hasEagerState:C.hasEagerState,eagerState:C.eagerState,next:null},k===null?(y=k=H,p=d):k=k.next=H,he.lanes|=R,$n|=R;H=C.action,Oa&&n(d,H),d=C.hasEagerState?C.eagerState:n(d,H)}else R={lane:H,revertLane:C.revertLane,gesture:C.gesture,action:C.action,hasEagerState:C.hasEagerState,eagerState:C.eagerState,next:null},k===null?(y=k=R,p=d):k=k.next=R,he.lanes|=H,$n|=H;C=C.next}while(C!==null&&C!==t);if(k===null?p=d:k.next=y,!St(d,e.memoizedState)&&(Ye=!0,B&&(n=hi,n!==null)))throw n;e.memoizedState=d,e.baseState=p,e.baseQueue=k,o.lastRenderedState=d}return l===null&&(o.lanes=0),[e.memoizedState,o.dispatch]}function uc(e){var t=Fe(),n=t.queue;if(n===null)throw Error(r(311));n.lastRenderedReducer=e;var o=n.dispatch,l=n.pending,d=t.memoizedState;if(l!==null){n.pending=null;var p=l=l.next;do d=e(d,p.action),p=p.next;while(p!==l);St(d,t.memoizedState)||(Ye=!0),t.memoizedState=d,t.baseQueue===null&&(t.baseState=d),n.lastRenderedState=d}return[d,o]}function Kh(e,t,n){var o=he,l=Fe(),d=ve;if(d){if(n===void 0)throw Error(r(407));n=n()}else n=t();var p=!St((Ce||l).memoizedState,n);if(p&&(l.memoizedState=n,Ye=!0),l=l.queue,gc(tf.bind(null,o,l,e),[e]),l.getSnapshot!==t||p||Ge!==null&&Ge.memoizedState.tag&1){if(o.flags|=2048,wi(9,{destroy:void 0},ef.bind(null,o,l,n,t),null),Ue===null)throw Error(r(349));d||(bn&127)!==0||Zh(o,t,n)}return n}function Zh(e,t,n){e.flags|=16384,e={getSnapshot:t,value:n},t=he.updateQueue,t===null?(t=Mo(),he.updateQueue=t,t.stores=[e]):(n=t.stores,n===null?t.stores=[e]:n.push(e))}function ef(e,t,n,o){t.value=n,t.getSnapshot=o,nf(t)&&af(e)}function tf(e,t,n){return n(function(){nf(t)&&af(e)})}function nf(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!St(e,n)}catch{return!0}}function af(e){var t=xa(e,2);t!==null&&_t(t,e,2)}function hc(e){var t=ht();if(typeof e=="function"){var n=e;if(e=n(),Oa){Bn(!0);try{n()}finally{Bn(!1)}}}return t.memoizedState=t.baseState=e,t.queue={pending:null,lanes:0,dispatch:null,lastRenderedReducer:wn,lastRenderedState:e},t}function sf(e,t,n,o){return e.baseState=n,dc(e,Ce,typeof o=="function"?o:wn)}function Pw(e,t,n,o,l){if(qo(e))throw Error(r(485));if(e=t.action,e!==null){var d={payload:l,action:e,next:null,isTransition:!0,status:"pending",value:null,reason:null,listeners:[],then:function(p){d.listeners.push(p)}};O.T!==null?n(!0):d.isTransition=!1,o(d),n=t.pending,n===null?(d.next=t.pending=d,of(t,d)):(d.next=n.next,t.pending=n.next=d)}}function of(e,t){var n=t.action,o=t.payload,l=e.state;if(t.isTransition){var d=O.T,p={};O.T=p;try{var y=n(l,o),k=O.S;k!==null&&k(p,y),rf(e,t,y)}catch(C){fc(e,t,C)}finally{d!==null&&p.types!==null&&(d.types=p.types),O.T=d}}else try{d=n(l,o),rf(e,t,d)}catch(C){fc(e,t,C)}}function rf(e,t,n){n!==null&&typeof n=="object"&&typeof n.then=="function"?n.then(function(o){lf(e,t,o)},function(o){return fc(e,t,o)}):lf(e,t,n)}function lf(e,t,n){t.status="fulfilled",t.value=n,cf(t),e.state=n,t=e.pending,t!==null&&(n=t.next,n===t?e.pending=null:(n=n.next,t.next=n,of(e,n)))}function fc(e,t,n){var o=e.pending;if(e.pending=null,o!==null){o=o.next;do t.status="rejected",t.reason=n,cf(t),t=t.next;while(t!==o)}e.action=null}function cf(e){e=e.listeners;for(var t=0;t<e.length;t++)(0,e[t])()}function df(e,t){return t}function uf(e,t){if(ve){var n=Ue.formState;if(n!==null){e:{var o=he;if(ve){if(Le){t:{for(var l=Le,d=Nt;l.nodeType!==8;){if(!d){l=null;break t}if(l=Pt(l.nextSibling),l===null){l=null;break t}}d=l.data,l=d==="F!"||d==="F"?l:null}if(l){Le=Pt(l.nextSibling),o=l.data==="F!";break e}}Hn(o)}o=!1}o&&(t=n[0])}}return n=ht(),n.memoizedState=n.baseState=t,o={pending:null,lanes:0,dispatch:null,lastRenderedReducer:df,lastRenderedState:t},n.queue=o,n=If.bind(null,he,o),o.dispatch=n,o=hc(!1),d=yc.bind(null,he,!1,o.queue),o=ht(),l={state:t,dispatch:null,action:e,pending:null},o.queue=l,n=Pw.bind(null,he,l,d,n),l.dispatch=n,o.memoizedState=e,[t,n,!1]}function hf(e){var t=Fe();return ff(t,Ce,e)}function ff(e,t,n){if(t=dc(e,t,df)[0],e=Do(wn)[0],typeof t=="object"&&t!==null&&typeof t.then=="function")try{var o=ds(t)}catch(p){throw p===fi?So:p}else o=t;t=Fe();var l=t.queue,d=l.dispatch;return n!==t.memoizedState&&(he.flags|=2048,wi(9,{destroy:void 0},Fw.bind(null,l,n),null)),[o,d,e]}function Fw(e,t){e.action=t}function gf(e){var t=Fe(),n=Ce;if(n!==null)return ff(t,n,e);Fe(),t=t.memoizedState,n=Fe();var o=n.queue.dispatch;return n.memoizedState=e,[t,o,!1]}function wi(e,t,n,o){return e={tag:e,create:n,deps:o,inst:t,next:null},t=he.updateQueue,t===null&&(t=Mo(),he.updateQueue=t),n=t.lastEffect,n===null?t.lastEffect=e.next=e:(o=n.next,n.next=e,e.next=o,t.lastEffect=e),e}function pf(){return Fe().memoizedState}function Bo(e,t,n,o){var l=ht();he.flags|=e,l.memoizedState=wi(1|t,{destroy:void 0}
vendor: 4,874 bytes, line 9
9,n,o===void 0?null:o)}function Lo(e,t,n,o){var l=Fe();o=o===void 0?null:o;var d=l.memoizedState.inst;Ce!==null&&o!==null&&ic(o,Ce.memoizedState.deps)?l.memoizedState=wi(t,d,n,o):(he.flags|=e,l.memoizedState=wi(1|t,d,n,o))}function mf(e,t){Bo(8390656,8,e,t)}function gc(e,t){Lo(2048,8,e,t)}function Jw(e){he.flags|=4;var t=he.updateQueue;if(t===null)t=Mo(),he.updateQueue=t,t.events=[e];else{var n=t.events;n===null?t.events=[e]:n.push(e)}}function bf(e){var t=Fe().memoizedState;return Jw({ref:t,nextImpl:e}),function(){if((Ae&2)!==0)throw Error(r(440));return t.impl.apply(void 0,arguments)}}function wf(e,t){return Lo(4,2,e,t)}function yf(e,t){return Lo(4,4,e,t)}function vf(e,t){if(typeof t=="function"){e=e();var n=t(e);return function(){typeof n=="function"?n():t(null)}}if(t!=null)return e=e(),t.current=e,function(){t.current=null}}function _f(e,t,n){n=n!=null?n.concat([e]):null,Lo(4,4,vf.bind(null,t,e),n)}function pc(){}function kf(e,t){var n=Fe();t=t===void 0?null:t;var o=n.memoizedState;return t!==null&&ic(t,o[1])?o[0]:(n.memoizedState=[e,t],e)}function xf(e,t){var n=Fe();t=t===void 0?null:t;var o=n.memoizedState;if(t!==null&&ic(t,o[1]))return o[0];if(o=e(),Oa){Bn(!0);try{e()}finally{Bn(!1)}}return n.memoizedState=[o,t],o}function mc(e,t,n){return n===void 0||(bn&1073741824)!==0&&(we&261930)===0?e.memoizedState=t:(e.memoizedState=n,e=Tg(),he.lanes|=e,$n|=e,n)}function Tf(e,t,n,o){return St(n,t)?n:pi.current!==null?(e=mc(e,n,o),St(e,t)||(Ye=!0),e):(bn&42)===0||(bn&1073741824)!==0&&(we&261930)===0?(Ye=!0,e.memoizedState=n):(e=Tg(),he.lanes|=e,$n|=e,t)}function Sf(e,t,n,o,l){var d=W.p;W.p=d!==0&&8>d?d:8;var p=O.T,y={};O.T=y,yc(e,!1,t,n);try{var k=l(),C=O.S;if(C!==null&&C(y,k),k!==null&&typeof k=="object"&&typeof k.then=="function"){var B=zw(k,o);us(e,t,B,Rt(e))}else us(e,t,o,Rt(e))}catch(H){us(e,t,{then:function(){},status:"rejected",reason:H},Rt())}finally{W.p=d,p!==null&&y.types!==null&&(p.types=y.types),O.T=p}}function Gw(){}function bc(e,t,n,o){if(e.tag!==5)throw Error(r(476));var l=Af(e).queue;Sf(e,l,t,ie,n===null?Gw:function(){return Ef(e),n(o)})}function Af(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:ie,baseState:ie,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:wn,lastRenderedState:ie},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:wn,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Ef(e){var t=Af(e);t.next===null&&(t=e.alternate.memoizedState),us(e,t.next.queue,{},Rt())}function wc(){return at(js)}function jf(){return Fe().memoizedState}function Cf(){return Fe().memoizedState}function Yw(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=Rt();e=Jn(n);var o=Gn(t,e,n);o!==null&&(_t(o,t,n),os(o,t,n)),t={cache:Vl()},e.payload=t;return}t=t.return}}function Vw(e,t,n){var o=Rt();n={lane:o,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},qo(e)?Rf(t,n):(n=Bl(e,t,n,o),n!==null&&(_t(n,e,o),Of(n,t,o)))}function If(e,t,n){var o=Rt();us(e,t,n,o)}function us(e,t,n,o){var l={lane:o,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(qo(e))Rf(t,l);else{var d=e.alternate;if(e.lanes===0&&(d===null||d.lanes===0)&&(d=t.lastRenderedReducer,d!==null))try{var p=t.lastRenderedState,y=d(p,n);if(l.hasEagerState=!0,l.eagerState=y,St(y,p))return wo(e,t,l,0),Ue===null&&bo(),!1}catch{}if(n=Bl(e,t,l,o),n!==null)return _t(n,e,o),Of(n,t,o),!0}return!1}function yc(e,t,n,o){if(o={lane:2,revertLane:Xc(),gesture:null,action:o,hasEagerState:!1,eagerState:null,next:null},qo(e)){if(t)throw Error(r(479))}else t=Bl(e,n,o,2),t!==null&&_t(t,e,2)}function qo(e){var t=e.alternate;return e===he||t!==null&&t===he}function Rf(e,t){mi=Ro=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Of(e,t,n){if((n&4194048)!==0){var o=t.lanes;o&=e.pendingLanes,n|=o,t.lanes=n,Bu(e,n)}}var hs={readContext:at,use:Uo,useCallback:Ne,useContext:Ne,useEffect:Ne,useImperativeHandle:Ne,useLayoutEffect:Ne,useInsertionEffect:Ne,useMemo:Ne,useReducer:Ne,useRef:Ne,useState:Ne,useDebugValue:Ne,useDeferredValue:Ne,useTransition:Ne,useSyncExternalStore:Ne,useId:Ne,useHostTransitionStatus:Ne,useFormState:Ne,useActionState:Ne,useOptimistic:Ne,useMemoCache:Ne,useCacheRefresh:Ne};hs.useEffectEvent=Ne;var Mf={readContext:at,use:Uo,useCallback:function(e,t){return ht().memoizedState=[e,t===void 0?null:t],e},useContext:at,useEffect:mf,useImperativeHandle:function(e,t,n){n=n!=null?n.concat([e]):null,Bo(4194308,4,vf.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Bo(4194308,4,e,t)},useInsertionEffect:function(e,t){Bo(4,2,e,t)},useMemo:function(e,t){var n=ht();t=t===void 0?null:t;var o=e();if(Oa){Bn(!0);try{e()}finally{Bn(!1)}}return n.memoizedState=[o,t],o}
vendor: 4,129 bytes, line 9
9,useReducer:function(e,t,n){var o=ht();if(n!==void 0){var l=n(t);if(Oa){Bn(!0);try{n(t)}finally{Bn(!1)}}}else l=t;return o.memoizedState=o.baseState=l,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:l},o.queue=e,e=e.dispatch=Vw.bind(null,he,e),[o.memoizedState,e]},useRef:function(e){var t=ht();return e={current:e},t.memoizedState=e},useState:function(e){e=hc(e);var t=e.queue,n=If.bind(null,he,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:pc,useDeferredValue:function(e,t){var n=ht();return mc(n,e,t)},useTransition:function(){var e=hc(!1);return e=Sf.bind(null,he,e.queue,!0,!1),ht().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var o=he,l=ht();if(ve){if(n===void 0)throw Error(r(407));n=n()}else{if(n=t(),Ue===null)throw Error(r(349));(we&127)!==0||Zh(o,t,n)}l.memoizedState=n;var d={value:n,getSnapshot:t};return l.queue=d,mf(tf.bind(null,o,d,e),[e]),o.flags|=2048,wi(9,{destroy:void 0},ef.bind(null,o,d,n,t),null),n},useId:function(){var e=ht(),t=Ue.identifierPrefix;if(ve){var n=en,o=Zt;n=(o&~(1<<32-Tt(o)-1)).toString(32)+n,t="_"+t+"R_"+n,n=Oo++,0<n&&(t+="H"+n.toString(32)),t+="_"}else n=Nw++,t="_"+t+"r_"+n.toString(32)+"_";return e.memoizedState=t},useHostTransitionStatus:wc,useFormState:uf,useActionState:uf,useOptimistic:function(e){var t=ht();t.memoizedState=t.baseState=e;var n={pending:null,lanes:0,dispatch:null,lastRenderedReducer:null,lastRenderedState:null};return t.queue=n,t=yc.bind(null,he,!0,n),n.dispatch=t,[e,t]},useMemoCache:cc,useCacheRefresh:function(){return ht().memoizedState=Yw.bind(null,he)},useEffectEvent:function(e){var t=ht(),n={impl:e};return t.memoizedState=n,function(){if((Ae&2)!==0)throw Error(r(440));return n.impl.apply(void 0,arguments)}}},vc={readContext:at,use:Uo,useCallback:kf,useContext:at,useEffect:gc,useImperativeHandle:_f,useInsertionEffect:wf,useLayoutEffect:yf,useMemo:xf,useReducer:Do,useRef:pf,useState:function(){return Do(wn)},useDebugValue:pc,useDeferredValue:function(e,t){var n=Fe();return Tf(n,Ce.memoizedState,e,t)},useTransition:function(){var e=Do(wn)[0],t=Fe().memoizedState;return[typeof e=="boolean"?e:ds(e),t]},useSyncExternalStore:Kh,useId:jf,useHostTransitionStatus:wc,useFormState:hf,useActionState:hf,useOptimistic:function(e,t){var n=Fe();return sf(n,Ce,e,t)},useMemoCache:cc,useCacheRefresh:Cf};vc.useEffectEvent=bf;var Uf={readContext:at,use:Uo,useCallback:kf,useContext:at,useEffect:gc,useImperativeHandle:_f,useInsertionEffect:wf,useLayoutEffect:yf,useMemo:xf,useReducer:uc,useRef:pf,useState:function(){return uc(wn)},useDebugValue:pc,useDeferredValue:function(e,t){var n=Fe();return Ce===null?mc(n,e,t):Tf(n,Ce.memoizedState,e,t)},useTransition:function(){var e=uc(wn)[0],t=Fe().memoizedState;return[typeof e=="boolean"?e:ds(e),t]},useSyncExternalStore:Kh,useId:jf,useHostTransitionStatus:wc,useFormState:gf,useActionState:gf,useOptimistic:function(e,t){var n=Fe();return Ce!==null?sf(n,Ce,e,t):(n.baseState=e,[e,n.queue.dispatch])},useMemoCache:cc,useCacheRefresh:Cf};Uf.useEffectEvent=bf;function _c(e,t,n,o){t=e.memoizedState,n=n(o,t),n=n==null?t:b({},t,n),e.memoizedState=n,e.lanes===0&&(e.updateQueue.baseState=n)}var kc={enqueueSetState:function(e,t,n){e=e._reactInternals;var o=Rt(),l=Jn(o);l.payload=t,n!=null&&(l.callback=n),t=Gn(e,l,o),t!==null&&(_t(t,e,o),os(t,e,o))},enqueueReplaceState:function(e,t,n){e=e._reactInternals;var o=Rt(),l=Jn(o);l.tag=1,l.payload=t,n!=null&&(l.callback=n),t=Gn(e,l,o),t!==null&&(_t(t,e,o),os(t,e,o))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var n=Rt(),o=Jn(n);o.tag=2,t!=null&&(o.callback=t),t=Gn(e,o,n),t!==null&&(_t(t,e,n),os(t,e,n))}};function Df(e,t,n,o,l,d,p){return e=e.stateNode,typeof e.shouldComponentUpdate=="function"?e.shouldComponentUpdate(o,d,p):t.prototype&&t.prototype.isPureReactComponent?!Ki(n,o)||!Ki(l,d):!0}function Bf(e,t,n,o){e=t.state,typeof t.componentWillReceiveProps=="function"&&t.componentWillReceiveProps(n,o),typeof t.UNSAFE_componentWillReceiveProps=="function"&&t.UNSAFE_componentWillReceiveProps(n,o),t.state!==e&&kc.enqueueReplaceState(t,t.state,null)}function Ma(e,t){var n=t;if("ref"in t){n={};
vendor: 14,861 bytes, line 9
9for(var o in t)o!=="ref"&&(n[o]=t[o])}if(e=e.defaultProps){n===t&&(n=b({},n));for(var l in e)n[l]===void 0&&(n[l]=e[l])}return n}function Lf(e){mo(e)}function qf(e){console.error(e)}function zf(e){mo(e)}function zo(e,t){try{var n=e.onUncaughtError;n(t.value,{componentStack:t.stack})}catch(o){setTimeout(function(){throw o})}}function Nf(e,t,n){try{var o=e.onCaughtError;o(n.value,{componentStack:n.stack,errorBoundary:t.tag===1?t.stateNode:null})}catch(l){setTimeout(function(){throw l})}}function xc(e,t,n){return n=Jn(n),n.tag=3,n.payload={element:null},n.callback=function(){zo(e,t)},n}function Hf(e){return e=Jn(e),e.tag=3,e}function Pf(e,t,n,o){var l=n.type.getDerivedStateFromError;if(typeof l=="function"){var d=o.value;e.payload=function(){return l(d)},e.callback=function(){Nf(t,n,o)}}var p=n.stateNode;p!==null&&typeof p.componentDidCatch=="function"&&(e.callback=function(){Nf(t,n,o),typeof l!="function"&&(Xn===null?Xn=new Set([this]):Xn.add(this));var y=o.stack;this.componentDidCatch(o.value,{componentStack:y!==null?y:""})})}function Ww(e,t,n,o,l){if(n.flags|=32768,o!==null&&typeof o=="object"&&typeof o.then=="function"){if(t=n.alternate,t!==null&&di(t,n,l,!0),n=Et.current,n!==null){switch(n.tag){case 31:case 13:return Ht===null?Xo():n.alternate===null&&He===0&&(He=3),n.flags&=-257,n.flags|=65536,n.lanes=l,o===Ao?n.flags|=16384:(t=n.updateQueue,t===null?n.updateQueue=new Set([o]):t.add(o),Wc(e,o,l)),!1;case 22:return n.flags|=65536,o===Ao?n.flags|=16384:(t=n.updateQueue,t===null?(t={transitions:null,markerInstances:null,retryQueue:new Set([o])},n.updateQueue=t):(n=t.retryQueue,n===null?t.retryQueue=new Set([o]):n.add(o)),Wc(e,o,l)),!1}throw Error(r(435,n.tag))}return Wc(e,o,l),Xo(),!1}if(ve)return t=Et.current,t!==null?((t.flags&65536)===0&&(t.flags|=256),t.flags|=65536,t.lanes=l,o!==Pl&&(e=Error(r(422),{cause:o}),ts(Lt(e,n)))):(o!==Pl&&(t=Error(r(423),{cause:o}),ts(Lt(t,n))),e=e.current.alternate,e.flags|=65536,l&=-l,e.lanes|=l,o=Lt(o,n),l=xc(e.stateNode,o,l),Zl(e,l),He!==4&&(He=2)),!1;var d=Error(r(520),{cause:o});if(d=Lt(d,n),vs===null?vs=[d]:vs.push(d),He!==4&&(He=2),t===null)return!0;o=Lt(o,n),n=t;do{switch(n.tag){case 3:return n.flags|=65536,e=l&-l,n.lanes|=e,e=xc(n.stateNode,o,e),Zl(n,e),!1;case 1:if(t=n.type,d=n.stateNode,(n.flags&128)===0&&(typeof t.getDerivedStateFromError=="function"||d!==null&&typeof d.componentDidCatch=="function"&&(Xn===null||!Xn.has(d))))return n.flags|=65536,l&=-l,n.lanes|=l,l=Hf(l),Pf(l,e,n,o),Zl(n,l),!1}n=n.return}while(n!==null);return!1}var Tc=Error(r(461)),Ye=!1;function it(e,t,n,o){t.child=e===null?Gh(t,null,n,o):Ra(t,e.child,n,o)}function Ff(e,t,n,o,l){n=n.render;var d=t.ref;if("ref"in o){var p={};for(var y in o)y!=="ref"&&(p[y]=o[y])}else p=o;return Ea(t),o=sc(e,t,n,p,d,l),y=oc(),e!==null&&!Ye?(rc(e,t,l),yn(e,t,l)):(ve&&y&&Nl(t),t.flags|=1,it(e,t,o,l),t.child)}function Jf(e,t,n,o,l){if(e===null){var d=n.type;return typeof d=="function"&&!Ll(d)&&d.defaultProps===void 0&&n.compare===null?(t.tag=15,t.type=d,Gf(e,t,d,o,l)):(e=vo(n.type,null,o,t,t.mode,l),e.ref=t.ref,e.return=t,t.child=e)}if(d=e.child,!Oc(e,l)){var p=d.memoizedProps;if(n=n.compare,n=n!==null?n:Ki,n(p,o)&&e.ref===t.ref)return yn(e,t,l)}return t.flags|=1,e=fn(d,o),e.ref=t.ref,e.return=t,t.child=e}function Gf(e,t,n,o,l){if(e!==null){var d=e.memoizedProps;if(Ki(d,o)&&e.ref===t.ref)if(Ye=!1,t.pendingProps=o=d,Oc(e,l))(e.flags&131072)!==0&&(Ye=!0);else return t.lanes=e.lanes,yn(e,t,l)}return Sc(e,t,n,o,l)}function Yf(e,t,n,o){var l=o.children,d=e!==null?e.memoizedState:null;if(e===null&&t.stateNode===null&&(t.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),o.mode==="hidden"){if((t.flags&128)!==0){if(d=d!==null?d.baseLanes|n:n,e!==null){for(o=t.child=e.child,l=0;o!==null;)l=l|o.lanes|o.childLanes,o=o.sibling;o=l&~d}else o=0,t.child=null;return Vf(e,t,d,n,o)}if((n&536870912)!==0)t.memoizedState={baseLanes:0,cachePool:null},e!==null&&To(t,d!==null?d.cachePool:null),d!==null?Wh(t,d):tc(),Qh(t);else return o=t.lanes=536870912,Vf(e,t,d!==null?d.baseLanes|n:n,n,o)}else d!==null?(To(t,d.cachePool),Wh(t,d),Vn(),t.memoizedState=null):(e!==null&&To(t,null),tc(),Vn());return it(e,t,l,n),t.child}function fs(e,t){return e!==null&&e.tag===22||t.stateNode!==null||(t.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null}),t.sibling}function Vf(e,t,n,o,l){var d=Ql();return d=d===null?null:{parent:Je._currentValue,pool:d},t.memoizedState={baseLanes:n,cachePool:d},e!==null&&To(t,null),tc(),Qh(t),e!==null&&di(e,t,o,!0),t.childLanes=l,null}function No(e,t){return t=Po({mode:t.mode,children:t.children},e.mode),t.ref=e.ref,e.child=t,t.return=e,t}function Wf(e,t,n){return Ra(t,e.child,null,n),e=No(t,t.pendingProps),e.flags|=2,jt(t),t.memoizedState=null,e}function Qw(e,t,n){var o=t.pendingProps,l=(t.flags&128)!==0;if(t.flags&=-129,e===null){if(ve){if(o.mode==="hidden")return e=No(t,o),t.lanes=536870912,fs(null,e);if(ac(t),(e=Le)?(e=op(e,Nt),e=e!==null&&e.data==="&"?e:null,e!==null&&(t.memoizedState={dehydrated:e,treeContext:zn!==null?{id:Zt,overflow:en}:null,retryLane:536870912,hydrationErrors:null},n=Ih(e),n.return=t,t.child=n,nt=t,Le=null)):e=null,e===null)throw Hn(t);return t.lanes=536870912,null}return No(t,o)}var d=e.memoizedState;if(d!==null){var p=d.dehydrated;if(ac(t),l)if(t.flags&256)t.flags&=-257,t=Wf(e,t,n);else if(t.memoizedState!==null)t.child=e.child,t.flags|=128,t=null;else throw Error(r(558));else if(Ye||di(e,t,n,!1),l=(n&e.childLanes)!==0,Ye||l){if(o=Ue,o!==null&&(p=Lu(o,n),p!==0&&p!==d.retryLane))throw d.retryLane=p,xa(e,p),_t(o,e,p),Tc;Xo(),t=Wf(e,t,n)}else e=d.treeContext,Le=Pt(p.nextSibling),nt=t,ve=!0,Nn=null,Nt=!1,e!==null&&Mh(t,e),t=No(t,o),t.flags|=4096;return t}return e=fn(e.child,{mode:o.mode,children:o.children}),e.ref=t.ref,t.child=e,e.return=t,e}function Ho(e,t){var n=t.ref;if(n===null)e!==null&&e.ref!==null&&(t.flags|=4194816);else{if(typeof n!="function"&&typeof n!="object")throw Error(r(284));(e===null||e.ref!==n)&&(t.flags|=4194816)}}function Sc(e,t,n,o,l){return Ea(t),n=sc(e,t,n,o,void 0,l),o=oc(),e!==null&&!Ye?(rc(e,t,l),yn(e,t,l)):(ve&&o&&Nl(t),t.flags|=1,it(e,t,n,l),t.child)}function Qf(e,t,n,o,l,d){return Ea(t),t.updateQueue=null,n=Xh(t,o,n,l),$h(e),o=oc(),e!==null&&!Ye?(rc(e,t,d),yn(e,t,d)):(ve&&o&&Nl(t),t.flags|=1,it(e,t,n,d),t.child)}function $f(e,t,n,o,l){if(Ea(t),t.stateNode===null){var d=oi,p=n.contextType;typeof p=="object"&&p!==null&&(d=at(p)),d=new n(o,d),t.memoizedState=d.state!==null&&d.state!==void 0?d.state:null,d.updater=kc,t.stateNode=d,d._reactInternals=t,d=t.stateNode,d.props=o,d.state=t.memoizedState,d.refs={},Xl(t),p=n.contextType,d.context=typeof p=="object"&&p!==null?at(p):oi,d.state=t.memoizedState,p=n.getDerivedStateFromProps,typeof p=="function"&&(_c(t,n,p,o),d.state=t.memoizedState),typeof n.getDerivedStateFromProps=="function"||typeof d.getSnapshotBeforeUpdate=="function"||typeof d.UNSAFE_componentWillMount!="function"&&typeof d.componentWillMount!="function"||(p=d.state,typeof d.componentWillMount=="function"&&d.componentWillMount(),typeof d.UNSAFE_componentWillMount=="function"&&d.UNSAFE_componentWillMount(),p!==d.state&&kc.enqueueReplaceState(d,d.state,null),ls(t,o,d,l),rs(),d.state=t.memoizedState),typeof d.componentDidMount=="function"&&(t.flags|=4194308),o=!0}else if(e===null){d=t.stateNode;var y=t.memoizedProps,k=Ma(n,y);d.props=k;var C=d.context,B=n.contextType;p=oi,typeof B=="object"&&B!==null&&(p=at(B));var H=n.getDerivedStateFromProps;B=typeof H=="function"||typeof d.getSnapshotBeforeUpdate=="function",y=t.pendingProps!==y,B||typeof d.UNSAFE_componentWillReceiveProps!="function"&&typeof d.componentWillReceiveProps!="function"||(y||C!==p)&&Bf(t,d,o,p),Fn=!1;var R=t.memoizedState;d.state=R,ls(t,o,d,l),rs(),C=t.memoizedState,y||R!==C||Fn?(typeof H=="function"&&(_c(t,n,H,o),C=t.memoizedState),(k=Fn||Df(t,n,k,o,R,C,p))?(B||typeof d.UNSAFE_componentWillMount!="function"&&typeof d.componentWillMount!="function"||(typeof d.componentWillMount=="function"&&d.componentWillMount(),typeof d.UNSAFE_componentWillMount=="function"&&d.UNSAFE_componentWillMount()),typeof d.componentDidMount=="function"&&(t.flags|=4194308)):(typeof d.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=o,t.memoizedState=C),d.props=o,d.state=C,d.context=p,o=k):(typeof d.componentDidMount=="function"&&(t.flags|=4194308),o=!1)}else{d=t.stateNode,Kl(e,t),p=t.memoizedProps,B=Ma(n,p),d.props=B,H=t.pendingProps,R=d.context,C=n.contextType,k=oi,typeof C=="object"&&C!==null&&(k=at(C)),y=n.getDerivedStateFromProps,(C=typeof y=="function"||typeof d.getSnapshotBeforeUpdate=="function")||typeof d.UNSAFE_componentWillReceiveProps!="function"&&typeof d.componentWillReceiveProps!="function"||(p!==H||R!==k)&&Bf(t,d,o,k),Fn=!1,R=t.memoizedState,d.state=R,ls(t,o,d,l),rs();var D=t.memoizedState;p!==H||R!==D||Fn||e!==null&&e.dependencies!==null&&ko(e.dependencies)?(typeof y=="function"&&(_c(t,n,y,o),D=t.memoizedState),(B=Fn||Df(t,n,B,o,R,D,k)||e!==null&&e.dependencies!==null&&ko(e.dependencies))?(C||typeof d.UNSAFE_componentWillUpdate!="function"&&typeof d.componentWillUpdate!="function"||(typeof d.componentWillUpdate=="function"&&d.componentWillUpdate(o,D,k),typeof d.UNSAFE_componentWillUpdate=="function"&&d.UNSAFE_componentWillUpdate(o,D,k)),typeof d.componentDidUpdate=="function"&&(t.flags|=4),typeof d.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof d.componentDidUpdate!="function"||p===e.memoizedProps&&R===e.memoizedState||(t.flags|=4),typeof d.getSnapshotBeforeUpdate!="function"||p===e.memoizedProps&&R===e.memoizedState||(t.flags|=1024),t.memoizedProps=o,t.memoizedState=D),d.props=o,d.state=D,d.context=k,o=B):(typeof d.componentDidUpdate!="function"||p===e.memoizedProps&&R===e.memoizedState||(t.flags|=4),typeof d.getSnapshotBeforeUpdate!="function"||p===e.memoizedProps&&R===e.memoizedState||(t.flags|=1024),o=!1)}return d=o,Ho(e,t),o=(t.flags&128)!==0,d||o?(d=t.stateNode,n=o&&typeof n.getDerivedStateFromError!="function"?null:d.render(),t.flags|=1,e!==null&&o?(t.child=Ra(t,e.child,null,l),t.child=Ra(t,null,n,l)):it(e,t,n,l),t.memoizedState=d.state,e=t.child):e=yn(e,t,l),e}function Xf(e,t,n,o){return Sa(),t.flags|=256,it(e,t,n,o),t.child}var Ac={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function Ec(e){return{baseLanes:e,cachePool:zh()}}function jc(e,t,n){return e=e!==null?e.childLanes&~n:0,t&&(e|=It),e}function Kf(e,t,n){var o=t.pendingProps,l=!1,d=(t.flags&128)!==0,p;if((p=d)||(p=e!==null&&e.memoizedState===null?!1:(Pe.current&2)!==0),p&&(l=!0,t.flags&=-129),p=(t.flags&32)!==0,t.flags&=-33,e===null){if(ve){if(l?Yn(t):Vn(),(e=Le)?(e=op(e,Nt),e=e!==null&&e.data!=="&"?e:null,e!==null&&(t.memoizedState={dehydrated:e,treeContext:zn!==null?{id:Zt,overflow:en}:null,retryLane:536870912,hydrationErrors:null},n=Ih(e),n.return=t,t.child=n,nt=t,Le=null)):e=null,e===null)throw Hn(t);return dd(e)?t.lanes=32:t.lanes=536870912,null}var y=o.children;return o=o.fallback,l?(Vn(),l=t.mode,y=Po({mode:"hidden",children:y},l),o=Ta(o,l,n,null),y.return=t,o.return=t,y.sibling=o,t.child=y,o=t.child,o.memoizedState=Ec(n),o.childLanes=jc(e,p,n),t.memoizedState=Ac,fs(null,o)):(Yn(t),Cc(t,y))}var k=e.memoizedState;if(k!==null&&(y=k.dehydrated,y!==null)){if(d)t.flags&256?(Yn(t),t.flags&=-257,t=Ic(e,t,n)):t.memoizedState!==null?(Vn(),t.child=e.child,t.flags|=128,t=null):(Vn(),y=o.fallback,l=t.mode,o=Po({mode:"visible",children:o.children},l),y=Ta(y,l,n,null),y.flags|=2,o.return=t,y.return=t,o.sibling=y,t.child=o,Ra(t,e.child,null,n),o=t.child,o.memoizedState=Ec(n),o.childLanes=jc(e,p,n),t.memoizedState=Ac,t=fs(null,o));else if(Yn(t),dd(y)){if(p=y.nextSibling&&y.nextSibling.dataset,p)var C=p.dgst;p=C,o=Error(r(419)),o.stack="",o.digest=p,ts({value:o,source:null,stack:null}),t=Ic(e,t,n)}else if(Ye||di(e,t,n,!1),p=(n&e.childLanes)!==0,Ye||p){if(p=Ue,p!==null&&(o=Lu(p,n),o!==0&&o!==k.retryLane))throw k.retryLane=o,xa(e,o),_t(p,e,o),Tc;cd(y)||Xo(),t=Ic(e,t,n)}else cd(y)?(t.flags|=192,t.child=e.child,t=null):(e=k.treeContext,Le=Pt(y.nextSibling),nt=t,ve=!0,Nn=null,Nt=!1,e!==null&&Mh(t,e),t=Cc(t,o.children),t.flags|=4096);return t}return l?(Vn(),y=o.fallback,l=t.mode,k=e.child,C=k.sibling,o=fn(k,{mode:"hidden",children:o.children}),o.subtreeFlags=k.subtreeFlags&65011712,C!==null?y=fn(C,y):(y=Ta(y,l,n,null),y.flags|=2),y.return=t,o.return=t,o.sibling=y,t.child=o,fs(null,o),o=t.child,y=e.child.memoizedState,y===null?y=Ec(n):(l=y.cachePool,l!==null?(k=Je._currentValue,l=l.parent!==k?{parent:k,pool:k}:l):l=zh(),y={baseLanes:y.baseLanes|n,cachePool:l}),o.memoizedState=y,o.childLanes=jc(e,p,n),t.memoizedState=Ac,fs(e.child,o)):(Yn(t),n=e.child,e=n.sibling,n=fn(n,{mode:"visible",children:o.children}),n.return=t,n.sibling=null,e!==null&&(p=t.deletions,p===null?(t.deletions=[e],t.flags|=16):p.push(e)),t.child=n,t.memoizedState=null,n)}function Cc(e,t){return t=Po({mode:"visible",children:t},e.mode),t.return=e,e.child=t}function Po(e,t){return e=At(22,e,null,t),e.lanes=0,e}function Ic(e,t,n){return Ra(t,e.child,null,n),e=Cc(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function Zf(e,t,n){e.lanes|=t;var o=e.alternate;o!==null&&(o.lanes|=t),Gl(e.return,t,n)}function Rc(e,t,n,o,l,d){var p=e.memoizedState;p===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:o,tail:n,tailMode:l,treeForkCount:d}:(p.isBackwards=t,p.rendering=null,p.renderingStartTime=0,p.last=o,p.tail=n,p.tailMode=l,p.treeForkCount=d)}function eg(e,t,n){var o=t.pendingProps,l=o.revealOrder,d=o.tail;o=o.children;var p=Pe.current,y=(p&2)!==0;if(y?(p=p&1|2,t.flags|=128):p&=1,Y(Pe,p),it(e,t,o,n),o=ve?es:0,!y&&e!==null&&(e.flags&128)!==0)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&Zf(e,n,t);else if(e.tag===19)Zf(e,n,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}switch(l){case"forwards":for(n=t.child,l=null;n!==null;)e=n.alternate,e!==null&&Io(e)===null&&(l=n),n=n.sibling;n=l,n===null?(l=t.child,t.child=null):(l=n.sibling,n.sibling=null),Rc(t,!1,l,n,d,o);break;case"backwards":case"unstable_legacy-backwards":for(n=null,l=t.child,t.child=null;l!==null;){if(e=l.alternate,e!==null&&Io(e)===null){t.child=l;break}e=l.sibling,l.sibling=n,n=l,l=e}Rc(t,!0,n,null,d,o);break;case"together":Rc(t,!1,null,null,void 0,o);break;default:t.memoizedState=null}return t.child}function yn(e,t,n){if(e!==null&&(t.dependencies=e.dependencies),$n|=t.lanes,(n&t.childLanes)===0)if(e!==null){if(di(e,t,n,!1),(n&t.childLanes)===0)return null}else return null;if(e!==null&&t.child!==e.child)throw Error(r(153));if(t.child!==null){for(e=t.child,n=fn(e,e.pendingProps),t.child=n,n.return=t;e.sibling!==null;)e=e.sibling,n=n.sibling=fn(e,e.pendingProps),n.return=t;n.sibling=null}return t.child}
vendor: 7,413 bytes, line 9
9function Oc(e,t){return(e.lanes&t)!==0?!0:(e=e.dependencies,!!(e!==null&&ko(e)))}function $w(e,t,n){switch(t.tag){case 3:Se(t,t.stateNode.containerInfo),Pn(t,Je,e.memoizedState.cache),Sa();break;case 27:case 5:Kt(t);break;case 4:Se(t,t.stateNode.containerInfo);break;case 10:Pn(t,t.type,t.memoizedProps.value);break;case 31:if(t.memoizedState!==null)return t.flags|=128,ac(t),null;break;case 13:var o=t.memoizedState;if(o!==null)return o.dehydrated!==null?(Yn(t),t.flags|=128,null):(n&t.child.childLanes)!==0?Kf(e,t,n):(Yn(t),e=yn(e,t,n),e!==null?e.sibling:null);Yn(t);break;case 19:var l=(e.flags&128)!==0;if(o=(n&t.childLanes)!==0,o||(di(e,t,n,!1),o=(n&t.childLanes)!==0),l){if(o)return eg(e,t,n);t.flags|=128}if(l=t.memoizedState,l!==null&&(l.rendering=null,l.tail=null,l.lastEffect=null),Y(Pe,Pe.current),o)break;return null;case 22:return t.lanes=0,Yf(e,t,n,t.pendingProps);case 24:Pn(t,Je,e.memoizedState.cache)}return yn(e,t,n)}function tg(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps)Ye=!0;else{if(!Oc(e,n)&&(t.flags&128)===0)return Ye=!1,$w(e,t,n);Ye=(e.flags&131072)!==0}else Ye=!1,ve&&(t.flags&1048576)!==0&&Oh(t,es,t.index);switch(t.lanes=0,t.tag){case 16:e:{var o=t.pendingProps;if(e=Ca(t.elementType),t.type=e,typeof e=="function")Ll(e)?(o=Ma(e,o),t.tag=1,t=$f(null,t,e,o,n)):(t.tag=0,t=Sc(null,t,e,o,n));else{if(e!=null){var l=e.$$typeof;if(l===G){t.tag=11,t=Ff(null,t,e,o,n);break e}else if(l===M){t.tag=14,t=Jf(null,t,e,o,n);break e}}throw t=ce(e)||e,Error(r(306,t,""))}}return t;case 0:return Sc(e,t,t.type,t.pendingProps,n);case 1:return o=t.type,l=Ma(o,t.pendingProps),$f(e,t,o,l,n);case 3:e:{if(Se(t,t.stateNode.containerInfo),e===null)throw Error(r(387));o=t.pendingProps;var d=t.memoizedState;l=d.element,Kl(e,t),ls(t,o,null,n);var p=t.memoizedState;if(o=p.cache,Pn(t,Je,o),o!==d.cache&&Yl(t,[Je],n,!0),rs(),o=p.element,d.isDehydrated)if(d={element:o,isDehydrated:!1,cache:p.cache},t.updateQueue.baseState=d,t.memoizedState=d,t.flags&256){t=Xf(e,t,o,n);break e}else if(o!==l){l=Lt(Error(r(424)),t),ts(l),t=Xf(e,t,o,n);break e}else for(e=t.stateNode.containerInfo,e.nodeType===9?e=e.body:e=e.nodeName==="HTML"?e.ownerDocument.body:e,Le=Pt(e.firstChild),nt=t,ve=!0,Nn=null,Nt=!0,n=Gh(t,null,o,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Sa(),o===l){t=yn(e,t,n);break e}it(e,t,o,n)}t=t.child}return t;case 26:return Ho(e,t),e===null?(n=hp(t.type,null,t.pendingProps,null))?t.memoizedState=n:ve||(n=t.type,e=t.pendingProps,o=ir(ge.current).createElement(n),o[tt]=t,o[pt]=e,st(o,n,e),Ke(o),t.stateNode=o):t.memoizedState=hp(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return Kt(t),e===null&&ve&&(o=t.stateNode=cp(t.type,t.pendingProps,ge.current),nt=t,Nt=!0,l=Le,ta(t.type)?(ud=l,Le=Pt(o.firstChild)):Le=l),it(e,t,t.pendingProps.children,n),Ho(e,t),e===null&&(t.flags|=4194304),t.child;case 5:return e===null&&ve&&((l=o=Le)&&(o=Ay(o,t.type,t.pendingProps,Nt),o!==null?(t.stateNode=o,nt=t,Le=Pt(o.firstChild),Nt=!1,l=!0):l=!1),l||Hn(t)),Kt(t),l=t.type,d=t.pendingProps,p=e!==null?e.memoizedProps:null,o=d.children,od(l,d)?o=null:p!==null&&od(l,p)&&(t.flags|=32),t.memoizedState!==null&&(l=sc(e,t,Hw,null,null,n),js._currentValue=l),Ho(e,t),it(e,t,o,n),t.child;case 6:return e===null&&ve&&((e=n=Le)&&(n=Ey(n,t.pendingProps,Nt),n!==null?(t.stateNode=n,nt=t,Le=null,e=!0):e=!1),e||Hn(t)),null;case 13:return Kf(e,t,n);case 4:return Se(t,t.stateNode.containerInfo),o=t.pendingProps,e===null?t.child=Ra(t,null,o,n):it(e,t,o,n),t.child;case 11:return Ff(e,t,t.type,t.pendingProps,n);case 7:return it(e,t,t.pendingProps,n),t.child;case 8:return it(e,t,t.pendingProps.children,n),t.child;case 12:return it(e,t,t.pendingProps.children,n),t.child;case 10:return o=t.pendingProps,Pn(t,t.type,o.value),it(e,t,o.children,n),t.child;case 9:return l=t.type._context,o=t.pendingProps.children,Ea(t),l=at(l),o=o(l),t.flags|=1,it(e,t,o,n),t.child;case 14:return Jf(e,t,t.type,t.pendingProps,n);case 15:return Gf(e,t,t.type,t.pendingProps,n);case 19:return eg(e,t,n);case 31:return Qw(e,t,n);case 22:return Yf(e,t,n,t.pendingProps);case 24:return Ea(t),o=at(Je),e===null?(l=Ql(),l===null&&(l=Ue,d=Vl(),l.pooledCache=d,d.refCount++,d!==null&&(l.pooledCacheLanes|=n),l=d),t.memoizedState={parent:o,cache:l},Xl(t),Pn(t,Je,l)):((e.lanes&n)!==0&&(Kl(e,t),ls(t,null,null,n),rs()),l=e.memoizedState,d=t.memoizedState,l.parent!==o?(l={parent:o,cache:o},t.memoizedState=l,t.lanes===0&&(t.memoizedState=t.updateQueue.baseState=l),Pn(t,Je,o)):(o=d.cache,Pn(t,Je,o),o!==l.cache&&Yl(t,[Je],n,!0))),it(e,t,t.pendingProps.children,n),t.child;case 29:throw t.pendingProps}throw Error(r(156,t.tag))}function vn(e){e.flags|=4}function Mc(e,t,n,o,l){if((t=(e.mode&32)!==0)&&(t=!1),t){if(e.flags|=16777216,(l&335544128)===l)if(e.stateNode.complete)e.flags|=8192;else if(jg())e.flags|=8192;else throw Ia=Ao,$l}else e.flags&=-16777217}function ng(e,t){if(t.type!=="stylesheet"||(t.state.loading&4)!==0)e.flags&=-16777217;else if(e.flags|=16777216,!bp(t))if(jg())e.flags|=8192;else throw Ia=Ao,$l}function Fo(e,t){t!==null&&(e.flags|=4),e.flags&16384&&(t=e.tag!==22?Uu():536870912,e.lanes|=t,ki|=t)}function gs(e,t){if(!ve)switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;t!==null;)t.alternate!==null&&(n=t),t=t.sibling;n===null?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var o=null;n!==null;)n.alternate!==null&&(o=n),n=n.sibling;o===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:o.sibling=null}}function qe(e){var t=e.alternate!==null&&e.alternate.child===e.child,n=0,o=0;if(t)for(var l=e.child;l!==null;)n|=l.lanes|l.childLanes,o|=l.subtreeFlags&65011712,o|=l.flags&65011712,l.return=e,l=l.sibling;else for(l=e.child;l!==null;)n|=l.lanes|l.childLanes,o|=l.subtreeFlags,o|=l.flags,l.return=e,l=l.sibling;return e.subtreeFlags|=o,e.childLanes=n,t}function Xw(e,t,n){var o=t.pendingProps;switch(Hl(t),t.tag){case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return qe(t),null;case 1:return qe(t),null;case 3:return n=t.stateNode,o=null,e!==null&&(o=e.memoizedState.cache),t.memoizedState.cache!==o&&(t.flags|=2048),mn(Je),De(),n.pendingContext&&(n.context=n.pendingContext,n.pendingContext=null),(e===null||e.child===null)&&(ci(t)?vn(t):e===null||e.memoizedState.isDehydrated&&(t.flags&256)===0||(t.flags|=1024,Fl())),qe(t),null;case 26:var l=t.type,d=t.memoizedState;return e===null?(vn(t),d!==null?(qe(t),ng(t,d)):(qe(t),Mc(t,l,null,o,n))):d?d!==e.memoizedState?(vn(t),qe(t),ng(t,d)):(qe(t),t.flags&=-16777217):(e=e.memoizedProps,e!==o&&vn(t),qe(t),Mc(t,l,e,o,n)),null;case 27:if(ma(t),n=ge.current,l=t.type,e!==null&&t.stateNode!=null)e.memoizedProps!==o&&vn(t);else{if(!o){if(t.stateNode===null)throw Error(r(166));return qe(t),null}e=K.current,ci(t)?Uh(t):(e=cp(l,o,n),t.stateNode=e,vn(t))}return qe(t),null;case 5:if(ma(t),l=t.type,e!==null&&t.stateNode!=null)e.memoizedProps!==o&&vn(t);else{if(!o){if(t.stateNode===null)throw Error(r(166));return qe(t),null}if(d=K.current,ci(t))Uh(t);else{var p=ir(ge.current);switch(d){case 1:d=p.createElementNS("http://www.w3.org/2000/svg",l);break;case 2:d=p.createElementNS("http://www.w3.org/1998/Math/MathML",l);break;default:switch(l){case"svg":d=p.createElementNS("http://www.w3.org/2000/svg",l);break;case"math":d=p.createElementNS("http://www.w3.org/1998/Math/MathML",l);break;case"script":d=p.createElement("div"),d.innerHTML="<script><\/script>",d=d.removeChild(d.firstChild);break;
vendor: 26,225 bytes, line 9
9case"select":d=typeof o.is=="string"?p.createElement("select",{is:o.is}):p.createElement("select"),o.multiple?d.multiple=!0:o.size&&(d.size=o.size);break;default:d=typeof o.is=="string"?p.createElement(l,{is:o.is}):p.createElement(l)}}d[tt]=t,d[pt]=o;e:for(p=t.child;p!==null;){if(p.tag===5||p.tag===6)d.appendChild(p.stateNode);else if(p.tag!==4&&p.tag!==27&&p.child!==null){p.child.return=p,p=p.child;continue}if(p===t)break e;for(;p.sibling===null;){if(p.return===null||p.return===t)break e;p=p.return}p.sibling.return=p.return,p=p.sibling}t.stateNode=d;e:switch(st(d,l,o),l){case"button":case"input":case"select":case"textarea":o=!!o.autoFocus;break e;case"img":o=!0;break e;default:o=!1}o&&vn(t)}}return qe(t),Mc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==o&&vn(t);else{if(typeof o!="string"&&t.stateNode===null)throw Error(r(166));if(e=ge.current,ci(t)){if(e=t.stateNode,n=t.memoizedProps,o=null,l=nt,l!==null)switch(l.tag){case 27:case 5:o=l.memoizedProps}e[tt]=t,e=!!(e.nodeValue===n||o!==null&&o.suppressHydrationWarning===!0||Kg(e.nodeValue,n)),e||Hn(t,!0)}else e=ir(e).createTextNode(o),e[tt]=t,t.stateNode=e}return qe(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(o=ci(t),n!==null){if(e===null){if(!o)throw Error(r(318));if(e=t.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(r(557));e[tt]=t}else Sa(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;qe(t),e=!1}else n=Fl(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(jt(t),t):(jt(t),null);if((t.flags&128)!==0)throw Error(r(558))}return qe(t),null;case 13:if(o=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(l=ci(t),o!==null&&o.dehydrated!==null){if(e===null){if(!l)throw Error(r(318));if(l=t.memoizedState,l=l!==null?l.dehydrated:null,!l)throw Error(r(317));l[tt]=t}else Sa(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;qe(t),l=!1}else l=Fl(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=l),l=!0;if(!l)return t.flags&256?(jt(t),t):(jt(t),null)}return jt(t),(t.flags&128)!==0?(t.lanes=n,t):(n=o!==null,e=e!==null&&e.memoizedState!==null,n&&(o=t.child,l=null,o.alternate!==null&&o.alternate.memoizedState!==null&&o.alternate.memoizedState.cachePool!==null&&(l=o.alternate.memoizedState.cachePool.pool),d=null,o.memoizedState!==null&&o.memoizedState.cachePool!==null&&(d=o.memoizedState.cachePool.pool),d!==l&&(o.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Fo(t,t.updateQueue),qe(t),null);case 4:return De(),e===null&&td(t.stateNode.containerInfo),qe(t),null;case 10:return mn(t.type),qe(t),null;case 19:if(z(Pe),o=t.memoizedState,o===null)return qe(t),null;if(l=(t.flags&128)!==0,d=o.rendering,d===null)if(l)gs(o,!1);else{if(He!==0||e!==null&&(e.flags&128)!==0)for(e=t.child;e!==null;){if(d=Io(e),d!==null){for(t.flags|=128,gs(o,!1),e=d.updateQueue,t.updateQueue=e,Fo(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)Ch(n,e),n=n.sibling;return Y(Pe,Pe.current&1|2),ve&&gn(t,o.treeForkCount),t.child}e=e.sibling}o.tail!==null&&kt()>Wo&&(t.flags|=128,l=!0,gs(o,!1),t.lanes=4194304)}else{if(!l)if(e=Io(d),e!==null){if(t.flags|=128,l=!0,e=e.updateQueue,t.updateQueue=e,Fo(t,e),gs(o,!0),o.tail===null&&o.tailMode==="hidden"&&!d.alternate&&!ve)return qe(t),null}else 2*kt()-o.renderingStartTime>Wo&&n!==536870912&&(t.flags|=128,l=!0,gs(o,!1),t.lanes=4194304);o.isBackwards?(d.sibling=t.child,t.child=d):(e=o.last,e!==null?e.sibling=d:t.child=d,o.last=d)}return o.tail!==null?(e=o.tail,o.rendering=e,o.tail=e.sibling,o.renderingStartTime=kt(),e.sibling=null,n=Pe.current,Y(Pe,l?n&1|2:n&1),ve&&gn(t,o.treeForkCount),e):(qe(t),null);case 22:case 23:return jt(t),nc(),o=t.memoizedState!==null,e!==null?e.memoizedState!==null!==o&&(t.flags|=8192):o&&(t.flags|=8192),o?(n&536870912)!==0&&(t.flags&128)===0&&(qe(t),t.subtreeFlags&6&&(t.flags|=8192)):qe(t),n=t.updateQueue,n!==null&&Fo(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),o=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(o=t.memoizedState.cachePool.pool),o!==n&&(t.flags|=2048),e!==null&&z(ja),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),mn(Je),qe(t),null;case 25:return null;case 30:return null}throw Error(r(156,t.tag))}function Kw(e,t){switch(Hl(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return mn(Je),De(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return ma(t),null;case 31:if(t.memoizedState!==null){if(jt(t),t.alternate===null)throw Error(r(340));Sa()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(jt(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(r(340));Sa()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return z(Pe),null;case 4:return De(),null;case 10:return mn(t.type),null;case 22:case 23:return jt(t),nc(),e!==null&&z(ja),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return mn(Je),null;case 25:return null;default:return null}}function ag(e,t){switch(Hl(t),t.tag){case 3:mn(Je),De();break;case 26:case 27:case 5:ma(t);break;case 4:De();break;case 31:t.memoizedState!==null&&jt(t);break;case 13:jt(t);break;case 19:z(Pe);break;case 10:mn(t.type);break;case 22:case 23:jt(t),nc(),e!==null&&z(ja);break;case 24:mn(Je)}}function ps(e,t){try{var n=t.updateQueue,o=n!==null?n.lastEffect:null;if(o!==null){var l=o.next;n=l;do{if((n.tag&e)===e){o=void 0;var d=n.create,p=n.inst;o=d(),p.destroy=o}n=n.next}while(n!==l)}}catch(y){je(t,t.return,y)}}function Wn(e,t,n){try{var o=t.updateQueue,l=o!==null?o.lastEffect:null;if(l!==null){var d=l.next;o=d;do{if((o.tag&e)===e){var p=o.inst,y=p.destroy;if(y!==void 0){p.destroy=void 0,l=t;var k=n,C=y;try{C()}catch(B){je(l,k,B)}}}o=o.next}while(o!==d)}}catch(B){je(t,t.return,B)}}function ig(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Vh(t,n)}catch(o){je(e,e.return,o)}}}function sg(e,t,n){n.props=Ma(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(o){je(e,t,o)}}function ms(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var o=e.stateNode;break;case 30:o=e.stateNode;break;default:o=e.stateNode}typeof n=="function"?e.refCleanup=n(o):n.current=o}}catch(l){je(e,t,l)}}function tn(e,t){var n=e.ref,o=e.refCleanup;if(n!==null)if(typeof o=="function")try{o()}catch(l){je(e,t,l)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n=="function")try{n(null)}catch(l){je(e,t,l)}else n.current=null}function og(e){var t=e.type,n=e.memoizedProps,o=e.stateNode;try{e:switch(t){case"button":case"input":case"select":case"textarea":n.autoFocus&&o.focus();break e;case"img":n.src?o.src=n.src:n.srcSet&&(o.srcset=n.srcSet)}}catch(l){je(e,e.return,l)}}function Uc(e,t,n){try{var o=e.stateNode;vy(o,e.type,n,t),o[pt]=t}catch(l){je(e,e.return,l)}}function rg(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&ta(e.type)||e.tag===4}function Dc(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||rg(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&ta(e.type)||e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Bc(e,t,n){var o=e.tag;if(o===5||o===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName==="HTML"?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName==="HTML"?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=un));else if(o!==4&&(o===27&&ta(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Bc(e,t,n),e=e.sibling;e!==null;)Bc(e,t,n),e=e.sibling}function Jo(e,t,n){var o=e.tag;if(o===5||o===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(o!==4&&(o===27&&ta(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(Jo(e,t,n),e=e.sibling;e!==null;)Jo(e,t,n),e=e.sibling}function lg(e){var t=e.stateNode,n=e.memoizedProps;try{for(var o=e.type,l=t.attributes;l.length;)t.removeAttributeNode(l[0]);st(t,o,n),t[tt]=e,t[pt]=n}catch(d){je(e,e.return,d)}}var _n=!1,Ve=!1,Lc=!1,cg=typeof WeakSet=="function"?WeakSet:Set,Ze=null;function Zw(e,t){if(e=e.containerInfo,id=ur,e=vh(e),Il(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var o=n.getSelection&&n.getSelection();if(o&&o.rangeCount!==0){n=o.anchorNode;var l=o.anchorOffset,d=o.focusNode;o=o.focusOffset;try{n.nodeType,d.nodeType}catch{n=null;break e}var p=0,y=-1,k=-1,C=0,B=0,H=e,R=null;t:for(;;){for(var D;H!==n||l!==0&&H.nodeType!==3||(y=p+l),H!==d||o!==0&&H.nodeType!==3||(k=p+o),H.nodeType===3&&(p+=H.nodeValue.length),(D=H.firstChild)!==null;)R=H,H=D;for(;;){if(H===e)break t;if(R===n&&++C===l&&(y=p),R===d&&++B===o&&(k=p),(D=H.nextSibling)!==null)break;H=R,R=H.parentNode}H=D}n=y===-1||k===-1?null:{start:y,end:k}}else n=null}n=n||{start:0,end:0}}else n=null;for(sd={focusedElem:e,selectionRange:n},ur=!1,Ze=t;Ze!==null;)if(t=Ze,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,Ze=e;else for(;Ze!==null;){switch(t=Ze,d=t.alternate,e=t.flags,t.tag){case 0:if((e&4)!==0&&(e=t.updateQueue,e=e!==null?e.events:null,e!==null))for(n=0;n<e.length;n++)l=e[n],l.ref.impl=l.nextImpl;break;case 11:case 15:break;case 1:if((e&1024)!==0&&d!==null){e=void 0,n=t,l=d.memoizedProps,d=d.memoizedState,o=n.stateNode;try{var X=Ma(n.type,l);e=o.getSnapshotBeforeUpdate(X,d),o.__reactInternalSnapshotBeforeUpdate=e}catch(re){je(n,n.return,re)}}break;case 3:if((e&1024)!==0){if(e=t.stateNode.containerInfo,n=e.nodeType,n===9)ld(e);else if(n===1)switch(e.nodeName){case"HEAD":case"HTML":case"BODY":ld(e);break;default:e.textContent=""}}break;case 5:case 26:case 27:case 6:case 4:case 17:break;default:if((e&1024)!==0)throw Error(r(163))}if(e=t.sibling,e!==null){e.return=t.return,Ze=e;break}Ze=t.return}}function dg(e,t,n){var o=n.flags;switch(n.tag){case 0:case 11:case 15:xn(e,n),o&4&&ps(5,n);break;case 1:if(xn(e,n),o&4)if(e=n.stateNode,t===null)try{e.componentDidMount()}catch(p){je(n,n.return,p)}else{var l=Ma(n.type,t.memoizedProps);t=t.memoizedState;try{e.componentDidUpdate(l,t,e.__reactInternalSnapshotBeforeUpdate)}catch(p){je(n,n.return,p)}}o&64&&ig(n),o&512&&ms(n,n.return);break;case 3:if(xn(e,n),o&64&&(e=n.updateQueue,e!==null)){if(t=null,n.child!==null)switch(n.child.tag){case 27:case 5:t=n.child.stateNode;break;case 1:t=n.child.stateNode}try{Vh(e,t)}catch(p){je(n,n.return,p)}}break;case 27:t===null&&o&4&&lg(n);case 26:case 5:xn(e,n),t===null&&o&4&&og(n),o&512&&ms(n,n.return);break;case 12:xn(e,n);break;case 31:xn(e,n),o&4&&fg(e,n);break;case 13:xn(e,n),o&4&&gg(e,n),o&64&&(e=n.memoizedState,e!==null&&(e=e.dehydrated,e!==null&&(n=ly.bind(null,n),jy(e,n))));break;case 22:if(o=n.memoizedState!==null||_n,!o){t=t!==null&&t.memoizedState!==null||Ve,l=_n;var d=Ve;_n=o,(Ve=t)&&!d?Tn(e,n,(n.subtreeFlags&8772)!==0):xn(e,n),_n=l,Ve=d}break;case 30:break;default:xn(e,n)}}function ug(e){var t=e.alternate;t!==null&&(e.alternate=null,ug(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&fl(t)),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}var ze=null,bt=!1;function kn(e,t,n){for(n=n.child;n!==null;)hg(e,t,n),n=n.sibling}function hg(e,t,n){if(xt&&typeof xt.onCommitFiberUnmount=="function")try{xt.onCommitFiberUnmount(Ni,n)}catch{}switch(n.tag){case 26:Ve||tn(n,t),kn(e,t,n),n.memoizedState?n.memoizedState.count--:n.stateNode&&(n=n.stateNode,n.parentNode.removeChild(n));break;case 27:Ve||tn(n,t);var o=ze,l=bt;ta(n.type)&&(ze=n.stateNode,bt=!1),kn(e,t,n),Ss(n.stateNode),ze=o,bt=l;break;case 5:Ve||tn(n,t);case 6:if(o=ze,l=bt,ze=null,kn(e,t,n),ze=o,bt=l,ze!==null)if(bt)try{(ze.nodeType===9?ze.body:ze.nodeName==="HTML"?ze.ownerDocument.body:ze).removeChild(n.stateNode)}catch(d){je(n,t,d)}else try{ze.removeChild(n.stateNode)}catch(d){je(n,t,d)}break;case 18:ze!==null&&(bt?(e=ze,ip(e.nodeType===9?e.body:e.nodeName==="HTML"?e.ownerDocument.body:e,n.stateNode),Ii(e)):ip(ze,n.stateNode));break;case 4:o=ze,l=bt,ze=n.stateNode.containerInfo,bt=!0,kn(e,t,n),ze=o,bt=l;break;case 0:case 11:case 14:case 15:Wn(2,n,t),Ve||Wn(4,n,t),kn(e,t,n);break;case 1:Ve||(tn(n,t),o=n.stateNode,typeof o.componentWillUnmount=="function"&&sg(n,t,o)),kn(e,t,n);break;case 21:kn(e,t,n);break;case 22:Ve=(o=Ve)||n.memoizedState!==null,kn(e,t,n),Ve=o;break;default:kn(e,t,n)}}function fg(e,t){if(t.memoizedState===null&&(e=t.alternate,e!==null&&(e=e.memoizedState,e!==null))){e=e.dehydrated;try{Ii(e)}catch(n){je(t,t.return,n)}}}function gg(e,t){if(t.memoizedState===null&&(e=t.alternate,e!==null&&(e=e.memoizedState,e!==null&&(e=e.dehydrated,e!==null))))try{Ii(e)}catch(n){je(t,t.return,n)}}function ey(e){switch(e.tag){case 31:case 13:case 19:var t=e.stateNode;return t===null&&(t=e.stateNode=new cg),t;case 22:return e=e.stateNode,t=e._retryCache,t===null&&(t=e._retryCache=new cg),t;default:throw Error(r(435,e.tag))}}function Go(e,t){var n=ey(e);t.forEach(function(o){if(!n.has(o)){n.add(o);var l=cy.bind(null,e,o);o.then(l,l)}})}function wt(e,t){var n=t.deletions;if(n!==null)for(var o=0;o<n.length;o++){var l=n[o],d=e,p=t,y=p;e:for(;y!==null;){switch(y.tag){case 27:if(ta(y.type)){ze=y.stateNode,bt=!1;break e}break;case 5:ze=y.stateNode,bt=!1;break e;case 3:case 4:ze=y.stateNode.containerInfo,bt=!0;break e}y=y.return}if(ze===null)throw Error(r(160));hg(d,p,l),ze=null,bt=!1,d=l.alternate,d!==null&&(d.return=null),l.return=null}if(t.subtreeFlags&13886)for(t=t.child;t!==null;)pg(t,e),t=t.sibling}var Gt=null;function pg(e,t){var n=e.alternate,o=e.flags;switch(e.tag){case 0:case 11:case 14:case 15:wt(t,e),yt(e),o&4&&(Wn(3,e,e.return),ps(3,e),Wn(5,e,e.return));break;case 1:wt(t,e),yt(e),o&512&&(Ve||n===null||tn(n,n.return)),o&64&&_n&&(e=e.updateQueue,e!==null&&(o=e.callbacks,o!==null&&(n=e.shared.hiddenCallbacks,e.shared.hiddenCallbacks=n===null?o:n.concat(o))));break;case 26:var l=Gt;if(wt(t,e),yt(e),o&512&&(Ve||n===null||tn(n,n.return)),o&4){var d=n!==null?n.memoizedState:null;if(o=e.memoizedState,n===null)if(o===null)if(e.stateNode===null){e:{o=e.type,n=e.memoizedProps,l=l.ownerDocument||l;t:switch(o){case"title":d=l.getElementsByTagName("title")[0],(!d||d[Fi]||d[tt]||d.namespaceURI==="http://www.w3.org/2000/svg"||d.hasAttribute("itemprop"))&&(d=l.createElement(o),l.head.insertBefore(d,l.querySelector("head > title"))),st(d,o,n),d[tt]=e,Ke(d),o=d;break e;case"link":var p=pp("link","href",l).get(o+(n.href||""));if(p){for(var y=0;y<p.length;y++)if(d=p[y],d.getAttribute("href")===(n.href==null||n.href===""?null:n.href)&&d.getAttribute("rel")===(n.rel==null?null:n.rel)&&d.getAttribute("title")===(n.title==null?null:n.title)&&d.getAttribute("crossorigin")===(n.crossOrigin==null?null:n.crossOrigin)){p.splice(y,1);break t}}d=l.createElement(o),st(d,o,n),l.head.appendChild(d);break;case"meta":if(p=pp("meta","content",l).get(o+(n.content||""))){for(y=0;y<p.length;y++)if(d=p[y],d.getAttribute("content")===(n.content==null?null:""+n.content)&&d.getAttribute("name")===(n.name==null?null:n.name)&&d.getAttribute("property")===(n.property==null?null:n.property)&&d.getAttribute("http-equiv")===(n.httpEquiv==null?null:n.httpEquiv)&&d.getAttribute("charset")===(n.charSet==null?null:n.charSet)){p.splice(y,1);break t}}d=l.createElement(o),st(d,o,n),l.head.appendChild(d);break;default:throw Error(r(468,o))}d[tt]=e,Ke(d),o=d}e.stateNode=o}else mp(l,e.type,e.stateNode);else e.stateNode=gp(l,o,e.memoizedProps);else d!==o?(d===null?n.stateNode!==null&&(n=n.stateNode,n.parentNode.removeChild(n)):d.count--,o===null?mp(l,e.type,e.stateNode):gp(l,o,e.memoizedProps)):o===null&&e.stateNode!==null&&Uc(e,e.memoizedProps,n.memoizedProps)}break;case 27:wt(t,e),yt(e),o&512&&(Ve||n===null||tn(n,n.return)),n!==null&&o&4&&Uc(e,e.memoizedProps,n.memoizedProps);break;case 5:if(wt(t,e),yt(e),o&512&&(Ve||n===null||tn(n,n.return)),e.flags&32){l=e.stateNode;try{Za(l,"")}catch(X){je(e,e.return,X)}}o&4&&e.stateNode!=null&&(l=e.memoizedProps,Uc(e,l,n!==null?n.memoizedProps:l)),o&1024&&(Lc=!0);break;case 6:if(wt(t,e),yt(e),o&4){if(e.stateNode===null)throw Error(r(162));o=e.memoizedProps,n=e.stateNode;try{n.nodeValue=o}catch(X){je(e,e.return,X)}}break;case 3:if(rr=null,l=Gt,Gt=sr(t.containerInfo),wt(t,e),Gt=l,yt(e),o&4&&n!==null&&n.memoizedState.isDehydrated)try{Ii(t.containerInfo)}catch(X){je(e,e.return,X)}Lc&&(Lc=!1,mg(e));break;case 4:o=Gt,Gt=sr(e.stateNode.containerInfo),wt(t,e),yt(e),Gt=o;break;case 12:wt(t,e),yt(e);break;case 31:wt(t,e),yt(e),o&4&&(o=e.updateQueue,o!==null&&(e.updateQueue=null,Go(e,o)));break;case 13:wt(t,e),yt(e),e.child.flags&8192&&e.memoizedState!==null!=(n!==null&&n.memoizedState!==null)&&(Vo=kt()),o&4&&(o=e.updateQueue,o!==null&&(e.updateQueue=null,Go(e,o)));break;case 22:l=e.memoizedState!==null;var k=n!==null&&n.memoizedState!==null,C=_n,B=Ve;if(_n=C||l,Ve=B||k,wt(t,e),Ve=B,_n=C,yt(e),o&8192)e:for(t=e.stateNode,t._visibility=l?t._visibility&-2:t._visibility|1,l&&(n===null||k||_n||Ve||Ua(e)),n=null,t=e;;){if(t.tag===5||t.tag===26){if(n===null){k=n=t;try{if(d=k.stateNode,l)p=d.style,typeof p.setProperty=="function"?p.setProperty("display","none","important"):p.display="none";else{y=k.stateNode;var H=k.memoizedProps.style,R=H!=null&&H.hasOwnProperty("display")?H.display:null;y.style.display=R==null||typeof R=="boolean"?"":(""+R).trim()}}catch(X){je(k,k.return,X)}}}else if(t.tag===6){if(n===null){k=t;try{k.stateNode.nodeValue=l?"":k.memoizedProps}catch(X){je(k,k.return,X)}}}else if(t.tag===18){if(n===null){k=t;try{var D=k.stateNode;l?sp(D,!0):sp(k.stateNode,!1)}catch(X){je(k,k.return,X)}}}else if((t.tag!==22&&t.tag!==23||t.memoizedState===null||t===e)&&t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break e;for(;t.sibling===null;){if(t.return===null||t.return===e)break e;n===t&&(n=null),t=t.return}n===t&&(n=null),t.sibling.return=t.return,t=t.sibling}o&4&&(o=e.updateQueue,o!==null&&(n=o.retryQueue,n!==null&&(o.retryQueue=null,Go(e,n))));break;case 19:wt(t,e),yt(e),o&4&&(o=e.updateQueue,o!==null&&(e.updateQueue=null,Go(e,o)));break;case 30:break;case 21:break;default:wt(t,e),yt(e)}}function yt(e){var t=e.flags;if(t&2){try{for(var n,o=e.return;o!==null;){if(rg(o)){n=o;break}o=o.return}if(n==null)throw Error(r(160));switch(n.tag){case 27:var l=n.stateNode,d=Dc(e);Jo(e,d,l);break;case 5:var p=n.stateNode;n.flags&32&&(Za(p,""),n.flags&=-33);var y=Dc(e);Jo(e,y,p);break;case 3:case 4:var k=n.stateNode.containerInfo,C=Dc(e);Bc(e,C,k);break;default:throw Error(r(161))}}catch(B){je(e,e.return,B)}e.flags&=-3}t&4096&&(e.flags&=-4097)}function mg(e){if(e.subtreeFlags&1024)for(e=e.child;e!==null;){var t=e;mg(t),t.tag===5&&t.flags&1024&&t.stateNode.reset(),e=e.sibling}}function xn(e,t){if(t.subtreeFlags&8772)for(t=t.child;t!==null;)dg(e,t.alternate,t),t=t.sibling}function Ua(e){for(e=e.child;e!==null;){var t=e;switch(t.tag){case 0:case 11:case 14:case 15:Wn(4,t,t.return),Ua(t);break;case 1:tn(t,t.return);var n=t.stateNode;typeof n.componentWillUnmount=="function"&&sg(t,t.return,n),Ua(t);break;case 27:Ss(t.stateNode);case 26:case 5:tn(t,t.return),Ua(t);break;case 22:t.memoizedState===null&&Ua(t);break;case 30:Ua(t);break;default:Ua(t)}e=e.sibling}}function Tn(e,t,n){for(n=n&&(t.subtreeFlags&8772)!==0,t=t.child;t!==null;){var o=t.alternate,l=e,d=t,p=d.flags;switch(d.tag){case 0:case 11:case 15:Tn(l,d,n),ps(4,d);break;case 1:if(Tn(l,d,n),o=d,l=o.stateNode,typeof l.componentDidMount=="function")try{l.componentDidMount()}catch(C){je(o,o.return,C)}if(o=d,l=o.updateQueue,l!==null){var y=o.stateNode;try{var k=l.shared.hiddenCallbacks;if(k!==null)for(l.shared.hiddenCallbacks=null,l=0;l<k.length;l++)Yh(k[l],y)}catch(C){je(o,o.return,C)}}n&&p&64&&ig(d),ms(d,d.return);break;case 27:lg(d);case 26:case 5:Tn(l,d,n),n&&o===null&&p&4&&og(d),ms(d,d.return);break;case 12:Tn(l,d,n);break;case 31:Tn(l,d,n),n&&p&4&&fg(l,d);break;case 13:Tn(l,d,n),n&&p&4&&gg(l,d);break;case 22:d.memoizedState===null&&Tn(l,d,n),ms(d,d.return);break;case 30:break;default:Tn(l,d,n)}t=t.sibling}}function qc(e,t){var n=null;e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),e=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(e=t.memoizedState.cachePool.pool),e!==n&&(e!=null&&e.refCount++,n!=null&&ns(n))}function zc(e,t){e=null,t.alternate!==null&&(e=t.alternate.memoizedState.cache),t=t.memoizedState.cache,t!==e&&(t.refCount++,e!=null&&ns(e))}function Yt(e,t,n,o){if(t.subtreeFlags&10256)for(t=t.child;t!==null;)bg(e,t,n,o),t=t.sibling}function bg(e,t,n,o){var l=t.flags;switch(t.tag){case 0:case 11:case 15:Yt(e,t,n,o),l&2048&&ps(9,t);break;case 1:Yt(e,t,n,o);break;case 3:Yt(e,t,n,o),l&2048&&(e=null,t.alternate!==null&&(e=t.alternate.memoizedState.cache),t=t.memoizedState.cache,t!==e&&(t.refCount++,e!=null&&ns(e)));break;case 12:if(l&2048){Yt(e,t,n,o),e=t.stateNode;try{var d=t.memoizedProps,p=d.id,y=d.onPostCommit;typeof y=="function"&&y(p,t.alternate===null?"mount":"update",e.passiveEffectDuration,-0)}catch(k){je(t,t.return,k)}}else Yt(e,t,n,o);break;case 31:Yt(e,t,n,o);break;case 13:Yt(e,t,n,o);break;case 23:break;case 22:d=t.stateNode,p=t.alternate,t.memoizedState!==null?d._visibility&2?Yt(e,t,n,o):bs(e,t):d._visibility&2?Yt(e,t,n,o):(d._visibility|=2,yi(e,t,n,o,(t.subtreeFlags&10256)!==0||!1)),l&2048&&qc(p,t);break;case 24:Yt(e,t,n,o),l&2048&&zc(t.alternate,t);break;default:Yt(e,t,n,o)}}function yi(e,t,n,o,l){for(l=l&&((t.subtreeFlags&10256)!==0||!1),t=t.child;t!==null;){var d=e,p=t,y=n,k=o,C=p.flags;switch(p.tag){case 0:case 11:case 15:yi(d,p,y,k,l),ps(8,p);break;case 23:break;case 22:var B=p.stateNode;p.memoizedState!==null?B._visibility&2?yi(d,p,y,k,l):bs(d,p):(B._visibility|=2,yi(d,p,y,k,l)),l&&C&2048&&qc(p.alternate,p);break;case 24:yi(d,p,y,k,l),l&&C&2048&&zc(p.alternate,p);break;default:yi(d,p,y,k,l)}t=t.sibling}}function bs(e,t){if(t.subtreeFlags&10256)for(t=t.child;t!==null;){var n=e,o=t,l=o.flags;switch(o.tag){case 22:bs(n,o),l&2048&&qc(o.alternate,o);break;case 24:bs(n,o),l&2048&&zc(o.alternate,o);break;default:bs(n,o)}t=t.sibling}}var ws=8192;function vi(e,t,n){if(e.subtreeFlags&ws)for(e=e.child;e!==null;)wg(e,t,n),e=e.sibling}function wg(e,t,n){switch(e.tag){case 26:vi(e,t,n),e.flags&ws&&e.memoizedState!==null&&Ny(n,Gt,e.memoizedState,e.memoizedProps);break;case 5:vi(e,t,n);break;case 3:case 4:var o=Gt;Gt=sr(e.stateNode.containerInfo),vi(e,t,n),Gt=o;break;case 22:e.memoizedState===null&&(o=e.alternate,o!==null&&o.memoizedState!==null?(o=ws,ws=16777216,vi(e,t,n),ws=o):vi(e,t,n));break;default:vi(e,t,n)}}function yg(e){var t=e.alternate;if(t!==null&&(e=t.child,e!==null)){t.child=null;do t=e.sibling,e.sibling=null,e=t;while(e!==null)}}function ys(e){var t=e.deletions;if((e.flags&16)!==0){if(t!==null)for(var n=0;n<t.length;n++){var o=t[n];Ze=o,_g(o,e)}yg(e)}if(e.subtreeFlags&10256)for(e=e.child;e!==null;)vg(e),e=e.sibling}function vg(e){switch(e.tag){case 0:case 11:case 15:ys(e),e.flags&2048&&Wn(9,e,e.return);break;case 3:ys(e);break;case 12:ys(e);break;case 22:var t=e.stateNode;e.memoizedState!==null&&t._visibility&2&&(e.return===null||e.return.tag!==13)?(t._visibility&=-3,Yo(e)):ys(e);break;default:ys(e)}}function Yo(e){var t=e.deletions;if((e.flags&16)!==0){if(t!==null)for(var n=0;n<t.length;n++){var o=t[n];Ze=o,_g(o,e)}yg(e)}for(e=e.child;e!==null;){switch(t=e,t.tag){case 0:case 11:case 15:Wn(8,t,t.return),Yo(t);break;case 22:n=t.stateNode,n._visibility&2&&(n._visibility&=-3,Yo(t));break;default:Yo(t)}e=e.sibling}}function _g(e,t){for(;Ze!==null;){var n=Ze;switch(n.tag){case 0:case 11:case 15:Wn(8,n,t);break;case 23:case 22:if(n.memoizedState!==null&&n.memoizedState.cachePool!==null){var o=n.memoizedState.cachePool.pool;o!=null&&o.refCount++}break;case 24:ns(n.memoizedState.cache)}if(o=n.child,o!==null)o.return=n,Ze=o;else e:for(n=e;Ze!==null;){o=Ze;var l=o.sibling,d=o.return;if(ug(o),o===n){Ze=null;break e}if(l!==null){l.return=d,Ze=l;break e}Ze=d}}}var ty={getCacheForType:function(e){var t=at(Je),n=t.data.get(e);return n===void 0&&(n=e(),t.data.set(e,n)),n},cacheSignal:function(){return at(Je).controller.signal}},ny=typeof WeakMap=="function"?WeakMap:Map,Ae=0,Ue=null,pe=null,we=0,Ee=0,Ct=null,Qn=!1,_i=!1,Nc=!1,Sn=0,He=0,$n=0,Da=0,Hc=0,It=0,ki=0,vs=null,vt=null,Pc=!1,Vo=0,kg=0,Wo=1/0,Qo=null,Xn=null,$e=0,Kn=null,xi=null,An=0,Fc=0,Jc=null,xg=null,_s=0,Gc=null;function Rt(){return(Ae&2)!==0&&we!==0?we&-we:O.T!==null?Xc():qu()}function Tg(){if(It===0)if((we&536870912)===0||ve){var e=no;no<<=1,(no&3932160)===0&&(no=262144),It=e}else It=536870912;return e=Et.current,e!==null&&(e.flags|=32),It}function _t(e,t,n){(e===Ue&&(Ee===2||Ee===9)||e.cancelPendingCommit!==null)&&(Ti(e,0),Zn(e,we,It,!1)),Pi(e,n),((Ae&2)===0||e!==Ue)&&(e===Ue&&((Ae&2)===0&&(Da|=n),He===4&&Zn(e,we,It,!1)),nn(e))}function Sg(e,t,n){if((Ae&6)!==0)throw Error(r(327));var o=!n&&(t&127)===0&&(t&e.expiredLanes)===0||Hi(e,t),l=o?sy(e,t):Vc(e,t,!0),d=o;do{if(l===0){_i&&!o&&Zn(e,t,0,!1);break}else{if(n=e.current.alternate,d&&!ay(n)){l=Vc(e,t,!1),d=!1;continue}if(l===2){if(d=t,e.errorRecoveryDisabledLanes&d)var p=0;else p=e.pendingLanes&-536870913,p=p!==0?p:p&536870912?536870912:0;if(p!==0){t=p;e:{var y=e;l=vs;var k=y.current.memoizedState.isDehydrated;if(k&&(Ti(y,p).flags|=256),p=Vc(y,p,!1),p!==2){if(Nc&&!k){y.errorRecoveryDisabledLanes|=d,Da|=d,l=4;break e}d=vt,vt=l,d!==null&&(vt===null?vt=d:vt.push.apply(vt,d))}l=p}if(d=!1,l!==2)continue}}if(l===1){Ti(e,0),Zn(e,t,0,!0);break}e:{switch(o=e,d=l,d){case 0:case 1:throw Error(r(345));case 4:if((t&4194048)!==t)break;case 6:Zn(o,t,It,!Qn);break e;case 2:vt=null;break;case 3:case 5:break;default:throw Error(r(329))}if((t&62914560)===t&&(l=Vo+300-kt(),10<l)){if(Zn(o,t,It,!Qn),io(o,0,!0)!==0)break e;An=t,o.timeoutHandle=np(Ag.bind(null,o,n,vt,Qo,Pc,t,It,Da,ki,Qn,d,"Throttled",-0,0),l);break e}Ag(o,n,vt,Qo,Pc,t,It,Da,ki,Qn,d,null,-0,0)}}break}while(!0);nn(e)}function Ag(e,t,n,o,l,d,p,y,k,C,B,H,R,D){if(e.timeoutHandle=-1,H=t.subtreeFlags,H&8
vendor: 22,983 bytes, lines 9-10
9192||(H&16785408)===16785408){H={stylesheets:null,count:0,imgCount:0,imgBytes:0,suspenseyImages:[],waitingForImages:!0,waitingForViewTransition:!1,unsuspend:un},wg(t,d,H);var X=(d&62914560)===d?Vo-kt():(d&4194048)===d?kg-kt():0;if(X=Hy(H,X),X!==null){An=d,e.cancelPendingCommit=X(Ug.bind(null,e,t,d,n,o,l,p,y,k,B,H,null,R,D)),Zn(e,d,p,!C);return}}Ug(e,t,d,n,o,l,p,y,k)}function ay(e){for(var t=e;;){var n=t.tag;if((n===0||n===11||n===15)&&t.flags&16384&&(n=t.updateQueue,n!==null&&(n=n.stores,n!==null)))for(var o=0;o<n.length;o++){var l=n[o],d=l.getSnapshot;l=l.value;try{if(!St(d(),l))return!1}catch{return!1}}if(n=t.child,t.subtreeFlags&16384&&n!==null)n.return=t,t=n;else{if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return!0;t=t.return}t.sibling.return=t.return,t=t.sibling}}return!0}function Zn(e,t,n,o){t&=~Hc,t&=~Da,e.suspendedLanes|=t,e.pingedLanes&=~t,o&&(e.warmLanes|=t),o=e.expirationTimes;for(var l=t;0<l;){var d=31-Tt(l),p=1<<d;o[d]=-1,l&=~p}n!==0&&Du(e,n,t)}function $o(){return(Ae&6)===0?(ks(0),!1):!0}function Yc(){if(pe!==null){if(Ee===0)var e=pe.return;else e=pe,pn=Aa=null,lc(e),gi=null,is=0,e=pe;for(;e!==null;)ag(e.alternate,e),e=e.return;pe=null}}function Ti(e,t){var n=e.timeoutHandle;n!==-1&&(e.timeoutHandle=-1,xy(n)),n=e.cancelPendingCommit,n!==null&&(e.cancelPendingCommit=null,n()),An=0,Yc(),Ue=e,pe=n=fn(e.current,null),we=t,Ee=0,Ct=null,Qn=!1,_i=Hi(e,t),Nc=!1,ki=It=Hc=Da=$n=He=0,vt=vs=null,Pc=!1,(t&8)!==0&&(t|=t&32);var o=e.entangledLanes;if(o!==0)for(e=e.entanglements,o&=t;0<o;){var l=31-Tt(o),d=1<<l;t|=e[l],o&=~d}return Sn=t,bo(),n}function Eg(e,t){he=null,O.H=hs,t===fi||t===So?(t=Ph(),Ee=3):t===$l?(t=Ph(),Ee=4):Ee=t===Tc?8:t!==null&&typeof t=="object"&&typeof t.then=="function"?6:1,Ct=t,pe===null&&(He=1,zo(e,Lt(t,e.current)))}function jg(){var e=Et.current;return e===null?!0:(we&4194048)===we?Ht===null:(we&62914560)===we||(we&536870912)!==0?e===Ht:!1}function Cg(){var e=O.H;return O.H=hs,e===null?hs:e}function Ig(){var e=O.A;return O.A=ty,e}function Xo(){He=4,Qn||(we&4194048)!==we&&Et.current!==null||(_i=!0),($n&134217727)===0&&(Da&134217727)===0||Ue===null||Zn(Ue,we,It,!1)}function Vc(e,t,n){var o=Ae;Ae|=2;var l=Cg(),d=Ig();(Ue!==e||we!==t)&&(Qo=null,Ti(e,t)),t=!1;var p=He;e:do try{if(Ee!==0&&pe!==null){var y=pe,k=Ct;switch(Ee){case 8:Yc(),p=6;break e;case 3:case 2:case 9:case 6:Et.current===null&&(t=!0);var C=Ee;if(Ee=0,Ct=null,Si(e,y,k,C),n&&_i){p=0;break e}break;default:C=Ee,Ee=0,Ct=null,Si(e,y,k,C)}}iy(),p=He;break}catch(B){Eg(e,B)}while(!0);return t&&e.shellSuspendCounter++,pn=Aa=null,Ae=o,O.H=l,O.A=d,pe===null&&(Ue=null,we=0,bo()),p}function iy(){for(;pe!==null;)Rg(pe)}function sy(e,t){var n=Ae;Ae|=2;var o=Cg(),l=Ig();Ue!==e||we!==t?(Qo=null,Wo=kt()+500,Ti(e,t)):_i=Hi(e,t);e:do try{if(Ee!==0&&pe!==null){t=pe;var d=Ct;t:switch(Ee){case 1:Ee=0,Ct=null,Si(e,t,d,1);break;case 2:case 9:if(Nh(d)){Ee=0,Ct=null,Og(t);break}t=function(){Ee!==2&&Ee!==9||Ue!==e||(Ee=7),nn(e)},d.then(t,t);break e;case 3:Ee=7;break e;case 4:Ee=5;break e;case 7:Nh(d)?(Ee=0,Ct=null,Og(t)):(Ee=0,Ct=null,Si(e,t,d,7));break;case 5:var p=null;switch(pe.tag){case 26:p=pe.memoizedState;case 5:case 27:var y=pe;if(p?bp(p):y.stateNode.complete){Ee=0,Ct=null;var k=y.sibling;if(k!==null)pe=k;else{var C=y.return;C!==null?(pe=C,Ko(C)):pe=null}break t}}Ee=0,Ct=null,Si(e,t,d,5);break;case 6:Ee=0,Ct=null,Si(e,t,d,6);break;case 8:Yc(),He=6;break e;default:throw Error(r(462))}}oy();break}catch(B){Eg(e,B)}while(!0);return pn=Aa=null,O.H=o,O.A=l,Ae=n,pe!==null?0:(Ue=null,we=0,bo(),He)}function oy(){for(;pe!==null&&!C0();)Rg(pe)}function Rg(e){var t=tg(e.alternate,e,Sn);e.memoizedProps=e.pendingProps,t===null?Ko(e):pe=t}function Og(e){var t=e,n=t.alternate;switch(t.tag){case 15:case 0:t=Qf(n,t,t.pendingProps,t.type,void 0,we);break;case 11:t=Qf(n,t,t.pendingProps,t.type.render,t.ref,we);break;case 5:lc(t);default:ag(n,t),t=pe=Ch(t,Sn),t=tg(n,t,Sn)}e.memoizedProps=e.pendingProps,t===null?Ko(e):pe=t}function Si(e,t,n,o){pn=Aa=null,lc(t),gi=null,is=0;var l=t.return;try{if(Ww(e,l,t,n,we)){He=1,zo(e,Lt(n,e.current)),pe=null;return}}catch(d){if(l!==null)throw pe=l,d;He=1,zo(e,Lt(n,e.current)),pe=null;return}t.flags&32768?(ve||o===1?e=!0:_i||(we&536870912)!==0?e=!1:(Qn=e=!0,(o===2||o===9||o===3||o===6)&&(o=Et.current,o!==null&&o.tag===13&&(o.flags|=16384))),Mg(t,e)):Ko(t)}function Ko(e){var t=e;do{if((t.flags&32768)!==0){Mg(t,Qn);return}e=t.return;var n=Xw(t.alternate,t,Sn);if(n!==null){pe=n;return}if(t=t.sibling,t!==null){pe=t;return}pe=t=e}while(t!==null);He===0&&(He=5)}function Mg(e,t){do{var n=Kw(e.alternate,e);if(n!==null){n.flags&=32767,pe=n;return}if(n=e.return,n!==null&&(n.flags|=32768,n.subtreeFlags=0,n.deletions=null),!t&&(e=e.sibling,e!==null)){pe=e;return}pe=e=n}while(e!==null);He=6,pe=null}function Ug(e,t,n,o,l,d,p,y,k){e.cancelPendingCommit=null;do Zo();while($e!==0);if((Ae&6)!==0)throw Error(r(327));if(t!==null){if(t===e.current)throw Error(r(177));if(d=t.lanes|t.childLanes,d|=Dl,z0(e,n,d,p,y,k),e===Ue&&(pe=Ue=null,we=0),xi=t,Kn=e,An=n,Fc=d,Jc=l,xg=o,(t.subtreeFlags&10256)!==0||(t.flags&10256)!==0?(e.callbackNode=null,e.callbackPriority=0,dy(eo,function(){return zg(),null})):(e.callbackNode=null,e.callbackPriority=0),o=(t.flags&13878)!==0,(t.subtreeFlags&13878)!==0||o){o=O.T,O.T=null,l=W.p,W.p=2,p=Ae,Ae|=4;try{Zw(e,t,n)}finally{Ae=p,W.p=l,O.T=o}}$e=1,Dg(),Bg(),Lg()}}function Dg(){if($e===1){$e=0;var e=Kn,t=xi,n=(t.flags&13878)!==0;if((t.subtreeFlags&13878)!==0||n){n=O.T,O.T=null;var o=W.p;W.p=2;var l=Ae;Ae|=4;try{pg(t,e);var d=sd,p=vh(e.containerInfo),y=d.focusedElem,k=d.selectionRange;if(p!==y&&y&&y.ownerDocument&&yh(y.ownerDocument.documentElement,y)){if(k!==null&&Il(y)){var C=k.start,B=k.end;if(B===void 0&&(B=C),"selectionStart"in y)y.selectionStart=C,y.selectionEnd=Math.min(B,y.value.length);else{var H=y.ownerDocument||document,R=H&&H.defaultView||window;if(R.getSelection){var D=R.getSelection(),X=y.textContent.length,re=Math.min(k.start,X),Re=k.end===void 0?re:Math.min(k.end,X);!D.extend&&re>Re&&(p=Re,Re=re,re=p);var E=wh(y,re),S=wh(y,Re);if(E&&S&&(D.rangeCount!==1||D.anchorNode!==E.node||D.anchorOffset!==E.offset||D.focusNode!==S.node||D.focusOffset!==S.offset)){var j=H.createRange();j.setStart(E.node,E.offset),D.removeAllRanges(),re>Re?(D.addRange(j),D.extend(S.node,S.offset)):(j.setEnd(S.node,S.offset),D.addRange(j))}}}}for(H=[],D=y;D=D.parentNode;)D.nodeType===1&&H.push({element:D,left:D.scrollLeft,top:D.scrollTop});for(typeof y.focus=="function"&&y.focus(),y=0;y<H.length;y++){var q=H[y];q.element.scrollLeft=q.left,q.element.scrollTop=q.top}}ur=!!id,sd=id=null}finally{Ae=l,W.p=o,O.T=n}}e.current=t,$e=2}}function Bg(){if($e===2){$e=0;var e=Kn,t=xi,n=(t.flags&8772)!==0;if((t.subtreeFlags&8772)!==0||n){n=O.T,O.T=null;var o=W.p;W.p=2;var l=Ae;Ae|=4;try{dg(e,t.alternate,t)}finally{Ae=l,W.p=o,O.T=n}}$e=3}}function Lg(){if($e===4||$e===3){$e=0,I0();var e=Kn,t=xi,n=An,o=xg;(t.subtreeFlags&10256)!==0||(t.flags&10256)!==0?$e=5:($e=0,xi=Kn=null,qg(e,e.pendingLanes));var l=e.pendingLanes;if(l===0&&(Xn=null),ul(n),t=t.stateNode,xt&&typeof xt.onCommitFiberRoot=="function")try{xt.onCommitFiberRoot(Ni,t,void 0,(t.current.flags&128)===128)}catch{}if(o!==null){t=O.T,l=W.p,W.p=2,O.T=null;try{for(var d=e.onRecoverableError,p=0;p<o.length;p++){var y=o[p];d(y.value,{componentStack:y.stack})}}finally{O.T=t,W.p=l}}(An&3)!==0&&Zo(),nn(e),l=e.pendingLanes,(n&261930)!==0&&(l&42)!==0?e===Gc?_s++:(_s=0,Gc=e):_s=0,ks(0)}}function qg(e,t){(e.pooledCacheLanes&=t)===0&&(t=e.pooledCache,t!=null&&(e.pooledCache=null,ns(t)))}function Zo(){return Dg(),Bg(),Lg(),zg()}function zg(){if($e!==5)return!1;var e=Kn,t=Fc;Fc=0;var n=ul(An),o=O.T,l=W.p;try{W.p=32>n?32:n,O.T=null,n=Jc,Jc=null;var d=Kn,p=An;if($e=0,xi=Kn=null,An=0,(Ae&6)!==0)throw Error(r(331));var y=Ae;if(Ae|=4,vg(d.current),bg(d,d.current,p,n),Ae=y,ks(0,!1),xt&&typeof xt.onPostCommitFiberRoot=="function")try{xt.onPostCommitFiberRoot(Ni,d)}catch{}return!0}finally{W.p=l,O.T=o,qg(e,t)}}function Ng(e,t,n){t=Lt(n,t),t=xc(e.stateNode,t,2),e=Gn(e,t,2),e!==null&&(Pi(e,2),nn(e))}function je(e,t,n){if(e.tag===3)Ng(e,e,n);else for(;t!==null;){if(t.tag===3){Ng(t,e,n);break}else if(t.tag===1){var o=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof o.componentDidCatch=="function"&&(Xn===null||!Xn.has(o))){e=Lt(n,e),n=Hf(2),o=Gn(t,n,2),o!==null&&(Pf(n,o,t,e),Pi(o,2),nn(o));break}}t=t.return}}function Wc(e,t,n){var o=e.pingCache;if(o===null){o=e.pingCache=new ny;var l=new Set;o.set(t,l)}else l=o.get(t),l===void 0&&(l=new Set,o.set(t,l));l.has(n)||(Nc=!0,l.add(n),e=ry.bind(null,e,t,n),t.then(e,e))}function ry(e,t,n){var o=e.pingCache;o!==null&&o.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,Ue===e&&(we&n)===n&&(He===4||He===3&&(we&62914560)===we&&300>kt()-Vo?(Ae&2)===0&&Ti(e,0):Hc|=n,ki===we&&(ki=0)),nn(e)}function Hg(e,t){t===0&&(t=Uu()),e=xa(e,t),e!==null&&(Pi(e,t),nn(e))}function ly(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Hg(e,n)}function cy(e,t){var n=0;switch(e.tag){case 31:case 13:var o=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:o=e.stateNode;break;case 22:o=e.stateNode._retryCache;break;default:throw Error(r(314))}o!==null&&o.delete(t),Hg(e,n)}function dy(e,t){return rl(e,t)}var er=null,Ai=null,Qc=!1,tr=!1,$c=!1,ea=0;function nn(e){e!==Ai&&e.next===null&&(Ai===null?er=Ai=e:Ai=Ai.next=e),tr=!0,Qc||(Qc=!0,hy())}function ks(e,t){if(!$c&&tr){$c=!0;do for(var n=!1,o=er;o!==null;){if(e!==0){var l=o.pendingLanes;if(l===0)var d=0;else{var p=o.suspendedLanes,y=o.pingedLanes;d=(1<<31-Tt(42|e)+1)-1,d&=l&~(p&~y),d=d&201326741?d&201326741|1:d?d|2:0}d!==0&&(n=!0,Gg(o,d))}else d=we,d=io(o,o===Ue?d:0,o.cancelPendingCommit!==null||o.timeoutHandle!==-1),(d&3)===0||Hi(o,d)||(n=!0,Gg(o,d));o=o.next}while(n);$c=!1}}function uy(){Pg()}function Pg(){tr=Qc=!1;var e=0;ea!==0&&ky()&&(e=ea);for(var t=kt(),n=null,o=er;o!==null;){var l=o.next,d=Fg(o,t);d===0?(o.next=null,n===null?er=l:n.next=l,l===null&&(Ai=n)):(n=o,(e!==0||(d&3)!==0)&&(tr=!0)),o=l}$e!==0&&$e!==5||ks(e),ea!==0&&(ea=0)}function Fg(e,t){for(var n=e.suspendedLanes,o=e.pingedLanes,l=e.expirationTimes,d=e.pendingLanes&-62914561;0<d;){var p=31-Tt(d),y=1<<p,k=l[p];k===-1?((y&n)===0||(y&o)!==0)&&(l[p]=q0(y,t)):k<=t&&(e.expiredLanes|=y),d&=~y}if(t=Ue,n=we,n=io(e,e===t?n:0,e.cancelPendingCommit!==null||e.timeoutHandle!==-1),o=e.callbackNode,n===0||e===t&&(Ee===2||Ee===9)||e.cancelPendingCommit!==null)return o!==null&&o!==null&&ll(o),e.callbackNode=null,e.callbackPriority=0;if((n&3)===0||Hi(e,n)){if(t=n&-n,t===e.callbackPriority)return t;switch(o!==null&&ll(o),ul(n)){case 2:case 8:n=Ou;break;case 32:n=eo;break;case 268435456:n=Mu;break;default:n=eo}return o=Jg.bind(null,e),n=rl(n,o),e.callbackPriority=t,e.callbackNode=n,t}return o!==null&&o!==null&&ll(o),e.callbackPriority=2,e.callbackNode=null,2}function Jg(e,t){if($e!==0&&$e!==5)return e.callbackNode=null,e.callbackPriority=0,null;var n=e.callbackNode;if(Zo()&&e.callbackNode!==n)return null;var o=we;return o=io(e,e===Ue?o:0,e.cancelPendingCommit!==null||e.timeoutHandle!==-1),o===0?null:(Sg(e,o,t),Fg(e,kt()),e.callbackNode!=null&&e.callbackNode===n?Jg.bind(null,e):null)}function Gg(e,t){if(Zo())return null;Sg(e,t,!0)}function hy(){Ty(function(){(Ae&6)!==0?rl(Ru,uy):Pg()})}function Xc(){if(ea===0){var e=ui;e===0&&(e=to,to<<=1,(to&261888)===0&&(to=256)),ea=e}return ea}function Yg(e){return e==null||typeof e=="symbol"||typeof e=="boolean"?null:typeof e=="function"?e:lo(""+e)}function Vg(e,t){var n=t.ownerDocument.createElement("input");return n.name=t.name,n.value=t.value,e.id&&n.setAttribute("form",e.id),t.parentNode.insertBefore(n,t),e=new FormData(e),n.parentNode.removeChild(n),e}function fy(e,t,n,o,l){if(t==="submit"&&n&&n.stateNode===l){var d=Yg((l[pt]||null).action),p=o.submitter;p&&(t=(t=p[pt]||null)?Yg(t.formAction):p.getAttribute("formAction"),t!==null&&(d=t,p=null));var y=new fo("action","action",null,o,l);e.push({event:y,listeners:[{instance:null,listener:function(){if(o.defaultPrevented){if(ea!==0){var k=p?Vg(l,p):new FormData(l);bc(n,{pending:!0,data:k,method:l.method,action:d},null,k)}}else typeof d=="function"&&(y.preventDefault(),k=p?Vg(l,p):new FormData(l),bc(n,{pending:!0,data:k,method:l.method,action:d},d,k))},currentTarget:l}]})}}for(var Kc=0;Kc<Ul.length;Kc++){var Zc=Ul[Kc],gy=Zc.toLowerCase(),py=Zc[0].toUpperCase()+Zc.slice(1);Jt(gy,"on"+py)}Jt(xh,"onAnimationEnd"),Jt(Th,"onAnimationIteration"),Jt(Sh,"onAnimationStart"),Jt("dblclick","onDoubleClick"),Jt("focusin","onFocus"),Jt("focusout","onBlur"),Jt(Rw,"onTransitionRun"),Jt(Ow,"onTransitionStart"),Jt(Mw,"onTransitionCancel"),Jt(Ah,"onTransitionEnd"),Xa("onMouseEnter",["mouseout","mouseover"]),Xa("onMouseLeave",["mouseout","mouseover"]),Xa("onPointerEnter",["pointerout","pointerover"]),Xa("onPointerLeave",["pointerout","pointerover"]),ya("onChange","change click focusin focusout input keydown keyup selectionchange".split(" ")),ya("onSelect","focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" ")),ya("onBeforeInput",["compositionend","keypress","textInput","paste"]),ya("onCompositionEnd","compositionend focusout keydown keypress keyup mousedown".split(" ")),ya("onCompositionStart","compositionstart focusout keydown keypress keyup mousedown".split(" ")),ya("onCompositionUpdate","compositionupdate focusout keydown keypress keyup mousedown".split(" "));var xs="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange resize seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),my=new Set("beforetoggle cancel close invalid load scroll scrollend toggle".split(" ").concat(xs));function Wg(e,t){t=(t&4)!==0;for(var n=0;n<e.length;n++){var o=e[n],l=o.event;o=o.listeners;e:{var d=void 0;if(t)for(var p=o.length-1;0<=p;p--){var y=o[p],k=y.instance,C=y.currentTarget;if(y=y.listener,k!==d&&l.isPropagationStopped())break e;d=y,l.currentTarget=C;try{d(l)}catch(B){mo(B)}l.currentTarget=null,d=k}else for(p=0;p<o.length;p++){if(y=o[p],k=y.instance,C=y.currentTarget,y=y.listener,k!==d&&l.isPropagationStopped())break e;d=y,l.currentTarget=C;try{d(l)}catch(B){mo(B)}l.currentTarget=null,d=k}}}}function me(e,t){var n=t[hl];n===void 0&&(n=t[hl]=new Set);var o=e+"__bubble";n.has(o)||(Qg(t,e,2,!1),n.add(o))}function ed(e,t,n){var o=0;t&&(o|=4),Qg(n,e,o,t)}var nr="_reactListening"+Math.random().toString(36).slice(2);function td(e){if(!e[nr]){e[nr]=!0,Hu.forEach(function(n){n!=="selectionchange"&&(my.has(n)||ed(n,!1,e),ed(n,!0,e))});var t=e.nodeType===9?e:e.ownerDocument;t===null||t[nr]||(t[nr]=!0,ed("selectionchange",!1,t))}}function Qg(e,t,n,o){switch(Tp(t)){case 2:var l=Jy;break;case 8:l=Gy;break;default:l=md}n=l.bind(null,t,n,e),l=void 0,!_l||t!=="touchstart"&&t!=="touchmove"&&t!=="wheel"||(l=!0),o?l!==void 0?e.addEventListener(t,n,{capture:!0,passive:l}):e.addEventListener(t,n,!0):l!==void 0?e.addEventListener(t,n,{passive:l}):e.addEventListener(t,n,!1)}function nd(e,t,n,o,l){var d=o;if((t&1)===0&&(t&2)===0&&o!==null)e:for(;;){if(o===null)return;var p=o.tag;if(p===3||p===4){var y=o.stateNode.containerInfo;if(y===l)break;if(p===4)for(p=o.return;p!==null;){var k=p.tag;if((k===3||k===4)&&p.stateNode.containerInfo===l)return;p=p.return}for(;y!==null;){if(p=Wa(y),p===null)return;if(k=p.tag,k===5||k===6||k===26||k===27){o=d=p;continue e}y=y.parentNode}}o=o.return}Zu(function(){var C=d,B=yl(n),H=[];e:{var R=Eh.get(e);if(R!==void 0){var D=fo,X=e;switch(e){case"keypress":if(uo(n)===0)break e;case"keydown":case"keyup":D=cw;break;case"focusin":X="focus",D=Sl;break;case"focusout":X="blur",D=Sl;break;case"beforeblur":case"afterblur":D=Sl;break;case"click":if(n.button===2)break e;case"auxclick":case"dblclick":case"mousedown":case"mousemove":case"mouseup":case"mouseout":case"mouseover":case"contextmenu":D=nh;break;case"drag":case"dragend":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"dragstart":case"drop":D=X0;break;case"touchcancel":case"touchend":case"touchmove":case"touchstart":D=hw;break;case xh:case Th:case Sh:D=ew;break;case Ah:D=gw;break;case"scroll":case"scrollend":D=Q0;break;case"wheel":D=mw;break;case"copy":case"cut":case"paste":D=nw;break;case"gotpointercapture":case"lostpointercapture":case"pointercancel":case"pointerdown":case"pointermove":case"pointerout":case"pointerover":case"pointerup":D=ih;break;case"toggle":case"beforetoggle":D=ww}var re=(t&4)!==0,Re=!re&&(e==="scroll"||e==="scrollend"),E=re?R!==null?R+"Capture":null:R;re=[];for(var S=C,j;S!==null;){var q=S;if(j=q.stateNode,q=q.tag,q!==5&&q!==26&&q!==27||j===null||E===null||(q=Gi(S,E),q!=null&&re.push(Ts(S,q,j))),Re)break;S=S.return}0<re.length&&(R=new D(R,X,null,n,B),H.push({event:R,listeners:re}))}}if((t&7)===0){e:{if(R=e==="mouseover"||e==="pointerover",D=e==="mouseout"||e==="pointerout",R&&n!==wl&&(X=n.relatedTarget||n.fromElement)&&(Wa(X)||X[Va]))break e;if((D||R)&&(R=B.window===B?B:(R=B.ownerDocument)?R.defaultView||R.parentWindow:window,D?(X=n.relatedTarget||n.toElement,D=C,X=X?Wa(X):null,X!==null&&(Re=u(X),re=X.tag,X!==Re||re!==5&&re!==27&&re!==6)&&(X=null)):(D=null,X=C),D!==X)){if(re=nh,q="onMouseLeave",E="onMouseEnter",S="mouse",(e==="pointerout"||e==="pointerover")&&(re=ih,q="onPointerLeave",E="onPointerEnter",S="pointer"),Re=D==null?R:Ji(D),j=X==null?R:Ji(X),R=new re(q,S+"leave",D,n,B),R.target=Re,R.relatedTarget=j,q=null,Wa(B)===C&&(re=new re(E,S+"enter",X,n,B),re.target=j,re.relatedTarget=Re,q=re),Re=q,D&&X)t:{for(re=by,E=D,S=X,j=0,q=E;q;q=re(q))j++;q=0;for(var ne=S;ne;ne=re(ne))q++;for(;0<j-q;)E=re(E),j--;for(;0<q-j;)S=re(S),q--;for(;j--;){if(E===S||S!==null&&E===S.alternate){re=E;break t}E=re(E),S=re(S)}re=null}else re=null;D!==null&&$g(H,R,D,re,!1),X!==null&&Re!==null&&$g(H,Re,X,re,!0)}}e:{if(R=C?Ji(C):window,D=R.nodeName&&R.nodeName.toLowerCase(),D==="select"||D==="input"&&R.type==="file")var ke=hh;else if(dh(R))if(fh)ke=jw;else{ke=Aw;var ee=Sw}else D=R.nodeName,!D||D.toLowerCase()!=="input"||R.type!=="checkbox"&&R.type!=="radio"?C&&bl(C.elementType)&&(ke=hh):ke=Ew;if(ke&&(ke=ke(e,C))){uh(H,ke,n,B);break e}ee&&ee(e,R,C),e==="focusout"&&C&&R.type==="number"&&C.memoizedProps.value!=null&&ml(R,"number",R.value)}switch(ee=C?Ji(C):window,e){case"focusin":(dh(ee)||ee.contentEditable==="true")&&(ai=ee,Rl=C,Zi=null);break;case"focusout":Zi=Rl=ai=null;break;case"mousedown":Ol=!0;break;case"contextmenu":case"mouseup":case"dragend":Ol=!1,_h(H,n,B);break;case"selectionchange":if(Iw)break;case"keydown":case"keyup":_h(H,n,B)}var fe;if(El)e:{switch(e){case"compositionstart":var ye="onCompositionStart";break e;case"compositionend":ye="onCompositionEnd";break e;case"compositionupdate":ye="onCompositionUpdate";break e}ye=void 0}else ni?lh(e,n)&&(ye="onCompositionEnd"):e==="keydown"&&n.keyCode===229&&(ye="onCompositionStart");ye&&(sh&&n.locale!=="ko"&&(ni||ye!=="onCompositionStart"?ye==="onCompositionEnd"&&ni&&(fe=eh()):(qn=B,kl="value"in qn?qn.value:qn.textContent,ni=!0)),ee=ar(C,ye),0<ee.length&&(ye=new ah(ye,e,null,n,B),H.push({event:ye,listeners:ee}),fe?ye.data=fe:(fe=ch(n),fe!==null&&(ye.data=fe)))),(fe=vw?_w(e,n):kw(e,n))&&(ye=ar(C,"onBeforeInput"),0<ye.length&&(ee=new ah("onBeforeInput","beforeinput",null,n,B),H.push({event:ee,listeners:ye}),ee.data=fe)),fy(H,e,C,n,B)}Wg(H,t)})}function Ts(e,t,n){return{instance:e,listener:t,currentTarget:n}}function ar(e,t){for(var n=t+"Capture",o=[];e!==null;){var l=e,d=l.stateNode;if(l=l.tag,l!==5&&l!==26&&l!==27||d===null||(l=Gi(e,n),l!=null&&o.unshift(Ts(e,l,d)),l=Gi(e,t),l!=null&&o.push(Ts(e,l,d))),e.tag===3)return o;e=e.return}return[]}function by(e){if(e===null)return null;do e=e.return;while(e&&e.tag!==5&&e.tag!==27);return e||null}function $g(e,t,n,o,l){for(var d=t._reactName,p=[];n!==null&&n!==o;){var y=n,k=y.alternate,C=y.stateNode;if(y=y.tag,k!==null&&k===o)break;y!==5&&y!==26&&y!==27||C===null||(k=C,l?(C=Gi(n,d),C!=null&&p.unshift(Ts(n,C,k))):l||(C=Gi(n,d),C!=null&&p.push(Ts(n,C,k)))),n=n.return}p.length!==0&&e.push({event:t,listeners:p})}var wy=/\r\n?/g,yy=/\u0000|\uFFFD/g;function Xg(e){return(typeof e=="string"?e:""+e).replace(wy,`
10`).replace(yy,"")}function Kg(e,t){return t=Xg(t),Xg(e)===t}function Ie(e,t,n,o,l,d){switch(n){case"children":typeof o=="string"?t==="body"||t==="textarea"&&o===""||Za(e,o):(typeof o=="number"||typeof o=="bigint")&&t!=="body"&&Za(e,""+o);break;case"className":oo(e,"class",o);break;case"tabIndex":oo(e,"tabindex",o);break;case"dir":case"role":case"viewBox":case"width":case"height":oo(e,n,o);break;case"style":Xu(e,o,d);break;case"data":if(t!=="object"){oo(e,"data",o);break}case"src":case"href":if(o===""&&(t!=="a"||n!=="href")){e.removeAttribute(n);break}if(o==null||typeof o=="function"||typeof o=="symbol"||typeof o=="boolean"){e.removeAttribute(n);break}o=lo(""+o),e.setAttribute(n,o);break;case"action":case"formAction":if(typeof o=="function"){e.setAttribute(n,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof d=="function"&&(n==="formAction"?(t!=="input"&&Ie(e,t,"name",l.name,l,null),Ie(e,t,"formEncType",l.formEncType,l,null),Ie(e,t,"formMethod",l.formMethod,l,null),Ie(e,t,"formTarget",l.formTarget,l,null)):(Ie(e,t,"encType",l.encType,l,null),Ie(e,t,"method",l.method,l,null),Ie(e,t,"target",l.target,l,null)));if(o==null||typeof o=="symbol"||typeof o=="boolean"){e.removeAttribute(n);break}o=lo(""+o),e.setAttribute(n,o);break;case"onClick":o!=null&&(e.onclick=un);break;case"onScroll":o!=null&&me("scroll",e);break;case"onScrollEnd":o!=null&&me("scrollend",e);break;case"dangerouslySetInnerHTML":if(o!=null){if(typeof o!="object"||!("__html"in o))throw Error(r(61));if(n=o.__html,n!=null){if(l.children!=null)throw Error(r(60));e.innerHTML=n}}break;case"multiple":e.multiple=o&&typeof o!="function"&&typeof o!="symbol";break;case"muted":e.muted=o&&typeof o!="function"&&typeof o!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(o==null||typeof o=="function"||typeof o=="boolean"||typeof o=="symbol"){e.removeAttribute("xlink:href");break}n=lo(""+o),e.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",n);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":o!=null&&typeof o!="function"&&typeof o!="symbol"?e.setAttribute(n,""+o):e.removeAttribute(n);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInl
10ine":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":o&&typeof o!="function"&&typeof o!="symbol"?e.setAttribute(n,""):e.removeAttribute(n);break;case"capture":case"download":o===!0?e.setAttribute(n,""):o!==!1&&o!=null&&typeof o!="function"&&typeof o!="symbol"?e.setAttribute(n,o):e.removeAttribute(n);break;case"cols":case"rows":case"size":case"span":o!=null&&typeof o!="function"&&typeof o!="symbol"&&!isNaN(o)&&1<=o?e.setAttribute(n,o):e.removeAttribute(n);break;case"rowSpan":case"start":o==null||typeof o=="function"||typeof o=="symbol"||isNaN(o)?e.removeAttribute(n):e.setAttribute(n,o);break;case"popover":me("beforetoggle",e),me("toggle",e),so(e,"popover",o);break;case"xlinkActuate":dn(e,"http://www.w3.org/1999/xlink","xlink:actuate",o);break;case"xlinkArcrole":dn(e,"http://www.w3.org/1999/xlink","xlink:arcrole",o);break;case"xlinkRole":dn(e,"http://www.w3.org/1999/xlink","xlink:role",o);break;case"xlinkShow":dn(e,"http://www.w3.org/1999/xlink","xlink:show",o);break;case"xlinkTitle":dn(e,"http://www.w3.org/1999/xlink","xlink:title",o);break;case"xlinkType":dn(e,"http://www.w3.org/1999/xlink","xlink:type",o);break;case"xmlBase":dn(e,"http://www.w3.org/XML/1998/namespace","xml:base",o);break;case"xmlLang":dn(e,"http://www.w3.org/XML/1998/namespace","xml:lang",o);break;case"xmlSpace":dn(e,"http://www.w3.org/XML/1998/namespace","xml:space",o);break;case"is":so(e,"is",o);break;case"innerText":case"textContent":break;default:(!(2<n.length)||n[0]!=="o"&&n[0]!=="O"||n[1]!=="n"&&n[1]!=="N")&&(n=V0.get(n)||n,so(e,n,o))}}function ad(e,t,n,o,l,d){switch(n){case"style":Xu(e,o,d);break;case"dangerouslySetInnerHTML":if(o!=null){if(typeof o!="object"||!("__html"in o))throw Error(r(61));if(n=o.__html,n!=null){if(l.children!=null)throw Error(r(60));e.innerHTML=n}}break;case"children":typeof o=="string"?Za(e,o):(typeof o=="number"||typeof o=="bigint")&&Za(e,""+o);break;case"onScroll":o!=null&&me("scroll",e);break;case"onScrollEnd":o!=null&&me("scrollend",e);break;case"onClick":o!=null&&(e.onclick=un);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"innerHTML":case"ref":break;case"innerText":case"textContent":break;default:if(!Pu.hasOwnProperty(n))e:{if(n[0]==="o"&&n[1]==="n"&&(l=n.endsWith("Capture"),t=n.slice(2,l?n.length-7:void 0),d=e[pt]||null,d=d!=null?d[n]:null,typeof d=="function"&&e.removeEventListener(t,d,l),typeof o=="function")){typeof d!="function"&&d!==null&&(n in e?e[n]=null:e.hasAttribute(n)&&e.removeAttribute(n)),e.addEventListener(t,o,l);break e}n in e?e[n]=o:o===!0?e.setAttribute(n,""):so(e,n,o)}}}function st(e,t,n){switch(t){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"img":me("error",e),me("load",e);var o=!1,l=!1,d;for(d in n)if(n.hasOwnProperty(d)){var p=n[d];if(p!=null)switch(d){case"src":o=!0;break;case"srcSet":l=!0;break;case"children":case"dangerouslySetInnerHTML":throw Error(r(137,t));default:Ie(e,t,d,p,n,null)}}l&&Ie(e,t,"srcSet",n.srcSet,n,null),o&&Ie(e,t,"src",n.src,n,null);return;case"input":me("invalid",e);var y=d=p=l=null,k=null,C=null;for(o in n)if(n.hasOwnProperty(o)){var B=n[o];if(B!=null)switch(o){case"name":l=B;break;case"type":p=B;break;case"checked":k=B;break;case"defaultChecked":C=B;break;case"value":d=B;break;case"defaultValue":y=B;break;case"children":case"dangerouslySetInnerHTML":if(B!=null)throw Error(r(137,t));break;default:Ie(e,t,o,B,n,null)}}Vu(e,d,y,k,C,p,l,!1);return;case"select":me("invalid",e),o=p=d=null;for(l in n)if(n.hasOwnProperty(l)&&(y=n[l],y!=null))switch(l){case"value":d=y;break;case"defaultValue":p=y;break;case"multiple":o=y;default:Ie(e,t,l,y,n,null)}t=d,n=p,e.multiple=!!o,t!=null?Ka(e,!!o,t,!1):n!=null&&Ka(e,!!o,n,!0);return;case"textarea":me("invalid",e),d=l=o=null;for(p in n)if(n.hasOwnProperty(p)&&(y=n[p],y!=null))switch(p){case"value":o=y;break;case"defaultValue":l=y;break;case"children":d=y;break;case"dangerouslySetInnerHTML":if(y!=null)throw Error(r(91));break;default:Ie(e,t,p,y,n,null)}Qu(e,o,l,d);return;case"option":for(k in n)n.hasOwnProperty(k)&&(o=n[k],o!=null)&&(k==="selected"?e.selected=o&&typeof o!="function"&&typeof o!="symbol":Ie(e,t,k,o,n,null));return;case"dialog":me("beforetoggle",e),me("toggle",e),me("cancel",e),me("close",e);break;case"iframe":case"object":me("load",e);break;case"video":case"audio":for(o=0;o<xs.length;o++)me(xs[o],e);break;case"image":me("error",e),me("load",e);break;case"details":me("toggle",e);break;case"embed":case"source":case"link":me("error",e),me("load",e);case"area":case"base":case"br":case"col":case"hr":case"keygen":case"meta":case"param":case"track":case"wbr":case"menuitem":for(C in n)if(n.hasOwnProperty(C)&&(o=n[C],o!=null))switch(C){case"children":case"dangerouslySetInnerHTML":throw Error(r(137,t));default:Ie(e,t,C,o,n,null)}return;default:if(bl(t)){for(B in n)n.hasOwnProperty(B)&&(o=n[B],o!==void 0&&ad(e,t,B,o,n,void 0));return}}for(y in n)n.hasOwnProperty(y)&&(o=n[y],o!=null&&Ie(e,t,y,o,n,null))}function vy(e,t,n,o){switch(t){case"div":case"span":case"svg":case"path":case"a":case"g":case"p":case"li":break;case"input":var l=null,d=null,p=null,y=null,k=null,C=null,B=null;for(D in n){var H=n[D];if(n.hasOwnProperty(D)&&H!=null)switch(D){case"checked":break;case"value":break;case"defaultValue":k=H;default:o.hasOwnProperty(D)||Ie(e,t,D,null,o,H)}}for(var R in o){var D=o[R];if(H=n[R],o.hasOwnProperty(R)&&(D!=null||H!=null))switch(R){case"type":d=D;break;case"name":l=D;break;case"checked":C=D;break;case"defaultChecked":B=D;break;case"value":p=D;break;case"defaultValue":y=D;break;case"children":case"dangerouslySetInnerHTML":if(D!=null)throw Error(r(137,t));break;default:D!==H&&Ie(e,t,R,D,o,H)}}pl(e,p,y,k,C,B,d,l);return;case"select":D=p=y=R=null;for(d in n)if(k=n[d],n.hasOwnProperty(d)&&k!=null)switch(d){case"value":break;case"multiple":D=k;default:o.hasOwnProperty(d)||Ie(e,t,d,null,o,k)}for(l in o)if(d=o[l],k=n[l],o.hasOwnProperty(l)&&(d!=null||k!=null))switch(l){case"value":R=d;break;case"defaultValue":y=d;break;case"multiple":p=d;default:d!==k&&Ie(e,t,l,d,o,k)}t=y,n=p,o=D,R!=null?Ka(e,!!n,R,!1):!!o!=!!n&&(t!=null?Ka(e,!!n,t,!0):Ka(e,!!n,n?[]:"",!1));return;case"textarea":D=R=null;
vendor: 4,650 bytes, line 10
10for(y in n)if(l=n[y],n.hasOwnProperty(y)&&l!=null&&!o.hasOwnProperty(y))switch(y){case"value":break;case"children":break;default:Ie(e,t,y,null,o,l)}for(p in o)if(l=o[p],d=n[p],o.hasOwnProperty(p)&&(l!=null||d!=null))switch(p){case"value":R=l;break;case"defaultValue":D=l;break;case"children":break;case"dangerouslySetInnerHTML":if(l!=null)throw Error(r(91));break;default:l!==d&&Ie(e,t,p,l,o,d)}Wu(e,R,D);return;case"option":for(var X in n)R=n[X],n.hasOwnProperty(X)&&R!=null&&!o.hasOwnProperty(X)&&(X==="selected"?e.selected=!1:Ie(e,t,X,null,o,R));for(k in o)R=o[k],D=n[k],o.hasOwnProperty(k)&&R!==D&&(R!=null||D!=null)&&(k==="selected"?e.selected=R&&typeof R!="function"&&typeof R!="symbol":Ie(e,t,k,R,o,D));return;case"img":case"link":case"area":case"base":case"br":case"col":case"embed":case"hr":case"keygen":case"meta":case"param":case"source":case"track":case"wbr":case"menuitem":for(var re in n)R=n[re],n.hasOwnProperty(re)&&R!=null&&!o.hasOwnProperty(re)&&Ie(e,t,re,null,o,R);for(C in o)if(R=o[C],D=n[C],o.hasOwnProperty(C)&&R!==D&&(R!=null||D!=null))switch(C){case"children":case"dangerouslySetInnerHTML":if(R!=null)throw Error(r(137,t));break;default:Ie(e,t,C,R,o,D)}return;default:if(bl(t)){for(var Re in n)R=n[Re],n.hasOwnProperty(Re)&&R!==void 0&&!o.hasOwnProperty(Re)&&ad(e,t,Re,void 0,o,R);for(B in o)R=o[B],D=n[B],!o.hasOwnProperty(B)||R===D||R===void 0&&D===void 0||ad(e,t,B,R,o,D);return}}for(var E in n)R=n[E],n.hasOwnProperty(E)&&R!=null&&!o.hasOwnProperty(E)&&Ie(e,t,E,null,o,R);for(H in o)R=o[H],D=n[H],!o.hasOwnProperty(H)||R===D||R==null&&D==null||Ie(e,t,H,R,o,D)}function Zg(e){switch(e){case"css":case"script":case"font":case"img":case"image":case"input":case"link":return!0;default:return!1}}function _y(){if(typeof performance.getEntriesByType=="function"){for(var e=0,t=0,n=performance.getEntriesByType("resource"),o=0;o<n.length;o++){var l=n[o],d=l.transferSize,p=l.initiatorType,y=l.duration;if(d&&y&&Zg(p)){for(p=0,y=l.responseEnd,o+=1;o<n.length;o++){var k=n[o],C=k.startTime;if(C>y)break;var B=k.transferSize,H=k.initiatorType;B&&Zg(H)&&(k=k.responseEnd,p+=B*(k<y?1:(y-C)/(k-C)))}if(--o,t+=8*(d+p)/(l.duration/1e3),e++,10<e)break}}if(0<e)return t/e/1e6}return navigator.connection&&(e=navigator.connection.downlink,typeof e=="number")?e:5}var id=null,sd=null;function ir(e){return e.nodeType===9?e:e.ownerDocument}function ep(e){switch(e){case"http://www.w3.org/2000/svg":return 1;case"http://www.w3.org/1998/Math/MathML":return 2;default:return 0}}function tp(e,t){if(e===0)switch(t){case"svg":return 1;case"math":return 2;default:return 0}return e===1&&t==="foreignObject"?0:e}function od(e,t){return e==="textarea"||e==="noscript"||typeof t.children=="string"||typeof t.children=="number"||typeof t.children=="bigint"||typeof t.dangerouslySetInnerHTML=="object"&&t.dangerouslySetInnerHTML!==null&&t.dangerouslySetInnerHTML.__html!=null}var rd=null;function ky(){var e=window.event;return e&&e.type==="popstate"?e===rd?!1:(rd=e,!0):(rd=null,!1)}var np=typeof setTimeout=="function"?setTimeout:void 0,xy=typeof clearTimeout=="function"?clearTimeout:void 0,ap=typeof Promise=="function"?Promise:void 0,Ty=typeof queueMicrotask=="function"?queueMicrotask:typeof ap<"u"?function(e){return ap.resolve(null).then(e).catch(Sy)}:np;function Sy(e){setTimeout(function(){throw e})}function ta(e){return e==="head"}function ip(e,t){var n=t,o=0;do{var l=n.nextSibling;if(e.removeChild(n),l&&l.nodeType===8)if(n=l.data,n==="/$"||n==="/&"){if(o===0){e.removeChild(l),Ii(t);return}o--}else if(n==="$"||n==="$?"||n==="$~"||n==="$!"||n==="&")o++;else if(n==="html")Ss(e.ownerDocument.documentElement);else if(n==="head"){n=e.ownerDocument.head,Ss(n);for(var d=n.firstChild;d;){var p=d.nextSibling,y=d.nodeName;d[Fi]||y==="SCRIPT"||y==="STYLE"||y==="LINK"&&d.rel.toLowerCase()==="stylesheet"||n.removeChild(d),d=p}}else n==="body"&&Ss(e.ownerDocument.body);n=l}while(n);Ii(t)}function sp(e,t){var n=e;e=0;do{var o=n.nextSibling;if(n.nodeType===1?t?(n._stashedDisplay=n.style.display,n.style.display="none"):(n.style.display=n._stashedDisplay||"",n.getAttribute("style")===""&&n.removeAttribute("style")):n.nodeType===3&&(t?(n._stashedText=n.nodeValue,n.nodeValue=""):n.nodeValue=n._stashedText||""),o&&o.nodeType===8)if(n=o.data,n==="/$"){if(e===0)break;e--}else n!=="$"&&n!=="$?"&&n!=="$~"&&n!=="$!"||e++;n=o}while(n)}function ld(e){var t=e.firstChild;for(t&&t.nodeType===10&&(t=t.nextSibling);t;){var n=t;switch(t=t.nextSibling,n.nodeName){case"HTML":case"HEAD":case"BODY":ld(n),fl(n);continue;case"SCRIPT":case"STYLE":continue;case"LINK":if(n.rel.toLowerCase()==="stylesheet")continue}e.removeChild(n)}}function Ay(e,t,n,o){for(;
vendor: 4,768 bytes, line 10
10e.nodeType===1;){var l=n;if(e.nodeName.toLowerCase()!==t.toLowerCase()){if(!o&&(e.nodeName!=="INPUT"||e.type!=="hidden"))break}else if(o){if(!e[Fi])switch(t){case"meta":if(!e.hasAttribute("itemprop"))break;return e;case"link":if(d=e.getAttribute("rel"),d==="stylesheet"&&e.hasAttribute("data-precedence"))break;if(d!==l.rel||e.getAttribute("href")!==(l.href==null||l.href===""?null:l.href)||e.getAttribute("crossorigin")!==(l.crossOrigin==null?null:l.crossOrigin)||e.getAttribute("title")!==(l.title==null?null:l.title))break;return e;case"style":if(e.hasAttribute("data-precedence"))break;return e;case"script":if(d=e.getAttribute("src"),(d!==(l.src==null?null:l.src)||e.getAttribute("type")!==(l.type==null?null:l.type)||e.getAttribute("crossorigin")!==(l.crossOrigin==null?null:l.crossOrigin))&&d&&e.hasAttribute("async")&&!e.hasAttribute("itemprop"))break;return e;default:return e}}else if(t==="input"&&e.type==="hidden"){var d=l.name==null?null:""+l.name;if(l.type==="hidden"&&e.getAttribute("name")===d)return e}else return e;if(e=Pt(e.nextSibling),e===null)break}return null}function Ey(e,t,n){if(t==="")return null;for(;e.nodeType!==3;)if((e.nodeType!==1||e.nodeName!=="INPUT"||e.type!=="hidden")&&!n||(e=Pt(e.nextSibling),e===null))return null;return e}function op(e,t){for(;e.nodeType!==8;)if((e.nodeType!==1||e.nodeName!=="INPUT"||e.type!=="hidden")&&!t||(e=Pt(e.nextSibling),e===null))return null;return e}function cd(e){return e.data==="$?"||e.data==="$~"}function dd(e){return e.data==="$!"||e.data==="$?"&&e.ownerDocument.readyState!=="loading"}function jy(e,t){var n=e.ownerDocument;if(e.data==="$~")e._reactRetry=t;else if(e.data!=="$?"||n.readyState!=="loading")t();else{var o=function(){t(),n.removeEventListener("DOMContentLoaded",o)};n.addEventListener("DOMContentLoaded",o),e._reactRetry=o}}function Pt(e){for(;e!=null;e=e.nextSibling){var t=e.nodeType;if(t===1||t===3)break;if(t===8){if(t=e.data,t==="$"||t==="$!"||t==="$?"||t==="$~"||t==="&"||t==="F!"||t==="F")break;if(t==="/$"||t==="/&")return null}}return e}var ud=null;function rp(e){e=e.nextSibling;for(var t=0;e;){if(e.nodeType===8){var n=e.data;if(n==="/$"||n==="/&"){if(t===0)return Pt(e.nextSibling);t--}else n!=="$"&&n!=="$!"&&n!=="$?"&&n!=="$~"&&n!=="&"||t++}e=e.nextSibling}return null}function lp(e){e=e.previousSibling;for(var t=0;e;){if(e.nodeType===8){var n=e.data;if(n==="$"||n==="$!"||n==="$?"||n==="$~"||n==="&"){if(t===0)return e;t--}else n!=="/$"&&n!=="/&"||t++}e=e.previousSibling}return null}function cp(e,t,n){switch(t=ir(n),e){case"html":if(e=t.documentElement,!e)throw Error(r(452));return e;case"head":if(e=t.head,!e)throw Error(r(453));return e;case"body":if(e=t.body,!e)throw Error(r(454));return e;default:throw Error(r(451))}}function Ss(e){for(var t=e.attributes;t.length;)e.removeAttributeNode(t[0]);fl(e)}var Ft=new Map,dp=new Set;function sr(e){return typeof e.getRootNode=="function"?e.getRootNode():e.nodeType===9?e:e.ownerDocument}var En=W.d;W.d={f:Cy,r:Iy,D:Ry,C:Oy,L:My,m:Uy,X:By,S:Dy,M:Ly};function Cy(){var e=En.f(),t=$o();return e||t}function Iy(e){var t=Qa(e);t!==null&&t.tag===5&&t.type==="form"?Ef(t):En.r(e)}var Ei=typeof document>"u"?null:document;function up(e,t,n){var o=Ei;if(o&&typeof t=="string"&&t){var l=Dt(t);l='link[rel="'+e+'"][href="'+l+'"]',typeof n=="string"&&(l+='[crossorigin="'+n+'"]'),dp.has(l)||(dp.add(l),e={rel:e,crossOrigin:n,href:t},o.querySelector(l)===null&&(t=o.createElement("link"),st(t,"link",e),Ke(t),o.head.appendChild(t)))}}function Ry(e){En.D(e),up("dns-prefetch",e,null)}function Oy(e,t){En.C(e,t),up("preconnect",e,t)}function My(e,t,n){En.L(e,t,n);var o=Ei;if(o&&e&&t){var l='link[rel="preload"][as="'+Dt(t)+'"]';t==="image"&&n&&n.imageSrcSet?(l+='[imagesrcset="'+Dt(n.imageSrcSet)+'"]',typeof n.imageSizes=="string"&&(l+='[imagesizes="'+Dt(n.imageSizes)+'"]')):l+='[href="'+Dt(e)+'"]';var d=l;switch(t){case"style":d=ji(e);break;case"script":d=Ci(e)}Ft.has(d)||(e=b({rel:"preload",href:t==="image"&&n&&n.imageSrcSet?void 0:e,as:t},n),Ft.set(d,e),o.querySelector(l)!==null||t==="style"&&o.querySelector(As(d))||t==="script"&&o.querySelector(Es(d))||(t=o.createElement("link"),st(t,"link",e),Ke(t),o.head.appendChild(t)))}}function Uy(e,t){En.m(e,t);var n=Ei;if(n&&e){var o=t&&typeof t.as=="string"?t.as:"script",l='link[rel="modulepreload"][as="'+Dt(o)+'"][href="'+Dt(e)+'"]',d=l;switch(o){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":d=Ci(e)}if(!Ft.has(d)&&(e=b({rel:"modulepreload",href:e},t),Ft.set(d,e),n.querySelector(l)===null)){switch(o){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(n.querySelector(Es(d)))return}o=n.createElement("link"),st(o,"link",e),Ke(o),n.head.appendChild(o)}}}
vendor: 6,020 bytes, line 10
10function Dy(e,t,n){En.S(e,t,n);var o=Ei;if(o&&e){var l=$a(o).hoistableStyles,d=ji(e);t=t||"default";var p=l.get(d);if(!p){var y={loading:0,preload:null};if(p=o.querySelector(As(d)))y.loading=5;else{e=b({rel:"stylesheet",href:e,"data-precedence":t},n),(n=Ft.get(d))&&hd(e,n);var k=p=o.createElement("link");Ke(k),st(k,"link",e),k._p=new Promise(function(C,B){k.onload=C,k.onerror=B}),k.addEventListener("load",function(){y.loading|=1}),k.addEventListener("error",function(){y.loading|=2}),y.loading|=4,or(p,t,o)}p={type:"stylesheet",instance:p,count:1,state:y},l.set(d,p)}}}function By(e,t){En.X(e,t);var n=Ei;if(n&&e){var o=$a(n).hoistableScripts,l=Ci(e),d=o.get(l);d||(d=n.querySelector(Es(l)),d||(e=b({src:e,async:!0},t),(t=Ft.get(l))&&fd(e,t),d=n.createElement("script"),Ke(d),st(d,"link",e),n.head.appendChild(d)),d={type:"script",instance:d,count:1,state:null},o.set(l,d))}}function Ly(e,t){En.M(e,t);var n=Ei;if(n&&e){var o=$a(n).hoistableScripts,l=Ci(e),d=o.get(l);d||(d=n.querySelector(Es(l)),d||(e=b({src:e,async:!0,type:"module"},t),(t=Ft.get(l))&&fd(e,t),d=n.createElement("script"),Ke(d),st(d,"link",e),n.head.appendChild(d)),d={type:"script",instance:d,count:1,state:null},o.set(l,d))}}function hp(e,t,n,o){var l=(l=ge.current)?sr(l):null;if(!l)throw Error(r(446));switch(e){case"meta":case"title":return null;case"style":return typeof n.precedence=="string"&&typeof n.href=="string"?(t=ji(n.href),n=$a(l).hoistableStyles,o=n.get(t),o||(o={type:"style",instance:null,count:0,state:null},n.set(t,o)),o):{type:"void",instance:null,count:0,state:null};case"link":if(n.rel==="stylesheet"&&typeof n.href=="string"&&typeof n.precedence=="string"){e=ji(n.href);var d=$a(l).hoistableStyles,p=d.get(e);if(p||(l=l.ownerDocument||l,p={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},d.set(e,p),(d=l.querySelector(As(e)))&&!d._p&&(p.instance=d,p.state.loading=5),Ft.has(e)||(n={rel:"preload",as:"style",href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},Ft.set(e,n),d||qy(l,e,n,p.state))),t&&o===null)throw Error(r(528,""));return p}if(t&&o!==null)throw Error(r(529,""));return null;case"script":return t=n.async,n=n.src,typeof n=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=Ci(n),n=$a(l).hoistableScripts,o=n.get(t),o||(o={type:"script",instance:null,count:0,state:null},n.set(t,o)),o):{type:"void",instance:null,count:0,state:null};default:throw Error(r(444,e))}}function ji(e){return'href="'+Dt(e)+'"'}function As(e){return'link[rel="stylesheet"]['+e+"]"}function fp(e){return b({},e,{"data-precedence":e.precedence,precedence:null})}function qy(e,t,n,o){e.querySelector('link[rel="preload"][as="style"]['+t+"]")?o.loading=1:(t=e.createElement("link"),o.preload=t,t.addEventListener("load",function(){return o.loading|=1}),t.addEventListener("error",function(){return o.loading|=2}),st(t,"link",n),Ke(t),e.head.appendChild(t))}function Ci(e){return'[src="'+Dt(e)+'"]'}function Es(e){return"script[async]"+e}function gp(e,t,n){if(t.count++,t.instance===null)switch(t.type){case"style":var o=e.querySelector('style[data-href~="'+Dt(n.href)+'"]');if(o)return t.instance=o,Ke(o),o;var l=b({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return o=(e.ownerDocument||e).createElement("style"),Ke(o),st(o,"style",l),or(o,n.precedence,e),t.instance=o;case"stylesheet":l=ji(n.href);var d=e.querySelector(As(l));if(d)return t.state.loading|=4,t.instance=d,Ke(d),d;o=fp(n),(l=Ft.get(l))&&hd(o,l),d=(e.ownerDocument||e).createElement("link"),Ke(d);var p=d;return p._p=new Promise(function(y,k){p.onload=y,p.onerror=k}),st(d,"link",o),t.state.loading|=4,or(d,n.precedence,e),t.instance=d;case"script":return d=Ci(n.src),(l=e.querySelector(Es(d)))?(t.instance=l,Ke(l),l):(o=n,(l=Ft.get(d))&&(o=b({},n),fd(o,l)),e=e.ownerDocument||e,l=e.createElement("script"),Ke(l),st(l,"link",o),e.head.appendChild(l),t.instance=l);case"void":return null;default:throw Error(r(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(o=t.instance,t.state.loading|=4,or(o,n.precedence,e));return t.instance}function or(e,t,n){for(var o=n.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),l=o.length?o[o.length-1]:null,d=l,p=0;p<o.length;p++){var y=o[p];if(y.dataset.precedence===t)d=y;else if(d!==l)break}d?d.parentNode.insertBefore(e,d.nextSibling):(t=n.nodeType===9?n.head:n,t.insertBefore(e,t.firstChild))}function hd(e,t){e.crossOrigin==null&&(e.crossOrigin=t.crossOrigin),e.referrerPolicy==null&&(e.referrerPolicy=t.referrerPolicy),e.title==null&&(e.title=t.title)}function fd(e,t){e.crossOrigin==null&&(e.crossOrigin=t.crossOrigin),e.referrerPolicy==null&&(e.referrerPolicy=t.referrerPolicy),e.integrity==null&&(e.integrity=t.integrity)}var rr=null;function pp(e,t,n){if(rr===null){var o=new Map,l=rr=new Map;l.set(n,o)}else l=rr,o=l.get(n),o||(o=new Map,l.set(n,o));if(o.has(e))return o;for(o.set(e,null),n=n.getElementsByTagName(e),l=0;l<n.length;l++){var d=n[l];if(!(d[Fi]||d[tt]||e==="link"&&d.getAttribute("rel")==="stylesheet")&&d.namespaceURI!=="http://www.w3.org/2000/svg"){var p=d.getAttribute(t)||"";p=e+p;var y=o.get(p);y?y.push(d):o.set(p,[d])}}return o}function mp(e,t,n){e=e.ownerDocument||e,e.head.insertBefore(n,t==="title"?e.querySelector("head > title"):null)}function zy(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case"meta":case"title":return!0;case"style":if(typeof t.precedence!="string"||typeof t.href!="string"||t.href==="")break;return!0;case"link":if(typeof t.rel!="string"||typeof t.href!="string"||t.href===""||t.onLoad||t.onError)break;return t.rel==="stylesheet"?(e=t.disabled,typeof t.precedence=="string"&&e==null):!0;case"script":if(t.async&&typeof t.async!="function"&&typeof t.async!="symbol"&&!t.onLoad&&!t.onError&&t.src&&typeof t.src=="string")return!0}return!1}function bp(e){return!(e.type==="stylesheet"&&(e.state.loading&3)===0)}function Ny(e,t,n,o){if(n.type==="stylesheet"&&(typeof o.media!="str
10ing"||matchMedia(o.media).matches!==!1)&&(n.state.loading&4)===0){if(n.instance===null){var l=ji(o.href),d=t.querySelector(As(l));if(d){t=d._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(e.count++,e=lr.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=d,Ke(d);return}d=t.ownerDocument||t,o=fp(o),(l=Ft.get(l))&&hd(o,l),d=d.createElement("link"),Ke(d);var p=d;p._p=new Promise(function(y,k){p.onload=y,p.onerror=k}),st(d,"link",o),n.instance=d}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&(n.state.loading&3)===0&&(e.count++,n=lr.bind(e),t.addEventListener("load",n),t.addEventListener("error",n))}}var gd=0;function Hy(e,t){return e.stylesheets&&e.count===0&&dr(e,e.stylesheets),0<e.count||0<e.imgCount?function(n){var o=setTimeout(function(){if(e.stylesheets&&dr(e,e.stylesheets),e.unsuspend){var d=e.unsuspend;e.unsuspend=null,d()}},6e4+t);0<e.imgBytes&&gd===0&&(gd=62500*_y());var l=setTimeout(function(){if(e.waitingForImages=!1,e.count===0&&(e.stylesheets&&dr(e,e.stylesheets),e.unsuspend)){var d=e.unsuspend;e.unsuspend=null,d()}},(e.imgBytes>gd?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(o),clearTimeout(l)}}:null}function lr(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)dr(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var cr=null;function dr(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,cr=new Map,t.forEach(Py,e),cr=null,lr.call(e))}function Py(e,t){if(!(t.state.loading&4)){var n=cr.get(e);if(n)var o=n.get(null);else{n=new Map,cr.set(e,n);for(var l=e.querySelectorAll("link[data-precedence],style[data-precedence]"),d=0;d<l.length;d++){var p=l[d];(p.nodeName==="LINK"||p.getAttribute("media")!=="not all")&&(n.set(p.dataset.precedence,p),o=p)}o&&n.set(null,o)}l=t.instance,p=l.getAttribute("data-precedence"),d=n.get(p)||o,d===o&&n.set(null,l),n.set(p,l),this.count++,o=lr.bind(this),l.addEventListener("load",o),l.addEventListener("error",o),d?d.parentNode.insertBefore(l,d.nextSibling):(e=e.nodeType===9?e.head:e,e.insertBefore(l,e.firstChild)),t.state.loading|=4}}var js={$$typeof:L,Provider:null,Consumer:null,_currentValue:ie,_currentValue2:ie,_threadCount:0};function Fy(e,t,n,o,l,d,p,y,k){this.tag=1,this.containerInfo=e,this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.next=this.pendingContext=this.context=this.cancelPendingCommit=null,this.callbackPriority=0,this.expirationTimes=cl(-1),this.entangledLanes=this.shellSuspendCounter=this.errorRecoveryDisabledLanes=this.expiredLanes=this.warmLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=cl(0),this.hiddenUpdates=cl(null),this.identifierPrefix=o,this.onUncaughtError=l,this.onCaughtError=d,this.onRecoverableError=p,this.pooledCache=null,this.pooledCacheLanes=0,this.formState=k,this.incompleteTransitions=new Map}function wp(e,t,n,o,l,d,p,y,k,C,B,H){return e=new Fy(e,t,n,p,k,C,B,H,y),t=1,d===!0&&(t|=24),d=At(3,null,null,t),e.current=d,d.stateNode=e,t=Vl(),t.refCount++,e.pooledCache=t,t.refCount++,d.memoizedState={element:o,isDehydrated:n,cache:t},Xl(d),e}function yp(e){return e?(e=oi,e):oi}function vp(e,t,n,o,l,d){l=yp(l),o.context===null?o.context=l:o.pendingContext=l,o=Jn(t),o.payload={element:n},d=d===void 0?null:d,d!==null&&(o.callback=d),n=Gn(e,o,t),n!==null&&(_t(n,e,t),os(n,e,t))}function _p(e,t){if(e=e.memoizedState,e!==null&&e.dehydrated!==null){var n=e.retryLane;e.retryLane=n!==0&&n<t?n:t}}function pd(e,t){_p(e,t),(e=e.alternate)&&_p(e,t)}function kp(e){if(e.tag===13||e.tag===31){var t=xa(e,67108864);t!==null&&_t(t,e,67108864),pd(e,67108864)}}function xp(e){if(e.tag===13||e.tag===31){var t=Rt();t=dl(t);var n=xa(e,t);n!==null&&_t(n,e,t),pd(e,t)}}var ur=!0;function Jy(e,t,n,o){var l=O.T;O.T=null;var d=W.p;try{W.p=2,md(e,t,n,o)}finally{W.p=d,O.T=l}}function Gy(e,t,n,o){var l=O.T;O.T=null;var d=W.p;try{W.p=8,md(e,t,n,o)}finally{W.p=d,O.T=l}}function md(e,t,n,o){if(ur){var l=bd(o);if(l===null)nd(e,t,o,hr,n),Sp(e,o);else if(Vy(l,e,t,n,o))o.stopPropagation();else if(Sp(e,o),t&4&&-1<Yy.indexOf(e)){for(;l!==null;){var d=Qa(l);if(d!==null)switch(d.tag){case 3:if(d=d.stateNode,d.current.memoizedState.isDehydrated){var p=wa(d.pendingLanes);if(p!==0){var y=d;for(y.pendingLanes|=2,y.entangledLanes|=2;p;){var k=1<<31-Tt(p);y.entanglements[1]|=k,p&=~k}nn(d),(Ae&6)===0&&(Wo=kt()+500,ks(0))}}break;case 31:case 13:y=xa(d,2),y!==null&&_t(y,d,2),$o(),pd(d,2)}if(d=bd(o),d===null&&nd(e,t,o,hr,n),d===l)break;l=d}l!==null&&o.stopPropagation()}else nd(e,t,o,null,n)}}function bd(e){return e=yl(e),wd(e)}var hr=null;function wd(e){if(hr=null,e=Wa(e),e!==null){var t=u(e);if(t===null)e=null;else{var n=t.tag;if(n===13){if(e=h(t),e!==null)return e;e=null}else if(n===31){if(e=f(t),e!==null)return e;e=null}else if(n===3){if(t.stateNode.current.memoizedState.isDehydrated)return t.tag===3?t.stateNode.containerInfo:null;e=null}else t!==e&&(e=null)}}return hr=e,null}function Tp(e){switch(e){case"beforetoggle":case"cancel":case"click":case"close"
vendor: 6,256 bytes, line 10
10:case"contextmenu":case"copy":case"cut":case"auxclick":case"dblclick":case"dragend":case"dragstart":case"drop":case"focusin":case"focusout":case"input":case"invalid":case"keydown":case"keypress":case"keyup":case"mousedown":case"mouseup":case"paste":case"pause":case"play":case"pointercancel":case"pointerdown":case"pointerup":case"ratechange":case"reset":case"resize":case"seeked":case"submit":case"toggle":case"touchcancel":case"touchend":case"touchstart":case"volumechange":case"change":case"selectionchange":case"textInput":case"compositionstart":case"compositionend":case"compositionupdate":case"beforeblur":case"afterblur":case"beforeinput":case"blur":case"fullscreenchange":case"focus":case"hashchange":case"popstate":case"select":case"selectstart":return 2;case"drag":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"mousemove":case"mouseout":case"mouseover":case"pointermove":case"pointerout":case"pointerover":case"scroll":case"touchmove":case"wheel":case"mouseenter":case"mouseleave":case"pointerenter":case"pointerleave":return 8;case"message":switch(R0()){case Ru:return 2;case Ou:return 8;case eo:case O0:return 32;case Mu:return 268435456;default:return 32}default:return 32}}var yd=!1,na=null,aa=null,ia=null,Cs=new Map,Is=new Map,sa=[],Yy="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset".split(" ");function Sp(e,t){switch(e){case"focusin":case"focusout":na=null;break;case"dragenter":case"dragleave":aa=null;break;case"mouseover":case"mouseout":ia=null;break;case"pointerover":case"pointerout":Cs.delete(t.pointerId);break;case"gotpointercapture":case"lostpointercapture":Is.delete(t.pointerId)}}function Rs(e,t,n,o,l,d){return e===null||e.nativeEvent!==d?(e={blockedOn:t,domEventName:n,eventSystemFlags:o,nativeEvent:d,targetContainers:[l]},t!==null&&(t=Qa(t),t!==null&&kp(t)),e):(e.eventSystemFlags|=o,t=e.targetContainers,l!==null&&t.indexOf(l)===-1&&t.push(l),e)}function Vy(e,t,n,o,l){switch(t){case"focusin":return na=Rs(na,e,t,n,o,l),!0;case"dragenter":return aa=Rs(aa,e,t,n,o,l),!0;case"mouseover":return ia=Rs(ia,e,t,n,o,l),!0;case"pointerover":var d=l.pointerId;return Cs.set(d,Rs(Cs.get(d)||null,e,t,n,o,l)),!0;case"gotpointercapture":return d=l.pointerId,Is.set(d,Rs(Is.get(d)||null,e,t,n,o,l)),!0}return!1}function Ap(e){var t=Wa(e.target);if(t!==null){var n=u(t);if(n!==null){if(t=n.tag,t===13){if(t=h(n),t!==null){e.blockedOn=t,zu(e.priority,function(){xp(n)});return}}else if(t===31){if(t=f(n),t!==null){e.blockedOn=t,zu(e.priority,function(){xp(n)});return}}else if(t===3&&n.stateNode.current.memoizedState.isDehydrated){e.blockedOn=n.tag===3?n.stateNode.containerInfo:null;return}}}e.blockedOn=null}function fr(e){if(e.blockedOn!==null)return!1;for(var t=e.targetContainers;0<t.length;){var n=bd(e.nativeEvent);if(n===null){n=e.nativeEvent;var o=new n.constructor(n.type,n);wl=o,n.target.dispatchEvent(o),wl=null}else return t=Qa(n),t!==null&&kp(t),e.blockedOn=n,!1;t.shift()}return!0}function Ep(e,t,n){fr(e)&&n.delete(t)}function Wy(){yd=!1,na!==null&&fr(na)&&(na=null),aa!==null&&fr(aa)&&(aa=null),ia!==null&&fr(ia)&&(ia=null),Cs.forEach(Ep),Is.forEach(Ep)}function gr(e,t){e.blockedOn===t&&(e.blockedOn=null,yd||(yd=!0,a.unstable_scheduleCallback(a.unstable_NormalPriority,Wy)))}var pr=null;function jp(e){pr!==e&&(pr=e,a.unstable_scheduleCallback(a.unstable_NormalPriority,function(){pr===e&&(pr=null);for(var t=0;t<e.length;t+=3){var n=e[t],o=e[t+1],l=e[t+2];if(typeof o!="function"){if(wd(o||n)===null)continue;break}var d=Qa(n);d!==null&&(e.splice(t,3),t-=3,bc(d,{pending:!0,data:l,method:n.method,action:o},o,l))}}))}function Ii(e){function t(k){return gr(k,e)}na!==null&&gr(na,e),aa!==null&&gr(aa,e),ia!==null&&gr(ia,e),Cs.forEach(t),Is.forEach(t);for(var n=0;n<sa.length;n++){var o=sa[n];o.blockedOn===e&&(o.blockedOn=null)}for(;0<sa.length&&(n=sa[0],n.blockedOn===null);)Ap(n),n.blockedOn===null&&sa.shift();if(n=(e.ownerDocument||e).$$reactFormReplay,n!=null)for(o=0;o<n.length;o+=3){var l=n[o],d=n[o+1],p=l[pt]||null;if(typeof d=="function")p||jp(n);else if(p){var y=null;if(d&&d.hasAttribute("formAction")){if(l=d,p=d[pt]||null)y=p.formAction;else if(wd(l)!==null)continue}else y=p.action;typeof y=="function"?n[o+1]=y:(n.splice(o,3),o-=3),jp(n)}}}function Cp(){function e(d){d.canIntercept&&d.info==="react-transition"&&d.intercept({handler:function(){return new Promise(function(p){return l=p})},focusReset:"manual",scroll:"manual"})}function t(){l!==null&&(l(),l=null),o||setTimeout(n,20)}function n(){if(!o&&!navigation.transition){var d=navigation.currentEntry;d&&d.url!=null&&navigation.navigate(d.url,{state:d.getState(),info:"react-transition",history:"replace"})}}if(typeof navigation=="object"){var o=!1,l=null;return navigation.addEventListener("navigate",e),navigation.addEventListener("navigatesuccess",t),navigation.addEventListener("navigateerror",t),setTimeout(n,100),function(){o=!0,navigation.removeEventListener("navigate",e),navigation.removeEventListener("navigatesuccess",t),navigation.removeEventListener("navigateerror",t),l!==null&&(l(),l=null)}}}function vd(e){this._internalRoot=e}mr.prototype.render=vd.prototype.render=function(e){var t=this._internalRoot;if(t===null)throw Error(r(409));var n=t.current,o=Rt();vp(n,o,e,t,null,null)},mr.prototype.unmount=vd.prototype.unmount=function(){var e=this._internalRoot;if(e!==null){this._internalRoot=null;var t=e.containerInfo;vp(e.current,2,null,e,null,null),$o(),t[Va]=null}};function mr(e){this._internalRoot=e}mr.prototype.unstable_scheduleHydration=function(e){if(e){var t=qu();e={blockedOn:null,target:e,priority:t};for(var n=0;n<sa.length&&t!==0&&t<sa[n].priority;n++);sa.splice(n,0,e),n===0&&Ap(e)}};var Ip=i.version;if(Ip!=="19.2.5")throw Error(r(527,Ip,"19.2.5"));W.findDOMNode=function(e){var t=e._reactInternals;if(t===void 0)throw typeof e.render=="function"?Error(r(188)):(e=Object.keys(e).join(","),Error(r(268,e)));return e=g(t),e=e!==null?w(e):null,e=e===null?null:e.stateNode,e};var Qy={bundleType:0,version:"19.2.5",rendererPackageName:"react-dom",currentDispatcherRef:O,reconcilerVersion:"19.2.5"};
10if(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"){var br=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!br.isDisabled&&br.supportsFiber)try{Ni=br.inject(Qy),xt=br}catch{}}return Ms.createRoot=function(e,t){if(!c(e))throw Error(r(299));var n=!1,o="",l=Lf,d=qf,p=zf;return t!=null&&(t.unstable_strictMode===!0&&(n=!0),t.identifierPrefix!==void 0&&(o=t.identifierPrefix),t.onUncaughtError!==void 0&&(l=t.onUncaughtError),t.onCaughtError!==void 0&&(d=t.onCaughtError),t.onRecoverableError!==void 0&&(p=t.onRecoverableError)),t=wp(e,1,!1,null,null,n,o,null,l,d,p,Cp),e[Va]=t.current,td(e),new vd(t)},Ms.hydrateRoot=function(e,t,n){if(!c(e))throw Error(r(299));var o=!1,l="",d=Lf,p=qf,y=zf,k=null;return n!=null&&(n.unstable_strictMode===!0&&(o=!0),n.identifierPrefix!==void 0&&(l=n.identifierPrefix),n.onUncaughtError!==void 0&&(d=n.onUncaughtError),n.onCaughtError!==void 0&&(p=n.onCaughtError),n.onRecoverableError!==void 0&&(y=n.onRecoverableError),n.formState!==void 0&&(k=n.formState)),t=wp(e,1,!0,t,n??null,o,l,k,d,p,y,Cp),t.context=yp(null),n=t.current,o=Rt(),o=dl(o),l=Jn(o),l.callback=null,Gn(n,l,o),n=o,t.current.lanes=n,Pi(t,n),nn(t),e[Va]=t.current,td(e),new mr(t)},Ms.version="19.2.5",Ms}var Np;function o6(){if(Np)return xd.exports;Np=1;function a(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(a)}catch(i){console.error(i)}}return a(),xd.exports=s6(),xd.exports}var r6=o6(),l6="__TSS_CONTEXT",Nd=Symbol.for("TSS_SERVER_FUNCTION"),c6="application/x-tss-framed",Um=()=>window.__TSS_START_OPTIONS__;function Zd(a={}){if(a.isNotFound=!0,a.throw)throw a;return a}function Na(a){return a?.isNotFound===!0}function Dm(a){a.statusCode=a.statusCode||a.code||307;const i=new Headers(a.headers);a.href&&i.get("Location")===null&&i.set("Location",a.href);const s=new Response(null,{status:a.statusCode,headers:i});if(s.options=a,a.throw)throw s;return s}function Bm(a){return a instanceof Response&&!!a.options}function d6(a){if(a!==null&&typeof a=="object"&&a.isSerializedRedirect)return Dm(a)}function u6(a){return a.replaceAll("\0","/").replaceAll("�","/").replace(/~([~0r])/g,(i,s)=>s==="0"?"\0":s==="r"?"�":s)}const Hp=!1;function ha(a){return a[a.length-1]}function Lm(a,i){return typeof a=="function"?a(i):a}const eu=Object.prototype.hasOwnProperty;function qm(a){for(const i in a)if(eu.call(a,i))return!0;return!1}const Er=()=>Object.create(null),Ri=(a,i)=>da(a,i,!0);function da(a,i,s,r=0){if(a===i)return a;if(r++>500)return i;const c=Array.isArray(a)&&Array.isArray(i);if(!c&&!(Dr(a)&&Dr(i)))return i;const u=Object.keys(a),h=u.length,f=Object.keys(i),m=f.length;if(c?h!==a.length||m!==i.length||h&&ha(u)!==`${h-1}`||m&&ha(f)!==`${m-1}`:h!==Object.getOwnPropertyNames(a).length||m!==Object.getOwnPropertyNames(i).length||Object.getOwnPropertySymbols(i).length)return i;let g=0,w,b,v;if(c){for(;g<m&&(v=g,b=a[v],w=i[v],w=b===w?b:typeof b=="object"?da(b,w,s,r):w,w===b);g++);if(g===m&&h===m)return a}else{let T=h===m,x=!0;for(;g<m;g++){v=f[g],b=a[v];const I=i[v];w=b===I?b:typeof b=="object"?da(b,I,s,r):I,T&&=w===b&&(u[g]===v||eu.call(a,v)),x&&=Object.is(w,I),u[g]=w}if(T)return Object.getOwnPropertySymbols(a).length?i:a;if(x)return i}const _=c?f.fill(0):s?Er():{};for(let T=0;T<m;T++)v=c?T:f[T],c?(b=a[v],T>g&&(w=i[v],w=b===w?b:typeof b=="object"?da(b,w,s,r):w),_[v]=T<g?b:w):_[v]=u[T];return _}function Dr(a){return!a||typeof a!="object"?!1:(Object.getPrototypeOf(a)?.constructor??Object)===Object}function $t(a,i,s,r){if(a===i)return!0;if(Array.isArray(a)&&Array.isArray(i)){if(a.length!==i.length)return!1;for(let c=0,u=a.length;c<u;c++){const h=a[c],f=i[c];if(h!==f&&!$t(h,f,s,r))return!1}return!0}if(Dr(a)&&Dr(i)){if(s){for(const u in i)if((r||i[u]!==void 0)&&!$t(a[u],i[u],s,r))return!1;return!0}let c=0;if(r)c=Object.keys(a).length;else for(const u in a)a[u]!==void 0&&c++;for(const u in i)if((r||i[u]!==void 0)&&(c--===0||!$t(a[u],i[u],s,r)))return!1;return c===0}return!1}function h6(a){return typeof a?.message!="string"?!1:a.message.startsWith("Failed to fetch dynamically imported module")||a.message.startsWith("error loading dynamically imported module")||a.message.startsWith("Importing a module script failed")}const f6=/[\x00-\x1f\x7f"<>`{}]/g;function g6(a){return a.replace(f6,i=>"%"+i.charCodeAt(0).toString(16).toUpperCase().padStart(2,"0"))}function Pp(a){let i;try{i=decodeURI(a)}catch{i=a.replaceAll(/%[0-9A-F]{2}/gi,s=>{try{return decodeURI(s)}catch{return s}})}return g6(i)}const p6=["http:","https:","mailto:","tel:"];function Hs(a){if(a[0]!=="/"&&a.includes(":"))return/^[\x00-\x20]*([a-z][a-z\d+.\t\n\r-]*:)/i.exec(a)?.[1]?.replace(/[\t\n\r]/g,"").toLowerCase()}const zm=/^[\x00-\x20]*[\\/][\t\n\r]*[\\/]/;function Nm(a,i){if(!a)return!1;if(zm.test(a))return!0;const s=Hs(a);return s?!i.has(s):!1}const m6={"&":"\\u0026",">":"\\u003e","<":"\\u003c","\u2028":"\\u2028","\u2029":"\\u2029"},b6=/[&><\u2028\u2029]/g;function w6(a){return a.replace(b6,i=>m6[i])}
10function Us(a){if(!a)return a;let i=a;if(/[%\\\x00-\x1f\x7f]/.test(a)){const s=/%25|%5C/gi;let r=0,c;for(i="";(c=s.exec(a))!==null;)i+=Pp(a.slice(r,c.index))+c[0],r=s.lastIndex;i+=Pp(r?a.slice(r):a)}return i}function y6(a){return/[\s\u0080-\uFFFF]/.test(a)?a.replace(/\s|[^\u0000-\u007F]/gu,encodeURIComponent):a}function v6(a,i){if(a===i)return!0;if(a.length!==i.length)return!1;for(let s=0;s<a.length;s++)if(a[s]!==i[s])return!1;return!0}function Di(a){return a.replace(/\/{2,}/g,"/")}function Hm(a){return a==="/"?a:a.replace(/^\/+/,"")}function on(a){const i=a.length;return i>1&&a[i-1]==="/"?a.replace(/\/+$/,""):a}function Pm(a){return on(Hm(a))}function Fp(a,i){return a?.endsWith("/")&&a!=="/"&&a!==`${i}/`?a.slice(0,-1):a}function Jp(a,i,s="never",r){if(i.includes("//")&&(i=Di(i)),i.startsWith("/"))return i.length===1||s==="preserve"?i:s==="always"?i.endsWith("/")?i:`${i}/`:i.endsWith("/")?i.slice(0,-1):i;const c=i===".";let u;if(r){u=c?a:a+"\0"+i;const g=r.get(u);if(g)return g}let h;if(c)h=a.split("/");else{for(a.includes("//")&&(a=Di(a)),h=a.split("/");h.length>1&&ha(h)==="";)h.pop();const g=i.split("/");for(let w=0,b=g.length;w<b;w++){const v=g[w];v===""?w?w===b-1&&h.push(v):h=[v]:v===".."?h.length>1?h.pop():h=[""]:v==="."||h.push(v)}}h.length>1&&(ha(h)===""?s==="never"&&h.pop():s==="always"&&h.push(""));const f=h.join("/"),m=(c?Di(f):f)||"/";return u&&r&&r.set(u,m),m}function _6(a){const i=new Map(a.map(r=>[encodeURIComponent(r),r])),s=new RegExp([...i.keys()].join("|").replace(/[.*()]/g,"\\$&"),"g");return r=>r.replace(s,c=>i.get(c)??c)}function k6(a){return a==null||a===""}function x6(a,i,s){if(typeof i!="string")return""+(i??void 0);const r=a==="_splat";if(r&&(!i||/^[a-zA-Z0-9\-._~!/]*$/.test(i)))return i;let c=encodeURIComponent(i);return r&&(c=c.replaceAll("%2F","/")),s?s(c):c}function Gp(a,i,s,r,c){const u=a.endsWith("/")?"/":"";let h="";for(const f of i){if(typeof f=="string"){h+=f;continue}const[m,g,w,b]=f,v=m===2,_=v&&b!==void 0?b+u:b;let T=s[g];if(!(m===3&&T==null)){if(c&&(c[g]=T,v&&(c["*"]=T)),v&&k6(T)){if(w==="/"&&!_)continue;T=""}h+=w+x6(g,T,r)+(_||"")}}return h+u||"/"}function Br(a){const i=new Map;let s,r;return{get(c){const u=i.get(c);if(u)return u.visited=!0,u.value},set(c,u){const h=i.get(c);if(h){h.value=u;return}if(i.size>=a){let m=s?.next().value;for(;!m||m.visited;)m?m.visited=!1:s=i.values(),m=s.next().value;m===r&&(s=void 0),i.delete(m.key)}const f={key:c,value:u,visited:!1};r=f,i.set(c,f)},clear(){i.clear(),s=void 0,r=void 0}}}function Ps(){throw new Error("Invariant failed")}const ca=4,Fm=5;function T6(a){const i=a.names;if(i)return i;const s=[];for(const r of a)typeof r!="string"&&s.push(r[1]);return a.names=s}function S6(a,i,s){const r=a.substring(i,s);if(r.charCodeAt(0)===36)return r.length===1?[2,"_splat","",void 0]:[1,r.substring(1),"",""];const c=r.indexOf("{");if(c>=0){const u=r.indexOf("}",c),h=r.charCodeAt(c+1)===45,f=c+(h?3:2);if(u>=0&&r.charCodeAt(f-1)===36&&(!h||f<u)){const m=r.substring(f,u);return[h?3:m?1:2,m||"_splat",r.substring(0,c),a.substring(i+u+1,m?s:a.length)]}}return r}function Fs(a,i,s,r,c,u){let h=s;const f=i.fullPath??i.from,m=i.options,g=f.length,w=f.endsWith("/")?g-1:g,b=m?.caseSensitive??a,v=m?.params?.parse??m?.parseParams;let _,T=u?s-1:0;if(!r||f.includes("$")){_=u?.slice()??[];const U=ha(_);U&&typeof U!="string"&&U[0]===2&&(_[_.length-1]=[U[0],U[1],U[2],U[3]===void 0?void 0:U[3]+f.substring(s-(f[s-2]==="/"?2:1),w)],T=g)}for(;h<g;){const U=h,N=f.indexOf("/",U);let L=N===-1?g:N;const G=S6(f,U,L);h=L+1;let Z;if(typeof G=="string"){if(!r)continue;let J=G,M;b?M=r.static??=new Map:(J=G.toLowerCase(),M=r.staticInsensitive??=new Map);const V=M.get(J);if(V)Z=V;else{const ae=Ba(r);Z=ae,M.set(J,ae)}}else{const J=G[0];let M=G[2],V=G[3]??"";if(J===2&&(L=g,h=L+1),_&&T<L&&(T<U-1&&_.push(f.substring(T,U-1)),G[2]="/"+M,J===2&&G[3]!==void 0&&w<g&&(G[3]=V.slice(0,-1)),_.push(G),T=L),!r)continue;const ae=b&&!!(M||V);b||(M=M.toLowerCase(),V=V.toLowerCase());const le=J===1?r.dynamic??=[]:J===3?r.optional??=[]:r.wildcard??=[],F=J!==2&&!v&&le.find(P=>!P.parse&&P.caseSensitive===ae&&P.prefix===M&&P.suffix===V);if(F)Z=F;else{const P=Ba(r,J,ae,M,V);Z=P,le.push(P),le.length===2&&c?.push(le)}}r=Z}_&&T<w&&_.push(f.substring(T,w));const x=_?.slice();if(!r)return x;
10if(v&&i.children&&!i.isRoot&&i.id&&i.id.charCodeAt(i.id.lastIndexOf("/")+1)===95){const U=Ba(r,Fm);(r.pathless??=[]).push(U),r=U}const I=(i.path||!i.children)&&!i.isRoot;if(I&&w<g){const U=Ba(r,ca);r.index=U,r=U}return r.parse=v??null,r.priority=m?.params?.priority??0,r.route||(r.data=x,I&&(r.route=i)),[r,h,x]}function Jm(a,i){if(a.parse&&!i.parse)return-1;if(!a.parse&&i.parse)return 1;if(a.parse&&i.parse&&(a.priority||i.priority))return i.priority-a.priority;if(a.prefix&&i.prefix&&a.prefix!==i.prefix){if(a.prefix.startsWith(i.prefix))return-1;if(i.prefix.startsWith(a.prefix))return 1}if(a.suffix&&i.suffix&&a.suffix!==i.suffix){if(a.suffix.endsWith(i.suffix))return-1;if(i.suffix.endsWith(a.suffix))return 1}return a.prefix&&!i.prefix?-1:!a.prefix&&i.prefix?1:a.suffix&&!i.suffix?-1:!a.suffix&&i.suffix?1:a.caseSensitive&&!i.caseSensitive?-1:!a.caseSensitive&&i.caseSensitive?1:0}function Ba(a,i=0,s,r,c){return{kind:i,depth:a?a.depth+1:0,pathless:null,index:null,static:null,staticInsensitive:null,dynamic:null,optional:null,wildcard:null,route:null,data:void 0,parent:a,parse:null,priority:0,caseSensitive:s,prefix:r,suffix:c}}function A6(a,i){const s=Ba(),r=[];function c(u,h,f,m){const[g,w,b]=Fs(!1,u,h,f,r,m);if(u.children)for(const v of u.children)c(v,w,g,b)}for(const u of a)c(u,1,s);for(const u of r)u.sort(Jm);i.masksTree=s,i.flatCache=Br(1e3)}function E6(a,i){a||="/";const s=i.flatCache.get(a);if(s!==void 0)return s;const r=tu(a,i.masksTree);return i.flatCache.set(a,r),r}function j6(a,i,s,r,c){a||="/",r||="/";const u=i?`case\0${a}`:a;let h=c.singleCache.get(u);return h||(h=Ba(),Fs(i,{from:a},1,h),c.singleCache.set(u,h)),tu(r,h,s)}function C6(a,i,s=!1){const r=s?a:`nofuzz\0${a}`,c=i.matchCache.get(r);if(c!==void 0)return c;a||="/";let u;try{u=tu(a,i.segmentTree,s)}catch(h){if(h instanceof URIError)u=null;else throw h}return u&&(u.branch=Ym(u.route)),i.matchCache.set(r,u),u}function I6(a,i=!1){const s=Ba(),r=[],c={},u={};let h=0;function f(m,g,w,b){if(m.init(h),m.id in c&&Ps(),c[m.id]=m,h!==0&&m.path){const x=on(m.fullPath);(!u[x]||m.fullPath.endsWith("/"))&&(u[x]=m)}h++;const[v,_,T]=Fs(i,m,g,w,r,b);if(m._interpolation=T,m.children)for(const x of m.children)f(x,_,v,T)}f(a,1,s);for(const m of r)m.sort(Jm);return{processedTree:{segmentTree:s,singleCache:Br(1e3),matchCache:Br(1e3),flatCache:null,masksTree:null},routesById:c,routesByPath:u}}function tu(a,i,s=!1){const r=a.split("/"),c=O6(a,r,i,s);if(!c)return null;const[u]=Gm(a,r,c);return{route:c.node.route,rawParams:u}}function Gm(a,i,s){const r=R6(s.node),c=s.node.data&&T6(s.node.data),u=Object.create(null);let h=s.extract?.part??0,f=s.extract?.node??0,m=s.extract?.path??0,g=s.extract?.param??0;for(;f<r.length;h++,f++,m++){const w=r[f];if(w.kind===ca)break;if(w.kind===Fm){h--,m--;continue}const b=i[h],v=m;if(b&&(m+=b.length),w.kind===1||w.kind===3){const _=c[g++];if(w.kind===3&&s.skipped&1<<f){h--,m=v-1;continue}const T=w.suffix||w.prefix?b.substring(w.prefix.length,b.length-w.suffix.length):b;(T||w.kind===1)&&(u[_]=decodeURIComponent(T))}else if(w.kind===2){const _=w,T=a.substring(v+_.prefix.length,a.length-_.suffix.length),x=decodeURIComponent(T);u["*"]=x,u._splat=x;break}}return s.rawParams&&Object.assign(u,s.rawParams),[u,{part:h,node:f,path:m,param:g}]}function Ym(a){const i=[a];for(;a.parentRoute;)a=a.parentRoute,i.push(a);return i.reverse(),i}function R6(a){const i=Array(a.depth+1);do i[a.depth]=a,a=a.parent;while(a);return i}function O6(a,i,s,r){if(a==="/"&&s.index)return{node:s.index,skipped:0};const c=!ha(i),u=c&&a!=="/",h=i.length-(c?1:0),f=[{node:s,index:1,skipped:0,statics:0,dynamics:0,optionals:0}];let m=null,g=null;for(;f.length;){const w=f.pop(),{node:b,index:v,skipped:_,statics:T,dynamics:x,optionals:I}=w;let{extract:U,rawParams:N}=w;if(b.kind===2&&b.route&&!yr(g,w))continue;if(b.parse){if(!Yp(a,i,w))continue;N=w.rawParams,U=w.extract}r&&b.route&&b.kind!==ca&&yr(m,w)&&(m=w);const L=v===h;if(L&&(b.route&&(!u||b.kind===ca||b.kind===2)&&yr(g,w)&&(g=w),!b.optional&&!b.wildcard&&!b.index&&!b.pathless))continue;const G=L?void 0:i[v];let Z;if(L&&b.index){const J={node:b.index,index:v,skipped:_,statics:T,dynamics:x,optionals:I,extract:U,rawParams:N};let M=!0;if(b.index.parse&&(Yp(a,i,J)||(M=!1)),M){if(!x&&!I&&!_&&M6(T,h))return J;yr(g,J)&&(g=J)}}if(b.wildcard)for(let J=b.wildcard.length-1;J>=0;J--){const M=b.wildcard[J],{prefix:V,suffix:ae}=M;if(!(V&&(L||!(M.caseSensitive?G:Z??=G.toLowerCase()).startsWith(V)))){if(ae){if(L)continue;const le=i.slice(v).join("/"),F=le.slice(-ae.length);if((M.caseSensitive?F:F.toLowerCase())!==ae||le.length-ae.length<V.length)continue}f.push({node:M,index:h,skipped:_,statics:T,dynamics:x,optionals:I,extract:U,rawParams:N})}}if(b.optional){const J=_|1<<b.depth+1;for(let M=b.optional.length-1;M>=0;M--){const V=b.optional[M];f.push({node:V,index:v,skipped:J,statics:T,dynamics:x,optionals:I,extract:U,rawParams:N})}if(!L)for(let M=b.optional.length-1;M>=0;M--){const V=b.optional[M],{prefix:ae,suffix:le}=V;if(ae||le){const F=V.caseSensitive?G:Z??=G.toLowerCase();if(ae&&!F.startsWith(ae)||le&&F.indexOf(le,F.length-le.length)<ae.length)continue}f.push({node:V,index:v+1,skipped:_,statics:T,dynamics:x,optionals:I+wr(h,v),extract:U,rawParams:N})}}if(!L&&b.dynamic&&G)for(let J=b.dynamic.length-1;J>=0;J--){const M=b.dynamic[J],{prefix:V,suffix:ae}=M;if(V||ae){const le=M.caseSensitive?G:Z??=G.toLowerCase();if(V&&!le.startsWith(V)||ae&&le.indexOf(ae,le.length-ae.length)<V.length)continue}f.push({node:M,index:v+1,skipped:_,statics:T,dynamics:x+wr(h,v),optionals:I,extract:U,rawParams:N})}if(!L&&b.staticInsensitive){const J=b.staticInsensitive.get(Z??=G.toLowerCase());J&&f.push({node:J,index:v+1,skipped:_,statics:T+wr(h,v),dynamics:x,optionals:I,extract:U,rawParams:N})}if(!L&&b.static){const J=b.static.get(G);J&&f.push({node:J,index:v+1,skipped:_,statics:T+wr(h,v),dynamics:x,optionals:I,extract:U,rawParams:N})}if(b.pathless)for(let J=b.pathless.length-1;J>=0;J--){const M=b.pathless[J];f.push({node:M,index:v,skipped:_,statics:T,dynamics:x,optionals:I,extract:U,rawParams:N})}}if(g)return g;if(r&&m){let w=m.index;for(let v=0;v<m.index;v++)w+=i[v].length;const b=w===a.length?"/":a.slice(w);return m.rawParams??=Object.create(null),m.rawParams["**"]=decodeURIComponent(b),m}return null}function wr(a,i){return 2**(a-i-1)}function M6(a,i){return a===2**(i-1)-1}function Yp(a,i,s){let r,c;try{[r,c]=Gm(a,i,s)}catch{return null}if(s.rawParams=r,s.extract=c,!s.node.parse)return!0;try{if(s.node.parse(r)===!1)return null}catch{}return!0}function yr(a,i){return a?i.statics>a.statics||i.statics===a.statics&&(i.dynamics>a.dynamics||i.dynamics===a.dynamics&&(i.optionals>a.optionals||i.optionals===a.optionals&&((i.node.kind===ca)>(a.node.kind===ca)||i.node.kind===ca==(a.node.kind===ca)&&i.node.depth>a.node.depth))):!0}function U6(){try{return sessionStorage}catch{return}}const D6="tsr-scroll-restoration-v1_3",Vm=U6();function B6(){try{return JSON.parse(Vm?.getItem("tsr-scroll-restoration-v1_3")||"{}")}catch{return{}}}const Oi=B6(),Vp="data-scroll-restoration-id",L6=a=>a.state.__TSR_key||a.href;function q6(a){const i=a.getAttribute(Vp);if(i)return`[${Vp}="${i}"]`;let s="",r=a,c;for(;c=r.parentNode;){let u=1,h=r;for(;h=h.previousElementSibling;)u++;const f=`${r.localName}:nth-child(${u})`;s=s?`${f} > ${s}`:f,r=c}return s}let vr=!1;const jr="window";function Hd(a){try{return typeof a=="function"?a():document.querySelector(a)}catch{}}function Wp(a){const i=new Set;for(const s of a){if(s===jr)continue;const r=Hd(s);r&&i.add(r)}return i}function z6(a,i){const s=a.options.scrollRestoration,r=a._scroll;s&&(r.e=!0);const c=a.options.getScrollRestorationKey||L6,u=new Set,h=f=>{const m=Oi[f]||={};for(const g of u)g===document?m[jr]={scrollX,scrollY}:g.isConnected&&(m[q6(g)]={scrollX:g.scrollLeft,scrollY:g.scrollTop})};s&&!r.s&&(r.s=!0,vr=!1,history.scrollRestoration="manual",document.addEventListener("scroll",f=>{vr||u.add(f.target)},!0),a.subscribe("onBeforeLoad",f=>{f.fromLocation&&h(c(f.fromLocation)),u.clear()}),addEventListener("pagehide",()=>{history.scrollRestoration="auto",h(c(a.stores.resolvedLocation.get()??a.stores.location.get()));try{Vm?.setItem(D6,JSON.stringify(Oi))}catch{}}),addEventListener("pageshow",f=>{f.persisted&&(history.scrollRestoration="manual")})),!r.r&&(r.r=!0,a.subscribe("onRendered",f=>{const m=a.options.scrollRestorationBehavior,g=a.options.scrollToTopSelectors,w=r.n,b=r.h;let v;if(u.clear(),r.n=!0,r.h=!1,typeof a.options.scrollRestoration=="function"&&!a.options.scrollRestoration({location:a.latestLocation}))return;const _=c(f.toLocation),T=f.fromLocation&&c(f.fromLocation);if(r.e&&T&&T!==_){const x=Oi[T];if(x){let I=Oi[_];for(const U in x){if(U===jr){if(w)continue}else{const N=Hd(U);if(!N||w&&g&&(v??=Wp(g),v.has(N)))continue}I||(I=Oi[_]={}),I[U]??=x[U]}}}vr=!0;try{const x=f.toLocation.hash,I=f.toLocation.state.__hashScrollIntoViewOptions??!0;let U=!1;if(w){!x&&g&&(v??=Wp(g));const N=x&&I&&b,L=r.e?Oi[_]:void 0;if(L)for(const G in L){const{scrollX:Z,scrollY:J}=L[G];if(G===jr){if(N)continue;scrollTo({top:J,left:Z,behavior:m}),U=!0}else{const M=Hd(G);M&&(M.scrollLeft=Z,M.scrollTop=J,v?.delete(M))}}if(!x){const G={top:0,left:0,behavior:m};if(U||scrollTo(G),v)for(const Z of v)Z.scrollTo(G)}}!U&&x&&I&&document.getElementById(x)?.scrollIntoView(I)}finally{vr=!1}}
10))}function Wm(a,i=String){let s;for(const r in a){const c=a[r];c!==void 0&&(s||=new URLSearchParams).set(r,i(c))}return s?s.toString():""}function Ed(a){return a?a==="false"?!1:a==="true"?!0:+a*0===0&&+a+""===a?+a:a:""}function N6(a){const i=new URLSearchParams(a),s=Object.create(null);for(const[r,c]of i.entries()){const u=s[r];u==null?s[r]=Ed(c):Array.isArray(u)?u.push(Ed(c)):s[r]=[u,Ed(c)]}return s}const Qm=/^(?:\s|["[{\d-]|fa|nu|tr)/,H6=F6(JSON.parse),P6=J6(JSON.stringify,JSON.parse);function F6(a){const i=a===JSON.parse;return s=>{s[0]==="?"&&(s=s.substring(1));const r=N6(s);for(const c in r){const u=r[c];if(typeof u=="string"){if(i&&!Qm.test(u))continue;try{r[c]=a(u)}catch{}}}return r}}function J6(a,i){const s=i===JSON.parse;function r(c){if(c&&typeof c=="object")try{return a(c)}catch{}else if(i&&typeof c=="string"){if(s&&!Qm.test(c))return c;try{return i(c),a(c)}catch{}}return c}return c=>{const u=Wm(c,r);return u?`?${u}`:""}}const Bi="__root__";function G6(a,i,s){const r=Pm(a),c=`/${r}`,u=i?c:c.toLowerCase(),h=`${u}/`,f={input:({url:m})=>{const g=i?m.pathname:m.pathname.toLowerCase();return g===u?m.pathname="/":g.startsWith(h)&&(m.pathname=m.pathname.slice(c.length)),m},output:({url:m})=>(m.pathname=Di(`/${r}${m.pathname}`),m)};return s?{input:({url:m})=>Pd(s,f.input({url:m})),output:({url:m})=>f.output({url:$m(s,m)})}:f}function Pd(a,i){const s=a?.input?.({url:i});if(s){if(typeof s=="string")return new URL(s);if(s instanceof URL)return s}return i}function $m(a,i){const s=a?.output?.({url:i});if(s){if(typeof s=="string")return new URL(s);if(s instanceof URL)return s}return i}function Y6(a,i){const{createMutableStore:s,createReadonlyStore:r,batch:c}=i,u=new Map,h=s("idle"),f=s(a),m=s(void 0),g=s([]),w=r(()=>g.get().map(x=>u.get(x).get())),b=r(()=>({status:h.get(),isLoading:h.get()==="pending",matches:w.get(),location:f.get(),resolvedLocation:m.get()}));function v(x){let I=u.get(x);return I||(I=s(void 0),u.set(x,I)),I}const _={status:h,location:f,resolvedLocation:m,ids:g,matches:w,byRoute:u,__store:b,getMatchStore:v,setMatches:T};function T(x){const I=g.get(),U=x.map(N=>N.routeId);c(()=>{v6(I,U)||g.set(U);for(const N of I)U.includes(N)||u.get(N).set(()=>{});for(const N of x){const L=v(N.routeId);L.get()!==N&&L.set(N)}})}return _}var ua="__TSR_index",Qp="popstate",$p="beforeunload",V6=/^[\x00-\x20]*(?:[\\/][\t\n\r]*){2,}/;function Cr(a){const i=V6.exec(a);return i?"/"+a.slice(i[0].length):a}function Xm(a){return/[\x00-\x1f\x7f]/.test(a)&&(a=a.replace(/[\x00-\x1f\x7f]/g,i=>`	
11\r`.includes(i)?"":encodeURIComponent(i))),Cr(a)}function W6(a){let i=a.getLocation();const s=new Set,r=h=>{i=a.getLocation(),s.forEach(f=>f({location:i,action:h}))},c=h=>{a.notifyOnIndexChange??!0?r(h):i=a.getLocation()},u=async({task:h,navigateOpts:f,...m})=>{if(f?.ignoreBlocker??!1){h();return}const g=a.getBlockers?.()??[],w=m.type==="PUSH"||m.type==="REPLACE";if(typeof document<"u"&&g.length&&w)for(const b of g){const v=Lr(m.path,m.state);if(await b.blockerFn({currentLocation:i,nextLocation:v,action:m.type})){a.onBlocked?.();return}}h()};return{get location(){return i},get length(){return a.getLength()},subscribers:s,subscribe:h=>(s.add(h),()=>{s.delete(h)}),push:(h,f,m)=>{const g=i.state[ua];f=Xp(g+1,f),u({task:()=>{a.pushState(h,f),r({type:"PUSH"})},navigateOpts:m,type:"PUSH",path:h,state:f})},replace:(h,f,m)=>{const g=i.state[ua];f=Xp(g,f),u({task:()=>{a.replaceState(h,f),r({type:"REPLACE"})},navigateOpts:m,type:"REPLACE",path:h,state:f})},go:(h,f)=>{u({task:()=>{a.go(h,f?.ignoreBlocker??!1),c({type:"GO",index:h})},navigateOpts:f,type:"GO"})},back:h=>{u({task:()=>{a.back(h?.ignoreBlocker??!1),c({type:"BACK"})},navigateOpts:h,type:"BACK"})},forward:h=>{u({task:()=>{a.forward(h?.ignoreBlocker??!1),c({type:"FORWARD"})},navigateOpts:h,type:"FORWARD"})},canGoBack:()=>i.state[ua]!==0,createHref:h=>a.createHref(h),block:h=>{if(!a.setBlockers)return()=>{};const f=a.getBlockers?.()??[];return a.setBlockers([...f,h]),()=>{const m=a.getBlockers?.()??[];a.setBlockers?.(m.filter(g=>g!==h))}},flush:()=>a.flush?.(),destroy:()=>a.destroy?.(),notify:r,_getBlockers:()=>a.getBlockers?.()??[]}}function Xp(a,i){const s=nu();return{...i,key:s,__TSR_key:s,[ua]:a}}function Q6(a){const i=typeof document<"u"?window:void 0,s=i.history.pushState,r=i.history.replaceState;let c=[];const u=()=>c,h=M=>c=M,f=M=>Xm(M),m=(()=>Lr(`${i.location.pathname}${i.location.search}${i.location.hash}`,i.history.state));if(!i.history.state?.__TSR_key&&!i.history.state?.key){const M=nu();i.history.replaceState({[ua]:0,key:M,__TSR_key:M},"")}let g=m(),w,b=!1,v=!1,_=!1,T=!1;const x=()=>g;let I;const U=()=>{I&&(J._ignoreSubscribers=!0,(I[2]?i.history.pushState:i.history.replaceState)(I[1],"",I[0]),J._ignoreSubscribers=!1,I=void 0,w=void 0)},N=(M,V,ae)=>{const le=!!I;le||(w=g),g=Lr(V,ae),I=[g.href,ae,I?.[2]||M],le||queueMicrotask(()=>U())},L=M=>{g=m(),J.notify({type:M})},G=async()=>
11{if(T=!1,v){v=!1;return}const M=m(),V=M.state[ua]-g.state[ua],ae=V===1,le=V===-1,F=!ae&&!le||b;b=!1;const P=F?"GO":le?"BACK":"FORWARD",$=F?{type:"GO",index:V}:{type:le?"BACK":"FORWARD"};if(_)_=!1;else{const ce=u();if(typeof document<"u"&&ce.length){for(const te of ce)if(await te.blockerFn({currentLocation:g,nextLocation:M,action:P})){v=!0,i.history.go(-V),J.notify($);return}}}g=m(),J.notify($)},Z=M=>{if(T){T=!1;return}let V=!1;const ae=u();if(typeof document<"u"&&ae.length)for(const le of ae){const F=le.enableBeforeUnload??!0;if(F===!0){V=!0;break}if(typeof F=="function"&&F()===!0){V=!0;break}}if(V)return M.preventDefault(),M.returnValue=""},J=W6({getLocation:x,getLength:()=>i.history.length,pushState:(M,V)=>N(!0,M,V),replaceState:(M,V)=>N(!1,M,V),back:M=>(M&&(_=!0,T=!0),i.history.back()),forward:M=>{M&&(_=!0,T=!0),i.history.forward()},go:(M,V)=>{b=!0,V&&(_=!0,T=!0),i.history.go(M)},createHref:M=>f(M),flush:U,destroy:()=>{i.history.pushState=s,i.history.replaceState=r,i.removeEventListener($p,Z,{capture:!0}),i.removeEventListener(Qp,G)},onBlocked:()=>{w&&g!==w&&(g=w)},getBlockers:u,setBlockers:h,notifyOnIndexChange:!1});return J._ignoreNextBeforeUnload=M=>{T=!1;try{M=new URL(M,i.document.baseURI).href,T=/^https?:/.test(M)&&(!M.includes("#")||M.split("#")[0]!==i.location.href.split("#")[0])}catch{}},i.addEventListener($p,Z,{capture:!0}),i.addEventListener(Qp,G),i.history.pushState=function(...M){const V=s.apply(i.history,M);return J._ignoreSubscribers||L("PUSH"),V},i.history.replaceState=function(...M){const V=r.apply(i.history,M);return J._ignoreSubscribers||L("REPLACE"),V},J}function Lr(a,i){const s=Xm(a),r=s.indexOf("#"),c=s.indexOf("?");if(!i){const u=nu();i={[ua]:0,key:u,__TSR_key:u}}return{href:s,pathname:s.substring(0,r>0?c>0?Math.min(r,c):r:c>0?c:s.length),hash:r>-1?s.substring(r):"",search:c>-1?s.slice(c,r===-1?void 0:r):"",state:i}}function nu(){return(Math.random()+1).toString(36).substring(7)}function jd(a,i){return a.protocol!=="http:"&&a.protocol!=="https:"||a.origin!==i||!!a.username||!!a.password}function Cd(a){return a.pathname+a.search+a.hash}function Kp(a){return a.options.loader||a.options.beforeLoad||a.lazyFn||a.options.component?.preload||a.options.pendingComponent?.preload}function $6(a){return a instanceof Error?{name:a.name,message:a.message}:{data:a}}function Gr(a,i){return{fromLocation:i,toLocation:a,pathChanged:i?.pathname!==a.pathname,hrefChanged:i?.href!==a.href,hashChanged:i?.hash!==a.hash}}function Zp({key:a,__TSR_key:i,__TSR_index:s,__hashScrollIntoViewOptions:r,...c}){return c}function Km(a){return a.findIndex(i=>i.status==="error"||i.status==="notFound"||i._notFound)+1}function X6(a,i,s,r,c,u){r&&(i=i.slice(0,r)),c&&(s=s.slice(0,c));for(const h of i){if(u&&a._tx!==u)return;s.some(f=>f.routeId===h.routeId)||a.routesById[h.routeId].options.onLeave?.(h)}for(const h of s){if(u&&a._tx!==u)return;a.routesById[h.routeId].options[i.some(f=>f.routeId===h.routeId)?"onStay":"onEnter"]?.(h)}}var K6=class{constructor(a,i){this.tempLocationKey=`${Math.round(Math.random()*1e7)}`,this._scroll={n:!0},this.subscribers=new Set,this._cache=new Map,this._committed=[],this.startTransition=async s=>(s(),!1),this.update=s=>{const r=this.options;this.options={...r,...s},this.isServer=this.options.isServer??Hp??typeof document>"u",this.staticLocations=new WeakMap,this.protocolAllowlist=new Set(this.options.protocolAllowlist),(!this.history||this.options.history&&this.options.history!==this.history)&&(this.options.history?this.history=this.options.history:this.history=Q6()),this.origin=this.options.origin,this.origin||(window?.origin&&window.origin!=="null"?this.origin=window.origin:this.origin="http://localhost");const c=this.options.basepath??"/",u=this.options.rewrite,h=this.basepath!==c||r?.rewrite!==u||r?.caseSensitive!==this.options.caseSensitive;if(h&&(this.basepath=c,this.rewrite=c!=="/"&&Pm(c)?G6(c,this.options.caseSensitive,u):u),this.history&&this.updateLatestLocation(),this.options.routeTree!==this.routeTree||Hp){this.routeTree=this.options.routeTree;let f;f=this.buildRouteTree(),this.setRoutes(f)}if(this.stores)h&&this.stores.location.set(this.latestLocation);else if(this.latestLocation){const f=this.getStoreConfig(this);this.batch=f.batch,this.stores=Y6(this.latestLocation,f),z6(this)}},this.updateLatestLocation=()=>{this.latestLocation=this.parseLocation(this.history.location,this.latestLocation)},this.buildRouteTree=()=>{const s=I6(this.routeTree,this.options.caseSensitive);return this.options.routeMasks&&A6(this.options.routeMasks,s.processedTree),{...s,resolvePathCache:Br(1e3)}},this.subscribe=(s,r)=>{const c={eventType:s,fn:r};return this.subscribers.add(c),()=>{this.subscribers.delete(c)}},this.emit=s=>{for(const r of this.subscribers)if(r.eventType===s.type)try{r.fn(s)}catch(c){console.error(c)}},this.parseLocation=(s,r)=>{const c=({pathname:m,search:g,hash:w,href:b},v)=>{if(!this.rewrite&&!/[ \x00-\x1f\x7f\u0080-\uffff]/.test(m)){const I=this.options.parseSearch(g),U=this.options.stringifySearch(I);return{href:m+U+w,publicHref:m+U+w,pathname:Us(m),external:!1,searchStr:U,search:Ri(r?.search,I),hash:Us(w.slice(1)),state:da(r?.state,v)}}const _=Pd(this.rewrite,new URL(b,this.origin)),T=this.options.parseSearch(_.search),x=this.options.stringifySearch(T);return _.search=x,{href:_.href.replace(_.origin,""),publicHref:b,pathname:Us(Cr(_.pathname)),external:!!this.rewrite&&jd(_,this.origin),searchStr:x,search:Ri(r?.search,T),hash:Us(_.hash.slice(1)),state:da(r?.state,v)}},u=c(s,s.state),{__tempLocation:h,__tempKey:f}=u.state;if(h&&(!f||f===this.tempLocationKey)){const m=c(h,{...h.state,__tempLocation:void 0,key:u.state.key,__TSR_key:u.state.__TSR_key});return m.maskedLocation=u,m}return u},this.matchRoutes=(s,r,c)=>typeof s=="string"?this.matchRoutesInternal({pathname:s,search:r},c):this.matchRoutesInternal(s,r),this.getMatchedRoutes=s=>{const r=Object.create(null),c=C6(on(s),this.processedTree,!0);return c&&Object.assign(r,c.rawParams),[c?.branch||[this.routesById.__root__],r,c?.route]},this.buildLocation=s=>{{const h=this.staticLocations.get(s);if(h)return h}let r=!1;const c=(h={})=>
11{if(h.href){const ce=Lr(h.href,{});h={...h,to:Pd(this.rewrite,new URL(ce.pathname,this.origin)).pathname,search:this.options.parseSearch(ce.search),hash:ce.hash.slice(1)}}const f=h._fromLocation||this._pendingLocation||this.latestLocation;let m;const g=()=>(r=!0,f),w=()=>(r=!0,m??=this.matchRoutesLightweight(f)),b=h.to?`${h.to}`:".",v=Jp(b[0]==="/"?"":h.unsafeRelative==="path"?g().pathname:h.from??w()[1],b,this.options.trailingSlash,this.resolvePathCache),_=this.routesByPath[on(v)],T=v.includes("$");let x;if(_)x=_._branch??=Ym(_);else if(T)x=[];else{const[ce,te,O]=this.getMatchedRoutes(v);x=ce,this.options.notFoundRoute&&(!O||O.path!=="/"&&te["**"])&&(x=[...x,this.options.notFoundRoute])}const I=T?_?._interpolation??Fs(!1,{fullPath:v},0):void 0;let U;for(const ce of x){const te=ce.options.params?.stringify??ce.options.stringifyParams;if(te){const O=w()[3];if(U??=Id(h.params,O),!qm(U))break;U===O&&(U=Object.assign(Er(),U));try{Object.assign(U,te(U))}catch{}}}U??=Id(h.params,ev(h.params,I)?w()[3]:Rd);const N=s.leaveParams?v:Cr(Us(I?Gp(v,I,U,this.pathParamsDecoder):v)),L=tv(x,s._includeValidateSearch),G=()=>{let ce=w()[2];if(s._includeValidateSearch&&this.options.search?.strict){const te={};x.forEach(O=>{if(O.options.validateSearch)try{Object.assign(te,Ir(O.options.validateSearch,{...te,...ce}))}catch{}}),ce=te}return ce},Z=L.length?nv(L,G(),h):h.search===!0?G():typeof h.search=="function"?h.search(G()):h.search||Rd,J=this.options.stringifySearch(Z),M=h.hash===!0?g().hash:typeof h.hash=="function"?h.hash(g().hash):h.hash||void 0,V=M?`#${M}`:"",ae=h.state?h.state===!0?g().state:typeof h.state=="function"?h.state(g().state):h.state:Rd,le=`${N}${J}${V}`;let F,P,$=!1;if(this.rewrite){const ce=new URL(le,this.origin),te=ce.origin,O=$m(this.rewrite,ce);F=Cd(ce),jd(O,te)?(P=O.href,$=!0):P=Cr(Cd(O))}else F=y6(le),P=F;return{publicHref:P,href:F,pathname:N,search:Z,searchStr:J,state:ae,hash:M??"",external:$,unmaskOnReload:h.unmaskOnReload}},u=c(s);if(s.mask)u.maskedLocation=c({from:s.from,...s.mask});else if(this.options.routeMasks){const h=E6(u.pathname,this.processedTree);if(h){const f=Object.assign(Er(),h.rawParams),{from:m,params:g,...w}=h.route,b=Id(g,f);u.maskedLocation=c({from:s.from,...w,params:b})}}return!r&&s._fromLocation&&!u.maskedLocation&&this.staticLocations.set(s,u),u},this.commitLocation=async({viewTransition:s,ignoreBlocker:r,...c})=>{const u=c.maskedLocation??c;if(u.external)return em(this,u.publicHref,{replace:c.replace,ignoreBlocker:r});let h;const f=on(this.latestLocation.href)===on(c.href)&&$t(Zp(c.state),Zp(this.latestLocation.state)),m=this._commitPromise;let g;const w=new Promise(b=>{g=b});if(w.resolve=()=>{g(),m?.resolve()},this._commitPromise=w,f)this.load();else{let{maskedLocation:b,hashScrollIntoView:v,..._}=c;b&&(_={...b,state:{...b.state,__tempKey:void 0,__tempLocation:{..._,search:_.searchStr,state:{..._.state,__tempKey:void 0,__tempLocation:void 0,__TSR_key:void 0,key:void 0}}}},(_.unmaskOnReload??this.options.unmaskOnReload??!1)&&(_.state.__tempKey=this.tempLocationKey)),_.state={..._.state,__hashScrollIntoViewOptions:v??this.options.defaultHashScrollIntoView??!0},this.shouldViewTransition=s,h=c.replace?"REPLACE":"PUSH",this.history[h==="REPLACE"?"replace":"push"](_.publicHref,_.state,{ignoreBlocker:r}),this.history.subscribers.size||this.load({action:{type:h}})}return this._scroll.n=c.resetScroll??!0,this._commitPromise},this.buildAndCommitLocation=({replace:s,resetScroll:r,hashScrollIntoView:c,viewTransition:u,ignoreBlocker:h,...f}={})=>{const m=this.buildLocation({...f,_includeValidateSearch:!0});this._pendingLocation=m;const g=this.commitLocation({...m,viewTransition:u,replace:s,resetScroll:r,hashScrollIntoView:c,ignoreBlocker:h});return queueMicrotask(()=>{this._pendingLocation===m&&(this._pendingLocation=void 0)}),g},this.navigate=async({to:s,reloadDocument:r,href:c,publicHref:u,...h})=>{const f=c?Hs(c):void 0;if(f||r){if(s!==void 0||!c){const g=this.buildLocation({to:s,...h}),w=g.maskedLocation??g;c??=w.publicHref,u??=w.publicHref}return em(this,!f&&u?u:c,h)}return this.buildAndCommitLocation({...h,href:c,to:s,_isNavigate:!0})},this.load=async s=>{this.updateLatestLocation(),s?.action&&(this._scroll.h=s.action.type==="PUSH"||s.action.type==="REPLACE"),await dv(this,s)},this.startViewTransition=s=>{const r=this.shouldViewTransition??this.options.defaultViewTransition;if(this.shouldViewTransition=void 0,r&&typeof document.startViewTransition=="function"){let c;if(typeof r=="object"&&window.CSS?.supports?.("selector(:active-view-transition-type(a))")){const u=this.latestLocation,h=this.stores.resolvedLocation.get(),f=typeof r.types=="function"?r.types(Gr(u,h)):r.types;if(f===!1)return s();c={update:s,types:f}}else c=s;return document.startViewTransition(c).updateCallbackDone}return s()},this.invalidate=s=>{const r=this._committed,c=s?.filter,u=this._preloads,h=new Set,f=w=>{(!c||c(w))&&h.add(w.id)};r.forEach(f),this._cache.forEach(f),u?.forEach(w=>w.forEach(f)),this._tx?.[3].forEach(f);const m=[];for(const[w,b]of u??[])b.some(v=>h.has(v.id))&&(u.delete(w),m.push(w));const g=w=>{if(h.has(w.id)){const b=this.routesById[w.routeId],v={...w,invalid:!0,...(s?.forcePending||w.status==="error"||w.status==="notFound")&&Kp(b)?{status:"pending",error:void 0}:void 0};return w._flight=void 0,v}return w};this._committed=r.map(g);
11for(const[w,b]of this._cache)h.has(w)&&(b.invalid=!0,s?.forcePending&&(b.status="pending"));for(const w of h){const b=this._flights?.get(w);this._flights?.delete(w),b&&!b[2]&&m.push(b[1])}for(const w of m)w.abort();return this.shouldViewTransition=!1,this.load({sync:s?.sync})},this.resolveRedirect=s=>{const r=s.options;let c=s.headers.get("Location")||r.href;if(!c){const h=this.buildLocation(r);c=(h.maskedLocation??h).publicHref||"/"}let u;if(zm.test(c)||(u=Hs(c))&&!this.protocolAllowlist.has(u))throw new Error("Redirect blocked: unsafe protocol");if(u==="http:"||u==="https:"){const h=new URL(c);h.pathname.startsWith("//")?c=h.href:jd(h,this.origin)||(c=Cd(h),u=void 0)}return u&&(r.reloadDocument=!0),r.href=c,s.headers.set("Location",c),s},this.clearCache=s=>{const r=this._cache,c=this._preloads,u=s?.filter,h=[],f=[];for(const[g,w]of r)(!u||u(w))&&(f.push(g),h.push(w));const m=[];for(const[g,w]of c??[])(!u||w.some(u))&&(m.push(g),h.push(...w));for(const g of f)r.delete(g);for(const g of m)c.delete(g);for(const g of h){const w=g._flight;g._flight=void 0,w&&!--w[2]&&(this._flights?.get(g.id)===w&&this._flights.delete(g.id),m.push(w[1]))}for(const g of m)g.abort()},this.loadRouteChunk=In,this.preloadRoute=s=>uv(this,s),this.matchRoute=(s,r)=>{const c={...s,to:s.to?Jp(s.from||"",s.to,this.options.trailingSlash,this.resolvePathCache):void 0,params:s.params||{},leaveParams:!0},u=this.buildLocation(c),h=this.stores.status.get()==="pending";if(r?.pending&&!h)return!1;const f=r?.pending??!h?this.latestLocation:this.stores.resolvedLocation.get()||this.stores.location.get(),m=j6(u.pathname,r?.caseSensitive??!1,r?.fuzzy??!1,f.pathname,this.processedTree);return!m||s.params&&!$t(m.rawParams,s.params,!0)?!1:r?.includeSearch??!0?$t(f.search,u.search,!0)?m.rawParams:!1:m.rawParams},this.getStoreConfig=i,a.pathParamsAllowedCharacters?.length&&(this.pathParamsDecoder=_6(a.pathParamsAllowedCharacters)),this.update({defaultPreloadDelay:50,defaultPendingMs:1e3,defaultPendingMinMs:500,context:void 0,...a,caseSensitive:a.caseSensitive??!1,notFoundMode:a.notFoundMode??"fuzzy",stringifySearch:a.stringifySearch??P6,parseSearch:a.parseSearch??H6,protocolAllowlist:a.protocolAllowlist??p6}),self.__TSR_ROUTER__=this}isShell(){return!!this.options.isShell}get state(){return this.stores.__store.get()}setRoutes(a){Object.assign(this,a),this.lightweightCache=new WeakMap,this.staticLocations=new WeakMap;const i=this.options.notFoundRoute;i&&(i.init(99999999999),this.routesById[i.id]!==i&&(i._interpolation=Fs(!1,i,0)),this.routesById[i.id]=i)}matchRoutesInternal(a,i){const[s,r,c]=this.getMatchedRoutes(a.pathname);let u=s,h=!1;(c?c.path!=="/"&&r["**"]:on(a.pathname))&&(this.options.notFoundRoute?u=[...u,this.options.notFoundRoute]:h=!0);const f=h?av(this.options.notFoundMode,u):void 0,m=new Array(u.length),g=this._committed,w=(v,_)=>{const T=g[_];return T?.routeId===v.id?T:v===this.options.notFoundRoute?g.find(x=>x.routeId===v.id):void 0};let b;for(let v=0;v<u.length;v++){const _=u[v],T=m[v-1];let x,I,U;{const $=T?.search??a.search,ce=T?._strictSearch??void 0;try{const te=Ir(_.options.validateSearch,{...$})??void 0;x={...$,...te},I={...ce,...te}}catch(te){let O=te;if(te instanceof qr||(O=new qr(te.message,{cause:te})),i?.throwOnError)throw O;x=$,I={},U=O}}let N="",L="";try{N=_.options.loaderDeps?.({search:x})??"",L=N&&JSON.stringify(N)||""}catch($){if(i?.throwOnError)throw $;U??=$}const G=Er(),Z=_._interpolation?Gp(_.fullPath,_._interpolation,r,this.pathParamsDecoder,G):_.fullPath,J=_.id+Z+L,M=w(_,v),V=this._cache.get(J)??(M?.id===J?M:void 0);b=V?._strictParams??Object.assign(G,b);let ae;if(!V)try{tm(_,b)}catch($){if(Na($)||Bm($)?ae=$:ae=new Z6($.message,{cause:$}),i?.throwOnError)throw ae}const le=M?"stay":"enter";let F;if(V)F={...V,cause:le,search:Ri(M?M.search:V.search,x),_strictSearch:I,searchError:U};else{const $=Kp(_)?"pending":"success";F={id:J,ssr:_.options.ssr,index:v,routeId:_.id,params:M?.params??b,_strictParams:b,pathname:Z,updatedAt:Date.now(),search:M?Ri(M.search,x):x,_strictSearch:I,searchError:U,status:$,isFetching:!1,error:void 0,paramsError:ae,context:{},abortController:i?._controller??new AbortController,cause:le,loaderDeps:M?da(M.loaderDeps,N):N,invalid:!1,preload:!1,staticData:_.options.staticData||{},fullPath:_.fullPath}}const P=f===_.id;F._notFound&&!P&&(F.error=void 0),F._notFound=P,m[v]=F}for(let v=0;v<m.length;v++){const _=m[v];_.params=_.cause==="stay"?Ri(_.params,b):b,i?._controller&&(_.context={})}return m}matchRoutesLightweight(a){const i=ha(this.stores.ids.get()),s=i?this.stores.byRoute.get(i).get():void 0,r=s?.id,c=this.lightweightCache.get(a);if(c&&c[0]===r)return c[1];const[u,h]=this.getMatchedRoutes(a.pathname),f=ha(u),m={...a.search};for(const v of u)try{Object.assign(m,Ir(v.options.validateSearch,m))}catch{}const g=s&&s.routeId===f.id&&s.pathname===a.pathname;let w;if(g)w=s.params;else{const v=h;for(const _ of u)try{tm(_,v)}catch{}w=v}const b=[u,f.fullPath,m,w];return this.lightweightCache.set(a,[r,b]),b}};async function em(a,i,{replace:s,ignoreBlocker:r}){if(!Nm(i,a.protocolAllowlist)){if(!r){const c=a.history._getBlockers();for(const u of c)if(u?.blockerFn&&await u.blockerFn({currentLocation:a.history.location,nextLocation:a.history.location,action:s?"REPLACE":"PUSH"}))return}a.history._ignoreNextBeforeUnload?.(i),s?window.location.replace(i):window.location.href=i}}
11var qr=class extends Error{},Z6=class extends Error{};function Ir(a,i){if(a==null)return{};if("~standard"in a){const s=a["~standard"].validate(i);if(s instanceof Promise)throw new qr("Async validation not supported");if(s.issues)throw new qr(JSON.stringify(s.issues,void 0,2),{cause:s});return s.value}return"parse"in a?a.parse(i):typeof a=="function"?a(i):{}}function Id(a,i){if(a===void 0||a===!0)return i;const s=Object.create(null);return a===!1||a===null?s:typeof a=="function"?(Object.assign(s,i),Object.assign(s,a(s))):Object.assign(s,i,a)}function ev(a,i){return typeof a=="function"?!0:!i||a===!1||a===null?!1:a===void 0||a===!0||i.some(s=>typeof s!="string"&&!eu.call(a,s[1]))}const Rd=Object.freeze({});function tv(a,i){const s=[];for(let r=0;r<a.length;r++){const c=a[r].options;if("search"in c)c.search?.middlewares&&s.push(...c.search.middlewares);else if(c.preSearchFilters||c.postSearchFilters){const h=({search:f,next:m})=>{const g=m(c.preSearchFilters?c.preSearchFilters.reduce((w,b)=>b(w),f):f);return c.postSearchFilters?c.postSearchFilters.reduce((w,b)=>b(w),g):g};s.push(h)}const u=c.validateSearch;if(i&&u){const h=({search:f,next:m,meta:g})=>{const w=m(f);try{const b=Ir(u,w);if(g&&b)for(const v in b)v in w||(g.defaulted||=new Map).set(v,b[v]);return{...w,...b}}catch{}return w};s.push(h)}}return s}function nv(a,i,s){const r=(c,u,h)=>{if(c>=a.length){if(!s.search)return{};if(s.search===!0)return u;const m=Lm(s.search,u);return h&&(h.explicit=m),m}const f=(m,g)=>{if(g){const w=h||{};return{search:r(c+1,m,w),meta:w}}return r(c+1,m,h)};return a[c]({search:u,next:f,meta:h})};return r(0,i)}function av(a,i){if(a!=="root"){let s;for(let r=i.length-1;r>=0;r--){const c=i[r];if(c.options.notFoundComponent)return c.id;s||=c.children&&c.id}if(s)return s}return Bi}function tm(a,i){const s=a.options.params?.parse??a.options.parseParams;s&&Object.assign(i,s(i))}function Fd(a,i){return a.options[i]?.preload?.()}function iv(a,i){const s=Fd(a,"component");let r=Fd(a,"pendingComponent");return i&&(r?r=r.then(i):i()),s&&r?Promise.all([s,r]).then(()=>{}):s??r}function In(a,i,s){const r=()=>i===!1?void 0:i?Fd(a,i):iv(a,s),c=a._lazy;if(c)return c===!0?r():c.then(r);if(!a.lazyFn)return r();const u=a.lazyFn().then(h=>{{const{id:f,...m}=h.options;Object.assign(a.options,m),a._lazy=!0}},h=>{throw a._lazy=void 0,h});return a._lazy=u,u.then(r)}function au(a){const i=a.findIndex(s=>s.status!=="success"||s._notFound)+1;return i&&i<a.length?a.slice(0,i):a}function Zm(a){let i=a.length;for(let s=0;s<i;s++){const r=a[s];if(r._assetEnd!==void 0){i=Math.min(i,Math.max(s+1,r._assetEnd));continue}if(r.status!=="success"||r._notFound){i=s+1;break}}return i<a.length?a.slice(0,i):a}const ln=0,Rn=1,Yr=2,ft=3,fa=[4];function Js(a){return typeof a[0]=="number"}function On(a,i){return i.aborted?Promise.race([Promise.reject(i),a]):new Promise((s,r)=>{const c=()=>r(i);i.addEventListener("abort",c,{once:!0}),Promise.resolve(a).then(s,r).then(()=>i.removeEventListener("abort",c))})}function Wt(a,i){return a.routesById[i.routeId]}function Gs(a,i,s){return Bm(a)?[ft,a]:Na(a)?(a.routeId||=s,[Yr,a]):i?(typeof a?.then=="function"&&(a=new Error("A Promise was thrown",{cause:a})),[Rn,a]):[ln,a]}function iu(a,i){let s=Gs(i,!0,a.id);if(s[0]!==Rn)return s;try{a.options.onError?.(s[1])}catch(r){s=Gs(r,!0,a.id)}return s}function qs(a,i,s,r,c){return c[0].signal.aborted?fa:ru(a,i,s,iu(s,r),c)}async function sv(a,i,s,r,c,u){const[h,f]=i,m=s[0].signal,g=!!s[3];for(let w=s[6]??0;w<r;w++){const b=f[w],v=Wt(a,b);b.abortController=s[0];const _=f[w-1]?.context??a.options.context??{},T={params:b.params,location:h,navigate:N=>a.navigate({...N,_fromLocation:h}),buildLocation:a.buildLocation,cause:g?"preload":b.cause,abortController:s[0],preload:g,matches:f,routeId:v.id};try{const N=b._ctx||=v.options.context?v.options.context({...T,deps:b.loaderDeps,context:_})||{}:void 0;b.context={..._,...N}}catch(N){return Cn(a,b),[w,qs(a,i,v,N,s)]}if(m.aborted)return[w,fa];const x=b.paramsError??b.searchError;if(x!==void 0)return Cn(a,b),[w,qs(a,i,v,x,s)];const I=v.options.beforeLoad;if(!I)continue;const U=b.status;w>=u&&(b.status="pending",s[7]?.());try{Ys(a,b,"beforeLoad",s[0]);const N=I({...T,search:b.search,context:b.context,...a.options.additionalContext}
11),L=await(typeof N?.then=="function"?On(N,m):N);if(m.aborted)return[w,fa];const G=ru(a,i,v,Gs(L,!1,v.id),s);if(G[0]!==ln)return Cn(a,b),[w,G];b.context={...b.context,...L}}catch(N){return Cn(a,b),[w,qs(a,i,v,N,s)]}finally{b.status=U,Ys(a,b,!1,s[0])}}c()}function su(a,i,s){if(!(!s||--s[2])){if(a._flights?.get(i.id)===s){const r=a._tx;if(r&&!r[0].signal.aborted&&!r[3].includes(i)&&r[3].some(c=>c.id===i.id)&&r[3].some(c=>c.isFetching==="beforeLoad"))return;a._flights.delete(i.id)}return s[1]}}function Cn(a,i){const s=i._flight;i._flight=void 0,su(a,i,s)?.abort()}function dt(a,i,s,r){const c=[];for(const u of i)if(!s?.includes(u)){const h=u._flight;if(u._flight=void 0,r&&h?.[2]===1&&a._flights?.get(u.id)===h&&s?.some(f=>f.id===u.id))h[2]=0;else{const f=su(a,u,h);f&&c.push(f)}}for(const u of c)u.abort()}function ou(a){for(const i of a){const s=i._flight;s&&s[2]++}}function Ys(a,i,s,r){if(i.isFetching=s,r&&a._tx?.[0]!==r)return;const c=a.stores.byRoute.get(i.routeId),u=c?.get();u?.id===i.id&&c.set({...u,isFetching:s})}function eb(a,i,s,r,c,u,h){const f=i[0];return{params:s.params,location:f,navigate:m=>a.navigate({...m,_fromLocation:f}),cause:h?"preload":s.cause,abortController:c,preload:h,deps:s.loaderDeps,parentMatchPromise:u,context:s.context,route:r,...a.options.additionalContext}}async function nm(a,i,s,r,c,u,h){const f=h[0],m=f.signal;if(m.aborted)return fa;if(!c)return[ln,void 0];let g=s._flight;Ys(a,s,"loader",f);try{if(!g){const w=new AbortController;g=[Promise.resolve().then(()=>c(eb(a,i,s,r,w,u,!!h[3]))).then(b=>Gs(b,!1,r.id),b=>Gs(b,!0,r.id)).then(b=>(b[0]!==ln&&a._flights?.get(s.id)===g&&(a._flights.delete(s.id),g[2]||w.abort()),b[0]===Rn&&g[2]?iu(r,b[1]):b)),w,1],(a._flights??=new Map).set(s.id,g)}return s._flight=g,s.abortController=g[1],ru(a,i,r,await On(g[0],m),h)}catch(w){if(w!==m||!m.aborted)throw w;return Cn(a,s),fa}finally{Ys(a,s,!1,f)}}function am(a,i,s){i[0]!==ft&&(a.status="success",a.error=void 0,i[0]===ln?(a.loaderData=i[1],a.invalid=!1,a.updatedAt=Date.now(),a.preload=s):a.invalid=!0)}function tb(a,i,s){const r=a._cache.get(i.id);if(r!==s||a._committed.some(u=>u.id===i.id&&u._flight===i._flight))return;const c={...i,_notFound:void 0,context:{}};c._flight&&c._flight[2]++,a._cache.set(i.id,c),r&&Cn(a,r)}function im(a,i){return i[0]===Rn||i[0]===Yr?{...a,status:i[0]===Rn?"error":"notFound",error:i[1],_flight:void 0}:a}function ov(a,i,s,r,c,u,h){const f=i[1][s],m=Wt(a,f),g=!!u[3],w=a._cache.get(f.id);let b,v=!1,_;try{if(f.status==="success"&&(b=m.options.shouldReload,typeof b=="function"&&(b=b(eb(a,i,f,m,u[0],c,g))),u[0].signal.aborted&&(_=fa)),!_)if(f.status!=="success")v=!0;else{const P=g||f.preload?m.options.preloadStaleTime??a.options.defaultPreloadStaleTime??3e4:m.options.staleTime??a.options.defaultStaleTime??0;v=!!(f.invalid||b||b===void 0&&Date.now()-f.updatedAt>=P&&(u[5]||f.cause==="enter"||u[2].some($=>$.routeId===f.routeId&&$.id!==f.id)))}}catch(P){f.invalid=!0,Cn(a,f),_=qs(a,i,m,P,u)}const T=m.options.loader,x=typeof T=="function",I=x?T:T?.handler,U=!g||m.options.preload!==!1;let N=U&&T?a._flights?.get(f.id):void 0;N===f._flight||_?N=void 0:N&&!v&&!g&&b===void 0?v=!0:v||(N=void 0);const L=!!(T&&v&&f.status==="success"&&!g&&!u[4]&&((x?void 0:T.staleReloadMode)??a.options.defaultStaleReloadMode)!=="blocking"),G=v&&U,Z=G&&!L&&(f.status!=="success"||!!T),J=s>=h?u[7]:void 0,M=m.lazyFn&&m._lazy!==!0?J:void 0;if(G&&!T&&(f.invalid=!1,f.updatedAt=Date.now()),N&&N[2]++,Z){const P=f._flight;f._flight=N,su(a,f,P)?.abort(),s>=h&&(f.status="pending"),J?.()}G||(f.isFetching=!1);const V=!_&&Z?nm(a,i,f,m,I,c,u).then(P=>(am(f,P,g),P[0]===ln&&(T&&!u[0].signal.aborted&&tb(a,f,w),s>=h&&(f.status="pending")),P)):Promise.resolve(_??[ln,f.loaderData]),ae=(async()=>{try{const $=In(m,void 0,M);$&&await On($,u[0].signal)}catch($){if(!i[1].some((ce,te)=>te<=s&&(ce.status==="error"||ce.status==="notFound"||ce._notFound)))return[s,qs(a,i,m,$,u)]}const P=await V;Z&&P[0]===ln&&f.status==="pending"&&!u[0].signal.aborted&&(f.status="success",J?.())})();if(r.push([s,V,ae]),!L)return V.then(P=>im(f,P));const le={...f,status:"pending",preload:!1,_flight:N};f.invalid=!1,f.isFetching="loader";const F=nm(a,i,le,m,I,c,u).then(P=>(f.isFetching=!1,am(le,P,!1),P));return(i[2]??=[]).push([s,F,ae,le]),F.then(P=>im(le,P))}async function Jd(a,i,s,r,c=0){const u=s?.[1][1];let h=u?.routeId?i.findIndex(f=>f.routeId===u.routeId):s?.[0]??i.length-1;h<0&&(h=0);for(let f=h;f>=0;f--){const m=Wt(a,i[f]);try{const g=In(m,!1);g&&await On(g,r)}catch(g){if(g===r&&r.aborted)throw g}if(m.options.notFoundComponent)return f}return u?.routeId?h:c}function qa(a,i){i[2]&&(dt(a,i[2].map(s=>s[3])),i[2]=void 0)}async function sm(a,i,s,r){let c;try{await Promise.all(a.map(u=>u[1].then(async h=>{const f=u[0];if(!(r&&f>=await r)){if(h[0]>=ft)throw[f,h];!c&&h[0]!==ln&&(c=[f,h],await Promise.all((s??[]).map(m=>{if(!(m[0]<=f))return m[1].then(g=>{if(g[0]===ft)throw[m[0],g]})})))}})))}catch(u){return u}return i??c}function ru(a,i,s,r,c,u){for(;r[0]===ft;){const h=r[1],f=h.options;try{if((f.href||h.headers.has("Location"))&&(a.resolveRedirect(h),f.reloadDocument)||(f.reloadDocument?c[3]:c[1]>=20))return r;const m=a.buildLocation({...f,_fromLocation:i[0],_includeValidateSearch:!0}),g=m.maskedLocation??m;if(g.external){const w=h.clone();return w.options={...f},w.headers.set("Location",g.publicHref),a.resolveRedirect(w),c[3]?[ft,w]:[ft,w,g]}return[ft,h,m]}catch(m){r=u?[Rn,m]:iu(s,m),u=!0}}return r}async function nb(a,i,s,r,c,u){const h=i[1];let f=await c,m=!1;const g=h.findIndex(_=>_._notFound),w=_=>_[1][0]===Yr?Jd(a,h,_,r.signal):_[0];let b=g<0?h.length:g;if((f?.[1][0]??0)>=ft)b=0;else if(f){b=f[2]??=await w(f);for(const _ of s){if(_[0]>=b)break;const T=await _[1];if(T[0]!==ln&&T[0]<ft&&!("loaderData"in h[_[0]])){f=[_[0],T],b=f[2]=await w(f);break}}}for(const _ of s){if(_[0]>=b)break;const T=await _[2];if(T){f=T;break}}if((f?.[1][0]??0)>=ft){const _=f[1];if(_[0]!==ft||_[1].options.reloadDocument||_[2])return qa(a,i),_;m=!0,f=[0,[Rn,new Error("Too many redirects")]]}const v=f?f[2]??await w(f):g;if(v>=0){const _=f?.[1],T=_?.[0],x=h[v],I=_?.[1],U=()=>{_&&(x._notFound=void 0,T===Rn?x.status="error":(I.routeId=x.routeId,x.routeId===a.routeTree.id?(x.status="success",x._notFound=!0):x.status="notFound"),x.error=I,x.isFetching=!1)};U(),_||u?.();const N=Wt(a,x);try{await On(_?Promise.resolve().then(()=>In(N,T===Rn?"errorComponent":"notFoundComponent")):Promise.all([In(N),In(N,"notFoundComponent")]),r.signal)}catch(L){if(L===r.signal&&r.signal.aborted)return qa(a,i),fa}_?m&&(r.abort(),await Promise.all([...s.map(L=>L[1]),...s.map(L=>L[2]),...(i[2]??[]).map(L=>L[1])]),qa(a,i),dt(a,h),U()):x.status="success"}return i}async function lu(a,i,s,r=0,c=i[1].length){const u=i[1];for(let h=r;h<c;h++){const f=u[h],m=Wt(a,f).options;if(m.head||m.scripts)try{const g={ssr:a.options.ssr,matches:u,match:f,params:f.params,loaderData:f.loaderData},[w,b]=await On(Promise.all([m.head?.(g),m.scripts?.(g)]),s);f.meta=w?.meta,f.links=w?.links,f.headScripts=w?.scripts,f.styles=w?.styles,f.scripts=b}catch(g){if(g===s&&s.aborted)break;console.error(g)}if(f.status!=="success"||f._notFound)break}return i}async function ab(a,i,s,r){const c=[i,s],u=r[0].signal;let h;try{const f=a.stores.matches.get();let m=s.findIndex(U=>U._notFound);if(a.options.notFoundMode!=="root"&&m>=0){const U=await Jd(a,s,void 0,u,m);s[m]._notFound=void 0,s[U]._notFound=!0,m=U}let g=m<0?s.length:m+1,w=0;for(;w<g&&w!==m;){const U=s[w],N=r[2][w],L=f[w];if(N?.id!==U.id||N.status!=="success"||U.preload||L?.id!==U.id||L.status!=="success"||(w++,N._notFound||L._notFound))break}const b=[],v=r[6]??0;let _=v?Promise.resolve(s[v-1]):void 0;const T=()=>{for(let U=v;U<g&&!u.aborted;U++)_=ov(a,c,U,b,_,r,w)},x=await sv(a,c,r,g,T,w);if(x){if(r[4]=!0,g=x[0],x[1][0]===Yr){const U=await Jd(a,s,x,u);x[2]=U,g=Math.min(g,U+1)}else x[1][0]>=ft&&(g=0);T()}if(!u.aborted&&!r[3]){const U=[];for(const[N,L]of a._flights??[])L[2]||(a._flights.delete(N),U.push(L[1]));for(const N of U)N.abort()}const I=nb(a,c,b,r[0],sm(b,x,c[2]),r[7]);c[2]?.length&&(c[3]=sm(c[2],void 0,void 0,I.then(U=>Js(U)?0:au(s).length,()=>0))),h=await I}catch(f){if(qa(a,c),f===u&&u.aborted)return fa;throw f}return Js(h)?h:lu(a,h,u,r[6]===s.length?r[6]:0)}function Gd(a,i){if(a._tx!==i)return;const s=i[3],r=a.stores.matches.get();let c=a._pending;
11for(let u=0;u<s.length;u++){const h=s[u],f=h.status==="success"&&!h._notFound,m=r[u]?.id===h.id&&r[u]?.status==="pending";if(f&&!m)continue;const g=Wt(a,h),w=f||h.invalid?0:g.options.pendingMs??a.options.defaultPendingMs,b=g.options.pendingComponent??a.options.defaultPendingComponent;if(!b||typeof w!="number"||w===1/0){c&&(c[0]=i,c[2]=0,c[4]=!0);return}const v=g.options.pendingMinMs??a.options.defaultPendingMinMs??0;let _=!1;if(c?.[1]===h.id?(_=c[0]!==i,c[0]=i):(clearTimeout(c?.[3]),a._pending=c=void 0),c||(a._pending=c=[i,h.id,m?Date.now()+v:i[4]+w,void 0,m||void 0,b]),c[4]&&!_&&c[5]===b)return;if(c[5]=b,!c[4]){clearTimeout(c[3]);const I=c[2]-Date.now();if(I>0){c[3]=setTimeout(()=>Gd(a,i),I);return}c[2]=0}const T=s.map(I=>({...I,_flight:void 0}));T[u].status="pending";const x=c[4]=a.startTransition(()=>a.stores.setMatches(T),T).then(I=>(I&&a._pending===c&&c[4]===x&&!c[2]&&(c[2]=Date.now()+v),I));return}}function Ds(a,i){const s=a._pending;(a._tx===i||!a._tx?.[3].some(r=>r.id===s?.[1]))&&(clearTimeout(s?.[3]),a._pending=void 0)}async function om(a,i){const s=a._pending;if(!s)return;clearTimeout(s[3]);const r=s[2]-Date.now();if(!s[4]||r<=0||!au(i[3]).some(u=>u.id===s[1]))return;let c;try{await On(new Promise(u=>{c=setTimeout(u,r)}),i[0].signal)}catch{}clearTimeout(c)}function ib(a,i){a._committed=i,a.stores.setMatches(i)}function rv(a,i,s,r){const c=a._committed,u=a._lifecycleEnd,h=a._cache;for(const w of s)w.preload=!1,r&&(w._assetEnd=void 0);const f=au(s).length,m=new Map;{const w=Date.now(),b=new Set;for(let v=0;v<s.length;v++){const _=s[v];(v<f||_.status==="success")&&b.add(_.id)}for(const v of[...c,...h.values()]){if(v.status!=="success"||b.has(v.id))continue;const _=Wt(a,v);!_.options.loader||w-v.updatedAt>=(v.preload?_.options.preloadGcTime??a.options.defaultPreloadGcTime??3e5:_.options.gcTime??a.options.defaultGcTime??3e5)||m.set(v.id,h.get(v.id)===v?v:{...v,_flight:void 0,isFetching:!1,context:{}})}}i[3]=[],a._cache=m;const g=a._lifecycleEnd=Km(s);ib(a,s),dt(a,[...h.values(),...c].filter(w=>w._flight&&m.get(w.id)!==w),s),X6(a,c,s,u,g,i)}async function _r(a,i){let s=a._tx;for(;s&&s!==i;)i=s,await s[5],s=a._tx}function sb(a,i,s){const r=s[1].options,c=s[2];if(!c)return a.navigate({...r,replace:!0,ignoreBlocker:!0});if(r.reloadDocument)return a.navigate({href:(c.maskedLocation??c).publicHref,reloadDocument:!0,replace:!0,ignoreBlocker:!0});c._redirects=i[1]+1,a._pendingLocation=c;const u=a.commitLocation({...c,viewTransition:r.viewTransition,replace:!0,resetScroll:r.resetScroll,hashScrollIntoView:r.hashScrollIntoView,ignoreBlocker:!0});return queueMicrotask(()=>{a._pendingLocation===c&&(a._pendingLocation=void 0)}),u}async function lv(a,i,s,r,c){const u=s.map(m=>({...m}));ou(u);for(const m of r)Cn(a,u[m[0]]),u[m[0]]=m[3];const h=[i[2],u];let f;try{f=await nb(a,h,r,i[0],c)}catch(m){throw dt(a,u),m}if(Js(f)){dt(a,u),f[0]===ft&&a._tx===i&&a._committed===s&&await sb(a,i,f);return}if(await lu(a,f,i[0].signal),a._tx!==i||a._committed!==s){dt(a,u);return}for(const m of u){const g=a._cache.get(m.id);g?._flight&&g._flight===m._flight&&(a._cache.delete(m.id),Cn(a,g))}ib(a,u),dt(a,s,u)}async function cv(a,i,s,r,c,u){const h=await ab(a,i[2],i[3],[i[0],i[1],a._committed,void 0,c,s,u,r]);if(Js(h)){const b=h[0]===ft&&a._tx===i;if((!b||h[1].options.reloadDocument)&&Ds(a,i),dt(a,i[3]),i[3]=[],!b)return;if(a._tx!==i){Ds(a,i);return}await sb(a,i,h);return}const f=h[1];if(a._tx===i&&await om(a,i),a._tx!==i){Ds(a,i),dt(a,f),qa(a,h);return}const m=i[2],g=Gr(m,a.stores.resolvedLocation.get()),w=h[2];await a.startViewTransition(async()=>{if(a._tx===i&&await om(a,i),a._tx!==i){Ds(a,i),dt(a,f),qa(a,h);return}const b=()=>{Ds(a,i),rv(a,i,f,u),a._tx===i&&(a.emit({type:"onLoad",...g}),a._tx===i&&a.emit({type:"onBeforeRouteMount",...g}))},v=await a.startTransition(b,f);if(a._tx!==i){qa(a,h);return}w?.length&&lv(a,i,f,w,h[3]).catch(console.error),a.batch(()=>{a.stores.resolvedLocation.set(m),a.stores.status.set("idle"),a._tx===i&&a.emit({type:"onResolved",...g}),v&&a._tx===i&&a.emit({type:"onRendered",...g})}),a._tx===i&&(a._commitPromise?.resolve(),a._commitPromise=void 0)})}async function dv(a,i){const s=a._tx,r=a.stores.resolvedLocation.get(),c=r??a.stores.location.get(),u=a.latestLocation,h=a._pendingLocation,f=h?.href===u.href?h._redirects??0:0,m=a._handoff,g=m?.[0](),w=new AbortController,b=a._preflight;if(a._preflight=w,g||m?.[1](),b?.abort(),!w.signal.aborted){const G=Gr(u,r);
11a.emit({type:"onBeforeNavigate",...G}),w.signal.aborted||a.emit({type:"onBeforeLoad",...G})}if(w.signal.aborted){await _r(a,s);return}const v=c.href===u.href;let _=w;const T=a.matchRoutes(u,{_controller:w});ou(T);const x=g?m[1](T):void 0;if(x?_=g:g?.abort(),w.signal.aborted){dt(a,T),await _r(a,s);return}a._preflight=void 0;let I;const U=()=>cv(a,L,v,()=>Gd(a,L),i?.sync,x),N=i?.sync?new Promise(G=>I=G):Promise.resolve().then(U),L=[_,f,u,T,Date.now(),N.then(()=>_r(a,L))];if(a._tx=L,s){for(const G of a.stores.matches.get()){if(a._tx!==L)break;G.isFetching&&Ys(a,G,!1)}s[0].abort(),dt(a,s[3],L[3],!0)}if(a._tx!==L){dt(a,L[3]),L[3]=[],I?.(),await _r(a,L);return}a.batch(()=>{a.stores.status.set("pending"),a.stores.location.set(u)}),(x||!a._committed.length&&T[0]?.status!=="success"&&!T.some(G=>G._notFound))&&Gd(a,L),I?.(U()),await L[5]}async function uv(a,i){let s=a.buildLocation(i);for(let r=0;;r++){const c=a._committed,u=new AbortController;let h,f,m;try{try{h=a.matchRoutes(s,{_controller:u}),ou(h),f=(a._preloads??=new Map).set(u,h),m=await ab(a,s,h,[u,r,c,!0])}finally{f&&(f=f.delete(u),dt(a,h)),u.abort()}if(!Js(m))return m[1];if(!f||m.length<3)return;s=m[2]}catch(g){Na(g)||console.error(g);return}}}async function hv(a){const i=window.$_TSR,s=a.options.serializationAdapters;s?.length&&(i.t=new Map(s.map(F=>[F.key,F.fromSerializable])),i.buffer.forEach(F=>F())),i.initialized=!0;const r=i.router;a.ssr={manifest:r.manifest},a.options.ssr={nonce:document.querySelector('meta[property="csp-nonce"]')?.content};const c=r.matches,u=new AbortController,h=a._preflight;a._preflight=u,h?.abort();const f=()=>a._preflight===u;let m,g,w,b;try{if(await On(a.options.hydrate?.(r.dehydratedData),u.signal),!f())return;const F=a.history.location;w=F.href,b=F.state,a.updateLatestLocation(),m=a.latestLocation,a.stores.location.set(m),g=a.matchRoutes(m,{_controller:u})}catch(F){if(f()&&(a._preflight=void 0),u.abort(F),F!==u.signal)throw F}if(!f())return;const v=[];let _,T=0;const x=F=>{T=Math.min(T,F+1);const P=v.splice(F);for(const $ of P)Wt(a,$).options.loader&&($.status==="success"||!$.invalid&&"loaderData"in $)&&tb(a,{...$,status:"success",error:void 0,preload:!0},a._cache.get($.id));dt(a,P)},I=c.length>g.length?g.findIndex(F=>F._notFound)+1:c.length;let U=!1;for(let F=0;F<I;F++){const P=g[F],$=c[F];if(typeof $.i!="string"||u6($.i)!==P.id){_??=F;break}T=F+1;const ce=Wt(a,P);if(("l"in $||$.s==="success"&&$.e===void 0&&ce.options.loader)&&(P.loaderData=$.l),P.status=$.s,P.ssr=$.ssr,ce.options.ssr=P.ssr,P.updatedAt=$.u,P.error=$.e,P._notFound||=$.g,P.status==="error"||P.status==="notFound"||P._notFound){U=!0,v.push(P),(P.ssr===!1||P.ssr==="data-only")&&(_??=F);break}if(P.status==="pending"){_??=F;break}v.push(P),P.ssr==="data-only"&&(_??=F)}!U&&v.length===I&&I<g.length&&(_=I);const N=v.map(async F=>{try{const P=Wt(a,F);return await(F._notFound?Promise.all([In(P),In(P,"notFoundComponent")]):In(P,F.status==="error"?"errorComponent":F.status==="notFound"?"notFoundComponent":void 0)),!0}catch{return!1}});let L=0;try{for(;L<N.length&&await On(N[L],u.signal);)L++}catch{return}if(!f())return;L<v.length&&x(L);const G=Math.max(_===v.length?v.length+1:v.length,L<N.length?L:T);for(let F=0;F<G;F++){const P=g[F],$=Wt(a,P),ce=g[F-1]?.context??a.options.context??{};let te;if($.options.context){try{te=P._ctx=$.options.context({deps:P.loaderDeps,params:P.params,context:ce,location:m,navigate:O=>a.navigate({...O,_fromLocation:m}),buildLocation:a.buildLocation,cause:P.cause,abortController:u,preload:!1,matches:g,routeId:$.id})||{}}catch{if(!f())return;if(P.status!=="error"&&P.status!=="notFound"&&!P._notFound){_=Math.min(_??F,F),x(F);break}}if(!f())return}P.context={...ce,...te,...v[F]&&c[F].b}}if(await lu(a,[m,g],u.signal,0,T),!f())return;const Z=_!==void 0||v.length<I,J=U&&v.length===I?g:v;let M=Z?g:J,V;if(Z&&_!==void 0){const F=M[_];V=F.ssr==="data-only"&&T>_+1?T:void 0,M=M.slice(),M[_]={...F,status:"pending",ssr:F.ssr==="data-only"?"data-only":!1,_assetEnd:V}}const ae=()=>{const F=a.history.location;return Z&&!a._tx&&F.href===w&&F.state===b&&a._committed===J&&J.length&&!u.signal.aborted?u:void 0},le=[ae,F=>{if(a._handoff!==le)return;a._handoff=void 0;const P=J.length;if(!F||!ae()||J.some((te,O)=>te.id!==F[O]?.id)){u.abort();return}let $=V;if($!==void 0){for(let te=P;te<$;te++)if(g[te]?.id!==F[te]?.id){$=te>_+1?te:void 0;break}}const ce=J.map(te=>({...te}));$!==void 0&&(ce[_]._assetEnd=$),dt(a,F.splice(0,P,...ce));for(let te=P;te<F.length;te++){const O=F[te],W=g[te];
11W?.id===O.id&&W._ctx&&(O._ctx=W._ctx),O.abortController=u}return P}];a._committed=J,a._lifecycleEnd=Km(J),a._handoff=le,a._preflight=void 0,a.batch(()=>{a.stores.setMatches(M),a.stores.status.set("idle"),Z||a.stores.resolvedLocation.set(a.stores.location.get())})}const Mn=Symbol.asyncIterator,ob=Symbol.hasInstance,Li=Symbol.isConcatSpreadable,Un=Symbol.iterator,rb=Symbol.match,lb=Symbol.matchAll,cb=Symbol.replace,db=Symbol.search,ub=Symbol.species,hb=Symbol.split,fb=Symbol.toPrimitive,qi=Symbol.toStringTag,gb=Symbol.unscopables,pb={[Mn]:0,[ob]:1,[Li]:2,[Un]:3,[rb]:4,[lb]:5,[cb]:6,[db]:7,[ub]:8,[hb]:9,[fb]:10,[qi]:11,[gb]:12},fv={0:Mn,1:ob,2:Li,3:Un,4:rb,5:lb,6:cb,7:db,8:ub,9:hb,10:fb,11:qi,12:gb},gv={2:!0,3:!1,1:void 0,0:null,4:-0,5:Number.POSITIVE_INFINITY,6:Number.NEGATIVE_INFINITY,7:NaN},pv={0:"Error",1:"EvalError",2:"RangeError",3:"ReferenceError",4:"SyntaxError",5:"TypeError",6:"URIError"},mv={0:Error,1:EvalError,2:RangeError,3:ReferenceError,4:SyntaxError,5:TypeError,6:URIError};function _e(a,i,s,r,c,u,h,f,m,g,w,b){return{t:a,i,s,c:r,m:c,p:u,e:h,a:f,f:m,b:g,o:w,l:b}}function ga(a){return _e(2,void 0,a,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0)}const mb=ga(2),bb=ga(3),bv=ga(1),wv=ga(0),yv=ga(4),vv=ga(5),_v=ga(6),kv=ga(7),xv=64,Tv=/[\x00-\x07\x0b\x0e-\x1f<\u2028\u2029\ud800-\udfff]/;function Sv(a){switch(a){case'"':return'\\"';case"\\":return"\\\\";case`
12`:return"\\n";case"\r":return"\\r";case"\b":return"\\b";case"	":return"\\t";case"\f":return"\\f";case"<":return"\\x3C";case"\u2028":return"\\u2028";case"\u2029":return"\\u2029";default:return}}function Pa(a){if(a.length>=xv&&!Tv.test(a))return JSON.stringify(a).slice(1,-1);let i="",s=0,r;for(let c=0,u=a.length;c<u;c++)r=Sv(a[c]),r&&(i+=a.slice(s,c)+r,s=c+1);return s===0?i=a:i+=a.slice(s),i}function Av(a){switch(a){case"\\\\":return"\\";case'\\"':return'"';case"\\n":return`
13`;case"\\r":return"\r";case"\\b":return"\b";case"\\t":return"	";case"\\f":return"\f";case"\\x3C":return"<";case"\\u2028":return"\u2028";case"\\u2029":return"\u2029";default:return a}}function pa(a){return typeof a=="string"&&!a.includes("\\")?a:a.replace(/(\\\\|\\"|\\n|\\r|\\b|\\t|\\f|\\u2028|\\u2029|\\x3C)/g,Av)}const Ev={parsing:1,serialization:2,deserialization:3};function jv(a){return`Seroval Error (step: ${Ev[a]})`}const Cv=(a,i)=>jv(a);var wb=class extends Error{constructor(a,i){super(Cv(a)),this.cause=i}},rm=class extends wb{constructor(a){super("parsing",a)}},Iv=class extends wb{constructor(a){super("deserialization",a)}};function Dn(a){return`Seroval Error (specific: ${a})`}var Fa=class extends Error{constructor(a){super(Dn(1)),this.value=a}},cu=class extends Error{constructor(a){super(Dn(2))}},Rv=class extends Error{constructor(a){super(Dn(3))}},Vr=class extends Error{constructor(a){super(Dn(4))}},Ov=class extends Error{constructor(a){super(Dn(5)),this.value=a}},Mv=class extends Error{constructor(a){super(Dn(6))}},Uv=class extends Error{constructor(a){super(Dn(7))}},ut=class extends Error{constructor(a){super(Dn(8))}},yb=class extends Error{constructor(a){super(Dn(9))}};const kr="__SEROVAL_REFS__",vb=new Map,Ui=new Map;function _b(a){return vb.has(a)}function Dv(a){return Ui.has(a)}function Bv(a){if(_b(a))return vb.get(a);throw new Ov(a)}function Lv(a){if(Dv(a))return Ui.get(a);throw new Mv(a)}typeof globalThis<"u"?Object.defineProperty(globalThis,kr,{value:Ui,configurable:!0,writable:!1,enumerable:!1}):typeof window<"u"?Object.defineProperty(window,kr,{value:Ui,configurable:!0,writable:!1,enumerable:!1}):typeof self<"u"?Object.defineProperty(self,kr,{value:Ui,configurable:!0,writable:!1,enumerable:!1}):typeof global<"u"&&Object.defineProperty(global,kr,{value:Ui,configurable:!0,writable:!1,enumerable:!1});function du(a){return a instanceof EvalError?1:a instanceof RangeError?2:a instanceof ReferenceError?3:a instanceof SyntaxError?4:a instanceof TypeError?5:a instanceof URIError?6:0}function qv(a){const i=pv[du(a)];return a.name!==i?{name:a.name}:a.constructor.name!==i?{name:a.constructor.name}:{}}function kb(a,i){let s=qv(a);const r=Object.getOwnPropertyNames(a);for(let c=0,u=r.length,h;c<u;c++)h=r[c],h!=="name"&&h!=="message"&&(h==="stack"?i&4&&(s=s||{},s[h]=a[h]):(s=s||{},s[h]=a[h]));return s}function xb(a){return Object.isFrozen(a)?3:Object.isSealed(a)?2:Object.isExtensible(a)?0:1}function zv(a){switch(a){case Number.POSITIVE_INFINITY:return vv;case Number.NEGATIVE_INFINITY:return _v}return a!==a?kv:Object.is(a,-0)?yv:_e(0,void 0,a,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0)}function Tb(a){return _e(1,void 0,Pa(a),void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0)}function Nv(a){return _e(3,void 0,""+a,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0)}function Hv(a){return _e(4,a,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0)}function Pv(a,i){const s=i.valueOf();return _e(5,a,s!==s?"":i.toISOString(),void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0)}function ra(a,i,s){return _e(36,a,s.toString(),i,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0)}function Fv(a,i){return _e(6,a,void 0,Pa(i.source),i.flags,void 0,void 0,void 0,void 0,void 0,void 0,void 0)}function Jv(a,i){return _e(17,a,pb[i],void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0)}function Gv(a,i){return _e(18,a,Pa(Bv(i)),void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0)}function Yv(a,i,s){return _e(25,a,s,Pa(i),void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0)}function Vv(a,i,s){return _e(9,a,void 0,void 0,void 0,void 0,void 0,s,void 0,void 0,xb(i),void 0)}function Wv(a,i){return _e(21,a,void 0,void 0,void 0,void 0,void 0,void 0,i,void 0,void 0,void 0)}const uu=1e6;function Qv(a,i,s){if(i.length>uu)throw new Fa(i);return _e(15,a,void 0,i.constructor.name,void 0,void 0,void 0,void 0,s,i.byteOffset,void 0,i.length)}function $v(a,i,s){if(i.length>uu)throw new Fa(i);return _e(16,a,void 0,i.constructor.name,void 0,void 0,void 0,void 0,s,i.byteOffset,void 0,i.length)}function Xv(a,i,s){if(i.byteLength>uu)throw new Fa(i);return _e(20,a,void 0,void 0,void 0,void 0,void 0,void 0,s,i.byteOffset,void 0,i.byteLength)}function Kv(a,i,s){return _e(13,a,du(i),void 0,Pa(i.message),s,void 0,void 0,void 0,void 0,void 0,void 0)}function Zv(a,i,s){return _e(14,a,du(i),void 0,Pa(i.message),s,void 0,void 0,void 0,void 0,void 0,void 0)}function e1(a,i){return _e(7,a,void 0,void 0,void 0,void 0,void 0,i,void 0,void 0,void 0,void 0)}function t1(a,i){return _e(28,void 0,void 0,void 0,void 0,void 0,void 0,[a,i],void 0,void 0,void 0,void 0)}function n1(a,i){return _e(30,void 0,void 0,void 0,void 0,void 0,void 0,[a,i],void 0,void 0,void 0,void 0)}function a1(a,i,s){return _e(31,a,void 0,void 0,void 0,void 0,void 0,s,i,void 0,void 0,void 0)}function i1(a,i){return _e(32,a,void 0,void 0,void 0,void 0,void 0,void 0,i,void 0,void 0,void 0)}function s1(a,i){return _e(33,a,void 0,void 0,void 0,void 0,void 0,void 0,i,void 0,void 0,void 0)}function o1(a,i){return _e(34,a,void 0,void 0,void 0,void 0,void 0,void 0,i,void 0,void 0,void 0)}function r1(a,i,s,r){return _e(35,a,s,void 0,void 0,void 0,void 0,i,void 0,void 0,void 0,r)}var l1=class{constructor(a,i){this.value=a,this.replacement=i}};const hu=()=>{const a={p:0,s:0,f:0};return a.p=new Promise((i,s)=>{a.s=i,a.f=s}),a},c1=()=>{const a=[],i=[];let s=!0,r=!1,c=0;const u={flush(h,f,m){for(m=0;m<c;m++){const g=i[m];g&&g[f](h)}},up(h,f,m,g){for(f=0,m=a.length;f<m;f++)g=a[f],!s&&f===m-1?h[r?"return":"throw"](g):h.next(g)},on(h,f=0){let m=s;if(s){for(f=0;f<c&&i[f];f++);f===c&&c++,i[f]=h}
13return u.up(h),()=>{if(s&&m){for(m=!1,i[f]=void 0;c>0&&!i[c-1];)c--;i.length=c}}}};return{__SEROVAL_STREAM__:!0,on(h){return u.on(h)},next(h){s&&(a.push(h),u.flush(h,"next"))},throw(h){s&&(a.push(h),u.flush(h,"throw"),s=!1,r=!1,i.length=0)},return(h){s&&(a.push(h),u.flush(h,"return"),s=!1,r=!0,i.length=0)}}},d1=a=>i=>()=>{let s=0;const r={[a](){return r},next(){if(s>i.d)return{done:!0,value:void 0};const c=s++,u=i.v[c];if(c===i.t)throw u;return{done:c===i.d,value:u}}};return r},u1=(a,i)=>s=>()=>{let r=0,c=-1,u=!1;const h=[],f=[],m={finalize(w=0,b=f.length){for(;w<b;w++)f[w].s({done:!0,value:void 0})}};s.on({next(w){const b=f.shift();b&&b.s({done:!1,value:w}),h.push(w)},throw(w){const b=f.shift();b&&b.f(w),m.finalize(),c=h.length,u=!0,h.push(w)},return(w){const b=f.shift();b&&b.s({done:!0,value:w}),m.finalize(),c=h.length,h.push(w)}});const g={[a](){return g},next(){if(c===-1){const v=r++;if(v>=h.length){const _=i();return f.push(_),_.p}return{done:!1,value:h[v]}}if(r>c)return{done:!0,value:void 0};const w=r++,b=h[w];if(w!==c)return{done:!1,value:b};if(u)throw b;return{done:!0,value:b}}};return g},h1=a=>{const i=atob(a),s=i.length,r=new Uint8Array(s);for(let c=0;c<s;c++)r[c]=i.charCodeAt(c);return r.buffer};function f1(a){return"__SEROVAL_SEQUENCE__"in a}function Sb(a,i,s){return{__SEROVAL_SEQUENCE__:!0,v:a,t:i,d:s}}function g1(a){const i=[];let s=-1,r=-1;const c=a[Un]();for(;;)try{const u=c.next();if(i.push(u.value),u.done){r=i.length-1;break}}catch(u){s=i.length,i.push(u)}return Sb(i,s,r)}const p1=d1(Un);function m1(a){return p1(a)}const b1={},w1={},y1={0:{},1:{},2:{},3:{},4:{},5:{}};function Ab(a){return"__SEROVAL_STREAM__"in a}function Qs(){return c1()}function v1(a,i){const s=Qs(),r=a[Mn]();let c=!1,u=!1;async function h(){try{for(;!c;){const f=await r.next();if(f.done){u=!0,s.return(f.value);break}s.next(f.value)}}catch(f){u=!0,s.throw(f)}}return h().catch(()=>{}),s}const _1=u1(Mn,hu);function k1(a){return _1(a)}async function x1(a){try{return[1,await a]}catch(i){return[0,i]}}function T1(a,i){var s;return{plugins:i.plugins,mode:a,marked:new Set,features:127^(i.disabledFeatures||0),refs:i.refs||new Map,depthLimit:i.depthLimit||1e3,compactArrayBufferViews:(s=i.compactArrayBufferViews)!==null&&s!==void 0?s:!1}}function Rr(a,i){a.marked.add(i)}function S1(a,i){const s=a.refs.size;return a.refs.set(i,s),s}function Wr(a,i){const s=a.refs.get(i);return s!=null?(Rr(a,s),{type:1,value:Hv(s)}):{type:0,value:S1(a,i)}}function fu(a,i){const s=Wr(a,i);return s.type===1?s:_b(i)?{type:2,value:Gv(s.value,i)}:s}function La(a,i){const s=fu(a,i);if(s.type!==0)return s.value;if(i in pb)return Jv(s.value,i);throw new Fa(i)}function Qr(a,i){const s=Wr(a,y1[i]);return s.type===1?s.value:_e(26,s.value,i,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0,void 0)}function A1(a){const i=Wr(a,b1);return i.type===1?i.value:_e(27,i.value,void 0,void 0,void 0,void 0,void 0,void 0,La(a,Un),void 0,void 0,void 0)}function E1(a){const i=Wr(a,w1);return i.type===1?i.value:_e(29,i.value,void 0,void 0,void 0,void 0,void 0,[Qr(a,1),La(a,Mn)],void 0,void 0,void 0,void 0)}function j1(a,i,s,r){return _e(s?11:10,a,void 0,void 0,void 0,r,void 0,void 0,void 0,void 0,xb(i),void 0)}function C1(a,i,s,r){return _e(8,i,void 0,void 0,void 0,void 0,{k:s,v:r},void 0,Qr(a,0),void 0,void 0,void 0)}function gu(a,i){if(!a.compactArrayBufferViews)return i;const s=new Uint8Array(i.buffer,i.byteOffset,i.byteLength).slice().buffer,r=i.constructor;return new r(s)}function I1(a){if(typeof Buffer<"u")return Buffer.from(a).toString("base64");const i=new Uint8Array(a);if(typeof i.toBase64=="function")return i.toBase64();let s="";for(let r=0,c=i.length;r<c;r++)s+=String.fromCharCode(i[r]);return btoa(s)}function R1(a,i,s){return _e(19,i,I1(s),void 0,void 0,void 0,void 0,void 0,Qr(a,5),void 0,void 0,void 0)}function O1(a,i){return{base:T1(a,i),child:void 0}}var M1=class{constructor(a,i){this._p=a,this.depth=i}parse(a){return Xe(this._p,this.depth,a)}};async function U1(a,i,s){const r=[];for(let c=0,u=s.length;c<u;c++)c in s?r[c]=await Xe(a,i,s[c]):r[c]=0;return r}async function D1(a,i,s,r){return Vv(s,r,await U1(a,i,r))}async function pu(a,i,s){const r=Object.entries(s),c=[],u=[];for(let h=0,f=r.length;h<f;h++)c.push(Pa(r[h][0])),u.push(await Xe(a,i,r[h][1]));return Un in s&&(c.push(La(a.base,Un)),u.push(t1(A1(a.base),await Xe(a,i,g1(s))))),Mn in s&&(c.push(La(a.base,Mn)),u.push(n1(E1(a.base),await Xe(a,i,v1(s))))),qi in s&&(c.push(La(a.base,qi)),u.push(Tb(s[qi]))),Li in s&&(c.push(La(a.base,Li)),u.push(s[Li]?mb:bb)),{k:c,v:u}}async function Od(a,i,s,r,c){return j1(s,r,c,await pu(a,i,r))}async function B1(a,i,s,r){return Wv(s,await Xe(a,i,r.valueOf()))}async function L1(a,i,s,r){return r=gu(a.base,r),Qv(s,r,await Xe(a,i,r.buffer))}async function q1(a,i,s,r){return r=gu(a.base,r),$v(s,r,await Xe(a,i,r.buffer))}async function z1(a,i,s,r){return r=gu(a.base,r),Xv(s,r,await Xe(a,i,r.buffer))}async function lm(a,i,s,r){const c=kb(r,a.base.features);return Kv(s,r,c?await pu(a,i,c):void 0)}async function N1(a,i,s,r){const c=kb(r,a.base.features);return Zv(s,r,c?await pu(a,i,c):void 0)}async function H1(a,i,s,r){const c=[],u=[];for(const[h,f]of r.entries())c.push(await Xe(a,i,h)),u.push(await Xe(a,i,f));return C1(a.base,s,c,u)}async function P1(a,i,s,r){const c=[];for(const u of r.keys())c.push(await Xe(a,i,u));return e1(s,c)}async function Eb(a,i,s,r){const c=a.base.plugins;if(c)for(let u=0,h=c.length;u<h;u++){const f=c[u];if(f.parse.async&&f.test(r))return Yv(s,f.tag,await f.parse.async(r,new M1(a,i),{id:s}))}}async function F1(a,i,s,r){const[c,u]=await x1(r);return _e(12,s,c,void 0,void 0,void 0,void 0,void 0,await Xe(a,i,u),void 0,void 0,void 0)}function J1(a,i,s,r,c){const u=[],h=s.on({next:f=>{Rr(this.base,i),Xe(this,a,f).then(m=>{u.push(i1(i,m))},m=>{c(m),h()})},throw:f=>{Rr(this.base,i),Xe(this,a,f).then(m=>{u.push(s1(i,m)),r(u),h()},m=>{c(m),h()})},return:f=>{Rr(this.base,i),Xe(this,a,f).then(m=>{u.push(o1(i,m)),r(u),h()},m=>{c(m),h()})}})}async function G1(a,i,s,r){return a1(s,Qr(a.base,4),await new Promise(J1.bind(a,i,s,r)))}async function Y1(a,i,s,r){const c=[];for(let u=0,h=r.v.length;u<h;u++)c[u]=await Xe(a,i,r.v[u]);return r1(s,c,r.t,r.d)}async function V1(a,i,s,r){if(Array.isArray(r))return D1(a,i,s,r);if(Ab(r))return G1(a,i,s,r);if(f1(r))return Y1(a,i,s,r);let c=r.constructor;if(c!==void 0&&typeof c!="function"){const f=Object.getPrototypeOf(r);c=f===null?void 0:f.constructor}if(c===l1)return Xe(a,i,r.replacement);const u=await Eb(a,i,s,r);if(u)return u;switch(c){case Object:return Od(a,i,s,r,!1);case void 0:return Od(a,i,s,r,!0);case Date:return Pv(s,r);case Error:case EvalError:case RangeError:case ReferenceError:case SyntaxError:case TypeError:case URIError:return lm(a,i,s,r);case Number:case Boolean:case String:case BigInt:return B1(a,i,s,r);case ArrayBuffer:return R1(a.base,s,r);case Int8Array:case Int16Array:case Int32Array:case Uint8Array:case Uint16Array:case Uint32Array:case Uint8ClampedArray:case Float32Array:case Float64Array:return L1(a,i,s,r);case DataView:return z1(a,i,s,r);case Map:return H1(a,i,s,r);case Set:return P1(a,i,s,r)}if(c===Promise||r instanceof Promise)return F1(a,i,s,r);const h=a.base.features;if(h&32&&c===RegExp)return Fv(s,r);if(h&16)switch(c){case BigInt64Array:case BigUint64Array:return q1(a,i,s,r)}if(h&1&&typeof AggregateError<"u"&&(c===AggregateError||r instanceof AggregateError))return N1(a,i,s,r);if(h&64&&typeof Temporal<"u")switch(c){case Temporal.Instant:return ra(s,0,r);case Temporal.Duration:return ra(s,1,r);case Temporal.PlainDate:return ra(s,2,r);case Temporal.PlainDateTime:return ra(s,3,r);case Temporal.PlainMonthDay:return ra(s,4,r);case Temporal.PlainTime:return ra(s,5,r);case Temporal.PlainYearMonth:return ra(s,6,r);case Temporal.ZonedDateTime:return ra(s,7,r)}if(r instanceof Error)return lm(a,i,s,r);if(Un in r||Mn in r)return Od(a,i,s,r,!!c);throw new Fa(r)}async function W1(a,i,s){const r=fu(a.base,s);if(r.type!==0)return r.value;const c=await Eb(a,i,r.value,s);if(c)return c;throw new Fa(s)}async function Xe(a,i,s){if(i>=a.base.depthLimit)throw new yb(a.base.depthLimit);switch(typeof s){case"boolean":return s?mb:bb;case"undefined":return bv;case"string":return Tb(s);case"number":return zv(s);case"bigint":return Nv(s);case"object":if(s){const r=fu(a.base,s);return r.type===0?await V1(a,i+1,r.value,s):r.value}return wv;case"symbol":return La(a.base,s);case"function":return W1(a,i,s);default:throw new Fa(s)}}async function Q1(a,i){try{return await Xe(a,0,i)}catch(s){throw s instanceof rm?s:new rm(s)}}function jb(a,i){for(let s=0,r=i.length;s<r;s++){const c=i[s];a.has(c)||(a.add(c),c.extends&&jb(a,c.extends))}}function Cb(a){if(a){const i=new Set;return jb(i,a),[...i]}}function $1(a){switch(a){case"Int8Array":return Int8Array;case"Int16Array":return Int16Array;case"Int32Array":return Int32Array;case"Uint8Array":return Uint8Array;case"Uint16Array":return Uint16Array;case"Uint32Array":return Uint32Array;case"Uint8ClampedArray":return Uint8ClampedArray;case"Float32Array":return Float32Array;case"Float64Array":return Float64Array;case"BigInt64Array":return BigInt64Array;case"BigUint64Array":return BigUint64Array;default:throw new Uv(a)}}function X1(a){switch(a){case"constructor":case"__proto__":case"prototype":case"__defineGetter__":case"__defineSetter__":case"__lookupGetter__":case"__lookupSetter__":return!1;default:return!0}}function K1(a){switch(a){case Mn:case Li:case qi:case Un:return!0;default:return!1}}const Z1=1e6,e5=512,t5=1e4,n5=2e4;function Ib(a,i){switch(i){case 3:return Object.freeze(a);case 1:return Object.preventExtensions(a);case 2:return Object.seal(a);default:return a}}const a5=1e3;function i5(a,i){var s,r;const c=(s=i.maxBase64Length)!==null&&s!==void 0?s:Z1;if(!Number.isSafeInteger(c)||c<0)throw new RangeError("maxBase64Length must be a non-negative safe integer");const u=i.refs||new Map;return"types"in u||Object.assign(u,{types:new Map}),{mode:a,plugins:i.plugins,refs:u,features:(r=i.features)!==null&&r!==void 0?r:127^(i.disabledFeatures||0),depthLimit:i.depthLimit||a5,maxBase64Length:c}}
13function s5(a){return{mode:2,base:i5(2,a),child:void 0}}var o5=class{constructor(a,i){this._p=a,this.depth=i}deserialize(a){return Be(this._p,this.depth,a)}};function Rb(a,i){if(i<0||!Number.isFinite(i)||!Number.isInteger(i))throw new ut({t:4,i});if(a.refs.has(i))throw new Error("Conflicted ref id: "+i)}function Ob(a){return!!a&&typeof a=="object"&&"then"in a&&typeof a.then=="function"}function r5(a,i,s){return Rb(a.base,i),a.state.marked.has(i)&&a.base.refs.set(i,s),s}function l5(a,i,s){return Rb(a.base,i),a.base.refs.set(i,s),s}function We(a,i,s){return a.mode===1?r5(a,i,s):l5(a,i,s)}function Yd(a,i,s){if(Object.hasOwn(i,s))return i[s];throw new ut(a)}function c5(a,i){return We(a,i.i,Lv(pa(i.s)))}function d5(a,i,s){const r=s.a,c=r.length,u=We(a,s.i,new Array(c));for(let h=0,f;h<c;h++)f=r[h],f&&(u[h]=Be(a,i,f));return Ib(u,s.o),u}function cm(a,i,s){X1(i)?a[i]=s:Object.defineProperty(a,i,{value:s,configurable:!0,enumerable:!0,writable:!0})}function u5(a,i,s,r,c){if(typeof r=="string")cm(s,pa(r),Be(a,i,c));else{const u=Be(a,i,r);switch(typeof u){case"string":cm(s,u,Be(a,i,c));break;case"symbol":K1(u)&&(s[u]=Be(a,i,c));break;default:throw new ut(r)}}}function mu(a,i,s){a.base.refs.types.set(i,s)}function zi(a,i,s,r){if(a.base.refs.types.get(s)!==r)throw new ut(i)}function Mb(a,i,s,r){const c=s.k;if(c.length>0)for(let u=0,h=s.v,f=c.length;u<f;u++)u5(a,i,r,c[u],h[u]);return r}function h5(a,i,s){const r=We(a,s.i,s.t===10?{}:Object.create(null));return Mb(a,i,s.p,r),Ib(r,s.o),r}function f5(a,i){return We(a,i.i,new Date(i.s))}function g5(a,i){if(!(a.base.features&64))throw new cu(i);let s;switch(i.c){case 0:s=Temporal.Instant.from(i.s);break;case 1:s=Temporal.Duration.from(i.s);break;case 2:s=Temporal.PlainDate.from(i.s);break;case 3:s=Temporal.PlainDateTime.from(i.s);break;case 4:s=Temporal.PlainMonthDay.from(i.s);break;case 5:s=Temporal.PlainTime.from(i.s);break;case 6:s=Temporal.PlainYearMonth.from(i.s);break;case 7:s=Temporal.ZonedDateTime.from(i.s);break;default:throw new ut(i)}return We(a,i.i,s)}function p5(a,i){if(a.base.features&32){const s=pa(i.c);if(s.length>n5)throw new ut(i);return We(a,i.i,new RegExp(s,i.m))}throw new cu(i)}function m5(a,i,s){const r=We(a,s.i,new Set);for(let c=0,u=s.a,h=u.length;c<h;c++)r.add(Be(a,i,u[c]));return r}function b5(a,i,s){const r=We(a,s.i,new Map);for(let c=0,u=s.e.k,h=s.e.v,f=u.length;c<f;c++)r.set(Be(a,i,u[c]),Be(a,i,h[c]));return r}function w5(a,i){if(typeof i.s!="string")throw new ut(i);if(i.s.length>a.base.maxBase64Length)throw new RangeError("ArrayBuffer exceeds maxBase64Length ("+a.base.maxBase64Length+")");const s=pa(i.s);let r;if(s.length<e5||typeof Buffer>"u")r=h1(s);else{const c=atob(s);r=new ArrayBuffer(c.length),Buffer.from(r).write(c,"latin1")}return We(a,i.i,r)}function y5(a,i,s){var r;const c=$1(s.c),u=Be(a,i,s.f);if(!(u instanceof ArrayBuffer))throw new ut(s);const h=(r=s.b)!==null&&r!==void 0?r:0;if(h<0||h>u.byteLength)throw new ut(s);return We(a,s.i,new c(u,h,s.l))}function v5(a,i,s){var r;const c=Be(a,i,s.f);if(!(c instanceof ArrayBuffer))throw new ut(s);const u=(r=s.b)!==null&&r!==void 0?r:0;if(u<0||u>c.byteLength)throw new ut(s);return We(a,s.i,new DataView(c,u,s.l))}function Ub(a,i,s,r){if(s.p){const c=Mb(a,i,s.p,{});Object.defineProperties(r,Object.getOwnPropertyDescriptors(c))}return r}function _5(a,i,s){return Ub(a,i,s,We(a,s.i,new AggregateError([],pa(s.m))))}function k5(a,i,s){const r=Yd(s,mv,s.s);return Ub(a,i,s,We(a,s.i,new r(pa(s.m))))}function x5(a,i,s){const r=hu(),c=We(a,s.i,r.p),u=Be(a,i,s.f);if(Ob(u))throw new ut(s.f);return s.s?r.s(u):r.f(u),c}function T5(a,i,s){return We(a,s.i,Object(Be(a,i,s.f)))}function S5(a,i,s){const r=a.base.plugins;if(r){const c=pa(s.c);for(let u=0,h=r.length;u<h;u++){const f=r[u];if(f.tag===c)return We(a,s.i,f.deserialize(s.s,new o5(a,i),{id:s.i}))}}throw new Rv(s.c)}function A5(a,i){const s=We(a,i.i,We(a,i.s,hu()).p);return mu(a,i.s,22),s}function E5(a,i,s){const r=a.base.refs.get(s.i);if(r){zi(a,s,s.i,22);const c=Be(a,i,s.a[1]);if(Ob(c))throw new ut(s.a[1]);s.t===23?r.s(c):r.f(c);return}throw new Vr("Promise")}function j5(a,i,s){Be(a,i,s.a[0]);const r=Be(a,i,s.a[1]);if(zi(a,s,s.a[1].i,35),!r)throw new ut(s.a[1]);return m1(r)}function C5(a,i,s){Be(a,i,s.a[0]);const r=Be(a,i,s.a[1]);if(zi(a,s,s.a[1].i,31),!r)throw new ut(s.a[1]);return k1(r)}function I5(a,i,s){const r=We(a,s.i,Qs());mu(a,s.i,31);const c=s.a,u=c.length;if(u)for(let h=0;h<u;h++)Be(a,i,c[h]);return r}function R5(a,i,s){const r=a.base.refs.get(s.i);if(r){zi(a,s,s.i,31),r.next(Be(a,i,s.f));return}throw new Vr("Stream")}function O5(a,i,s){const r=a.base.refs.get(s.i);if(r){zi(a,s,s.i,31),r.throw(Be(a,i,s.f));return}throw new Vr("Stream")}function M5(a,i,s){const r=a.base.refs.get(s.i);if(r){zi(a,s,s.i,31),r.return(Be(a,i,s.f));return}throw new Vr("Stream")}function U5(a,i,s){Be(a,i,s.f)}function D5(a,i,s){Be(a,i,s.a[1])}function B5(a,i,s){const r=We(a,s.i,Sb([],s.s,s.l));mu(a,s.i,35);for(let c=0,u=s.a.length;c<u;c++)r.v[c]=Be(a,i,s.a[c]);return r}function Be(a,i,s){if(i>a.base.depthLimit)throw new yb(a.base.depthLimit);switch(i+=1,s.t){case 2:return Yd(s,gv,s.s);case 0:return Number(s.s);case 1:return pa(String(s.s));case 3:if(String(s.s).length>t5)throw new ut(s);return BigInt(s.s);case 4:return a.base.refs.get(s.i);case 18:return c5(a,s);case 9:return d5(a,i,s);case 10:case 11:return h5(a,i,s);case 5:return f5(a,s);case 6:return p5(a,s);case 7:return m5(a,i,s);case 8:return b5(a,i,s);case 19:return w5(a,s);case 16:case 15:return y5(a,i,s);case 20:return v5(a,i,s);case 14:return _5(a,i,s);case 13:return k5(a,i,s);case 12:return x5(a,i,s);case 17:return Yd(s,fv,s.s);case 21:return T5(a,i,s);case 25:return S5(a,i,s);case 22:return A5(a,s);case 23:case 24:return E5(a,i,s);case 28:return j5(a,i,s);case 30:return C5(a,i,s);case 31:return I5(a,i,s);case 32:return R5(a,i,s);case 33:return O5(a,i,s);case 34:return M5(a,i,s);case 27:return U5(a,i,s);case 29:return D5(a,i,s);case 35:return B5(a,i,s);case 36:return g5(a,s);default:throw new cu(s)}}function L5(a,i){try{return Be(a,0,i)}catch(s){throw new Iv(s)}}function q5(a,i){const s=Cb(i.plugins);return L5(s5({maxBase64Length:i.maxBase64Length,plugins:s,refs:i.refs,features:i.features,disabledFeatures:i.disabledFeatures,depthLimit:i.depthLimit}),a)}async function z5(a,i={}){const s=Cb(i.plugins),r=O1(1,{compactArrayBufferViews:i.compactArrayBufferViews,plugins:s,disabledFeatures:i.disabledFeatures});return{t:await Q1(r,a),f:r.base.features,m:Array.from(r.base.marked)}}function N5(a){return{tag:"$TSR/t/"+a.key,test:a.test,parse:{sync(i,s){return{v:s.parse(a.toSerializable(i))}},async async(i,s){return{v:await s.parse(a.toSerializable(i))}},stream(i,s){return{v:s.parse(a.toSerializable(i))}}},serialize:void 0,deserialize(i,s){return a.fromSerializable(s.deserialize(i.v))}}}const H5={tag:"$TSR/Error",test(a){return a instanceof Error},parse:{sync(a,i){return{message:i.parse(a.message)}},async async(a,i){return{message:await i.parse(a.message)}},stream(a,i){return{message:i.parse(a.message)}}}
13,serialize(a,i){return"new Error("+i.serialize(a.message)+")"},deserialize(a,i){return new Error(i.deserialize(a.message))}};var P5=class{constructor(a,i){this.stream=a,this.hint=i?.hint??"binary"}};function Db(a){const i=[];for(let s=0;s<a.length;s+=32768)i.push(String.fromCharCode.apply(null,a.subarray(s,s+32768)));return btoa(i.join(""))}function Bb(a){const i=atob(a),s=new Uint8Array(i.length);for(let r=0;r<i.length;r++)s[r]=i.charCodeAt(r);return s}const F5=new TextDecoder("utf-8",{fatal:!0,ignoreBOM:!0});function J5(a){try{return"t"+F5.decode(a)}catch{return"b"+Db(a)}}const G5=new TextEncoder;function Y5(a){const i=a.slice(1);return a[0]==="t"?G5.encode(i):Bb(i)}function V5(a,i,s){s?.throwIfAborted();const r=Qs(),c=a.getReader();let u=!0;const h=()=>{u=!1,s?.removeEventListener("abort",m),c.releaseLock()},f=g=>u?(c.cancel(g).catch(()=>{}),h(),!0):!1,m=()=>{f(s.reason)&&r.throw(s.reason)};return s?.addEventListener("abort",m),(async()=>{try{for(;u;){const{done:g,value:w}=await c.read();if(!u)return;if(g){h(),r.return(void 0);return}r.next(i(w))}}catch(g){f(g)&&r.throw(g)}})(),[r,f]}function W5(a,i){let s,r=!1;return new ReadableStream({start(c){const u=a.on({next(h){if(!r)try{c.enqueue(i(h))}catch(f){r=!0;const m=s;s=void 0,m?.(),c.error(f)}},throw(h){r||(r=!0,s=void 0,c.error(h))},return(){r||(r=!0,s=void 0,c.close())}});r?u():s=u},cancel(){const c=s;s=void 0,c?.()}})}function Lb(a){return{tag:"tss/RawStream",test:i=>i instanceof P5,parse:{async:async(i,s)=>{const r=await s.parse(i.hint==="text"),[c]=V5(i.stream,i.hint==="text"?J5:Db,a);return{text:r,stream:await s.parse(c)}}},serialize:void 0,deserialize:void 0}}const Q5=Lb(),$5={tag:"tss/RawStream",test:()=>!1,parse:{},serialize:void 0,deserialize(a,i){return W5(i.deserialize(a.stream),i.deserialize(a.text)?Y5:Bb)}},la={},qb=a=>new ReadableStream({start(i){a.on({next(s){try{i.enqueue(s)}catch{}},throw(s){i.error(s)},return(){try{i.close()}catch{}}})}}),X5={tag:"seroval-plugins/web/ReadableStreamFactory",test(a){return a===la},parse:{sync(){return la},async async(){return await Promise.resolve(la)},stream(){return la}},serialize(){return qb.toString()},deserialize(){return la}};async function K5(a,i){try{for(;;){const s=await i.read();if(s.done){a.return(s.value),i.releaseLock();break}a.next(s.value)}}catch(s){i.releaseLock(),a.throw(s)}}function Z5(a){a.cancel().catch(()=>{}),a.releaseLock()}function dm(a){const i=Qs(),s=a.getReader(),r=Z5.bind(null,s);return K5(i,s).catch(r),[i,r]}const e2={tag:"seroval/plugins/web/ReadableStream",extends:[X5],test(a){return typeof ReadableStream>"u"?!1:a instanceof ReadableStream},parse:{sync(a,i){return{factory:i.parse(la),stream:i.parse(Qs())}},async async(a,i){return{factory:await i.parse(la),stream:await i.parse(dm(a)[0])}},stream(a,i){const[s,r]=dm(a);return i.addCleanup(r),{factory:i.parse(la),stream:i.parse(s)}}},serialize(a,i){return"("+i.serialize(a.factory)+")("+i.serialize(a.stream)+")"},deserialize(a,i){const s=i.deserialize(a.stream);if(!s||typeof s!="object"||!Ab(s))throw new Error("Expected a stream source.");return qb(s)}};function zb(a){return[H5,a?Lb(a):Q5,e2]}const t2=zb();[...t2];function n2(a){return{tag:"tss/RawStream",test:()=>!1,parse:{},serialize:void 0,deserialize(i,s){return a(s.deserialize(i.streamId))}}}function a2(a){return[...Um()?.serializationAdapters?.map(N5)??[],...a]}function Nb(a){return a2(zb(a))}var um=new TextDecoder,an=new Uint8Array,i2=new ByteLengthQueuingStrategy({highWaterMark:0});function s2(a){const i=a.getReader(),s=new Map;let r=0,c,u;const h=()=>{c?.(),c=void 0},f=(b,v)=>{const _=b[1];b[1]=!1,_&&(v===1?_.close():_.error(v[0]))},m=new ReadableStream({start(b){u=b},pull:h,cancel(b){const v=[b===void 0?new Error("Framed response cancelled"):b];r=v,h(),i.cancel(b).catch(()=>{});for(const _ of s.values())f(_,v)}});function g(b){const v=s.get(b);if(v)return v;if(s.size>=1024)throw new Error("Too many raw streams");let _;const T=[new ReadableStream({start(x){_=x},cancel(){T[1]!==!1&&(T[1]=null)}},i2),_];return s.set(b,T),r!==0&&f(T,r),T}function w(b){if(b===0||b>>>0!==b)throw new RangeError("Invalid raw stream ID");return g(b)[0]}return(async()=>{let b=an,v=0;async function _(){for(;v===b.byteLength;){b=an,v=0;const x=await i.read();if(r!==0||x.done)return!1;b=x.value}return!0}async function T(x,I){if(x===0)return an;if(!await _()){if(I)return;throw new Error("Incomplete frame")}if(b.byteLength-v>=x){const L=b.subarray(v,v+x);return v+=x,v===b.byteLength&&(b=an,v=0),L}const U=new Uint8Array(x);let N=0;for(;N<x;){if(!await _())throw new Error("Incomplete frame");const L=Math.min(x-N,b.byteLength-v);U.set(b.subarray(v,v+L),N),v+=L,N+=L}return v===b.byteLength&&(b=an,v=0),U}try{for(;r===0;){let x=await T(9,!0);if(r!==0)return;if(!x){for(const Z of s.values())if(Z[1])throw new Error("Incomplete raw 
13stream");r=1,u.close();return}const I=x[0],U=(x[1]<<24|x[2]<<16|x[3]<<8|x[4])>>>0,N=(x[5]<<24|x[6]<<16|x[7]<<8|x[8])>>>0;if(x=an,I>3||I===0!=(U===0)||N>16777216||I===2&&N!==0)throw new Error("Invalid frame");const L=I===0?void 0:g(U);if(L?.[1]===!1)throw new Error("Raw stream already ended");let G=await T(N);if(r!==0)return;if(!L){const Z=um.decode(G);for(G=an,u.enqueue(Z);r===0&&u.desiredSize<=0;)await new Promise(J=>{c=J});continue}if(I===1){const Z=L[1];if(Z){if(-Z.desiredSize>134217728){Z.error(new Error(`Raw stream ${U} has too many unread bytes`)),L[1]=null,G=an;continue}const J=G.byteLength*4<G.buffer.byteLength?G.slice():G;G=an,Z.enqueue(J)}}else f(L,I===2?1:[new Error(um.decode(G))])}}catch(x){if(r===0){const I=[x];r=I,i.cancel(x).catch(()=>{}),u.error(x);for(const U of s.values())f(U,I)}}finally{b=an,i.releaseLock()}})(),[m,w]}const sn=Symbol.for("TSR_DEFERRED_PROMISE");function o2(a,i){const s=a;return s[sn]||(s[sn]={status:"pending"},s.then(r=>{s[sn].status="success",s[sn].data=r}).catch(r=>{s[sn].status="error",s[sn].error={data:$6(r),__isServerError:!0}})),s}const r2="Error preloading route! ☝️";function Hb(a,i){if(a)return typeof a=="string"?a:a[i]}function l2(a){return a?.scriptFormat??"module"}function c2(a,i,s){const r=d2(i),c=Hb(s,"script")??r.crossOrigin;return{...l2(a)==="iife"?{rel:"preload",as:"script"}:{rel:"modulepreload"},href:r.href,...c?{crossOrigin:c}:{}}}function d2(a){return typeof a=="string"?{href:a,crossOrigin:void 0}:a}function xr(a,i){if(i.length===0)return;if(i.length===1){a.push(i[0]);return}const s=new Set;for(const r of i){const c=JSON.stringify(r);s.has(c)||(s.add(c),a.push(r))}}function u2(a){return typeof a=="string"?{href:a,crossOrigin:void 0}:a}function h2(a,i,s,r){const c=Zm(a),u=[],h=[];for(const f of c)for(const m of Array.isArray(f.scripts)?f.scripts:[]){if(!m)continue;const{children:g,...w}=m;u.push({tag:"script",attrs:{...w,...r,nonce:s},children:g})}if(i)for(const f of c)for(const m of i.routes[f.routeId]?.scripts??[])h.push({tag:"script",attrs:{...m.attrs,nonce:s},children:m.children});return[u,h]}function f2([a,i],s){return[...a,...i]}var Pb=class{get to(){return this._to}get id(){return this._id}get path(){return this._path}get fullPath(){return this._fullPath}constructor(a){if(this.init=i=>{this.originalIndex=i,this._branch=void 0;const s=this.options,r=!s?.path&&!s?.id;this.parentRoute=this.options.getParentRoute?.(),r?this._path=Bi:this.parentRoute||Ps();let c=r?Bi:s?.path;c&&c!=="/"&&(c=Hm(c));const u=s?.id||c,h=r?Bi:Di((this.parentRoute.id==="__root__"?"":this.parentRoute.id)+"/"+(u??""));c==="__root__"&&(c="/");const f=h==="__root__"?"/":c===void 0?this.parentRoute.fullPath:Di(this.parentRoute.fullPath+"/"+c);this._path=c,this._id=h,this._fullPath=f,this._to=on(f)},this.addChildren=i=>this._addFileChildren(i),this._addFileChildren=i=>(Array.isArray(i)&&(this.children=i),typeof i=="object"&&i!==null&&(this.children=Object.values(i)),this),this._addFileTypes=()=>this,this.updateLoader=i=>(Object.assign(this.options,i),this),this.update=i=>(Object.assign(this.options,i),this),this.lazy=i=>(this.lazyFn=i,this),this.redirect=i=>Dm({from:this.fullPath,...i}),this.options=a||{},this.isRoot=!a?.getParentRoute,a?.id&&a?.path)throw new Error("Route cannot have both an 'id' and a 'path' option.")}},g2=class extends Pb{constructor(a){super(a)}};var Vs;function Vd(a,i,s){try{return q5(a,i)}catch(r){throw Jb(s),r}}async function Fb(a){a.length>0&&(await Promise.allSettled(a),a.length=0)}function Jb(a){for(const i of a)i.catch(()=>{});a.length=0}var p2=Object.prototype.hasOwnProperty;function Gb(a){for(const i in a)if(p2.call(a,i))return!0;return!1}async function m2(a,i,s){Vs||(Vs=Nb());const r=i[0],c=r.fetch??s,u=r.data instanceof FormData,h=new Headers(r.headers);if(h.set("x-tsr-serverFn","true"),u||h.set("accept",`${c6}, application/x-ndjson, application/json`),r.method==="GET"){if(u)throw new Error("FormData is not supported with GET requests");const m=await Yb(r);if(m!==void 0){const g=Wm({payload:m});a.includes("?")?a+=`&${g}`:a+=`?${g}`}}let f;return r.method==="POST"&&(f=await b2(r),typeof f=="string"&&h.set("content-type","application/json")),w2(()=>c(a,{method:r.method,headers:h,signal:r.signal,body:f}))}async function Yb(a){let i;return a.data!==void 0&&(i={data:a.data}),a.context&&Gb(a.context)&&((i??={}).context=a.context),i?Vb(i,a.signal):void 0}async function Vb(a,i){i?.throwIfAborted();let s;try{s=await z5(a,{plugins:i?Nb(i):Vs})}finally{i?.throwIfAborted()}return JSON.stringify(s)}async function b2(a){if(a.data instanceof FormData){let i;return a.context&&Gb(a.context)&&(i=await Vb(a.context,a.signal)),i!==void 0&&a.data.set(l6,i),a.data}return Yb(a)}async function w2(a){let i;try{i=await a()}catch(r){if(r instanceof Response)i=r;else throw r}if(i.headers.get("x-tss-raw")==="true")return i;const s=i.headers.get("content-type");if(s||Ps(),i.headers.get("x-tss-serialized")){let r;if(s.includes("application/x-tss-framed")){const c=/;\s*v=(\d+)/.exec(s)?.[1];if(c&&+c!=1)throw new Error(`Unsupported framed protocol version ${c}`);if(!i.body)throw new Error("No response body for framed response");const[u,h]=s2(i.body);r=await y2(u,[n2(h),...Vs])}else if(s.includes("application/json")){const c=await i.json(),u=[];r=Vd(c,{plugins:Vs},u),await Fb(u)}if(r||Ps(),r instanceof Error)throw r;return r}if(s.includes("application/json")){const r=await i.json(),c=d6(r);if(c)throw c;if(Na(r))throw r;return r}if(!i.ok)throw new Error(await i.text());return i}
13async function y2(a,i){const s=a.getReader(),r={refs:new Map,plugins:i},c=f=>{s.cancel(f).catch(()=>{})};let u;const h=[];try{const f=await s.read();if(f.done)throw new Error("Stream ended before first object");u=Vd(JSON.parse(f.value),r,h)}catch(f){throw c(f),s.releaseLock(),f}return(async()=>{const f=[];try{for(;;){const m=await s.read();if(m.done)return;Vd(JSON.parse(m.value),r,f),Jb(f)}}catch(m){c(m),console.error("Stream processing error:",m)}finally{s.releaseLock()}})(),await Fb(h),u}function v2(a){const i="/_serverFn/"+a;return Object.assign((...c)=>{const u=Um()?.serverFns?.fetch;return m2(i,c,u??fetch)},{url:i,serverFnMeta:{id:a},[Nd]:!0})}var _2={key:"$TSS/serverfn",test:a=>typeof a!="function"||!(Nd in a)?!1:!!a[Nd],toSerializable:({serverFnMeta:a})=>({functionId:a.id}),fromSerializable:({functionId:a})=>v2(a)},zr=se.use,Wb=se.useLayoutEffect;function k2({promise:a}){if(zr)return zr(a);const i=o2(a);if(i[sn].status==="pending")throw i;if(i[sn].status==="error")throw i[sn].error;return i[sn].data}function x2(a){const i=Q.jsx(T2,{...a});return a.fallback?Q.jsx(se.Suspense,{fallback:a.fallback,children:i}):i}function T2(a){const i=k2(a);return a.children(i)}var bu=class extends se.Component{constructor(...a){super(...a),this.state={error:0},this.reset=()=>{this.setState({error:0})}}static getDerivedStateFromProps(a,i){const s=a.getResetKey();return i.error&&i.resetKey!==s?{resetKey:s,error:0}:{resetKey:s}}static getDerivedStateFromError(a){return{error:[a]}}componentDidCatch(a,i){this.props.onCatch?.(a,i)}render(){const a=this.state.error;return a?se.createElement(this.props.errorComponent??S2,{error:a[0],reset:this.reset}):this.props.children}};function S2({error:a}){const[i,s]=se.useState(!1);return Q.jsxs("div",{style:{padding:".5rem",maxWidth:"100%"},children:[Q.jsxs("div",{style:{display:"flex",alignItems:"center",gap:".5rem"},children:[Q.jsx("strong",{style:{fontSize:"1rem"},children:"Something went wrong!"}),Q.jsx("button",{style:{appearance:"none",fontSize:".6em",border:"1px solid currentColor",padding:".1rem .2rem",fontWeight:"bold",borderRadius:".25rem"},onClick:()=>s(r=>!r),children:i?"Hide Error":"Show Error"})]}),Q.jsx("div",{style:{height:".25rem"}}),i?Q.jsx("div",{children:Q.jsx("pre",{style:{fontSize:".7em",border:"1px solid red",borderRadius:".25rem",padding:".3rem",color:"red",overflow:"auto"},children:a?.message?Q.jsx("code",{children:a.message}):null})}):null]})}var hm=()=>!0,A2=()=>!1;function E2({children:a,fallback:i=null}){return Q.jsx(Jr.Fragment,{children:wu()?a:i})}function wu(a=!0){return Jr.useSyncExternalStore(j2,hm,a?A2:hm)}function j2(){return()=>{}}var Qb=se.createContext(null);function gt(a){return se.useContext(Qb)}
13var yu=se.createContext(void 0),C2=se.createContext(void 0);function I2({update:a,notify:i,unwatched:s}){return{link:r,unlink:c,propagate:u,checkDirty:h,shallowPropagate:f};function r(g,w,b){const v=w.depsTail;if(v!==void 0&&v.dep===g)return;const _=v!==void 0?v.nextDep:w.deps;if(_!==void 0&&_.dep===g){_.version=b,w.depsTail=_;return}const T=g.subsTail;if(T!==void 0&&T.version===b&&T.sub===w)return;const x=w.depsTail=g.subsTail={version:b,dep:g,sub:w,prevDep:v,nextDep:_,prevSub:T,nextSub:void 0};_!==void 0&&(_.prevDep=x),v!==void 0?v.nextDep=x:w.deps=x,T!==void 0?T.nextSub=x:g.subs=x}function c(g,w=g.sub){const b=g.dep,v=g.prevDep,_=g.nextDep,T=g.nextSub,x=g.prevSub;return _!==void 0?_.prevDep=v:w.depsTail=v,v!==void 0?v.nextDep=_:w.deps=_,T!==void 0?T.prevSub=x:b.subsTail=x,x!==void 0?x.nextSub=T:(b.subs=T)===void 0&&s(b),_}function u(g){let w=g.nextSub,b;e:do{const v=g.sub;let _=v.flags;if(_&60?_&12?_&4?!(_&48)&&m(g,v)?(v.flags=_|40,_&=1):_=0:v.flags=_&-9|32:_=0:v.flags=_|32,_&2&&i(v),_&1){const T=v.subs;if(T!==void 0){const x=(g=T).nextSub;x!==void 0&&(b={value:w,prev:b},w=x);continue}}if((g=w)!==void 0){w=g.nextSub;continue}for(;b!==void 0;)if(g=b.value,b=b.prev,g!==void 0){w=g.nextSub;continue e}break}while(!0)}function h(g,w){let b,v=0,_=!1;e:do{const T=g.dep,x=T.flags;if(w.flags&16)_=!0;else if((x&17)===17){if(a(T)){const I=T.subs;I.nextSub!==void 0&&f(I),_=!0}}else if((x&33)===33){(g.nextSub!==void 0||g.prevSub!==void 0)&&(b={value:g,prev:b}),g=T.deps,w=T,++v;continue}if(!_){const I=g.nextDep;if(I!==void 0){g=I;continue}}for(;v--;){const I=w.subs,U=I.nextSub!==void 0;if(U?(g=b.value,b=b.prev):g=I,_){if(a(w)){U&&f(I),w=g.sub;continue}_=!1}else w.flags&=-33;w=g.sub;const N=g.nextDep;if(N!==void 0){g=N;continue e}}return _}while(!0)}function f(g){do{const w=g.sub,b=w.flags;(b&48)===32&&(w.flags=b|16,(b&6)===2&&i(w))}while((g=g.nextSub)!==void 0)}function m(g,w){let b=w.depsTail;for(;b!==void 0;){if(b===g)return!0;b=b.prevDep}return!1}}function R2(a,i,s){const r=typeof a=="object",c=r?a:void 0;return{next:(r?a.next:a)?.bind(c),error:(r?a.error:i)?.bind(c),complete:(r?a.complete:s)?.bind(c)}}const Wd=[];let Or=0;const{link:fm,unlink:O2,propagate:M2,checkDirty:$b,shallowPropagate:gm}=I2({update(a){return a._update()},notify(a){Wd[Qd++]=a,a.flags&=-3},unwatched(a){a.depsTail!==void 0&&(a.depsTail=void 0,a.flags=17,Nr(a))}});let Tr=0,Qd=0,Vt,$d=0;function U2(a){try{++$d,a()}finally{--$d||Xb()}}function Nr(a){const i=a.depsTail;let s=i!==void 0?i.nextDep:a.deps;for(;s!==void 0;)s=O2(s,a)}function Xb(){if(!($d>0)){for(;Tr<Qd;){const a=Wd[Tr];Wd[Tr++]=void 0,a.notify()}Tr=0,Qd=0}}function pm(a,i){const s=typeof a=="function",r=a,c={_snapshot:s?void 0:a,subs:void 0,subsTail:void 0,deps:void 0,depsTail:void 0,flags:s?0:1,get(){return Vt!==void 0&&fm(c,Vt,Or),c._snapshot},subscribe(u){const h=R2(u),f={current:!1},m=D2(()=>{c.get(),f.current?(Vt=void 0,h.next?.(c._snapshot)):f.current=!0});return{unsubscribe:()=>{m.stop()}}},_update(u){const h=Vt,f=i?.compare??Object.is;if(s)Vt=c,++Or,c.depsTail=void 0;else if(u===void 0)return!1;s&&(c.flags=5);try{const m=c._snapshot,g=typeof u=="function"?u(m):u===void 0&&s?r(m):u;return m===void 0||!f(m,g)?(c._snapshot=g,!0):!1}finally{Vt=h,s&&(c.flags&=-5),Nr(c)}}};return s?(c.flags=17,c.get=function(){const u=c.flags;if(u&16||u&32&&$b(c.deps,c)){if(c._update()){const h=c.subs;h!==void 0&&gm(h)}}else u&32&&(c.flags=u&-33);return Vt!==void 0&&fm(c,Vt,Or),c._snapshot}):c.set=function(u){if(c._update(u)){const h=c.subs;h!==void 0&&(M2(h),gm(h),Xb())}},c}function D2(a){const i=()=>{const r=Vt;Vt=s,++Or,s.depsTail=void 0,s.flags=6;try{return a()}finally{Vt=r,s.flags&=-5,Nr(s)}},s={deps:void 0,depsTail:void 0,subs:void 0,subsTail:void 0,flags:6,notify(){const r=this.flags;r&16||r&32&&$b(this.deps,this)?i():this.flags=2},stop(){this.flags=0,this.depsTail=void 0,Nr(this)}};return i(),s}var Md={exports:{}},Ud={},Dd={exports:{}},Bd={};var mm;function B2(){if(mm)return Bd;mm=1;var a=Ws();function i(b,v){return b===v&&(b!==0||1/b===1/v)||b!==b&&v!==v}var s=typeof Object.is=="function"?Object.is:i,r=a.useState,c=a.useEffect,u=a.useLayoutEffect,h=a.useDebugValue;function f(b,v){var _=v(),T=r({inst:{value:_,getSnapshot:v}}),x=T[0].inst,I=T[1];return u(function(){x.value=_,x.getSnapshot=v,m(x)&&I({inst:x})},[b,_,v]),c(function(){return m(x)&&I({inst:x}),b(function(){m(x)&&I({inst:x})})},[b]),h(_),_}function m(b){var v=b.getSnapshot;b=b.value;try{var _=v();return!s(b,_)}catch{return!0}}function g(b,v){return v()}var w=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?g:f;return Bd.useSyncExternalStore=a.useSyncExternalStore!==void 0?a.useSyncExternalStore:w,Bd}var bm;function L2(){return bm||(bm=1,Dd.exports=B2()),Dd.exports}var wm;function q2(){if(wm)return Ud;wm=1;var a=Ws(),i=L2();function s(g,w){return g===w&&(g!==0||1/g===1/w)||g!==g&&w!==w}var r=typeof Object.is=="function"?Object.is:s,c=i.useSyncExternalStore,u=a.useRef,h=a.useEffect,f=a.useMemo,m=a.useDebugValue;return Ud.useSyncExternalStoreWithSelector=function(g,w,b,v,_){var T=u(null);if(T.current===null){var x={hasValue:!1,value:null};T.current=x}else x=T.current;T=f(function(){function U(J){if(!N){if(N=!0,L=J,J=v(J),_!==void 0&&x.hasValue){var M=x.value;if(_(M,J))return G=M}return G=J}if(M=G,r(L,J))return M;var V=v(J);return _!==void 0&&_(M,V)?(L=J,M):(L=J,G=V)}var N=!1,L,G,Z=b===void 0?null:b;return[function(){return U(w())},Z===null?void 0:function(){return U(Z())}]},[w,b,v,_]);var I=c(g,T[0],T[1]);return h(function(){x.hasValue=!0,x.value=I},[I]),m(I),I},Ud}var ym;function z2(){return ym||(ym=1,Md.exports=q2()),Md.exports}var N2=z2();function H2(a,i){return a===i}function Xt(a,i=r=>r,s){const r=s?.compare??H2,c=se.useCallback(h=>{const{unsubscribe:f}=a.subscribe(h);return f},[a]),u=se.useCallback(()=>
13a.get(),[a]);return N2.useSyncExternalStoreWithSelector(c,u,u,i,r)}var vm={};function Kb(a,i){const s=se.useRef();return r=>{const c=a?.select?a.select(r):r;return a?.structuralSharing??i.options.defaultStructuralSharing?s.current=da(s.current,c):c}}function Ja(a){const i=gt(),s=se.useContext(a.from?C2:yu),r=a.from??s,c=i.stores.getMatchStore(r),u=Kb(a,i),h=Xt(c,f=>f?u(f):vm);if(h!==vm)return h;(a.shouldThrow??!0)&&Ps()}function Zb(a){return Ja({from:a.from,strict:a.strict,structuralSharing:a.structuralSharing,select:i=>a.select?a.select(i.loaderData):i.loaderData})}function e0(a){const{select:i,...s}=a;return Ja({...s,select:r=>i?i(r.loaderDeps):r.loaderDeps})}function t0(a){return Ja({from:a.from,shouldThrow:a.shouldThrow,structuralSharing:a.structuralSharing,strict:a.strict,select:i=>{const s=a.strict===!1?i.params:i._strictParams;return a.select?a.select(s):s}})}function n0(a){return Ja({from:a.from,strict:a.strict,shouldThrow:a.shouldThrow,structuralSharing:a.structuralSharing,select:i=>a.select?a.select(i.search):i.search})}function a0(a){const i=gt();return se.useCallback(s=>i.navigate({...s,from:s.from??a?.from}),[a?.from,i])}function i0(a){return Ja({...a,select:i=>a.select?a.select(i.context):i.context})}function P2(...a){const i=se.useRef(a),s=i.current;return a.forEach((r,c)=>{$t(s[c],r,!1,!0)||(s[c]=r)}),i.current}function Sr(a,i){a.preloadRoute(i).catch(s=>{console.warn(s),console.warn(r2)})}var F2={compare:(a,i)=>a[0]===i[0]&&a[1]===i[1]};function J2(a,i){const s=typeof a=="string"&&Hs(a);if(s)return i.has(s)?a:null}function G2(a,i,s,r,c){const u=Fp(a.pathname,r),h=Fp(i.pathname,r);return(s?.exact?u!==h:!(u.startsWith(h)&&(u.length===h.length||u[h.length]==="/")))||(s?.includeSearch??!0)&&!$t(a.search,i.search,!s?.exact,s?.explicitUndefined)?!1:s?.includeHash?c&&a.hash===i.hash:!0}function Y2(a,i,s){const r=gt(),c=se.useRef(null),u=se.useCallback(oe=>{if(c.current=oe,typeof i=="function")return i(oe);i&&(i.current=oe)},[i]),{activeOptions:h,to:f,preload:m,preloadDelay:g,hashScrollIntoView:w,replace:b,startTransition:v,resetScroll:_,viewTransition:T,ignoreBlocker:x,disabled:I,target:U,onClick:N,onBlur:L,onFocus:G,onMouseEnter:Z,onMouseLeave:J,onTouchStart:M}=a,V=wu(!!h?.includeHash),[ae,le,F]=P2(a.search,a.params,h),[P,$]=se.useMemo(()=>[a,{...a}],[r,a.from,a._fromLocation,a.hash,a.to,ae,le,a.state,a.mask,a.unsafeRelative]),ce=se.useMemo(()=>{const oe=J2(f,r.protocolAllowlist);if(oe!==void 0){const Kt=[oe??void 0];return()=>Kt}let Se,De;return Kt=>{P._fromLocation||($._fromLocation=Kt);const ma=r.buildLocation($),cn=X2(ma,r,I);return(!Se||Se[0]!==cn)&&(Se=[cn,!(I||cn&&!Hs(cn))&&void 0],De=[cn,!0]),Se[1]!==void 0&&G2(Kt,ma,F,r.basepath,V)?De:Se}},[F,I,V,P,$,r,f]),[te,O]=Xt(r.stores.location,ce,F2),W=O===void 0&&te,ie=I||te===void 0,Te=se.useRef(!1),be=a.reloadDocument||W||ie?!1:m??r.options.defaultPreload,A=g??r.options.defaultPreloadDelay??0,z=se.useCallback(oe=>{const Se=oe?.isIntersecting;if(!(Se??be==="intent")){Se===!1&&Ld(c);return}if(!A){Sr(r,P);return}zs.has(c)||zs.set(c,setTimeout(()=>{zs.delete(c),Sr(r,P)},A))},[r,P,c,be,A]);se.useEffect(()=>{if(!be)return;be==="render"&&!Te.current&&(Te.current=!0,Sr(r,P));let oe=!0,Se;return be==="viewport"&&c.current&&typeof IntersectionObserver=="function"&&(Se=new IntersectionObserver(De=>{oe&&z(De.pop())},{rootMargin:"100px"}),Se.observe(c.current)),()=>{oe=!1,Se?.disconnect(),Ld(c)}},[r,P,be,z,c]);const Y=Q2(a,s);if(Y.ref=i?u:c,W)return Y.href=W,Y;const K=oe=>{const Se=U??oe.currentTarget.getAttribute("target");!ie&&!(oe.metaKey||oe.altKey||oe.ctrlKey||oe.shiftKey)&&!oe.defaultPrevented&&(!Se||Se==="_self")&&oe.button===0&&(oe.preventDefault(),r.navigate({...P,replace:b,resetScroll:_,hashScrollIntoView:w,startTransition:v,viewTransition:T,ignoreBlocker:x}))},de=()=>{be==="intent"&&Sr(r,P)},ge=()=>{be==="intent"&&Ld(c)};return Y.onClick=Mi(N,K),Y.onBlur=Mi(L,ge),Y.onFocus=Mi(G,z),Y.onMouseEnter=Mi(Z,z),Y.onMouseLeave=Mi(J,ge),Y.onTouchStart=Mi(M,de),$2(Y,a,O,te,ie,s)}var V2={},W2={className:"active"},s0=new Set(["to","params","search","hash","state","mask","from","unsafeRelative","_fromLocation","reloadDocument","preload","preloadDelay","preloadIntentProximity","hashScrollIntoView","replace","startTransition","resetScroll","viewTransition","ignoreBlocker","activeProps","inactiveProps","activeOptions","_asChild"]);function Q2(a,i){const s={};for(const r in a)s0.has(r)||r==="type"&&i!==void 0||r==="disabled"&&i==="a"||(s[r]=a[r]);return s}function $2(a,i,s,r,c,u){const{activeProps:h,inactiveProps:f,className:m,style:g,target:w}=i,b=Lm(s?h:f,{})??(s?W2:V2);Object.assign(a,b),a.href=r,u!=="a"&&(a.disabled=c),a.target=w;const v=b.style;(g||v)&&(a.style=g&&v?{...g,...v}:g||v);const _=b.className;return(m||_)&&(a.className=m?_?`${m} ${_}`:m:_),c&&(a.role="link",a["aria-disabled"]=!0),s&&(a["data-status"]="active",a["aria-current"]="page"),a}var zs=new WeakMap,Ld=a=>{clearTimeout(zs.get(a)),zs.delete(a)},Mi=(a,i)=>a?s=>s.defaultPrevented||(a(s),s.defaultPrevented||i(s)):i;function X2(a,i,s){if(s)return;const r=a.maskedLocation??a,c=r.external?r.publicHref:i.history.createHref(r.publicHref)||"/";if(!((r.external||c!==r.publicHref)&&Nm(c,i.protocolAllowlist)))return c}var o0=se.memo(se.forwardRef((a,i)=>{const s=a._asChild||"a",r=Y2(a,i,s),c=typeof a.children=="function"?a.children({isActive:r["data-status"]==="active"}):a.children;return se.createElement(s,r,c)}),K2);function K2(a,i){let s=0;for(const r in i)if(s++,a[r]!==i[r]&&(!s0.has(r)||!$t(a[r],i[r],!1,!0)))return!1;for(const r in a)s--;return s===0}var Z2=class extends Pb{constructor(i){super(i),this.useMatch=s=>Ja({...s,from:this.id}),this.useRouteContext=s=>i0({...s,from:this.id}),this.useSearch=s=>n0({...s,from:this.id}),this.useParams=s=>t0({...s,from:this.id}),this.useLoaderDeps=s=>e0({...s,from:this.id}),this.useLoaderData=s=>Zb({...s,from:this.id}),this.useNavigate=()=>a0({from:this.fullPath}),this.Link=Jr.forwardRef((s,r)=>Q.jsx(o0,{ref:r,from:this.fullPath,...s}))}};function e8(a){return new Z2(a)}
13var t8=class extends g2{constructor(a){super(a),this.useMatch=i=>Ja({...i,from:this.id}),this.useRouteContext=i=>i0({...i,from:this.id}),this.useSearch=i=>n0({...i,from:this.id}),this.useParams=i=>t0({...i,from:this.id}),this.useLoaderDeps=i=>e0({...i,from:this.id}),this.useLoaderData=i=>Zb({...i,from:this.id}),this.useNavigate=()=>a0({from:this.fullPath}),this.Link=Jr.forwardRef((i,s)=>Q.jsx(o0,{ref:s,from:this.fullPath,...i}))}};function n8(a){return new t8(a)}function ot(a){return i=>{const s=e8(i);return s.isRoot=!1,s}}function rt(a,i){let s,r,c;const u=()=>(s||(c=void 0,s=a().then(f=>{s=void 0,h.preload=void 0,r=f[i]}).catch(f=>{s=void 0,c=f})),s),h=function(m){if(c){if(h6(c)&&typeof sessionStorage<"u"){const g=`tanstack_router_reload:${c.message}`;if(!sessionStorage.getItem(g))throw sessionStorage.setItem(g,"1"),window.location.reload(),new Promise(()=>{})}throw c}if(!r)if(zr)zr(u());else throw u();return se.createElement(r,m)};return h.preload=u,h}function a8(a){const i=gt(),s=`not-found-${Xt(i.stores.location,r=>r.pathname)}-${Xt(i.stores.status)}`;return Q.jsx(bu,{getResetKey:()=>s,onCatch:(r,c)=>{if(Na(r))a.onCatch?.(r,c);else throw r},errorComponent:({error:r})=>{if(Na(r))return a.fallback?.(r);throw r},children:a.children})}function i8(){return Q.jsx("p",{children:"Not Found"})}function r0(a,i,s){return i.options.notFoundComponent?Q.jsx(i.options.notFoundComponent,{...s}):a.options.defaultNotFoundComponent?Q.jsx(a.options.defaultNotFoundComponent,{...s}):Q.jsx(i8,{})}function $r(a,i){const s=i?.options.pendingComponent??a.options.defaultPendingComponent;return s?Q.jsx(s,{}):null}var s8=(a,i)=>a[0]===i[0]&&a[1]===i[1],l0=(a,i,s)=>!i.isRoot||i.options.shellComponent||i.options.wrapInSuspense||s===!1||s==="data-only"||!a.ssr,c0=se.memo(function({routeId:i}){const s=gt();return Q.jsx(o8,{router:s,match:Xt(s.stores.getMatchStore(i))})});function o8({router:a,match:i}){const s=a.routesById[i.routeId],r=$r(a,s),c=s.options.errorComponent??a.options.defaultErrorComponent,u=s.options.onCatch??a.options.defaultOnCatch,h=s.isRoot?s.options.notFoundComponent??a.options.notFoundRoute?.options.component:s.options.notFoundComponent,f=i.ssr===!1||i.ssr==="data-only",m=l0(a,s,i.ssr)&&(s.options.wrapInSuspense??r??(s.options.errorComponent?.preload||f));let g=Q.jsx(r8,{match:i});f&&(g=Q.jsx(E2,{fallback:r,children:g})),h&&(g=Q.jsx(a8,{fallback:v=>{if(v.routeId??=i.routeId,v.routeId!==i.routeId)throw v;return se.createElement(h,v)},children:g})),c&&(g=Q.jsx(bu,{getResetKey:()=>i,errorComponent:c,onCatch:(v,_)=>{if(Na(v))throw v.routeId??=i.routeId,v;u?.(v,_)},children:g})),m&&(g=Q.jsx(se.Suspense,{fallback:r,children:g}));const w=null,b=s.isRoot?s.options.shellComponent:void 0;return Q.jsx(yu.Provider,{value:i.routeId,children:b?Q.jsxs(b,{children:[g,w]}):Q.jsxs(Q.Fragment,{children:[g,w]})})}var r8=se.memo(function({match:i}){const s=gt(),r=i.routeId,c=s.routesById[r],u=se.useMemo(()=>{const f=(c.options.remountDeps??s.options.defaultRemountDeps)?.({routeId:r,loaderDeps:i.loaderDeps,params:i._strictParams,search:i._strictSearch});return f?JSON.stringify(f):void 0},[r,i.loaderDeps,i._strictParams,i._strictSearch,c.options.remountDeps,s.options.defaultRemountDeps]),h=se.useMemo(()=>{const f=c.options.component??s.options.defaultComponent;return f?Q.jsx(f,{},u):Q.jsx(l8,{})},[u,c.options.component,s.options.defaultComponent]);if(i.status==="pending"){if(s.ssr&&!l0(s,c,i.ssr))return h;if(s._tx)throw s._tx[5];return $r(s,c)}if(i.status==="notFound")return r0(s,c,i.error);if(i.status==="error")throw i.error;return h}),l8=se.memo(function(){const i=gt(),s=se.useContext(yu);let r,c,u;{const f=i.stores.getMatchStore(s);[r,c]=Xt(f,m=>[!!m._notFound,m.error],{compare:s8}),u=Xt(i.stores.ids,m=>m[m.indexOf(s)+1])}if(r)return r0(i,i.routesById[s],c);if(!u)return null;const h=Q.jsx(c0,{routeId:u});return s===Bi?Q.jsx(se.Suspense,{fallback:$r(i),children:h}):h});function d0(a,i){const s=a[1];a.length=0,s?.(i)}function c8({t:a}){const i=gt(),s=i._rendered??=[];return i.startTransition=(r,c)=>new Promise(u=>{d0(s,!1),s.push(c,u),a(i),se.startTransition(r)}),Wb(()=>{const r=i.history.subscribe(i.load);i.updateLatestLocation();const c=i.latestLocation,u=i.buildLocation({to:c.pathname,search:!0,params:!0,hash:!0,state:!0,_includeValidateSearch:!0});if(on(c.publicHref)!==on(u.publicHref))return i.commitLocation({...u,replace:!0,ignoreBlocker:!0}),r;const h=i.stores.resolvedLocation.get();return h?.href===c.href&&h.state.__TSR_key===c.state.__TSR_key?s.push(i.stores.matches.get(),f=>{f&&i.emit({type:"onRendered",...Gr(h,h)})}):i._tx||i.load({sync:!0}).catch(console.error),r},[i,i.history]),null}function d8(){const a=gt(),i=a.routesById[Bi],s=$r(a,i),r=Q.jsxs(Q.Fragment,{children:[Q.jsx(c8,{t:se.useState()[1]}),a.ssr?Q.jsx(_m,{}):Q.jsx(se.Suspense,{fallback:s,children:Q.jsx(_m,{})})]});return a.options.InnerWrap?Q.jsx(a.options.InnerWrap,{children:r}):r}function _m(){const a=gt(),i=a._rendered,s=Xt(a.stores.matches,h=>
13i[0]??h),r=s[0],c=r?.routeId;Wb(()=>{i[0]===s&&d0(i,!0)},[i,s]);const u=c?Q.jsx(c0,{routeId:c}):null;return a.options.disableGlobalCatchBoundary?u:Q.jsx(bu,{getResetKey:()=>r,onCatch:void 0,children:u})}var u8=a=>({createMutableStore:pm,createReadonlyStore:pm,batch:U2}),h8=a=>new f8(a),f8=class extends K6{constructor(a){super(a,u8)}};function g8({router:a,children:i,...s}){qm(s)&&a.update({...a.options,...s,context:{...a.options.context,...s.context}});const r=Q.jsx(Qb.Provider,{value:a,children:i});return a.options.Wrap?Q.jsx(a.options.Wrap,{children:r}):r}function p8({router:a,...i}){return Q.jsx(g8,{router:a,...i,children:Q.jsx(d8,{})})}function vu(a){const i=gt({warn:a?.router===void 0}),s=a?.router||i;return Xt(s.stores.__store,Kb(a,s))}function km(a,i){if(i)for(const[s,r]of Object.entries(i))s!=="suppressHydrationWarning"&&r!==void 0&&r!==!1&&a.setAttribute(s,typeof r=="boolean"?"":String(r))}function u0(a){const{attrs:i,children:s,nonce:r,preventScriptHoist:c}=a,u=se.useMemo(()=>s===void 0?void 0:{__html:s},[s]);switch(a.tag){case"title":return Q.jsx("title",{...i,suppressHydrationWarning:!0,children:s});case"meta":return Q.jsx("meta",{...i,suppressHydrationWarning:!0});case"link":return Q.jsx("link",{...i,precedence:i?.precedence??(i?.rel==="stylesheet"?"default":void 0),nonce:r,suppressHydrationWarning:!0});case"style":return a.inlineCss,Q.jsx("style",{...i,dangerouslySetInnerHTML:u,nonce:r});case"script":return Q.jsx(m8,{attrs:i,preventScriptHoist:c,children:s});default:return null}}function m8({attrs:a,children:i,preventScriptHoist:s}){gt();const r=wu(),c=se.useMemo(()=>i===void 0?void 0:{__html:i},[i]),u=typeof a?.type=="string"&&a.type!==""&&a.type!=="text/javascript"&&a.type!=="module";if(se.useEffect(()=>{if(!u){if(a?.src){const h=document.createElement("a");h.href=a.src;const f=h.href;for(const g of document.scripts)if(g.src===f)return;const m=document.createElement("script");return km(m,a),document.head.appendChild(m),()=>m.remove()}if(typeof i=="string"){const h=typeof a?.type=="string"?a.type:"text/javascript",f=typeof a?.nonce=="string"?a.nonce:void 0;for(const g of document.scripts){if(g.hasAttribute("src"))continue;const w=g.getAttribute("type")??"text/javascript",b=g.getAttribute("nonce")??void 0;if(g.textContent===i&&w===h&&b===f)return}
13const m=document.createElement("script");return m.textContent=i,km(m,a),document.head.appendChild(m),()=>m.remove()}}},[a,i,u]),u&&typeof i=="string")return Q.jsx("script",{...a,suppressHydrationWarning:!0,dangerouslySetInnerHTML:c});if(!r){if(a?.src)return Q.jsx("script",{...a,suppressHydrationWarning:!0});if(typeof i=="string")return Q.jsx("script",{...a,dangerouslySetInnerHTML:c,suppressHydrationWarning:!0})}return null}function b8(a,i,s,r){s=Zm(s);const c=s.map(x=>x.meta).filter(x=>x!==void 0),u=[],h={};let f;for(let x=c.length-1;x>=0;x--){const I=c[x];for(let U=I.length-1;U>=0;U--){const N=I[U];if(N)if(N.title)f||(f={tag:"title",children:N.title});else if("script:ld+json"in N)try{const L=JSON.stringify(N["script:ld+json"]);u.push({tag:"script",attrs:{type:"application/ld+json"},children:w6(L)})}catch{}else{const L=N.name??N.property;if(L){if(h[L])continue;h[L]=!0}u.push({tag:"meta",attrs:{...N,nonce:i}})}}}f&&u.push(f),i&&u.push({tag:"meta",attrs:{property:"csp-nonce",content:i}}),u.reverse();const m=s.flatMap(x=>x.links??[]).filter(x=>x!==void 0).map(x=>({tag:"link",attrs:{...x,nonce:i}})),g=a.ssr?.manifest,w=[];g&&(s.forEach(x=>{g.routes[x.routeId]?.css?.forEach(I=>{const U=u2(I);w.push({tag:"link",attrs:{rel:"stylesheet",...U,crossOrigin:Hb(r,"stylesheet")??U.crossOrigin,suppressHydrationWarning:!0,nonce:i}})})}),g.inlineStyle&&w.push({tag:"style",attrs:{...g.inlineStyle.attrs,nonce:i},children:g.inlineStyle.children,inlineCss:!0}));const b=[];g&&s.forEach(x=>{g.routes[x.routeId]?.preloads?.forEach(I=>{b.push({tag:"link",attrs:{...c2(g,I,r),nonce:i}})})});const v=s.flatMap(x=>x.styles??[]).filter(x=>x!==void 0).map(({children:x,...I})=>({tag:"style",attrs:{...I,nonce:i},children:x})),_=s.flatMap(x=>x.headScripts??[]).filter(x=>x!==void 0).map(({children:x,...I})=>({tag:"script",attrs:{...I,nonce:i},children:x})),T=[];return xr(T,u),T.push(...b),xr(T,m),T.push(...w),xr(T,v),xr(T,_),T}var w8=a=>{const i=gt(),s=i.options.ssr?.nonce,r=se.useCallback(c=>b8(i,s,c,a),[a,s,i]);return Xt(i.stores.matches,r,{compare:$t})};function y8(a){const i=w8(a.assetCrossOrigin),s=gt().options.ssr?.nonce;return Q.jsx(Q.Fragment,{children:i.map(r=>se.createElement(u0,{...r,key:`tsr-meta-${JSON.stringify(r)}`,nonce:s}))})}var v8={suppressHydrationWarning:!0},_8=()=>{const a=gt(),i=a.options.ssr?.nonce,s=c=>{const u=h2(c,a.ssr?.manifest,i,v8);for(const h of u[1])if(typeof h.attrs?.src=="string"){const f=h;f.preventScriptHoist=!0}return u},r=c=>f2(s(c));return k8(Xt(a.stores.matches,r,{compare:$t}))};function k8(a){return Q.jsx(Q.Fragment,{children:a.map((i,s)=>se.createElement(u0,{...i,key:`tsr-scripts-${i.tag}-${s}`}))})}const Ga=a=>{try{const i=`${a}=`,s=document.cookie?document.cookie.split("; "):[];for(const r of s)if(r.startsWith(i))return decodeURIComponent(r.slice(i.length));return null}catch{return null}},Xr=(a,i,s,r)=>{const c=new Date(Date.now()+s*24*60*60*1e3).toUTCString();let u=`${a}=${encodeURIComponent(i)}; expires=${c}; path=/; SameSite=Lax`;r&&(u+=`; domain=${r}`),window.location.protocol==="https:"&&(u+="; Secure"),document.cookie=u},_u=a=>{if(a==="localhost"||/^[0-9.]+$/.test(a))return null;const i=a.split(".");return i.length<=2?a:i.slice(-2).join(".")},x8=a=>{const i=Mr(a.email);if(i)return i;if(a.responses)for(const[s,r]of Object.entries(a.responses)){if(!/email/i.test(s))continue;const c=Mr(r)??Mr(typeof r=="object"&&r!=null&&"value"in r?r.value:void 0);if(c)return c}return T8(a.actionValue)},T8=a=>{if(a)try{const i=new URL(a,"https://unused.invalid");return Mr(i.searchParams.get("email"))}catch{return}},Mr=a=>{if(typeof a!="string")return;const i=a.trim();if(!(i.length===0||!i.includes("@")))return i.slice(0,256)},S8=1e4,A8=200,h0=(a,i)=>{const s=S8,r=A8,c=[];let u=!1;const h=()=>{try{return a()??null}catch{return null}},f=g=>{const w=c.splice(0);for(const b of w)try{b(g)}catch{}},m=()=>{if(u)return;u=!0;const g=Date.now(),w=()=>{const b=h();if(b){u=!1,f(b);return}if(Date.now()-g>=s){u=!1,c.length=0;return}setTimeout(w,r)};w()};return g=>{const w=h();if(w){try{g(w)}catch{}return}c.push(g),m()}},E8="https://app.meticulous.ai/api/confirm-booking",$s=()=>
13{try{return window._DATADOG_SYNTHETICS_BROWSER!=null}catch{return!1}},f0=["utm_source","utm_medium","utm_campaign","utm_content","utm_term"],j8=()=>{const a={};try{const i=new URLSearchParams(window.location.search);for(const s of f0){const r=i.get(s)||Ga(s);r&&(a[s]=r)}}catch{}return a};let Xd=null;const C8=a=>{const i={};a.actionType&&(i.actionType=a.actionType),a.actionValue&&(i.actionValue=a.actionValue);const s=x8(a);return s&&(i.answers={email:s}),i},I8=3e3,R8=200;let xm=!1;const O8=()=>{$s()||xm||(xm=!0,M8(()=>{p0("PageVisit",ku(),g0())}))},M8=a=>{const i=Date.now(),s=()=>{let r=!1;try{r=window.posthog?.__loaded===!0}catch{r=!0}if(r||Date.now()-i>=I8){a();return}setTimeout(s,R8)};s()},k7=()=>{$s()||(el("routing_form_view"),Kr("ViewContent"),Zr(28760570))},x7=()=>{$s()||(el("demo_form_view"),Kr("Search"),Zr(28760594))},T7=a=>{Xd=C8(a)},S7=()=>{$s()||(el("routing_form_complete"),Kr("SignUp"),Zr(28760586))},A7=a=>{if($s())return;const i=Kr("Lead",{calBookingUid:a});el("demo_booked",{transaction_id:i}),Zr(28760602)};let U8=null;const D8=()=>U8??=ku(),B8=new Set(["SignUp","Lead"]),Kr=(a,i)=>{const s=i?.calBookingUid??ku();N8(c=>{c("track",a,{conversionId:s})});const r=g0();return i?.calBookingUid&&(r.calBookingUid=i.calBookingUid),p0(a,s,r),s},g0=()=>{const a={},i=F8(),s=G8(),r=Y8(),c=V8();return i&&(a.rdtCid=i),s&&(a.rdtUuid=s),r&&(a.externalId=r),c&&(a.screenWidth=c.width,a.screenHeight=c.height),a},p0=(a,i,s)=>{const r=JSON.stringify({eventId:i,eventAt:new Date().toISOString(),eventName:a,user:s,browserSessionId:D8(),pageUrl:Mt(window.location.href,2048),referrer:Mt(document.referrer,2048)||void 0,originalReferrer:P8()??void 0,gclid:J8()??void 0,utm:q8(j8()),...L8(),...B8.has(a)&&Xd?{routing:z8(Xd)}:{}}),c=new Blob([r],{type:"text/plain"});navigator.sendBeacon?.(E8,c)},L8=()=>{try{const a=window.posthog;if(!a)return{};const i=a.get_distinct_id?.(),s=a.get_session_id?.();return{...i?{posthogDistinctId:Mt(String(i),256)}:{},...s?{posthogSessionId:Mt(String(s),256)}:{}}}catch{return{}}},q8=a=>{const i={};for(const s of f0){const r=a[s];r&&(i[s.replace("utm_","")]=Mt(r,256))}return Object.keys(i).length>0?i:void 0},z8=a=>{const i={};return a.actionType&&(i.actionType=Mt(a.actionType,64)),a.actionValue&&(i.actionValue=Mt(a.actionValue,4096)),a.answers?.email&&(i.answers={email:Mt(a.answers.email,256)}),i},Mt=(a,i)=>a.length>i?a.slice(0,i):a,N8=h0(()=>{const a=window.rdt;return typeof a=="function"?a:null}),H8=h0(()=>{const a=window.lintrk;return typeof a=="function"?a:null}),Zr=a=>{H8(i=>{i("track",{conversion_id:a})})},el=(a,i)=>{window.dataLayer=window.dataLayer??[],window.dataLayer.push({event:a,...i??{}})},ku=()=>window.crypto?.randomUUID?.()??`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`,P8=()=>{try{const a=Ga("originalReferrer");return a?Mt(a,2048):null}catch{return null}},F8=()=>m0("rdt_cid"),J8=()=>m0("gclid"),G8=()=>{try{const a=Ga("_rdt_uuid");return a?Mt(a,256):null}catch{return null}},Y8=()=>{try{const a=Ga("met_ext_id");return a?Mt(a,256):null}catch{return null}},V8=()=>{try{const a=window.screen?.width,i=window.screen?.height;return typeof a!="number"||typeof i!="number"||!Number.isFinite(a)||!Number.isFinite(i)||a<=0||i<=0?null:{width:Math.round(a),height:Math.round(i)}}catch{return null}},m0=a=>{try{const i=new URLSearchParams(window.location.search).get(a);if(i)return Mt(i,256);const s=Ga(a);return s?Mt(s,256):null}catch{return null}},W8=30,Q8=400,Tm="met_ext_id",$8=["rdt_cid","gclid"],X8=()=>{const a=_u(window.location.hostname);for(const i of $8)K8(i,W8,a);if(!Ga(Tm)){const i=window.crypto?.randomUUID?.()??`${Date.now()}-${Math.random().toString(36).slice(2)}`;Xr(Tm,i,Q8,a)}},K8=(a,i,s)=>{const r=new URLSearchParams(window.location.search).get(a);r&&Xr(a,r,i,s)},Sm="originalReferrer",Z8=1,e9=()=>{if(Ga(Sm)!==null)return;const a=document.referrer;if(!a)return;let i;try{i=new URL(a).hostname}catch{return}const s=_u(window.location.hostname);s!=null&&t9(i,s)||Xr(Sm,a,Z8,s)},t9=(a,i)=>a===i||a.endsWith(`.${i}`),n9=["utm_source","utm_medium","utm_campaign","utm_content","utm_term"],a9=90,i9=()=>{const a=new URLSearchParams(window.location.search),i={};for(const r of n9){const c=a.get(r);c&&(i[r]=c)}if(Object.keys(i).length===0)return;const s=_u(window.location.hostname);for(const[r,c]of Object.entries(i))c!=null&&Xr(r,c,a9,s)}
13,s9=()=>{try{i9()}catch{}try{X8()}catch{}try{e9()}catch{}},o9=()=>{s9();try{O8()}catch{}},r9=()=>{const a=vu({select:i=>i.location.searchStr});se.useEffect(()=>{if(a==="")return;const i=a.startsWith("?")?a:`?${a}`;for(const s of document.querySelectorAll("a")){const r=s.getAttribute("href");r===null||r.includes("?")||r.startsWith("mailto:")||r.startsWith("tel:")||s.setAttribute("href",r+i)}},[a])},l9='!function(o,c){var n=c.documentElement,t=" w-mod-";n.className+=t+"js",("ontouchstart"in o||o.DocumentTouch&&c instanceof DocumentTouch)&&(n.className+=t+"touch")}(window,document);',c9=()=>{const a="88KztM4fECyle122NObL7p61q9kPzFUu";return`// Load PostHog JS
14!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getFeatureFlag getFeatureFlagPayload reloadFeatureFlags group updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures getActiveMatchingSurveys getSurveys getNextSurveyStep".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
15        !function(){var analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware"];analytics.factory=function(e){return function(){var t=Array.prototype.slice.call(arguments);t.unshift(e);analytics.push(t);return analytics}};for(var e=0;e<analytics.methods.length;e++){var key=analytics.methods[e];analytics[key]=analytics.factory(key)}analytics.load=function(key,e){var t=document.createElement("script");t.type="text/javascript";t.async=!0;t.src="https://cdn.segment.com/analytics.js/v1/" + key + "/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);analytics._loadOptions=e};analytics._writeKey=${JSON.stringify(a)};;analytics.SNIPPET_VERSION="4.15.3";
16        analytics.load(${JSON.stringify(a)});
17        analytics.ready(() => {
18          window.posthog.init(${JSON.stringify("phc_antGuUKbSRSxwV9HrVllbMOxWXWiOTC1YgJ4CVLbjJR")}, {
19            api_host: 'https://data.meticulous.ai',
20            ui_host: 'https://us.posthog.com',
21            segment: window.analytics,
22            capture_pageview: false,
23            capture_pageleave: true,
24            loaded: (posthog) => {
25              window.analytics.page()
26            }
27        });
28		});
29        }}();`},d9='!function(){var e=["identify","page","startAutoPage","stopAutoPage","startAutoIdentify","stopAutoIdentify"];function t(o){return Object.assign([],e.reduce(function(r,n){return r[n]=function(){return o.push([n,[].slice.call(arguments)]),o},r},{}))}window.unify||(window.unify=t(window.unify)),window.unifyBrowser||(window.unifyBrowser=t(window.unifyBrowser));var n=document.createElement("script");n.async=!0,n.setAttribute("src","https://tag.unifyintent.com/v1/ByqaN9tJEwZvnpL9SKQNWF/script.js"),n.setAttribute("data-api-key","wk_dzDE77cy_Aoi7TT3Svzbo77rN91HpL5hDijGQis7X"),n.setAttribute("id","unifytag"),(document.body||document.head).appendChild(n)}();',u9='!function(key) {if (window.reb2b) return;window.reb2b = {loaded: true};var s = document.createElement("script");s.async = true;
29s.src = "https://ddwl4m2hdecbv.cloudfront.net/b/" + key + "/" + key + ".js.gz";document.getElementsByTagName("script")[0].parentNode.insertBefore(s, document.getElementsByTagName("script")[0]);}("W6Z57HZ97QOX");',h9="https://cdn-cookieyes.com/client_data/2249329c0b1ab6d0df2fe478d1765a2f/script.js",f9=()=>{const a=vu({select:s=>s.location.pathname}),i=se.useRef(a);return r9(),se.useEffect(()=>{const s=()=>{Am("marketing-unify",d9),Am("marketing-reb2b",u9),o9()};if(document.readyState==="complete"){s();return}return window.addEventListener("load",s,{once:!0}),()=>window.removeEventListener("load",s)},[]),se.useEffect(()=>{a!==i.current&&window.analytics?.page()},[a]),null},Am=(a,i)=>{if(document.getElementById(a)!==null)return;const s=document.createElement("script");s.id=a,s.textContent=i,document.body.appendChild(s)},g9=()=>Q.jsx("div",{className:"utility-page-wrap",children:Q.jsxs("div",{className:"utility-page-content",children:[Q.jsx("img",{src:"/images/page-not-found.svg",alt:""}),Q.jsx("h2",{children:"Page Not Found"}),Q.jsx("div",{children:"The page you are looking for doesn't exist or has been moved"})]})}),p9="https://snippet.meticulous.ai/v1/meticulous.js",m9=()=>"IrxZ0wgf8TIQiAFjMpt2NMNwbRf5UrDA9DQWoMam",b9=()=>!0,w9=a=>a.map(i=>`${i.url} ${i.width}w`).join(", "),y9={"/images/Bilt-logo-1.webp":{width:585,height:99,isAnimated:!1,fallbackUrl:"/images/Bilt-logo-1.webp",avif:[],webp:[{width:585,url:"/images/generated/Bilt-logo-1.eed1fd7208-585.webp"}]},"/images/Brex.webp":{width:4723,height:1250,isAnimated:!1,fallbackUrl:"/images/Brex.webp",avif:[{width:400,url:"/images/generated/Brex.a9b4c9b085-400.avif"},{width:800,url:"/images/generated/Brex.a9b4c9b085-800.avif"},{width:1200,url:"/images/generated/Brex.a9b4c9b085-1200.avif"},{width:1600,url:"/images/generated/Brex.a9b4c9b085-1600.avif"},{width:2e3,url:"/images/generated/Brex.a9b4c9b085-2000.avif"},{width:4723,url:"/images/generated/Brex.a9b4c9b085-4723.avif"}],webp:[{width:400,url:"/images/generated/Brex.a9b4c9b085-400.webp"},{width:800,url:"/images/generated/Brex.a9b4c9b085-800.webp"},{width:1200,url:"/images/generated/Brex.a9b4c9b085-1200.webp"},{width:1600,url:"/images/generated/Brex.a9b4c9b085-1600.webp"},{width:2e3,url:"/images/generated/Brex.a9b4c9b085-2000.webp"}]},"/images/Canary-techn.-logo-1.webp":{width:537,height:141,isAnimated:!1,fallbackUrl:"/images/Canary-techn.-logo-1.webp",avif:[{width:400,url:"/images/generated/Canary-techn.-logo-1.06957e6c90-400.avif"},{width:537,url:"/images/generated/Canary-techn.-logo-1.06957e6c90-537.avif"}],webp:[{width:400,url:"/images/generated/Canary-techn.-logo-1.06957e6c90-400.webp"},{width:537,url:"/images/generated/Canary-techn.-logo-1.06957e6c90-537.webp"}]},"/images/Colin-Hanna.jpeg":{width:249,height:249,isAnimated:!1,fallbackUrl:"/images/Colin-Hanna.jpeg",avif:[{width:249,url:"/images/generated/Colin-Hanna.121ee9ad97-249.avif"}],webp:[{width:249,url:"/images/generated/Colin-Hanna.121ee9ad97-249.webp"}]},"/images/Colin-Hanna.webp":{width:800,height:670,isAnimated:!1,fallbackUrl:"/images/Colin-Hanna.webp",avif:[{width:400,url:"/images/generated/Colin-Hanna.e44568ac51-400.avif"},{width:800,url:"/images/generated/Colin-Hanna.e44568ac51-800.avif"}],webp:[{width:400,url:"/images/generated/Colin-Hanna.e44568ac51-400.webp"},{width:800,url:"/images/generated/Colin-Hanna.e44568ac51-800.webp"}]},"/images/Engine-Picture-of-Colin.webp":{width:1920,height:1080,isAnimated:!1,fallbackUrl:"/images/Engine-Picture-of-Colin.webp",avif:[{width:400,url:"/images/generated/Engine-Picture-of-Colin.f9eb6d7072-400.avif"},{width:800,url:"/images/generated/Engine-Picture-of-Colin.f9eb6d7072-800.avif"},{width:1200,url:"/images/generated/Engine-Picture-of-Colin.f9eb6d7072-1200.avif"},{width:1600,url:"/images/generated/Engine-Picture-of-Colin.f9eb6d7072-1600.avif"},{width:1920,url:"/images/generated/Engine-Picture-of-Colin.f9eb6d7072-1920.avif"}],webp:[{width:400,url:"/images/generated/Engine-Picture-of-Colin.f9eb6d7072-400.webp"},{width:800,url:"/images/generated/Engine-Picture-of-Colin.f9eb6d7072-800.webp"},{width:1200,url:"/images/generated/Engine-Picture-of-Colin.f9eb6d7072-1200.webp"},{width:1600,url:"/images/generated/Engine-Picture-of-Colin.f9eb6d7072-1600.webp"},{width:1920,url:"/images/generated/Engine-Picture-of-Colin.f9eb6d7072-1920.webp"}]},"/images/Engine.webp":{width:3390,height:1880,isAnimated:!1,fallbackUrl:"/images/Engine.webp",avif:[{width:400,url:"/images/generated/Engine.3c520f4be6-400.avif"},{width:800,url:"/images/generated/Engine.3c520f4be6-800.avif"},{width:1200,url:"/images/generated/Engine.3c520f4be6-1200.avif"},{width:1600,url:"/images/generated/Engine.3c520f4be6-1600.avif"},{width:2e3,url:"/images/generated/Engine.3c520f4be6-2000.avif"},{width:3390,url:"/images/generated/Engine.3c520f4be6-3390.avif"}],webp:[{width:400,url:"/images/generated/Engine.3c520f4be6-400.webp"},{width:800,url:"/images/generated/Engine.3c520f4be6-800.webp"},{width:1200,url:"/images/generated/Engine.3c520f4be6-1200.webp"},{width:1600,url:"/images/generated/Engine.3c520f4be6-1600.webp"},{width:2e3,url:"/images/generated/Engine.3c520f4be6-2000.webp"},{width:3390,url:"/images/generated/Engine.3c520f4be6-3390.webp"}]},"/images/Erdem-Alparslan.webp":{width:800,height:670,isAnimated:!1,fallbackUrl:"/images/Erdem-Alparslan.webp",avif:[{width:400,url:"/images/generated/Erdem-Alparslan.3013553b82-400.avif"},{width:800,url:"/images/generated/Erdem-Alparslan.3013553b82-800.avif"}],webp:[{width:400,url:"/images/generated/Erdem-Alparslan.3013553b82-400.webp"},{width:800,url:"/images/generated/Erdem-Alparslan.3013553b82-800.webp"}]},"/images/Launch-Darkly.webp":{width:3390,height:1880,isAnimated:!1,fallbackUrl:"/images/Launch-Darkly.webp",avif:[{width:400,url:"/images/generated/Launch-Darkly.fa7ddd974a-400.avif"},{width:800,url:"/images/generated/Launch-Darkly.fa7ddd974a-800.avif"},{width:1200,url:"/images/generated/Launch-Darkly.fa7ddd974a-1200.avif"},{width:1600,url:"/images/generated/Launch-Darkly.fa7ddd974a-1600.avif"},{width:2e3,url:"/images/generated/Launch-Darkly.fa7ddd974a-2000.avif"},{width:3390,url:"/images/generated/Launch-Darkly.fa7ddd974a-3390.avif"}],webp:[{width:400,url:"/images/generated/Launch-Darkly.fa7ddd974a-400.webp"},{width:800,url:"/images/generated/Launch-Darkly.fa7ddd974a-800.webp"},{width:1200,url:"/images/generated/Launch-Darkly.fa7ddd974a-1200.webp"},{width:1600,url:"/images/generated/Launch-Darkly.fa7ddd974a-1600.webp"},{width:2e3,url:"/images/generated/Launch-Darkly.fa7ddd974a-2000.webp"},{width:3390,url:"/images/generated/Launch-Darkly.fa7ddd974a-3390.webp"}]},"/images/LaunchDarkly-Videos-_-Thumbnail.webp":{width:1223,height:740,isAnimated:!1,fallbackUrl:"/images/LaunchDarkly-Videos-_-Thumbnail.webp",avif:[{width:400,url:"/images/generated/LaunchDarkly-Videos-_-Thumbnail.7bf1eaf990-400.avif"},{width:800,url:"/images/generated/LaunchDarkly-Videos-_-Thumbnail.7bf1eaf990-800.avif"},{width:1200,url:"/images/generated/LaunchDarkly-Videos-_-Thumbnail.7bf1eaf990-1200.avif"},{width:1223,url:"/images/generated/LaunchDarkly-Videos-_-Thumbnail.7bf1eaf990-1223.avif"}],webp:[{width:400,url:"/images/generated/LaunchDarkly-Videos-_-Thumbnail.7bf1eaf990-400.webp"},{width:800,url:"/images/generated/LaunchDarkly-Videos-_-Thumbnail.7bf1eaf990-800.webp"},{width:1200,url:"/images/generated/LaunchDarkly-Videos-_-Thumbnail.7bf1eaf990-1200.webp"},{width:1223,url:"/images/generated/LaunchDarkly-Videos-_-Thumbnail.7bf1eaf990-1223.webp"}]},"/images/Notion-Picture-of-Erdem.webp":{width:1920,height:1080,isAnimated:!1,fallbackUrl:"/images/Notion-Picture-of-Erdem.webp",avif:[{width:400,url:"/images/generated/Notion-Picture-of-Erdem.df910b638e-400.avif"},{width:800,url:"/images/generated/Notion-Picture-of-Erdem.df910b638e-800.avif"},{width:1200,url:"/images/generated/Notion-Picture-of-Erdem.df910b638e-1200.avif"},{width:1600,url:"/images/generated/Notion-Picture-of-Erdem.df910b638e-1600.avif"},{width:1920,url:"/images/generated/Notion-Picture-of-Erdem.df910b638e-1920.avif"}],webp:[{width:400,url:"/images/generated/Notion-Picture-of-Erdem.df910b638e-400.webp"},{width:800,url:"/images/generated/Notion-Picture-of-Erdem.df910b638e-800.webp"},{width:1200,url:"/images/generated/Notion-Picture-of-Erdem.df910b638e-1200.webp"},{width:1600,url:"/images/generated/Notion-Picture-of-Erdem.df910b638e-1600.webp"},{width:1920,url:"/images/generated/Notion-Picture-of-Erdem.df910b638e-1920.webp"}]},"/images/Notion-logo-icon.png":{width:1024,height:1024,isAnimated:!1,fallbackUrl:"/images/Notion-logo-icon.png",avif:[{width:400,url:"/images/generated/Notion-logo-icon.4eef85fdc3-400.avif"},{width:800,url:"/images/generated/Notion-logo-icon.4eef85fdc3-800.avif"},{width:1024,url:"/images/generated/Notion-logo-icon.4eef85fdc3-1024.avif"}],webp:[{width:400,url:"/images/generated/Notion-logo-icon.4eef85fdc3-400.webp"},{width:800,url:"/images/generated/Notion-logo-icon.4eef85fdc3-800.webp"},{width:1024,url:"/images/generated/Notion-logo-icon.4eef85fdc3-1024.webp"}]},"/images/Notion_logo.png":{width:2882,height:1e3,isAnimated:!1,fallbackUrl:"/images/Notion_logo.png",avif:[{width:400,url:"/images/generated/Notion_logo.d633c154e4-400.avif"},{width:800,url:"/images/generated/Notion_logo.d633c154e4-800.avif"},{width:1200,url:"/images/generated/Notion_logo.d633c154e4-1200.avif"},{width:1600,url:"/images/generated/Notion_logo.d633c154e4-1600.avif"},{width:2e3,url:"/images/generated/Notion_logo.d633c154e4-2000.avif"},{width:2882,url:"/images/generated/Notion_logo.d633c154e4-2882.avif"}],webp:[{width:400,url:"/images/generated/Notion_logo.d633c154e4-400.webp"},{width:800,url:"/images/generated/Notion_logo.d633c154e4-800.webp"},{width:1200,url:"/images/generated/Notion_logo.d633c154e4-1200.webp"},{width:1600,url:"/images/generated/Notion_logo.d633c154e4-1600.webp"}]},"/images/Social-Graph-Image-1.png":{width:1200,height:630,isAnimated:!1,fallbackUrl:"/images/Social-Graph-Image-1.png",avif:[{width:400,url:"/images/generated/Social-Graph-Image-1.b804bd1e6d-400.avif"},{width:800,url:"/images/generated/Social-Graph-Image-1.b804bd1e6d-800.avif"},{width:1200,url:"/images/generated/Social-Graph-Image-1.b804bd1e6d-1200.avif"}],webp:[{width:400,url:"/images/generated/Social-Graph-Image-1.b804bd1e6d-400.webp"},{width:800,url:"/images/generated/Social-Graph-Image-1.b804bd1e6d-800.webp"},{width:1200,url:"/images/generated/Social-Graph-Image-1.b804bd1e6d-1200.webp"}]},"/images/Zach-Davis.webp":{width:800,height:670,isAnimated:!1,fallbackUrl:"/images/Zach-Davis.webp",avif:[{width:400,url:"/images/generated/Zach-Davis.1ba1f59353-400.avif"},{width:800,url:"/images/generated/Zach-Davis.1ba1f59353-800.avif"}],webp:[{width:400,url:"/images/generated/Zach-Davis.1ba1f59353-400.webp"},{width:800,url:"/images/generated/Zach-Davis.1ba1f59353-800.webp"}]},"/images/abstract-texture.png":{width:1178,height:1176,isAnimated:!1,fallbackUrl:"/images/abstract-texture.png",avif:[{width:400,url:"/images/generated/abstract-texture.e6338270a6-400.avif"},{width:800,url:"/images/generated/abstract-texture.e6338270a6-800.avif"},{width:1178,url:"/images/generated/abstract-texture.e6338270a6-1178.avif"}],webp:[{width:400,url:"/images/generated/abstract-texture.e6338270a6-400.webp"},{width:800,url:"/images/generated/abstract-texture.e6338270a6-800.webp"},{width:1178,url:"/images/generated/abstract-texture.e6338270a6-1178.webp"}]},"/images/abstracttexture-new.png":{width:3456,height:1299,isAnimated:!1,fallbackUrl:"/images/abstracttexture-new.png",avif:[{width:400,url:"/images/generated/abstracttexture-new.59f283b9fc-400.avif"},{width:800,url:"/images/generated/abstracttexture-new.59f283b9fc-800.avif"},{width:1200,url:"/images/generated/abstracttexture-new.59f283b9fc-1200.avif"},{width:1600,url:"/images/generated/abstracttexture-new.59f283b9fc-1600.avif"},{width:2e3,url:"/images/generated/abstracttexture-new.59f283b9fc-2000.avif"},{width:3456,url:"/images/generated/abstracttexture-new.59f283b9fc-3456.avif"}],webp:[{width:400,url:"/images/generated/abstracttexture-new.59f283b9fc-400.webp"},{width:800,url:"/images/generated/abstracttexture-new.59f283b9fc-800.webp"},{width:1200,url:"/images/generated/abstracttexture-new.59f283b9fc-1200.webp"},{width:1600,url:"/images/generated/abstracttexture-new.59f283b9fc-1600.webp"},{width:2e3,url:"/images/generated/abstracttexture-new.59f283b9fc-2000.webp"},{width:3456,url:"/images/generated/abstracttexture-new.59f283b9fc-3456.webp"}]},"/images/alana-goyal.png":{width:180,height:180,isAnimated:!1,fallbackUrl:"/images/alana-goyal.png",avif:[{width:180,url:"/images/generated/alana-goyal.9d8a7671f3-180.avif"}],webp:[{width:180,url:"/images/generated/alana-goyal.9d8a7671f3-180.webp"}]},"/images/andy-chen.png":{width:180,height:180,isAnimated:!1,fallbackUrl:"/images/andy-chen.png",avif:[{width:180,url:"/images/generated/andy-chen.b0ec3707fd-180.avif"}],webp:[{width:180,url:"/images/generated/andy-chen.b0ec3707fd-180.webp"}]},"/images/blog/a-beginners-guide-to-debugging-react-testing-library-tests/6568e96a667dc66d07f3d6a7_debugging-react.webp":{width:1200,height:630,isAnimated:!1,fallbackUrl:"/images/blog/a-beginners-guide-to-debugging-react-testing-library-tests/6568e96a667dc66d07f3d6a7_debugging-react.webp",avif:[{width:400,url:"/images/generated/6568e96a667dc66d07f3d6a7_debugging-react.f5699f71fa-400.avif"},{width:800,url:"/images/generated/6568e96a667dc66d07f3d6a7_debugging-react.f5699f71fa-800.avif"},{width:1200,url:"/images/generated/6568e96a667dc66d07f3d6a7_debugging-react.f5699f71fa-1200.avif"}],webp:[{width:400,url:"/images/generated/6568e96a667dc66d07f3d6a7_debugging-react.f5699f71fa-400.webp"},{width:800,url:"/images/generated/6568e96a667dc66d07f3d6a7_debugging-react.f5699f71fa-800.webp"},{width:1200,url:"/images/generated/6568e96a667dc66d07f3d6a7_debugging-react.f5699f71fa-1200.webp"}]},"/images/blog/a-beginners-guide-to-debugging-react-testing-library-tests/ie.jpg":{width:160,height:80,isAnimated:!1,fallbackUrl:"/images/blog/a-beginners-guide-to-debugging-react-testing-library-tests/ie.jpg",avif:[{width:160,url:"/images/generated/ie.3a8fa3b8d2-160.avif"}],webp:[{width:160,url:"/images/generated/ie.3a8fa3b8d2-160.webp"}]},"/images/blog/a-beginners-guide-to-debugging-react-testing-library-tests/im.jpg":{width:160,height:80,isAnimated:!1,fallbackUrl:"/images/blog/a-beginners-guide-to-debugging-react-testing-library-tests/im.jpg",avif:[{width:160,url:"/images/generated/im.56008c8dac-160.avif"}],webp:[{width:160,url:"/images/generated/im.56008c8dac-160.webp"}]},"/images/blog/a-beginners-guide-to-debugging-react-testing-library-tests/us.jpg":{width:160,height:84,isAnimated:!1,fallbackUrl:"/images/blog/a-beginners-guide-to-debugging-react-testing-library-tests/us.jpg",avif:[{width:160,url:"/images/generated/us.f03748ed85-160.avif"}],webp:[{width:160,url:"/images/generated/us.f03748ed85-160.webp"}]},"/images/blog/authors/6556f52006849f153f151c05_kasper-siig.png":{width:160,height:160,isAnimated:!1,fallbackUrl:"/images/blog/authors/6556f52006849f153f151c05_kasper-siig.png",avif:[{width:160,url:"/images/generated/6556f52006849f153f151c05_kasper-siig.3e6144fef2-160.avif"}],webp:[{width:160,url:"/images/generated/6556f52006849f153f151c05_kasper-siig.3e6144fef2-160.webp"}]},"/images/blog/authors/655c3f789adcc1019a5a2caf_gabriel.png":{width:128,height:128,isAnimated:!1,fallbackUrl:"/images/blog/authors/655c3f789adcc1019a5a2caf_gabriel.png",avif:[{width:128,url:"/images/generated/655c3f789adcc1019a5a2caf_gabriel.d9e4a86b85-128.avif"}],webp:[{width:128,url:"/images/generated/655c3f789adcc1019a5a2caf_gabriel.d9e4a86b85-128.webp"}]},"/images/blog/authors/655c8ea4571721b00f779bac_myles-scolnick.png":{width:160,height:160,isAnimated:!1,fallbackUrl:"/images/blog/authors/655c8ea4571721b00f779bac_myles-scolnick.png",avif:[{width:160,url:"/images/generated/655c8ea4571721b00f779bac_myles-scolnick.a58e9172c1-160.avif"}],webp:[{width:160,url:"/images/generated/655c8ea4571721b00f779bac_myles-scolnick.a58e9172c1-160.webp"}]},"/images/blog/authors/6568d05a61cacbddd7afcac0_geshan-manandhar.jpeg":{width:428,height:428,isAnimated:!1,fallbackUrl:"/images/blog/authors/6568d05a61cacbddd7afcac0_geshan-manandhar.jpeg",avif:[{width:400,url:"/images/generated/6568d05a61cacbddd7afcac0_geshan-manandhar.6c4dfd04da-400.avif"},{width:428,url:"/images/generated/6568d05a61cacbddd7afcac0_geshan-manandhar.6c4dfd04da-428.avif"}],webp:[{width:400,url:"/images/generated/6568d05a61cacbddd7afcac0
29_geshan-manandhar.6c4dfd04da-400.webp"},{width:428,url:"/images/generated/6568d05a61cacbddd7afcac0_geshan-manandhar.6c4dfd04da-428.webp"}]},"/images/blog/authors/6568e8c0efa94e4ab8341350_alex-langdon.jpeg":{width:500,height:500,isAnimated:!1,fallbackUrl:"/images/blog/authors/6568e8c0efa94e4ab8341350_alex-langdon.jpeg",avif:[{width:400,url:"/images/generated/6568e8c0efa94e4ab8341350_alex-langdon.23dadc5483-400.avif"},{width:500,url:"/images/generated/6568e8c0efa94e4ab8341350_alex-langdon.23dadc5483-500.avif"}],webp:[{width:400,url:"/images/generated/6568e8c0efa94e4ab8341350_alex-langdon.23dadc5483-400.webp"},{width:500,url:"/images/generated/6568e8c0efa94e4ab8341350_alex-langdon.23dadc5483-500.webp"}]},"/images/blog/authors/6568ed0d46a96d27bd953794_oluseun-falodun.webp":{width:984,height:984,isAnimated:!1,fallbackUrl:"/images/blog/authors/6568ed0d46a96d27bd953794_oluseun-falodun.webp",avif:[{width:400,url:"/images/generated/6568ed0d46a96d27bd953794_oluseun-falodun.1db5e6601f-400.avif"},{width:800,url:"/images/generated/6568ed0d46a96d27bd953794_oluseun-falodun.1db5e6601f-800.avif"},{width:984,url:"/images/generated/6568ed0d46a96d27bd953794_oluseun-falodun.1db5e6601f-984.avif"}],webp:[{width:400,url:"/images/generated/6568ed0d46a96d27bd953794_oluseun-falodun.1db5e6601f-400.webp"},{width:800,url:"/images/generated/6568ed0d46a96d27bd953794_oluseun-falodun.1db5e6601f-800.webp"},{width:984,url:"/images/generated/6568ed0d46a96d27bd953794_oluseun-falodun.1db5e6601f-984.webp"}]},"/images/blog/authors/6568f0c1857f542a8026378a_ibas-majid.webp":{width:256,height:256,isAnimated:!1,fallbackUrl:"/images/blog/authors/6568f0c1857f542a8026378a_ibas-majid.webp",avif:[{width:256,url:"/images/generated/6568f0c1857f542a8026378a_ibas-majid.1a0f22bbc5-256.avif"}],webp:[{width:256,url:"/images/generated/6568f0c1857f542a8026378a_ibas-majid.1a0f22bbc5-256.webp"}]},"/images/blog/authors/6568f9276e20a9ef23a6e08d_alex-merced.webp":{width:128,height:128,isAnimated:!1,fallbackUrl:"/images/blog/authors/6568f9276e20a9ef23a6e08d_alex-merced.webp",avif:[{width:128,url:"/images/generated/6568f9276e20a9ef23a6e08d_alex-merced.7e8a35cadd-128.avif"}],webp:[{width:128,url:"/images/generated/6568f9276e20a9ef23a6e08d_alex-merced.7e8a35cadd-128.webp"}]},"/images/blog/authors/6568fb928be7bcaa1d425ece_johann-michael-thiebaut.jpeg":{width:800,height:800,isAnimated:!1,fallbackUrl:"/images/blog/authors/6568fb928be7bcaa1d425ece_johann-michael-thiebaut.jpeg",avif:[{width:400,url:"/images/generated/6568fb928be7bcaa1d425ece_johann-michael-thiebaut.603ec6079e-400.avif"},{width:800,url:"/images/generated/6568fb928be7bcaa1d425ece_johann-michael-thiebaut.603ec6079e-800.avif"}],webp:[{width:400,url:"/images/generated/6568fb928be7bcaa1d425ece_johann-michael-thiebaut.603ec6079e-400.webp"},{width:800,url:"/images/generated/6568fb928be7bcaa1d425ece_johann-michael-thiebaut.603ec6079e-800.webp"}]},"/images/blog/authors/6568fd7b74ebf8bcd6377d05_goodness-chris-ugari.jpeg":{width:400,height:400,isAnimated:!1,fallbackUrl:"/images/blog/authors/6568fd7b74ebf8bcd6377d05_goodness-chris-ugari.jpeg",avif:[{width:400,url:"/images/generated/6568fd7b74ebf8bcd6377d05_goodness-chris-ugari.ed667ab8e7-400.avif"}],webp:[{width:400,url:"/images/generated/6568fd7b74ebf8bcd6377d05_goodness-chris-ugari.ed667ab8e7-400.webp"}]},"/images/blog/authors/656901c3335c1d421430d47a_aniket-bhattacharyea.jpeg":{width:799,height:799,isAnimated:!1,fallbackUrl:"/images/blog/authors/656901c3335c1d421430d47a_aniket-bhattacharyea.jpeg",avif:[{width:400,url:"/images/generated/656901c3335c1d421430d47a_aniket-bhattacharyea.741af76d85-400.avif"},{width:799,url:"/images/generated/656901c3335c1d421430d47a_aniket-bhattacharyea.741af76d85-799.avif"}],webp:[{width:400,url:"/images/generated/656901c3335c1d421430d47a_aniket-bhattacharyea.741af76d85-400.webp"},{width:799,url:"/images/generated/656901c3335c1d421430d47a_aniket-bhattacharyea.741af76d85-799.webp"}]},"/images/blog/authors/6569047bd92fdcf8eed94848_marie-starck.webp":{width:354,height:354,isAnimated:!1,fallbackUrl:"/images/blog/authors/6569047bd92fdcf8eed94848_marie-starck.webp",avif:[{width:354,url:"/images/generated/6569047bd92fdcf8eed94848_marie-starck.1207856990-354.avif"}],webp:[{width:354,url:"/images/generated/6569047bd92fdcf8eed94848_marie-starck.1207856990-354.webp"}]},"/images/blog/authors/656906c69c90ec66afecebf9_charly-poly.jpeg":{width:512,height:512,isAnimated:!1,fallbackUrl:"/images/blog/authors/656906c69c90ec66afecebf9_charly-poly.jpeg",avif:[{width:400,url:"/images/generated/656906c69c90ec66afecebf9_charly-poly.4b7a3cb018-400.avif"},{width:512,url:"/images/generated/656906c69c90ec66afecebf9_charly-poly.4b7a3cb018-512.avif"}],webp:[{width:400,url:"/images/generated/656906c69c90ec66afecebf9_charly-poly.4b7a3cb018-400.webp"},{width:512,url:"/images/generated/656906c69c90ec66afecebf9_charly-poly.4b7a3cb018-512.webp"}]},"/images/blog/authors/65690c31a516e88cfaf546ff_Elly-Obare.jpeg":{width:560,height:560,isAnimated:!1,fallbackUrl:"/images/blog/authors/65690c31a516e88cfaf546ff_Elly-Obare.jpeg",avif:[{width:400,url:"/images/generated/65690c31a516e88cfaf546ff_Elly-Obare.0762c24a49-400.avif"},{width:560,url:"/images/generated/65690c31a516e88cfaf546ff_Elly-Obare.0762c24a49-560.avif"}],webp:[{width:400,url:"/images/generated/65690c31a516e88cfaf546ff_Elly-Obare.0762c24a49-400.webp"},{width:560,url:"/images/generated/65690c31a516e88cfaf546ff_Elly-Obare.0762c24a49-560.webp"}]},"/images/blog/authors/65690e0f652302dc9edba136_david-herbert.jpeg":{width:400,height:400,isAnimated:!1,fallbackUrl:"/images/blog/authors/65690e0f652302dc9edba136_david-herbert.jpeg",avif:[{width:400,url:"/images/generated/65690e0f652302dc9edba136_david-herbert.06024696c0-400.avif"}],webp:[{width:400,url:"/images/generated/65690e0f652302dc9edba136_david-herbert.06024696c0-400.webp"}]},"/images/blog/authors/65690ef1db4eb7f27e6ffc40_james-walker.jpeg":{width:800,height:800,isAnimated:!1,fallbackUrl:"/images/blog/authors/65690ef1db4eb7f27e6ffc40_james-walker.jpeg",avif:[{width:400,url:"/images/generated/65690ef1db4eb7f27e6ffc40_james-walker.bbe03827d5-400.avif"},{width:800,url:"/images/generated/65690ef1db4eb7f27e6ffc40_james-walker.bbe03827d5-800.avif"}],webp:[{width:400,url:"/images/generated/65690ef1db4eb7f27e6ffc40_james-walker.bbe03827d5-400.webp"},{width:800,url:"/images/generated/65690ef1db4eb7f27e6ffc40_james-walker.bbe03827d5-800.webp"}]},"/images/blog/authors/65691077aff83d28a9084929_rhuan-souza.jpeg":{width:800,height:800,isAnimated:!1,fallbackUrl:"/images/blog/authors/65691077aff83d28a9084929_rhuan-souza.jpeg",avif:[{width:400,url:"/images/generated/65691077aff83d28a9084929_rhuan-souza.0f6afa1137-400.avif"},{width:800,url:"/images/generated/65691077aff83d28a9084929_rhuan-souza.0f6afa1137-800.avif"}],webp:[{width:400,url:"/images/generated/65691077aff83d28a9084929_rhuan-souza.0f6afa1137-400.webp"},{width:800,url:"/images/generated/65691077aff83d28a9084929_rhuan-souza.0f6afa1137-800.webp"}]},"/images/blog/authors/65691319711713f7df7a83e8_daniel-li.jpeg":{width:389,height:389,isAnimated:!1,fallbackUrl:"/images/blog/authors/65691319711713f7df7a83e8_daniel-li.jpeg",avif:[{width:389,url:"/images/generated/65691319711713f7df7a83e8_daniel-li.96255ccf7d-389.avif"}],webp:[{width:389,url:"/images/generated/65691319711713f7df7a83e8_daniel-li.96255ccf7d-389.webp"}]},"/images/blog/authors/6569147b8286039be11784df_faraz-kelhini.jpeg":{width:616,height:616,isAnimated:!1,fallbackUrl:"/images/blog/authors/6569147b8286039be11784df_faraz-kelhini.jpeg",avif:[{width:400,url:"/images/generated/6569147b8286039be11784df_faraz-kelhini.2390a37c14-400.avif"},{width:616,url:"/images/generated/6569147b8286039be11784df_faraz-kelhini.2390a37c14-616.avif"}],webp:[{width:400,url:"/images/generated/6569147b8286039be11784df_faraz-kelhini.2390a37c14-400.webp"},{width:616,url:"/images/generated/6569147b8286039be11784df_faraz-kelhini.2390a37c14-616.webp"}]},"/images/blog/authors/656918cf162348c627e0eb1d_subha-chanda.jpeg":{width:612,height:612,isAnimated:!1,fallbackUrl:"/images/blog/authors/656918cf162348c627e0eb1d_subha-chanda.jpeg",avif:[{width:400,url:"/images/generated/656918cf162348c627e0eb1d_subha-chanda.c03327ec51-400.avif"},{width:612,url:"/images/generated/656918cf162348c627e0eb1d_subha-chanda.c03327ec51-612.avif"}],webp:[{width:400,url:"/images/generated/656918cf162348c627e0eb1d_subha-chanda.c03327ec51-400.webp"},{width:612,url:"/images/generated/656918cf162348c627e0eb1d_subha-chanda.c03327ec51-612.webp"}]},"/images/blog/authors/65691bb96a65d8fb22b2f2e2_jacob-goldfarb.jpeg":{width:487,height:487,isAnimated:!1,fallbackUrl:"/images/blog/authors/65691bb96a65d8fb22b2f2e2_jacob-goldfarb.jpeg",avif:[{width:400,url:"/images/generated/65691bb96a65d8fb22b2f2e2_jacob-goldfarb.f71363ce8b-400.avif"},{width:487,url:"/images/generated/65691bb96a65d8fb22b2f2e2_jacob-goldfarb.f71363ce8b-487.avif"}],webp:[{width:400,url:"/images/generated/65691bb96a65d8fb22b2f2e2_jacob-goldfarb.f71363ce8b-400.webp"},{width:487,url:"/images/generated/65691bb96a65d8fb22b2f2e2_jacob-goldfarb.f71363ce8b-487.webp"}]},"/images/blog/authors/65691e3f6bbef6ac2df2fd06_tahanima-chowdhury.jpeg":{width:800,height:800,isAnimated:!1,fallbackUrl:"/images/blog/authors/65691e3f6bbef6ac2df2fd06_tahanima-chowdhury.jpeg",avif:[{width:400,url:"/images/generated/65691e3f6bbef6ac2df2fd06_tahanima-chowdhury.70df86f39d-400.avif"},{width:800,url:"/images/generated/65691e3f6bbef6ac2df2fd06_tahanima-chowdhury.70df86f39d-800.avif"}],webp:[{width:400,url:"/images/generated/65691e3f6bbef6ac2df2fd06_tahanima-chowdhury.70df86f39d-400.webp"},{width:800,url:"/images/generated/65691e3f6bbef6ac2df2fd06_tahanima-chowdhury.70df86f39d-800.webp"}]},"/images/blog/authors/656921b4178a0f7b8b8add38_johnny-reilly.jpeg":{width:381,height:381,isAnimated:!1,fallbackUrl:"/images/blog/authors/656921b4178a0f7b8b8add38_johnny-reilly.jpeg",avif:[{width:381,url:"/images/generated/656921b4178a0f7b8b8add38_johnny-reilly.c0f288f386-381.avif"}],webp:[{width:381,url:"/images/generated/656921b4178a0f7b8b8add38_johnny-reilly.c0f288f386-381.webp"}]},"/images/blog/authors/6590373d3fcec21648cdc202_profile.jpeg":{width:500,height:500,isAnimated:!1,fallbackUrl:"/images/blog/authors/6590373d3fcec21648cdc202_profile.jpeg",avif:[{width:400,url:"/images/generated/6590373d3fcec21648cdc202_profile.1beddf0fb9-400.avif"},{width:500,url:"/images/generated/6590373d3fcec21648cdc202_profile.1beddf0fb9-500.avif"}],webp:[{width:400,url:"/images/generated/6590373d3fcec21648cdc202_profile.1beddf0fb9-400.webp"},{width:500,url:"/images/generated/6590373d3fcec21648cdc202_profile.1beddf0fb9-500.webp"}]},"/images/blog/browserstack-vs-saucelabs/6568db8e80d256f17b587010_browserstack-website.png":{width:2e3,height:1219,isAnimated:!1,fallbackUrl:"/images/blog/browserstack-vs-saucelabs/6568db8e80d256f17b587010_browserstack-website.png",avif:[{width:400,url:"/images/generated/6568db8e80d256f17b587010_browserstack-website.dd6b7ceee8-400.avif"},{width:800,url:"/images/generated/6568db8e80d256f17b587010_browserstack-website.dd6b7ceee8-800.avif"},{width:1200,url:"/images/generated/6568db8e80d256f17b587010_browserstack-website.dd6b7ceee8-1200.avif"},{width:1600,url:"/images/generated/6568db8e80d256f17b587010_browserstack-website.dd6b7ceee8-1600.avif"},{width:2e3,url:"/images/generated/6568db8e80d256f17b587010_browserstack-website.dd6b7ceee8-2000.avif"}],webp:[{width:400,url:"/images/generated/6568db8e80d256f17b587010_browserstack-website.dd6b7ceee8-400.webp"},{width:800,url:"/images/generated/6568db8e80d256f17b587010_browserstack-website.dd6b7ceee8-800.webp"},{width:1200,url:"/images/generated/6568db8e80d256f17b587010_browserstack-website.dd6b7ceee8-1200.webp"},{width:1600,url:"/images/generated/6568db8e80d256f17b587010_browserstack-website.dd6b7ceee8-1600.webp"},{width:2e3,url:"/images/generated/6568db8e80d256f17b587010_browserstack-website.dd6b7ceee8-2000.webp"}]},"/images/blog/browserstack-vs-saucelabs/6568db8ef4bb378dc6c5fa2d_browserstack-pricing-page.png":{width:2e3,height:902,isAnimated:!1,fallbackUrl:"/images/blog/browserstack-vs-saucelabs/6568db8ef4bb378dc6c5fa2d_browserstack-pricing-page.png",avif:[{width:400,url:"/images/generated/6568db8ef4bb378dc6c5fa2d_browserstack-pricing-page.b5dee6dbb1-400.avif"},{width:800,url:"/images/generated/6568db8ef4bb378dc6c5fa2d_browserstack-pricing-page.b5dee6dbb1-800.avif"},{width:1200,url:"/images/generated/6568db8ef4bb378dc6c5fa2d_browserstack-pricing-page.b5dee6dbb1-1200.avif"},{width:1600,url:"/images/generated/6568db8ef4bb378dc6c5fa2d_browserstack-pricing-page.b5dee6dbb1-1600.avif"},{width:2e3,url:"/images/generated/6568db8ef4bb378dc6c5fa2d_browserstack-pricing-page.b5dee6dbb1-2000.avif"}],webp:[{width:400,url:"/images/generated/6568db8ef4bb378dc6c5fa2d_browserstack-pricing-page.b5dee6dbb1-400.webp"},{width:800,url:"/images/generated/6568db8ef4bb378dc6c5fa2d_browserstack-pricing-page.b5dee6dbb1-800.webp"},{width:1200,url:"/images/generated/6568db8ef4bb378dc6c5fa2d_browserstack-pricing-page.b5dee6dbb1-1200.webp"},{width:1600,url:"/images/generated/6568db8ef4bb378dc6c5fa2d_browserstack-pricing-page.b5dee6dbb1-1600.webp"},{width:2e3,url:"/images/generated/6568db8ef4bb378dc6c5fa2d_browserstack-pricing-page.b5dee6dbb1-2000.webp"}]},"/images/blog/browserstack-vs-saucelabs/6568db90af7766b254ad6888_browserstack-features.png":{width:2e3,height:864,isAnimated:!1,fallbackUrl:"/images/blog/browserstack-vs-saucelabs/6568db90af7766b254ad6888_browserstack-features.png",avif:[{width:400,url:"/images/generated/6568db90af7766b254ad6888_browserstack-features.69dd924d6f-400.avif"},{width:800,url:"/images/generated/6568db90af7766b254ad6888_browserstack-features.69dd924d6f-800.avif"},{width:1200,url:"/images/generated/6568db90af7766b254ad6888_browserstack-features.69dd924d6f-1200.avif"},{width:1600,url:"/images/generated/6568db90af7766b254ad6888_browserstack-features.69dd924d6f-1600.avif"},{width:2e3,url:"/images/generated/6568db90af7766b254ad6888_browserstack-features.69dd924d6f-2000.avif"}],webp:[{width:400,url:"/images/generated/6568db90af7766b254ad6888_browserstack-features.69dd924d6f-400.webp"},{width:800,url:"/images/generated/6568db90af7766b254ad6888_browserstack-features.69dd924d6f-800.webp"},{width:1200,url:"/images/generated/6568db90af7766b254ad6888_browserstack-features.69dd924d6f-1200.webp"},{width:1600,url:"/images/generated/6568db90af7766b254ad6888_browserstack-features.69dd924d6f-1600.webp"},{width:2e3,url:"/images/generated/6568db90af7766b254ad6888_browserstack-features.69dd924d6f-2000.webp"}]},"/images/blog/browserstack-vs-saucelabs/6568dcff3f304b534014ae09_saucelabs-test-toolchain.png":{width:2e3,height:981,isAnimated:!1,fallbackUrl:"/images/blog/browserstack-vs-saucelabs/6568dcff3f304b534014ae09_saucelabs-test-toolchain.png",avif:[{width:400,url:"/images/generated/6568dcff3f304b534014ae09_saucelabs-test-toolchain.937b4ed376-400.avif"},{width:800,url:"/images/generated/6568dcff3f304b534014ae09_saucelabs-test-toolchain.937b4ed376-800.avif"},{width:1200,url:"/images/generated/6568dcff3f304b534014ae09_saucelabs-test-toolchain.937b4ed376-1200.avif"},{width:1600,url:"/images/generated/6568dcff3f304b534014ae09_saucelabs-test-toolchain.937b4ed376-1600.avif"},{width:2e3,url:"/images/generated/6568dcff3f304b534014ae09_saucelabs-test-toolchain.937b4ed376-2000.avif"}],webp:[{width:400,url:"/images/generated/6568dcff3f304b534014ae09_saucelabs-test-toolchain.937b4ed376-400.webp"},{width:800,url:"/images/generated/6568dcff3f304b534014ae09_saucelabs-test-toolchain.937b4ed376-800.webp"},{width:1200,url:"/images/generated/6568dcff3f304b534014ae09_saucelabs-test-toolchain.937b4ed376-1200.webp"},{width:1600,url:"/images/generated/6568dcff3f304b534014ae09_saucelabs-test-toolchain.937b4ed376-1600.webp"},{width:2e3,url:"/images/generated/6568dcff3f304b534014ae09_saucelabs-test-toolchain.937b4ed376-2000.webp"}]},"/images/blog/browserstack-vs-saucelabs/6568dcff8d9b73c476d2e291_saucelabs-features.png":{width:2e3,height:896,isAnimated:!1,fallbackUrl:"/images/blog/browserstack-vs-saucelabs/6568dcff8d9b73c476d2e291_saucelabs-features.png",avif:[{width:400,url:"/images/generated/6568dcff8d9b73c476d2e291_saucelabs-features.317fffd8c1-400.avif"},{width:800,url:"/images/generated/6568dcff8d9b73c476d2e291_saucelabs-features.317fffd8c1-800.avif"},{width:1200,url:"/images/generated/6568dcff8d9b73c476d2e291_saucelabs-features.317fffd8c1-1200.avif"},{width:1600,url:"/images/generated/6568dcff
298d9b73c476d2e291_saucelabs-features.317fffd8c1-1600.avif"},{width:2e3,url:"/images/generated/6568dcff8d9b73c476d2e291_saucelabs-features.317fffd8c1-2000.avif"}],webp:[{width:400,url:"/images/generated/6568dcff8d9b73c476d2e291_saucelabs-features.317fffd8c1-400.webp"},{width:800,url:"/images/generated/6568dcff8d9b73c476d2e291_saucelabs-features.317fffd8c1-800.webp"},{width:1200,url:"/images/generated/6568dcff8d9b73c476d2e291_saucelabs-features.317fffd8c1-1200.webp"},{width:1600,url:"/images/generated/6568dcff8d9b73c476d2e291_saucelabs-features.317fffd8c1-1600.webp"},{width:2e3,url:"/images/generated/6568dcff8d9b73c476d2e291_saucelabs-features.317fffd8c1-2000.webp"}]},"/images/blog/browserstack-vs-saucelabs/6568dcffb076ff0e9cbf3848_saucelabs-pricing-page.png":{width:2e3,height:1137,isAnimated:!1,fallbackUrl:"/images/blog/browserstack-vs-saucelabs/6568dcffb076ff0e9cbf3848_saucelabs-pricing-page.png",avif:[{width:400,url:"/images/generated/6568dcffb076ff0e9cbf3848_saucelabs-pricing-page.4be6dea3cb-400.avif"},{width:800,url:"/images/generated/6568dcffb076ff0e9cbf3848_saucelabs-pricing-page.4be6dea3cb-800.avif"},{width:1200,url:"/images/generated/6568dcffb076ff0e9cbf3848_saucelabs-pricing-page.4be6dea3cb-1200.avif"},{width:1600,url:"/images/generated/6568dcffb076ff0e9cbf3848_saucelabs-pricing-page.4be6dea3cb-1600.avif"},{width:2e3,url:"/images/generated/6568dcffb076ff0e9cbf3848_saucelabs-pricing-page.4be6dea3cb-2000.avif"}],webp:[{width:400,url:"/images/generated/6568dcffb076ff0e9cbf3848_saucelabs-pricing-page.4be6dea3cb-400.webp"},{width:800,url:"/images/generated/6568dcffb076ff0e9cbf3848_saucelabs-pricing-page.4be6dea3cb-800.webp"},{width:1200,url:"/images/generated/6568dcffb076ff0e9cbf3848_saucelabs-pricing-page.4be6dea3cb-1200.webp"},{width:1600,url:"/images/generated/6568dcffb076ff0e9cbf3848_saucelabs-pricing-page.4be6dea3cb-1600.webp"},{width:2e3,url:"/images/generated/6568dcffb076ff0e9cbf3848_saucelabs-pricing-page.4be6dea3cb-2000.webp"}]},"/images/blog/browserstack-vs-saucelabs/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.png":{width:2e3,height:1295,isAnimated:!1,fallbackUrl:"/images/blog/browserstack-vs-saucelabs/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.png",avif:[{width:400,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-400.avif"},{width:800,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-800.avif"},{width:1200,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-1200.avif"},{width:1600,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-1600.avif"},{width:2e3,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-2000.avif"}],webp:[{width:400,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-400.webp"},{width:800,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-800.webp"},{width:1200,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-1200.webp"},{width:1600,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-1600.webp"},{width:2e3,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-2000.webp"}]},"/images/blog/browserstack-vs-saucelabs/656a24f7b14bc510a964b55d_browserstack-vs-saucelabs.png":{width:1742,height:1006,isAnimated:!1,fallbackUrl:"/images/blog/browserstack-vs-saucelabs/656a24f7b14bc510a964b55d_browserstack-vs-saucelabs.png",avif:[{width:400,url:"/images/generated/656a24f7b14bc510a964b55d_browserstack-vs-saucelabs.a8994045d9-400.avif"},{width:800,url:"/images/generated/656a24f7b14bc510a964b55d_browserstack-vs-saucelabs.a8994045d9-800.avif"},{width:1200,url:"/images/generated/656a24f7b14bc510a964b55d_browserstack-vs-saucelabs.a8994045d9-1200.avif"},{width:1600,url:"/images/generated/656a24f7b14bc510a964b55d_browserstack-vs-saucelabs.a8994045d9-1600.avif"},{width:1742,url:"/images/generated/656a24f7b14bc510a964b55d_browserstack-vs-saucelabs.a8994045d9-1742.avif"}],webp:[{width:400,url:"/images/generated/656a24f7b14bc510a964b55d_browserstack-vs-saucelabs.a8994045d9-400.webp"},{width:800,url:"/images/generated/656a24f7b14bc510a964b55d_browserstack-vs-saucelabs.a8994045d9-800.webp"},{width:1200,url:"/images/generated/656a24f7b14bc510a964b55d_browserstack-vs-saucelabs.a8994045d9-1200.webp"},{width:1600,url:"/images/generated/656a24f7b14bc510a964b55d_browserstack-vs-saucelabs.a8994045d9-1600.webp"},{width:1742,url:"/images/generated/656a24f7b14bc510a964b55d_browserstack-vs-saucelabs.a8994045d9-1742.webp"}]},"/images/blog/cannot-read-property-split-of-undefined/656a18f4e9738d8ac141281d_cannot-read-property-split-of-undefined.png":{width:1544,height:886,isAnimated:!1,fallbackUrl:"/images/blog/cannot-read-property-split-of-undefined/656a18f4e9738d8ac141281d_cannot-read-property-split-of-undefined.png",avif:[{width:400,url:"/images/generated/656a18f4e9738d8ac141281d_cannot-read-property-split-of-undefined.5ebd6511f1-400.avif"},{width:800,url:"/images/generated/656a18f4e9738d8ac141281d_cannot-read-property-split-of-undefined.5ebd6511f1-800.avif"},{width:1200,url:"/images/generated/656a18f4e9738d8ac141281d_cannot-read-property-split-of-undefined.5ebd6511f1-1200.avif"},{width:1544,url:"/images/generated/656a18f4e9738d8ac141281d_cannot-read-property-split-of-undefined.5ebd6511f1-1544.avif"}],webp:[{width:400,url:"/images/generated/656a18f4e9738d8ac141281d_cannot-read-property-split-of-undefined.5ebd6511f1-400.webp"},{width:800,url:"/images/generated/656a18f4e9738d8ac141281d_cannot-read-property-split-of-undefined.5ebd6511f1-800.webp"},{width:1200,url:"/images/generated/656a18f4e9738d8ac141281d_cannot-read-property-split-of-undefined.5ebd6511f1-1200.webp"},{width:1544,url:"/images/generated/656a18f4e9738d8ac141281d_cannot-read-property-split-of-undefined.5ebd6511f1-1544.webp"}]},"/images/blog/cypress-testing/655d9ff349fe9628bcf0b35a_cypress-main.png":{width:940,height:540,isAnimated:!1,fallbackUrl:"/images/blog/cypress-testing/655d9ff349fe9628bcf0b35a_cypress-main.png",avif:[{width:400,url:"/images/generated/655d9ff349fe9628bcf0b35a_cypress-main.4e645399bc-400.avif"},{width:800,url:"/images/generated/655d9ff349fe9628bcf0b35a_cypress-main.4e645399bc-800.avif"},{width:940,url:"/images/generated/655d9ff349fe9628bcf0b35a_cypress-main.4e645399bc-940.avif"}],webp:[{width:400,url:"/images/generated/655d9ff349fe9628bcf0b35a_cypress-main.4e645399bc-400.webp"},{width:800,url:"/images/generated/655d9ff349fe9628bcf0b35a_cypress-main.4e645399bc-800.webp"},{width:940,url:"/images/generated/655d9ff349fe9628bcf0b35a_cypress-main.4e645399bc-940.webp"}]},"/images/blog/cypress-testing/65675603ffedcc5b58a56f2d_meticulous-testing-app.png":{width:2e3,height:1295,isAnimated:!1,fallbackUrl:"/images/blog/cypress-testing/65675603ffedcc5b58a56f2d_meticulous-testing-app.png",avif:[{width:400,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-400.avif"},{width:800,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-800.avif"},{width:1200,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-1200.avif"},{width:1600,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-1600.avif"},{width:2e3,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-2000.avif"}],webp:[{width:400,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-400.webp"},{width:800,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-800.webp"},{width:1200,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-1200.webp"},{width:1600,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-1600.webp"},{width:2e3,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-2000.webp"}]},"/images/blog/cypress-testing/6567818c84a6aa6c1588ece2_cypress-test-runner-command-log-debugging-features.png":{width:2e3,height:1949,isAnimated:!1,fallbackUrl:"/images/blog/cypress-testing/6567818c84a6aa6c1588ece2_cypress-test-runner-command-log-debugging-features.png",avif:[{width:400,url:"/images/generated/6567818c84a6aa6c1588ece2_cypress-test-runner-command-log-debugging-features.7d93a201f2-400.avif"},{width:800,url:"/images/generated/6567818c84a6aa6c1588ece2_cypress-test-runner-command-log-debugging-features.7d93a201f2-800.avif"},{width:1200,url:"/images/generated/6567818c84a6aa6c1588ece2_cypress-test-runner-command-log-debugging-features.7d93a201f2-1200.avif"},{width:1600,url:"/images/generated/6567818c84a6aa6c1588ece2_cypress-test-runner-command-log-debugging-features.7d93a201f2-1600.avif"},{width:2e3,url:"/images/generated/6567818c84a6aa6c1588ece2_cypress-test-runner-command-log-debugging-features.7d93a201f2-2
29000.avif"}],webp:[{width:400,url:"/images/generated/6567818c84a6aa6c1588ece2_cypress-test-runner-command-log-debugging-features.7d93a201f2-400.webp"},{width:800,url:"/images/generated/6567818c84a6aa6c1588ece2_cypress-test-runner-command-log-debugging-features.7d93a201f2-800.webp"},{width:1200,url:"/images/generated/6567818c84a6aa6c1588ece2_cypress-test-runner-command-log-debugging-features.7d93a201f2-1200.webp"},{width:1600,url:"/images/generated/6567818c84a6aa6c1588ece2_cypress-test-runner-command-log-debugging-features.7d93a201f2-1600.webp"},{width:2e3,url:"/images/generated/6567818c84a6aa6c1588ece2_cypress-test-runner-command-log-debugging-features.7d93a201f2-2000.webp"}]},"/images/blog/cypress-testing/6567818cd5169dfe9cefc1a4_cypress-interface.png":{width:2e3,height:1057,isAnimated:!1,fallbackUrl:"/images/blog/cypress-testing/6567818cd5169dfe9cefc1a4_cypress-interface.png",avif:[{width:400,url:"/images/generated/6567818cd5169dfe9cefc1a4_cypress-interface.ce563d501e-400.avif"},{width:800,url:"/images/generated/6567818cd5169dfe9cefc1a4_cypress-interface.ce563d501e-800.avif"},{width:1200,url:"/images/generated/6567818cd5169dfe9cefc1a4_cypress-interface.ce563d501e-1200.avif"},{width:1600,url:"/images/generated/6567818cd5169dfe9cefc1a4_cypress-interface.ce563d501e-1600.avif"},{width:2e3,url:"/images/generated/6567818cd5169dfe9cefc1a4_cypress-interface.ce563d501e-2000.avif"}],webp:[{width:400,url:"/images/generated/6567818cd5169dfe9cefc1a4_cypress-interface.ce563d501e-400.webp"},{width:800,url:"/images/generated/6567818cd5169dfe9cefc1a4_cypress-interface.ce563d501e-800.webp"},{width:1200,url:"/images/generated/6567818cd5169dfe9cefc1a4_cypress-interface.ce563d501e-1200.webp"},{width:1600,url:"/images/generated/6567818cd5169dfe9cefc1a4_cypress-interface.ce563d501e-1600.webp"},{width:2e3,url:"/images/generated/6567818cd5169dfe9cefc1a4_cypress-interface.ce563d501e-2000.webp"}]},"/images/blog/fetch-vs-axios/6568ef0d5295615c17bc2a0f_console-showing-JSON-data.png":{width:628,height:284,isAnimated:!1,fallbackUrl:"/images/blog/fetch-vs-axios/6568ef0d5295615c17bc2a0f_console-showing-JSON-data.png",avif:[{width:400,url:"/images/generated/6568ef0d5295615c17bc2a0f_console-showing-JSON-data.24d58abcb5-400.avif"},{width:628,url:"/images/generated/6568ef0d5295615c17bc2a0f_console-showing-JSON-data.24d58abcb5-628.avif"}],webp:[{width:400,url:"/images/generated/6568ef0d5295615c17bc2a0f_console-showing-JSON-data.24d58abcb5-400.webp"},{width:628,url:"/images/generated/6568ef0d5295615c17bc2a0f_console-showing-JSON-data.24d58abcb5-628.webp"}]},"/images/blog/fetch-vs-axios/6568ef5e48713011713261cb_response-object.png":{width:565,height:204,isAnimated:!1,fallbackUrl:"/images/blog/fetch-vs-axios/6568ef5e48713011713261cb_response-object.png",avif:[{width:400,url:"/images/generated/6568ef5e48713011713261cb_response-object.98f1fad6fb-400.avif"},{width:565,url:"/images/generated/6568ef5e48713011713261cb_response-object.98f1fad6fb-565.avif"}],webp:[{width:400,url:"/images/generated/6568ef5e48713011713261cb_response-object.98f1fad6fb-400.webp"},{width:565,url:"/images/generated/6568ef5e48713011713261cb_response-object.98f1fad6fb-565.webp"}]},"/images/blog/fetch-vs-axios/6568f00c34f7a16be9ecde2b_response-object-27s-status.png":{width:588,height:242,isAnimated:!1,fallbackUrl:"/images/blog/fetch-vs-axios/6568f00c34f7a16be9ecde2b_response-object-27s-status.png",avif:[{width:400,url:"/images/generated/6568f00c34f7a16be9ecde2b_response-object-27s-status.4efe68e772-400.avif"},{width:588,url:"/images/generated/6568f00c34f7a16be9ecde2b_response-object-27s-status.4efe68e772-588.avif"}],webp:[{width:400,url:"/images/generated/6568f00c34f7a16be9ecde2b_response-object-27s-status.4efe68e772-400.webp"},{width:588,url:"/images/generated/6568f00c34f7a16be9ecde2b_response-object-27s-status.4efe68e772-588.webp"}]},"/images/blog/fetch-vs-axios/6568f0461075fe4b3629dd0d_comparing-performance-of-axios-vs-fetch.png":{width:980,height:264,isAnimated:!1,fallbackUrl:"/images/blog/fetch-vs-axios/6568f0461075fe4b3629dd0d_comparing-performance-of-axios-vs-fetch.png",avif:[{width:400,url:"/images/generated/6568f0461075fe4b3629dd0d_comparing-performance-of-axios-vs-fetch.495f634da8-400.avif"},{width:800,url:"/images/generated/6568f0461075fe4b3629dd0d_comparing-performance-of-axios-vs-fetch.495f634da8-800.avif"},{width:980,url:"/images/generated/6568f0461075fe4b3629dd0d_comparing-performance-of-axios-vs-fetch.495f634da8-980.avif"}],webp:[{width:400,url:"/images/generated/6568f0461075fe4b3629dd0d_comparing-performance-of-axios-vs-fetch.495f634da8-400.webp"},{width:800,url:"/images/generated/6568f0461075fe4b3629dd0d_comparing-performance-of-axios-vs-fetch.495f634da8-800.webp"},{width:980,url:"/images/generated/6568f0461075fe4b3629dd0d_comparing-performance-of-axios-vs-fetch.495f634da8-980.webp"}]},"/images/blog/fetch-vs-axios/6568f04691810d1c386db009_graph-comparing-performance-of-axios-vs-fetch.png":{width:908,height:206,isAnimated:!1,fallbackUrl:"/images/blog/fetch-vs-axios/6568f04691810d1c386db009_graph-comparing-performance-of-axios-vs-fetch.png",avif:[{width:400,url:"/images/generated/6568f04691810d1c386db009_graph-comparing-performance-of-axios-vs-fetch.1b5c999309-400.avif"},{width:800,url:"/images/generated/6568f04691810d1c386db009_graph-comparing-performance-of-axios-vs-fetch.1b5c999309-800.avif"},{width:908,url:"/images/generated/6568f04691810d1c386db009_graph-comparing-performance-of-axios-vs-fetch.1b5c999309-908.avif"}],webp:[{width:400,url:"/images/generated/6568f04691810d1c386db009_graph-comparing-performance-of-axios-vs-fetch.1b5c999309-400.webp"},{width:800,url:"/images/generated/6568f04691810d1c386db009_graph-comparing-performance-of-axios-vs-fetch.1b5c999309-800.webp"},{width:908,url:"/images/generated/6568f04691810d1c386db009_graph-comparing-performance-of-axios-vs-fetch.1b5c999309-908.webp"}]},"/images/blog/fetch-vs-axios/656a5b55303eec6586c2c166_fetch-vs-axios.png":{width:1880,height:1080,isAnimated:!1,fallbackUrl:"/images/blog/fetch-vs-axios/656a5b55303eec6586c2c166_fetch-vs-axios.png",avif:[{width:400,url:"/images/generated/656a5b55303eec6586c2c166_fetch-vs-axios.9b612b7c5c-400.avif"},{width:800,url:"/images/generated/656a5b55303eec6586c2c166_fetch-vs-axios.9b612b7c5c-800.avif"},{width:1200,url:"/images/generated/656a5b55303eec6586c2c166_fetch-vs-axios.9b612b7c5c-1200.avif"},{width:1600,url:"/images/generated/656a5b55303eec6586c2c166_fetch-vs-axios.9b612b7c5c-1600.avif"},{width:1880,url:"/images/generated/656a5b55303eec6586c2c166_fetch-vs-axios.9b612b7c5c-1880.avif"}],webp:[{width:400,url:"/images/generated/656a5b55303eec6586c2c166_fetch-vs-axios.9b612b7c5c-400.webp"},{width:800,url:"/images/generated/656a5b55303eec6586c2c166_fetch-vs-axios.9b612b7c5c-800.webp"},{width:1200,url:"/images/generated/656a5b55303eec6586c2c166_fetch-vs-axios.9b612b7c5c-1200.webp"},{width:1600,url:"/images/generated/656a5b55303eec6586c2c166_fetch-vs-axios.9b612b7c5c-1600.webp"},{width:1880,url:"/images/generated/656a5b55303eec6586c2c166_fetch-vs-axios.9b612b7c5c-1880.webp"}]},"/images/blog/frontend-unit-testing-best-practices/6568f17dc69f48950b0e8004_using-linting-rules-for-tests.jpeg":{width:936,height:350,isAnimated:!1,fallbackUrl:"/images/blog/frontend-unit-testing-best-practices/6568f17dc69f48950b0e8004_using-linting-rules-for-tests.jpeg",avif:[{width:400,url:"/images/generated/6568f17dc69f48950b0e8004_using-linting-rules-for-tests.c36634a3d7-400.avif"},{width:800,url:"/images/generated/6568f17dc69f48950b0e8004_using-linting-rules-for-tests.c36634a3d7-800.avif"},{width:936,url:"/images/generated/6568f17dc69f48950b0e8004_using-linting-rules-for-tests.c36634a3d7-936.avif"}],webp:[{width:400,url:"/images/generated/6568f17dc69f48950b0e8004_using-linting-rules-for-tests.c36634a3d7-400.webp"},{width:800,url:"/images/generated/6568f17dc69f48950b0e8004_using-linting-rules-for-tests.c36634a3d7-800.webp"},{width:936,url:"/images/generated/6568f17dc69f48950b0e8004_using-linting-rules-for-tests.c36634a3d7-936.webp"}]},"/images/blog/frontend-unit-testing-best-practices/656a194b02a2a8e8bfe8aa11_frontend-unit-testing-best-practices.png":{width:1880,height:1082,isAnimated:!1,fallbackUrl:"/images/blog/frontend-unit-testing-best-practices/656a194b02a2a8e8bfe8aa11_frontend-unit-testing-best-practices.png",avif:[{width:400,url:"/images/generated/656a194b02a2a8e8bfe8aa11_frontend-unit-testing-best-practices.e842db41cd-400.avif"},{width:800,url:"/images/generated/656a194b02a2a8e8bfe8aa11_frontend-unit-testing-best-practices.e842db41cd-800.avif"},{width:1200,url:"/images/generated/656a194b02a2a8e8bfe8aa11_frontend-unit-testing-best-practices.e842db41cd-1200.avif"},{width:1600,url:"/images/generated/656a194b02a2a8e8bfe8aa11_frontend-unit-testing-best-practices.e842db41cd-1600.avif"},{width:1880,url:"/images/generated/656a194b02a2a8e8bfe8aa11_frontend-unit-testing-best-practices.e842db41cd-1880.avif"}],webp:[{width:400,url:"/images/generated/656a194b02a2a8e8bfe8aa11_frontend-unit-testing-best-practices.e842db41cd-400.webp"},{width:800,url:"/images/generated/656a194b02a2a8e8bfe8aa11_frontend-unit-testing-best-practices.e842db41cd-800.webp"},{width:1200,url:"/images/generated/656a194b02a2a8e8bfe8aa11_frontend-unit-testing-best-practices.e842db41cd-1200.webp"},{width:1600,url:"/images/generated/656a194b02a2a8e8bfe8aa11_frontend-unit-testing-best-practices.e842db41cd-1600.webp"},{width:1880,url:"/images/generated/656a194b02a2a8e8bfe8aa11_frontend-unit-testing-best-practices.e842db41cd-1880.webp"}]},"/images/blog/getting-started-with-indexeddb/6568f7fe3de1438cc155f581_getting-started-with-indexeddb.webp":{width:1200,height:630,isAnimated:!1,fallbackUrl:"/images/blog/getting-started-with-indexeddb/6568f7fe3de1438cc155f581_getting-started-with-indexeddb.webp",avif:[{width:400,url:"/images/generated/6568f7fe3de1438cc155f581_getting-started-with-indexeddb.ce17ea72d2-400.avif"},{width:800,url:"/images/generated/6568f7fe3de1438cc155f581_getting-started-with-indexeddb.ce17ea72d2-800.avif"},{width:1200,url:"/images/generated/6568f7fe3de1438cc155f581_getting-started-with-indexeddb.ce17ea72d2-1200.avif"}],webp:[{width:400,url:"/images/generated/6568f7fe3de1438cc155f581_getting-started-with-indexeddb.ce17ea72d2-400.webp"},{width:800,url:"/images/generated/6568f7fe3de1438cc155f581_getting-started-with-indexeddb.ce17ea72d2-800.webp"},{width:1200,url:"/images/generated/6568f7fe3de1438cc155f581_getting-started-with-indexeddb.ce17ea72d2-1200.webp"}]},"/images/blog/getting-started-with-indexeddb/6568f848e349504198a06f62_caniuse-27s-compatibility-list-of-desktop-web-browsers.png":{width:491,height:382,isAnimated:!1,fallbackUrl:"/images/blog/getting-started-with-indexeddb/6568f848e349504198a06f62_caniuse-27s-compatibility-list-of-desktop-web-browsers.png",avif:[{width:400,url:"/images/generated/6568f848e349504198a06f62_caniuse-27s-compatibility-list-of-desktop-web-browsers.3b69efea3b-400.avif"},{width:491,url:"/images/generated/6568f848e349504198a06f62_caniuse-27s-compatibility-list-of-desktop-web-browsers.3b69efea3b-491.avif"}],webp:[{width:400,url:"/images/generated/6568f848e349504198a06f62_caniuse-27s-compatibility-list-of-desktop-web-browsers.3b69efea3b-400.webp"},{width:491,url:"/images/generated/6568f848e349504198a06f62_caniuse-27s-compatibility-list-of-desktop-web-browsers.3b69efea3b-491.webp"}]},"/images/blog/getting-started-with-indexeddb/6568f8c75295615c17c39fb6_adding-todos-using-IndexedDB.gif":{width:600,height:338,isAnimated:!0,fallbackUrl:"/images/blog/getting-started-with-indexeddb/6568f8c75295615c17c39fb6_adding-todos-using-IndexedDB.gif",avif:[],webp:[{width:600,url:"/images/generated/6568f8c75295615c17c39fb6_adding-todos-using-IndexedDB.4ddb99fd3d.webp"}]},"/images/blog/getting-started-with-react-logging/6568f9f7f053c1165c4ac2bb_log-everything.jpeg":{width:553,height:451,isAnimated:!1,fallbackUrl:"/images/blog/getting-started-with-react-logging/6568f9f7f053c1165c4ac2bb_log-everything.jpeg",avif:[{width:400,url:"/images/generated/6568f9f7f053c1165c4ac2bb_log-everything.86e7a8c0d6-400.avif"},{width:553,url:"/images/generated/6568f9f7f053c1165c4ac2bb_log-everything.86e7a8c0d6-553.avif"}],webp:[{width:400,url:"/images/generated/6568f9f7f053c1165c4ac2bb_log-everything.86e7a8c0d6-400.webp"},{width:553,url:"/images/generated/6568f9f7f053c1165c4ac2bb_log-everything.86e7a8c0d6-553.webp"}]},"/images/blog/getting-started-with-react-logging/6568fa2853dd376c63e93524_logging-ohno.png":{width:601,height:67,isAnimated:!1,fallbackUrl:"/images/blog/getting-started-with-react-logging/6568fa2853dd376c63e93524_logging-ohno.png",avif:[{width:400,url:"/images/generated/6568fa2853dd376c63e93524_logging-ohno.69e7b9fdb6-400.avif"},{width:601,url:"/images/generated/6568fa2853dd376c63e93524_logging-ohno.69e7b9fdb6-601.avif"}],webp:[{width:400,url:"/images/generated/6568fa2853dd376c63e93524_logging-ohno.69e7b9fdb6-400.webp"},{width:601,url:"/images/generated/6568fa2853dd376c63e93524_logging-ohno.69e7b9fdb6-601.webp"}]},"/images/blog/getting-started-with-react-logging/6568fa56da43aff18ec14b6c_console.png":{width:591,height:64,isAnimated:!1,fallbackUrl:"/images/blog/getting-started-with-react-logging/6568fa56da43aff18ec14b6c_console.png",avif:[{width:400,url:"/images/generated/6568fa56da43aff18ec14b6c_console.8219f33697-400.avif"},{width:591,url:"/images/generated/6568fa56da43aff18ec14b6c_console.8219f33697-591.avif"}],webp:[{width:400,url:"/images/generated/6568fa56da43aff18ec14b6c_console.8219f33697-400.webp"},{width:591,url:"/images/generated/6568fa56da43aff18ec14b6c_console.8219f33697-591.webp"}]},"/images/blog/getting-started-with-react-logging/6568fb46498487fdebc8b4e3_celebrate.gif":{width:375,height:211,isAnimated:!0,fallbackUrl:"/images/blog/getting-started-with-react-logging/6568fb46498487fdebc8b4e3_celebrate.gif",avif:[],webp:[{width:375,url:"/images/generated/6568fb46498487fdebc8b4e3_celebrate.4c00febde3.webp"}]},"/images/blog/getting-started-with-react-logging/656a5d03303eec6586c3d56e_getting-started-with-react-logging.png":{width:1920,height:1104,isAnimated:!1,fallbackUrl:"/images/blog/getting-started-with-react-logging/656a5d03303eec6586c3d56e_getting-started-with-react-logging.png",avif:[{width:400,url:"/images/generated/656a5d03303eec6586c3d56e_getting-started-with-react-logging.e9dfda70ec-400.avif"},{width:800,url:"/images/generated/656a5d03303eec6586c3d56e_getting-started-with-react-logging.e9dfda70ec-800.avif"},{width:1200,url:"/images/generated/656a5d03303eec6586c3d56e_getting-started-with-react-logging.e9dfda70ec-1200.avif"},{width:1600,url:"/images/generated/656a5d03303eec6586c3d56e_getting-started-with-react-logging.e9dfda70ec-1600.avif"},{width:1920,url:"/images/generated/656a5d03303eec6586c3d56e_getting-started-with-react-logging.e9dfda70ec-1920.avif"}],webp:[{width:400,url:"/images/generated/656a5d03303eec6586c3d56e_getting-started-with-react-logging.e9dfda70ec-400.webp"},{width:800,url:"/images/generated/656a5d03303eec6586c3d56e_getting-started-with-react-logging.e9dfda70ec-800.webp"},{width:1200,url:"/images/generated/656a5d03303eec6586c3d56e_getting-started-with-react-logging.e9dfda70ec-1200.webp"},{width:1600,url:"/images/generated/656a5d03303eec6586c3d56e_getting-started-with-react-logging.e9dfda70ec-1600.webp"},{width:1920,url:"/images/generated/656a5d03303eec6586c3d56e_getting-started-with-react-logging.e9dfda70ec-1920.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db189df99e4950a49d_meticulous-recorder-script.
29jpeg":{width:2048,height:1251,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db189df99e4950a49d_meticulous-recorder-script.jpeg",avif:[{width:400,url:"/images/generated/656757db189df99e4950a49d_meticulous-recorder-script.c6fe5633e5-400.avif"},{width:800,url:"/images/generated/656757db189df99e4950a49d_meticulous-recorder-script.c6fe5633e5-800.avif"},{width:1200,url:"/images/generated/656757db189df99e4950a49d_meticulous-recorder-script.c6fe5633e5-1200.avif"},{width:1600,url:"/images/generated/656757db189df99e4950a49d_meticulous-recorder-script.c6fe5633e5-1600.avif"},{width:2e3,url:"/images/generated/656757db189df99e4950a49d_meticulous-recorder-script.c6fe5633e5-2000.avif"},{width:2048,url:"/images/generated/656757db189df99e4950a49d_meticulous-recorder-script.c6fe5633e5-2048.avif"}],webp:[{width:400,url:"/images/generated/656757db189df99e4950a49d_meticulous-recorder-script.c6fe5633e5-400.webp"},{width:800,url:"/images/generated/656757db189df99e4950a49d_meticulous-recorder-script.c6fe5633e5-800.webp"},{width:1200,url:"/images/generated/656757db189df99e4950a49d_meticulous-recorder-script.c6fe5633e5-1200.webp"},{width:1600,url:"/images/generated/656757db189df99e4950a49d_meticulous-recorder-script.c6fe5633e5-1600.webp"},{width:2e3,url:"/images/generated/656757db189df99e4950a49d_meticulous-recorder-script.c6fe5633e5-2000.webp"},{width:2048,url:"/images/generated/656757db189df99e4950a49d_meticulous-recorder-script.c6fe5633e5-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db3399677431d95674_visual-regression-test-using-the-name-nationality-app.jpeg":{width:1416,height:1352,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db3399677431d95674_visual-regression-test-using-the-name-nationality-app.jpeg",avif:[{width:400,url:"/images/generated/656757db3399677431d95674_visual-regression-test-using-the-name-nationality-app.7b5b6895cb-400.avif"},{width:800,url:"/images/generated/656757db3399677431d95674_visual-regression-test-using-the-name-nationality-app.7b5b6895cb-800.avif"},{width:1200,url:"/images/generated/656757db3399677431d95674_visual-regression-test-using-the-name-nationality-app.7b5b6895cb-1200.avif"},{width:1416,url:"/images/generated/656757db3399677431d95674_visual-regression-test-using-the-name-nationality-app.7b5b6895cb-1416.avif"}],webp:[{width:400,url:"/images/generated/656757db3399677431d95674_visual-regression-test-using-the-name-nationality-app.7b5b6895cb-400.webp"},{width:800,url:"/images/generated/656757db3399677431d95674_visual-regression-test-using-the-name-nationality-app.7b5b6895cb-800.webp"},{width:1200,url:"/images/generated/656757db3399677431d95674_visual-regression-test-using-the-name-nationality-app.7b5b6895cb-1200.webp"},{width:1416,url:"/images/generated/656757db3399677431d95674_visual-regression-test-using-the-name-nationality-app.7b5b6895cb-1416.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db34323702a27ec242_vercel-registration.jpeg":{width:2048,height:1348,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db34323702a27ec242_vercel-registration.jpeg",avif:[{width:400,url:"/images/generated/656757db34323702a27ec242_vercel-registration.bd690947c7-400.avif"},{width:800,url:"/images/generated/656757db34323702a27ec242_vercel-registration.bd690947c7-800.avif"},{width:1200,url:"/images/generated/656757db34323702a27ec242_vercel-registration.bd690947c7-1200.avif"},{width:1600,url:"/images/generated/656757db34323702a27ec242_vercel-registration.bd690947c7-1600.avif"},{width:2e3,url:"/images/generated/656757db34323702a27ec242_vercel-registration.bd690947c7-2000.avif"},{width:2048,url:"/images/generated/656757db34323702a27ec242_vercel-registration.bd690947c7-2048.avif"}],webp:[{width:400,url:"/images/generated/656757db34323702a27ec242_vercel-registration.bd690947c7-400.webp"},{width:800,url:"/images/generated/656757db34323702a27ec242_vercel-registration.bd690947c7-800.webp"},{width:1200,url:"/images/generated/656757db34323702a27ec242_vercel-registration.bd690947c7-1200.webp"},{width:1600,url:"/images/generated/656757db34323702a27ec242_vercel-registration.bd690947c7-1600.webp"},{width:2e3,url:"/images/generated/656757db34323702a27ec242_vercel-registration.bd690947c7-2000.webp"},{width:2048,url:"/images/generated/656757db34323702a27ec242_vercel-registration.bd690947c7-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db360fac73f7aecea6_meticulous-installation.jpeg":{width:2048,height:726,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db360fac73f7aecea6_meticulous-installation.jpeg",avif:[{width:400,url:"/images/generated/656757db360fac73f7aecea6_meticulous-installation.bdac8f0dc3-400.avif"},{width:800,url:"/images/generated/656757db360fac73f7aecea6_meticulous-installation.bdac8f0dc3-800.avif"},{width:1200,url:"/images/generated/656757db360fac73f7aecea6_meticulous-installation.bdac8f0dc3-1200.avif"},{width:1600,url:"/images/generated/656757db360fac73f7aecea6_meticulous-installation.bdac8f0dc3-1600.avif"},{width:2e3,url:"/images/generated/656757db360fac73f7aecea6_meticulous-installation.bdac8f0dc3-2000.avif"},{width:2048,url:"/images/generated/656757db360fac73f7aecea6_meticulous-installation.bdac8f0dc3-2048.avif"}],webp:[{width:400,url:"/images/generated/656757db360fac73f7aecea6_meticulous-installation.bdac8f0dc3-400.webp"},{width:800,url:"/images/generated/656757db360fac73f7aecea6_meticulous-installation.bdac8f0dc3-800.webp"},{width:1200,url:"/images/generated/656757db360fac73f7aecea6_meticulous-installation.bdac8f0dc3-1200.webp"},{width:1600,url:"/images/generated/656757db360fac73f7aecea6_meticulous-installation.bdac8f0dc3-1600.webp"},{width:2e3,url:"/images/generated/656757db360fac73f7aecea6_meticulous-installation.bdac8f0dc3-2000.webp"},{width:2048,url:"/images/generated/656757db360fac73f7aecea6_meticulous-installation.bdac8f0dc3-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db6211d254c828af8b_meticulous-github-gitlab-integration.jpeg":{width:2048,height:1198,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db6211d254c828af8b_meticulous-github-gitlab-integration.jpeg",avif:[{width:400,url:"/images/generated/656757db6211d254c828af8b_meticulous-github-gitlab-integration.f24b8dc139-400.avif"},{width:800,url:"/images/generated/656757db6211d254c828af8b_meticulous-github-gitlab-integration.f24b8dc139-800.avif"},{width:1200,url:"/images/generated/656757db6211d254c828af8b_meticulous-github-gitlab-integration.f24b8dc139-1200.avif"},{width:1600,url:"/images/generated/656757db6211d254c828af8b_meticulous-github-gitlab-integration.f24b8dc139-1600.avif"},{width:2e3,url:"/images/generated/656757db6211d254c828af8b_meticulous-github-gitlab-integration.f24b8dc139-2000.avif"},{width:2048,url:"/images/generated/656757db6211d254c828af8b_meticulous-github-gitlab-integration.f24b8dc139-2048.avif"}],webp:[{width:400,url:"/images/generated/656757db6211d254c828af8b_meticulous-github-gitlab-integration.f24b8dc139-400.webp"},{width:800,url:"/images/generated/656757db6211d254c828af8b_meticulous-github-gitlab-integration.f24b8dc139-800.webp"},{width:1200,url:"/images/generated/656757db6211d254c828af8b_meticulous-github-gitlab-integration.f24b8dc139-1200.webp"},{width:1600,url:"/images/generated/656757db6211d254c828af8b_meticulous-github-gitlab-integration.f24b8dc139-1600.webp"},{width:2e3,url:"/images/generated/656757db6211d254c828af8b_meticulous-github-gitlab-integration.f24b8dc139-2000.webp"},{width:2048,url:"/images/generated/656757db6211d254c828af8b_meticulous-github-gitlab-integration.f24b8dc139-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db64f0a2ada09bcff4_meticulous-setup.jpeg":{width:2048,height:1198,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db64f0a2ada09bcff4_meticulous-setup.jpeg",avif:[{width:400,url:"/images/generated/656757db64f0a2ada09bcff4_meticulous-setup.07b1c496cf-400.avif"},{width:800,url:"/images/generated/656757db64f0a2ada09bcff4_meticulous-setup.07b1c496cf-800.avif"},{width:1200,url:"/images/generated/656757db64f0a2ada09bcff4_meticulous-setup.07b1c496cf-1200.avif"},{width:1600,url:"/images/generated/656757db64f0a2ada09bcff4_meticulous-setup.07b1c496cf-1600.avif"},{width:2e3,url:"/images/generated/656757db64f0a2ada09bcff4_meticulous-setup.07b1c496cf-2000.avif"},{width:2048,url:"/images/generated/656757db64f0a2ada09bcff4_meticulous-setup.07b1c496cf-2048.avif"}],webp:[{width:400,url:"/images/generated/656757db64f0a2ada09bcff4_meticulous-setup.07b1c496cf-400.webp"},{width:800,url:"/images/generated/656757db64f0a2ada09bcff4_meticulous-setup.07b1c496cf-800.webp"},{width:1200,url:"/images/generated/656757db64f0a2ada09bcff4_meticulous-setup.07b1c496cf-1200.webp"},{width:1600,url:"/images/generated/656757db64f0a2ada09bcff4_meticulous-setup.07b1c496cf-1600.webp"},{width:2e3,url:"/images/generated/656757db64f0a2ada09bcff4_meticulous-setup.07b1c496cf-2000.webp"},{width:2048,url:"/images/generated/656757db64f0a2ada09bcff4_meticulous-setup.07b1c496cf-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db68332f74985e70f6_meticulous-test-execution.jpeg":{width:2048,height:1517,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db68332f74985e70f6_meticulous-test-execution.jpeg",avif:[{width:400,url:"/images/generated/656757db68332f74985e70f6_meticulous-test-execution.84ccfaa868-400.avif"},{width:800,url:"/images/generated/656757db68332f74985e70f6_meticulous-test-execution.84ccfaa868-800.avif"},{width:1200,url:"/images/generated/656757db68332f74985e70f6_meticulous-test-execution.84ccfaa868-1200.avif"},{width:1600,url:"/images/generated/656757db68332f74985e70f6_meticulous-test-execution.84ccfaa868-1600.avif"},{width:2e3,url:"/images/generated/656757db68332f74985e70f6_meticulous-test-execution.84ccfaa868-2000.avif"},{width:2048,url:"/images/generated/656757db68332f74985e70f6_meticulous-test-execution.84ccfaa868-2048.avif"}],webp:[{width:400,url:"/images/generated/656757db68332f74985e70f6_meticulous-test-execution.84ccfaa868-400.webp"},{width:800,url:"/images/generated/656757db68332f74985e70f6_meticulous-test-execution.84ccfaa868-800.webp"},{width:1200,url:"/images/generated/656757db68332f74985e70f6_meticulous-test-execution.84ccfaa868-1200.webp"},{width:1600,url:"/images/generated/656757db68332f74985e70f6_meticulous-test-execution.84ccfaa868-1600.webp"},{width:2e3,url:"/images/generated/656757db68332f74985e70f6_meticulous-test-execution.84ccfaa868-2000.webp"},{width:2048,url:"/images/generated/656757db68332f74985e70f6_meticulous-test-execution.84ccfaa868-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db72ee2a1fb465d2ad_meticulous-vercel.jpeg":{width:2048,height:1456,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db72ee2a1fb465d2ad_meticulous-vercel.jpeg",avif:[{width:400,url:"/images/generated/656757db72ee2a1fb465d2ad_meticulous-vercel.4bc72fc423-400.avif"},{width:800,url:"/images/generated/656757db72ee2a1fb465d2ad_meticulous-vercel.4bc72fc423-800.avif"},{width:1200,url:"/images/generated/656757db72ee2a1fb465d2ad_meticulous-vercel.4bc72fc423-1200.avif"},{width:1600,url:"/images/generated/656757db72ee2a1fb465d2ad_meticulous-vercel.4bc72fc423-1600.avif"},{width:2e3,url:"/images/generated/656757db72ee2a1fb465d2ad_meticulous-vercel.4bc72fc423-2000.avif"},{width:2048,url:"/images/generated/656757db72ee2a1fb465d2ad_meticulous-vercel.4bc72fc423-2048.avif"}],webp:[{width:400,url:"/images/generated/656757db72ee2a1fb465d2ad_meticulous-vercel.4bc72fc423-400.webp"},{width:800,url:"/images/generated/656757db72ee2a1fb465d2ad_meticulous-vercel.4bc72fc423-800.webp"},{width:1200,url:"/images/generated/656757db72ee2a1fb465d2ad_meticulous-vercel.4bc72fc423-1200.webp"},{width:1600,url:"/images/generated/656757db72ee2a1fb465d2ad_meticulous-vercel.4bc72fc423-1600.webp"},{width:2e3,url:"/images/generated/656757db72ee2a1fb465d2ad_meticulous-vercel.4bc72fc423-2000.webp"},{width:2048,url:"/images/generated/656757db72ee2a1fb465d2ad_meticulous-vercel.4bc72fc423-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db7981669aeeb71b94_meticulous-projects-settings-page.jpeg":{width:1878,height:1096,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db7981669aeeb71b94_meticulous-projects-settings-page.jpeg",avif:[{width:400,url:"/images/generated/656757db7981669aeeb71b94_meticulous-projects-settings-page.67f3512d99-400.avif"},{width:800,url:"/images/generated/656757db7981669aeeb71b94_meticulous-projects-settings-page.67f3512d99-800.avif"},{width:1200,url:"/images/generated/656757db7981669aeeb71b94_meticulous-projects-settings-page.67f3512d99-1200.avif"},{width:1600,url:"/images/generated/656757db7981669aeeb71b94_meticulous-projects-settings-page.67f3512d99-1600.avif"},{width:1878,url:"/images/generated/656757db7981669aeeb71b94_meticulous-projects-settings-page.67f3512d99-1878.avif"}],webp:[{width:400,url:"/images/generated/656757db7981669aeeb71b94_meticulous-projects-settings-page.67f3512d99-400.webp"},{width:800,url:"/images/generated/656757db7981669aeeb71b94_meticulous-projects-settings-page.67f3512d99-800.webp"},{width:1200,url:"/images/generated/656757db7981669aeeb71b94_meticulous-projects-settings-page.67f3512d99-1200.webp"},{width:1600,url:"/images/generated/656757db7981669aeeb71b94_meticulous-projects-settings-page.67f3512d99-1600.webp"},{width:1878,url:"/images/generated/656757db7981669aeeb71b94_meticulous-projects-settings-page.67f3512d99-1878.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db7f2d8153a5b96948_visual-regression-testing-on-reactjs-app.gif":{width:2500,height:1351,isAnimated:!0,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db7f2d8153a5b96948_visual-regression-testing-on-reactjs-app.gif",avif:[],webp:[{width:2500,url:"/images/generated/656757db7f2d8153a5b96948_visual-regression-testing-on-reactjs-app.c5b6a
29d1a3d.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db9ca984cf1d3addf4_visual-regression-tests-failing-on-meticulous.jpeg":{width:1626,height:1430,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db9ca984cf1d3addf4_visual-regression-tests-failing-on-meticulous.jpeg",avif:[{width:400,url:"/images/generated/656757db9ca984cf1d3addf4_visual-regression-tests-failing-on-meticulous.1070c3c652-400.avif"},{width:800,url:"/images/generated/656757db9ca984cf1d3addf4_visual-regression-tests-failing-on-meticulous.1070c3c652-800.avif"},{width:1200,url:"/images/generated/656757db9ca984cf1d3addf4_visual-regression-tests-failing-on-meticulous.1070c3c652-1200.avif"},{width:1600,url:"/images/generated/656757db9ca984cf1d3addf4_visual-regression-tests-failing-on-meticulous.1070c3c652-1600.avif"},{width:1626,url:"/images/generated/656757db9ca984cf1d3addf4_visual-regression-tests-failing-on-meticulous.1070c3c652-1626.avif"}],webp:[{width:400,url:"/images/generated/656757db9ca984cf1d3addf4_visual-regression-tests-failing-on-meticulous.1070c3c652-400.webp"},{width:800,url:"/images/generated/656757db9ca984cf1d3addf4_visual-regression-tests-failing-on-meticulous.1070c3c652-800.webp"},{width:1200,url:"/images/generated/656757db9ca984cf1d3addf4_visual-regression-tests-failing-on-meticulous.1070c3c652-1200.webp"},{width:1600,url:"/images/generated/656757db9ca984cf1d3addf4_visual-regression-tests-failing-on-meticulous.1070c3c652-1600.webp"},{width:1626,url:"/images/generated/656757db9ca984cf1d3addf4_visual-regression-tests-failing-on-meticulous.1070c3c652-1626.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.jpeg":{width:2048,height:1336,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.jpeg",avif:[{width:400,url:"/images/generated/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.892a1e4f89-400.avif"},{width:800,url:"/images/generated/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.892a1e4f89-800.avif"},{width:1200,url:"/images/generated/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.892a1e4f89-1200.avif"},{width:1600,url:"/images/generated/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.892a1e4f89-1600.avif"},{width:2e3,url:"/images/generated/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.892a1e4f89-2000.avif"},{width:2048,url:"/images/generated/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.892a1e4f89-2048.avif"}],webp:[{width:400,url:"/images/generated/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.892a1e4f89-400.webp"},{width:800,url:"/images/generated/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.892a1e4f89-800.webp"},{width:1200,url:"/images/generated/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.892a1e4f89-1200.webp"},{width:1600,url:"/images/generated/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.892a1e4f89-1600.webp"},{width:2e3,url:"/images/generated/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.892a1e4f89-2000.webp"},{width:2048,url:"/images/generated/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.892a1e4f89-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbb28f6df423e2d749_meticulous-registration.jpeg":{width:936,height:606,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbb28f6df423e2d749_meticulous-registration.jpeg",avif:[{width:400,url:"/images/generated/656757dbb28f6df423e2d749_meticulous-registration.596bf2e219-400.avif"},{width:800,url:"/images/generated/656757dbb28f6df423e2d749_meticulous-registration.596bf2e219-800.avif"},{width:936,url:"/images/generated/656757dbb28f6df423e2d749_meticulous-registration.596bf2e219-936.avif"}],webp:[{width:400,url:"/images/generated/656757dbb28f6df423e2d749_meticulous-registration.596bf2e219-400.webp"},{width:800,url:"/images/generated/656757dbb28f6df423e2d749_meticulous-registration.596bf2e219-800.webp"},{width:936,url:"/images/generated/656757dbb28f6df423e2d749_meticulous-registration.596bf2e219-936.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbb2922d9e3a813c66_meticulous-session-recording.jpeg":{width:2048,height:1174,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbb2922d9e3a813c66_meticulous-session-recording.jpeg",avif:[{width:400,url:"/images/generated/656757dbb2922d9e3a813c66_meticulous-session-recording.be0d39c2c3-400.avif"},{width:800,url:"/images/generated/656757dbb2922d9e3a813c66_meticulous-session-recording.be0d39c2c3-800.avif"},{width:1200,url:"/images/generated/656757dbb2922d9e3a813c66_meticulous-session-recording.be0d39c2c3-1200.avif"},{width:1600,url:"/images/generated/656757dbb2922d9e3a813c66_meticulous-session-recording.be0d39c2c3-1600.avif"},{width:2e3,url:"/images/generated/656757dbb2922d9e3a813c66_meticulous-session-recording.be0d39c2c3-2000.avif"},{width:2048,url:"/images/generated/656757dbb2922d9e3a813c66_meticulous-session-recording.be0d39c2c3-2048.avif"}],webp:[{width:400,url:"/images/generated/656757dbb2922d9e3a813c66_meticulous-session-recording.be0d39c2c3-400.webp"},{width:800,url:"/images/generated/656757dbb2922d9e3a813c66_meticulous-session-recording.be0d39c2c3-800.webp"},{width:1200,url:"/images/generated/656757dbb2922d9e3a813c66_meticulous-session-recording.be0d39c2c3-1200.webp"},{width:1600,url:"/images/generated/656757dbb2922d9e3a813c66_meticulous-session-recording.be0d39c2c3-1600.webp"},{width:2e3,url:"/images/generated/656757dbb2922d9e3a813c66_meticulous-session-recording.be0d39c2c3-2000.webp"},{width:2048,url:"/images/generated/656757dbb2922d9e3a813c66_meticulous-session-recording.be0d39c2c3-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbb99d6f30f60ef566_vecel-new-project-deployed.jpeg":{width:2048,height:1308,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbb99d6f30f60ef566_vecel-new-project-deployed.jpeg",avif:[{width:400,url:"/images/generated/656757dbb99d6f30f60ef566_vecel-new-project-deployed.e46c43d1cf-400.avif"},{width:800,url:"/images/generated/656757dbb99d6f30f60ef566_vecel-new-project-deployed.e46c43d1cf-800.avif"},{width:1200,url:"/images/generated/656757dbb99d6f30f60ef566_vecel-new-project-deployed.e46c43d1cf-1200.avif"},{width:1600,url:"/images/generated/656757dbb99d6f30f60ef566_vecel-new-project-deployed.e46c43d1cf-1600.avif"},{width:2e3,url:"/images/generated/656757dbb99d6f30f60ef566_vecel-new-project-deployed.e46c43d1cf-2000.avif"},{width:2048,url:"/images/generated/656757dbb99d6f30f60ef566_vecel-new-project-deployed.e46c43d1cf-2048.avif"}],webp:[{width:400,url:"/images/generated/656757dbb99d6f30f60ef566_vecel-new-project-deployed.e46c43d1cf-400.webp"},{width:800,url:"/images/generated/656757dbb99d6f30f60ef566_vecel-new-project-deployed.e46c43d1cf-800.webp"},{width:1200,url:"/images/generated/656757dbb99d6f30f60ef566_vecel-new-project-deployed.e46c43d1cf-1200.webp"},{width:1600,url:"/images/generated/656757dbb99d6f30f60ef566_vecel-new-project-deployed.e46c43d1cf-1600.webp"},{width:2e3,url:"/images/generated/656757dbb99d6f30f60ef566_vecel-new-project-deployed.e46c43d1cf-2000.webp"},{width:2048,url:"/images/generated/656757dbb99d6f30f60ef566_vecel-new-project-deployed.e46c43d1cf-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.jpeg":{width:2048,height:1300,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.jpeg",avif:[{width:400,url:"/images/generated/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.cedceab29a-400.avif"},{width:800,url:"/images/generated/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.cedceab29a-800.avif"},{width:1200,url:"/images/generated/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.cedceab29a-1200.avif"},{width:1600,url:"/images/generated/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.cedceab29a-1600.avif"},{width:2e3,url:"/images/generated/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.cedceab29a-2000.avif"},{width:2048,url:"/images/generated/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.cedceab29a-2048.avif"}],webp:[{width:400,url:"/images/generated/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.cedceab29a-400.webp"},{width:800,url:"/images/generated/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.cedceab29a-800.webp"},{width:1200,url:"/images/generated/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.cedceab29a-1200.webp"},{width:1600,url:"/images/generated/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.cedceab29a-1600.webp"},{width:2e3,url:"/images/generated/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.cedceab29a-2000.webp"},{width:2048,url:"/images/generated/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.cedceab29a-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbd0335d980bc742e0_meticulous-select-framework.jpeg":{width:2048,height:1190,isA
29nimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbd0335d980bc742e0_meticulous-select-framework.jpeg",avif:[{width:400,url:"/images/generated/656757dbd0335d980bc742e0_meticulous-select-framework.6536e9fbe4-400.avif"},{width:800,url:"/images/generated/656757dbd0335d980bc742e0_meticulous-select-framework.6536e9fbe4-800.avif"},{width:1200,url:"/images/generated/656757dbd0335d980bc742e0_meticulous-select-framework.6536e9fbe4-1200.avif"},{width:1600,url:"/images/generated/656757dbd0335d980bc742e0_meticulous-select-framework.6536e9fbe4-1600.avif"},{width:2e3,url:"/images/generated/656757dbd0335d980bc742e0_meticulous-select-framework.6536e9fbe4-2000.avif"},{width:2048,url:"/images/generated/656757dbd0335d980bc742e0_meticulous-select-framework.6536e9fbe4-2048.avif"}],webp:[{width:400,url:"/images/generated/656757dbd0335d980bc742e0_meticulous-select-framework.6536e9fbe4-400.webp"},{width:800,url:"/images/generated/656757dbd0335d980bc742e0_meticulous-select-framework.6536e9fbe4-800.webp"},{width:1200,url:"/images/generated/656757dbd0335d980bc742e0_meticulous-select-framework.6536e9fbe4-1200.webp"},{width:1600,url:"/images/generated/656757dbd0335d980bc742e0_meticulous-select-framework.6536e9fbe4-1600.webp"},{width:2e3,url:"/images/generated/656757dbd0335d980bc742e0_meticulous-select-framework.6536e9fbe4-2000.webp"},{width:2048,url:"/images/generated/656757dbd0335d980bc742e0_meticulous-select-framework.6536e9fbe4-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbd0335d980bc742e6_meticulous-project.jpeg":{width:2048,height:1193,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbd0335d980bc742e6_meticulous-project.jpeg",avif:[{width:400,url:"/images/generated/656757dbd0335d980bc742e6_meticulous-project.c57d9da00f-400.avif"},{width:800,url:"/images/generated/656757dbd0335d980bc742e6_meticulous-project.c57d9da00f-800.avif"},{width:1200,url:"/images/generated/656757dbd0335d980bc742e6_meticulous-project.c57d9da00f-1200.avif"},{width:1600,url:"/images/generated/656757dbd0335d980bc742e6_meticulous-project.c57d9da00f-1600.avif"},{width:2e3,url:"/images/generated/656757dbd0335d980bc742e6_meticulous-project.c57d9da00f-2000.avif"},{width:2048,url:"/images/generated/656757dbd0335d980bc742e6_meticulous-project.c57d9da00f-2048.avif"}],webp:[{width:400,url:"/images/generated/656757dbd0335d980bc742e6_meticulous-project.c57d9da00f-400.webp"},{width:800,url:"/images/generated/656757dbd0335d980bc742e6_meticulous-project.c57d9da00f-800.webp"},{width:1200,url:"/images/generated/656757dbd0335d980bc742e6_meticulous-project.c57d9da00f-1200.webp"},{width:1600,url:"/images/generated/656757dbd0335d980bc742e6_meticulous-project.c57d9da00f-1600.webp"},{width:2e3,url:"/images/generated/656757dbd0335d980bc742e6_meticulous-project.c57d9da00f-2000.webp"},{width:2048,url:"/images/generated/656757dbd0335d980bc742e6_meticulous-project.c57d9da00f-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.jpeg":{width:2006,height:1170,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.jpeg",avif:[{width:400,url:"/images/generated/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.d59a66a426-400.avif"},{width:800,url:"/images/generated/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.d59a66a426-800.avif"},{width:1200,url:"/images/generated/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.d59a66a426-1200.avif"},{width:1600,url:"/images/generated/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.d59a66a426-1600.avif"},{width:2e3,url:"/images/generated/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.d59a66a426-2000.avif"},{width:2006,url:"/images/generated/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.d59a66a426-2006.avif"}],webp:[{width:400,url:"/images/generated/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.d59a66a426-400.webp"},{width:800,url:"/images/generated/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.d59a66a426-800.webp"},{width:1200,url:"/images/generated/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.d59a66a426-1200.webp"},{width:1600,url:"/images/generated/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.d59a66a426-1600.webp"},{width:2e3,url:"/images/generated/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.d59a66a426-2000.webp"},{width:2006,url:"/images/generated/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.d59a66a426-2006.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.jpeg":{width:2048,height:1191,isA
29nimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.jpeg",avif:[{width:400,url:"/images/generated/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.55ad81505e-400.avif"},{width:800,url:"/images/generated/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.55ad81505e-800.avif"},{width:1200,url:"/images/generated/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.55ad81505e-1200.avif"},{width:1600,url:"/images/generated/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.55ad81505e-1600.avif"},{width:2e3,url:"/images/generated/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.55ad81505e-2000.avif"},{width:2048,url:"/images/generated/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.55ad81505e-2048.avif"}],webp:[{width:400,url:"/images/generated/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.55ad81505e-400.webp"},{width:800,url:"/images/generated/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.55ad81505e-800.webp"},{width:1200,url:"/images/generated/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.55ad81505e-1200.webp"},{width:1600,url:"/images/generated/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.55ad81505e-1600.webp"},{width:2e3,url:"/images/generated/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.55ad81505e-2000.webp"},{width:2048,url:"/images/generated/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.55ad81505e-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbf5a49ac6efa430d4_meticulous-onboarding.jpeg":{width:2048,height:1193,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbf5a49ac6efa430d4_meticulous-onboarding.jpeg",avif:[{width:400,url:"/images/generated/656757dbf5a49ac6efa430d4_meticulous-onboarding.dcded0c504-400.avif"},{width:800,url:"/images/generated/656757dbf5a49ac6efa430d4_meticulous-onboarding.dcded0c504-800.avif"},{width:1200,url:"/images/generated/656757dbf5a49ac6efa430d4_meticulous-onboarding.dcded0c504-1200.avif"},{width:1600,url:"/images/generated/656757dbf5a49ac6efa430d4_meticulous-onboarding.dcded0c504-1600.avif"},{width:2e3,url:"/images/generated/656757dbf5a49ac6efa430d4_meticulous-onboarding.dcded0c504-2000.avif"},{width:2048,url:"/images/generated/656757dbf5a49ac6efa430d4_meticulous-onboarding.dcded0c504-2048.avif"}],webp:[{width:400,url:"/images/generated/656757dbf5a49ac6efa430d4_meticulous-onboarding.dcded0c504-400.webp"},{width:800,url:"/images/generated/656757dbf5a49ac6efa430d4_meticulous-onboarding.dcded0c504-800.webp"},{width:1200,url:"/images/generated/656757dbf5a49ac6efa430d4_meticulous-onboarding.dcded0c504-1200.webp"},{width:1600,url:"/images/generated/656757dbf5a49ac6efa430d4_meticulous-onboarding.dcded0c504-1600.webp"},{width:2e3,url:"/images/generated/656757dbf5a49ac6efa430d4_meticulous-onboarding.dcded0c504-2000.webp"},{width:2048,url:"/images/generated/656757dbf5a49ac6efa430d4_meticulous-onboarding.dcded0c504-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbf9ad18723691e538_name-nationalities-app.jpeg":{width:1348,height:1358,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbf9ad18723691e538_name-nationalities-app.jpeg",avif:[{width:400,url:"/images/generated/656757dbf9ad18723691e538_name-nationalities-app.dcfb3e58ae-400.avif"},{width:800,url:"/images/generated/656757dbf9ad18723691e538_name-nationalities-app.dcfb3e58ae-800.avif"},{width:1200,url:"/images/generated/656757dbf9ad18723691e538_name-nationalities-app.dcfb3e58ae-1200.avif"},{width:1348,url:"/images/generated/656757dbf9ad18723691e538_name-nationalities-app.dcfb3e58ae-1348.avif"}],webp:[{width:400,url:"/images/generated/656757dbf9ad18723691e538_name-nationalities-app.dcfb3e58ae-400.webp"},{width:800,url:"/images/generated/656757dbf9ad18723691e538_name-nationalities-app.dcfb3e58ae-800.webp"},{width:1200,url:"/images/generated/656757dbf9ad18723691e538_name-nationalities-app.dcfb3e58ae-1200.webp"},{width:1348,url:"/images/generated/656757dbf9ad18723691e538_name-nationalities-app.dcfb3e58ae-1348.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.jpeg":{width:2048,height:1277,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.jpeg",avif:[{width:400,url:"/images/generated/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.f959924235-400.avif"},{width:800,url:"/images/generated/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.f959924235-800.avif"},{width:1200,url:"/images/generated/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.f959924235-1200.avif"},{width:1600,url:"/images/generated/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.f959924235-1600.avif"},{width:2e3,url:"/images/generated/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.f959924235-2000.avif"},{width:2048,url:"/images/generated/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.f959924235-2048.avif"}],webp:[{width:400,url:"/images/generated/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.f959924235-400.webp"},{width:800,url:"/images/generated/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.f959924235-800.webp"},{width:1200,url:"/images/generated/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.f959924235-1200.webp"},{width:1600,url:"/images/generated/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.f959924235-1600.webp"},{width:2e3,url:"/images/generated/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.f959924235-2000.webp"},{width:2048,url:"/images/generated/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.f959924235-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dc21408d4ac4cbc4f7_visual-regression-tests-passing-on-meticulous.jpeg":{width:1620,height:1446,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dc21408d4ac4cbc4f7_visual-regression-tests-passing-on-meticulous.jpeg",avif:[{width:400,url:"/images/generated/656757dc21408d4ac4cbc4f7_visual-regression-tests-passing-on-meticulous.d900a7eb4c-400.avif"},{width:800,url:"/images/generated/656757dc21408d4ac4cbc4f7_visual-regression-tests-passing-on-meticulous.d900a7eb4c-800.avif"},{width:1200,url:"/images/generated/656757dc21408d4ac4cbc4f7_visual-regression-tests-passing-on-meticulous.d900a7eb4c-1200.avif"},{width:1600,url:"/images/generated/656757dc21408d4ac4cbc4f7_visual-regression-tests-passing-on-meticulous.d900a7eb4c-1600.avif"},{width:1620,url:"/images/generated/656757dc21408d4ac4cbc4f7_visual-regression-tests-passing-on-meticulous.d900a7eb4c-1620.avif"}],webp:[{width:400,url:"/images/generated/656757dc21408d4ac4cbc4f7_visual-regression-tests-passing-on-meticulous.d900a7eb4c-400.webp"},{width:800,url:"/images/generated/656757dc21408d4ac4cbc4f7_visual-regression-tests-passing-on-meticulous.d900a7eb4c-800.webp"},{width:1200,url:"/images/generated/656757dc21408d4ac4cbc4f7_visual-regression-tests-passing-on-meticulous.d900a7eb4c-1200.webp"},{width:1600,url:"/images/generated/656757dc21408d4ac4cbc4f7_visual-regression-tests-passing-on-meticulous.d900a7eb4c-1600.webp"},{width:1620,url:"/images/generated/656757dc21408d4ac4cbc4f7_visual-regression-tests-passing-on-meticulous.d900a7eb4c-1620.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dc5b2fa9e353868d07_vercel-create-new-project.
29jpeg":{width:2048,height:444,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dc5b2fa9e353868d07_vercel-create-new-project.jpeg",avif:[{width:400,url:"/images/generated/656757dc5b2fa9e353868d07_vercel-create-new-project.969bcef877-400.avif"},{width:800,url:"/images/generated/656757dc5b2fa9e353868d07_vercel-create-new-project.969bcef877-800.avif"},{width:1200,url:"/images/generated/656757dc5b2fa9e353868d07_vercel-create-new-project.969bcef877-1200.avif"},{width:1600,url:"/images/generated/656757dc5b2fa9e353868d07_vercel-create-new-project.969bcef877-1600.avif"},{width:2e3,url:"/images/generated/656757dc5b2fa9e353868d07_vercel-create-new-project.969bcef877-2000.avif"},{width:2048,url:"/images/generated/656757dc5b2fa9e353868d07_vercel-create-new-project.969bcef877-2048.avif"}],webp:[{width:400,url:"/images/generated/656757dc5b2fa9e353868d07_vercel-create-new-project.969bcef877-400.webp"},{width:800,url:"/images/generated/656757dc5b2fa9e353868d07_vercel-create-new-project.969bcef877-800.webp"},{width:1200,url:"/images/generated/656757dc5b2fa9e353868d07_vercel-create-new-project.969bcef877-1200.webp"},{width:1600,url:"/images/generated/656757dc5b2fa9e353868d07_vercel-create-new-project.969bcef877-1600.webp"},{width:2e3,url:"/images/generated/656757dc5b2fa9e353868d07_vercel-create-new-project.969bcef877-2000.webp"},{width:2048,url:"/images/generated/656757dc5b2fa9e353868d07_vercel-create-new-project.969bcef877-2048.webp"}]},"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656a5334ecfb394aeffc2d5b_visual-regression-testing-in-a-react-app.png":{width:1816,height:1044,isAnimated:!1,fallbackUrl:"/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656a5334ecfb394aeffc2d5b_visual-regression-testing-in-a-react-app.png",avif:[{width:400,url:"/images/generated/656a5334ecfb394aeffc2d5b_visual-regression-testing-in-a-react-app.f3a0cf4c29-400.avif"},{width:800,url:"/images/generated/656a5334ecfb394aeffc2d5b_visual-regression-testing-in-a-react-app.f3a0cf4c29-800.avif"},{width:1200,url:"/images/generated/656a5334ecfb394aeffc2d5b_visual-regression-testing-in-a-react-app.f3a0cf4c29-1200.avif"},{width:1600,url:"/images/generated/656a5334ecfb394aeffc2d5b_visual-regression-testing-in-a-react-app.f3a0cf4c29-1600.avif"},{width:1816,url:"/images/generated/656a5334ecfb394aeffc2d5b_visual-regression-testing-in-a-react-app.f3a0cf4c29-1816.avif"}],webp:[{width:400,url:"/images/generated/656a5334ecfb394aeffc2d5b_visual-regression-testing-in-a-react-app.f3a0cf4c29-400.webp"},{width:800,url:"/images/generated/656a5334ecfb394aeffc2d5b_visual-regression-testing-in-a-react-app.f3a0cf4c29-800.webp"},{width:1200,url:"/images/generated/656a5334ecfb394aeffc2d5b_visual-regression-testing-in-a-react-app.f3a0cf4c29-1200.webp"},{width:1600,url:"/images/generated/656a5334ecfb394aeffc2d5b_visual-regression-testing-in-a-react-app.f3a0cf4c29-1600.webp"},{width:1816,url:"/images/generated/656a5334ecfb394aeffc2d5b_visual-regression-testing-in-a-react-app.f3a0cf4c29-1816.webp"}]},"/images/blog/how-to-prevent-javascript-syntax-errors/6568fd2704f3082ea5d6dcfa_visual-studio-code-showing-a-highlighted-syntax-error.png":{width:1366,height:328,isAnimated:!1,fallbackUrl:"/images/blog/how-to-prevent-javascript-syntax-errors/6568fd2704f3082ea5d6dcfa_visual-studio-code-showing-a-highlighted-syntax-error.png",avif:[{width:400,url:"/images/generated/6568fd2704f3082ea5d6dcfa_visual-studio-code-showing-a-highlighted-syntax-error.a8f0b1f979-400.avif"},{width:800,url:"/images/generated/6568fd2704f3082ea5d6dcfa_visual-studio-code-showing-a-highlighted-syntax-error.a8f0b1f979-800.avif"},{width:1200,url:"/images/generated/6568fd2704f3082ea5d6dcfa_visual-studio-code-showing-a-highlighted-syntax-error.a8f0b1f979-1200.avif"},{width:1366,url:"/images/generated/6568fd2704f3082ea5d6dcfa_visual-studio-code-showing-a-highlighted-syntax-error.a8f0b1f979-1366.avif"}],webp:[{width:400,url:"/images/generated/6568fd2704f3082ea5d6dcfa_visual-studio-code-showing-a-highlighted-syntax-error.a8f0b1f979-400.webp"},{width:800,url:"/images/generated/6568fd2704f3082ea5d6dcfa_visual-studio-code-showing-a-highlighted-syntax-error.a8f0b1f979-800.webp"},{width:1200,url:"/images/generated/6568fd2704f3082ea5d6dcfa_visual-studio-code-showing-a-highlighted-syntax-error.a8f0b1f979-1200.webp"},{width:1366,url:"/images/generated/6568fd2704f3082ea5d6dcfa_visual-studio-code-showing-a-highlighted-syntax-error.a8f0b1f979-1366.webp"}]},"/images/blog/how-to-prevent-javascript-syntax-errors/6568fd27879a6b03caeee108_typescript-compile-displaying-errors-in-the-terminal.png":{width:678,he
29ight:352,isAnimated:!1,fallbackUrl:"/images/blog/how-to-prevent-javascript-syntax-errors/6568fd27879a6b03caeee108_typescript-compile-displaying-errors-in-the-terminal.png",avif:[{width:400,url:"/images/generated/6568fd27879a6b03caeee108_typescript-compile-displaying-errors-in-the-terminal.ae22dcebc7-400.avif"},{width:678,url:"/images/generated/6568fd27879a6b03caeee108_typescript-compile-displaying-errors-in-the-terminal.ae22dcebc7-678.avif"}],webp:[{width:400,url:"/images/generated/6568fd27879a6b03caeee108_typescript-compile-displaying-errors-in-the-terminal.ae22dcebc7-400.webp"},{width:678,url:"/images/generated/6568fd27879a6b03caeee108_typescript-compile-displaying-errors-in-the-terminal.ae22dcebc7-678.webp"}]},"/images/blog/how-to-prevent-javascript-syntax-errors/6568fd27a399bb647d9f43dc_visual-studio-code-when-the-error-is-fixed.png":{width:1366,height:343,isAnimated:!1,fallbackUrl:"/images/blog/how-to-prevent-javascript-syntax-errors/6568fd27a399bb647d9f43dc_visual-studio-code-when-the-error-is-fixed.png",avif:[{width:400,url:"/images/generated/6568fd27a399bb647d9f43dc_visual-studio-code-when-the-error-is-fixed.d1273220dc-400.avif"},{width:800,url:"/images/generated/6568fd27a399bb647d9f43dc_visual-studio-code-when-the-error-is-fixed.d1273220dc-800.avif"},{width:1200,url:"/images/generated/6568fd27a399bb647d9f43dc_visual-studio-code-when-the-error-is-fixed.d1273220dc-1200.avif"},{width:1366,url:"/images/generated/6568fd27a399bb647d9f43dc_visual-studio-code-when-the-error-is-fixed.d1273220dc-1366.avif"}],webp:[{width:400,url:"/images/generated/6568fd27a399bb647d9f43dc_visual-studio-code-when-the-error-is-fixed.d1273220dc-400.webp"},{width:800,url:"/images/generated/6568fd27a399bb647d9f43dc_visual-studio-code-when-the-error-is-fixed.d1273220dc-800.webp"},{width:1200,url:"/images/generated/6568fd27a399bb647d9f43dc_visual-studio-code-when-the-error-is-fixed.d1273220dc-1200.webp"},{width:1366,url:"/images/generated/6568fd27a399bb647d9f43dc_visual-studio-code-when-the-error-is-fixed.d1273220dc-1366.webp"}]},"/images/blog/how-to-prevent-javascript-syntax-errors/656a5c0e34337f6f7792be56_how-to-prevent-javascript-syntax-errors.png":{width:1882,height:1080,isAnimated:!1,fallbackUrl:"/images/blog/how-to-prevent-javascript-syntax-errors/656a5c0e34337f6f7792be56_how-to-prevent-javascript-syntax-errors.png",avif:[{width:400,url:"/images/generated/656a5c0e34337f6f7792be56_how-to-prevent-javascript-syntax-errors.0b97ec5bac-400.avif"},{width:800,url:"/images/generated/656a5c0e34337f6f7792be56_how-to-prevent-javascript-syntax-errors.0b97ec5bac-800.avif"},{width:1200,url:"/images/generated/656a5c0e34337f6f7792be56_how-to-prevent-javascript-syntax-errors.0b97ec5bac-1200.avif"},{width:1600,url:"/images/generated/656a5c0e34337f6f7792be56_how-to-prevent-javascript-syntax-errors.0b97ec5bac-1600.avif"},{width:1882,url:"/images/generated/656a5c0e34337f6f7792be56_how-to-prevent-javascript-syntax-errors.0b97ec5bac-1882.avif"}],webp:[{width:400,url:"/images/generated/656a5c0e34337f6f7792be56_how-to-prevent-javascript-syntax-errors.0b97ec5bac-400.webp"},{width:800,url:"/images/generated/656a5c0e34337f6f7792be56_how-to-prevent-javascript-syntax-errors.0b97ec5bac-800.webp"},{width:1200,url:"/images/generated/656a5c0e34337f6f7792be56_how-to-prevent-javascript-syntax-errors.0b97ec5bac-1200.webp"},{width:1600,url:"/images/generated/656a5c0e34337f6f7792be56_how-to-prevent-javascript-syntax-errors.0b97ec5bac-1600.webp"},{width:1882,url:"/images/generated/656a5c0e34337f6f7792be56_how-to-prevent-javascript-syntax-errors.0b97ec5bac-1882.webp"}]},"/images/blog/how-to-prevent-the-error-cannot-read-property-0-of-undefined/6568feb9b553c4a4a34c75b1_logging-an-array-to-the-console-shows-its-object-structure.png":{width:490,height:242,isAnimated:!1,fallbackUrl:"/images/blog/how-to-prevent-the-error-cannot-read-property-0-of-undefined/6568feb9b553c4a4a34c75b1_logging-an-array-to-the-console-shows-its-object-structure.png",avif:[{width:400,url:"/images/generated/6568feb9b553c4a4a34c75b1_logging-an-array-to-the-console-shows-its-object-structure.a7b5c85a84-400.avif"},{width:490,url:"/images/generated/6568feb9b553c4a4a34c75b1_logging-an-array-to-the-console-shows-its-object-structure.a7b5c85a84-490.avif"}],webp:[{width:400,url:"/images/generated/6568feb9b553c4a4a34c75b1_logging-an-array-to-the-console-shows-its-object-structure.a7b5c85a84-400.webp"},{width:490,url:"/images/generated/6568feb9b553c4a4a34c75b1_logging-an-array-to-the-console-shows-its-object-structure.a7b5c85a84-490.webp"}]},"/images/blog/how-to-prevent-the-error-cannot-read-property-0-of-undefined/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.png":{width:4317,height:2484,isAnimated:!1,fallbackUrl:"/images/blog/how-to-prevent-the-error-cannot-read-property-0-of-undefined/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.png",avif:[{width:400,url:"/images/generated/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.715ea19abe-400.avif"},{width:800,url:"/images/generated/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.715ea19abe-800.avif"},{width:1200,url:"/images/generated/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.715ea19abe-1200.avif"},{width:1600,url:"/images/generated/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.715ea19abe-1600.avif"},{width:2e3,url:"/images/generated/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.715ea19abe-2000.avif"},{width:4317,url:"/images/generated/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.715ea19abe-4317.avif"}],webp:[{width:400,url:"/images/generated/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.715ea19abe-400.webp"},{width:800,url:"/images/generated/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.715ea19abe-800.webp"},{width:1200,url:"/images/generated/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.715ea19abe-1200.webp"},{width:1600,url:"/images/generated/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.715ea19abe-1600.webp"},{width:2e3,url:"/images/generated/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.715ea19abe-2000.webp"},{width:4317,url:"/images/generated/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.715ea19abe-4317.webp"}]},"/images/blog/how-to-prevent-typeerror-cannot-read-property-map-of-undefined/656903dd224dde6d4e30040d_error-found-as-a-result-of-using-variable-before-it-E2-80-99s-set.png":{width:678,he
29ight:538,isAnimated:!1,fallbackUrl:"/images/blog/how-to-prevent-typeerror-cannot-read-property-map-of-undefined/656903dd224dde6d4e30040d_error-found-as-a-result-of-using-variable-before-it-E2-80-99s-set.png",avif:[{width:400,url:"/images/generated/656903dd224dde6d4e30040d_error-found-as-a-result-of-using-variable-before-it-E2-80-99s-set.4f95cb6df0-400.avif"},{width:678,url:"/images/generated/656903dd224dde6d4e30040d_error-found-as-a-result-of-using-variable-before-it-E2-80-99s-set.4f95cb6df0-678.avif"}],webp:[{width:400,url:"/images/generated/656903dd224dde6d4e30040d_error-found-as-a-result-of-using-variable-before-it-E2-80-99s-set.4f95cb6df0-400.webp"},{width:678,url:"/images/generated/656903dd224dde6d4e30040d_error-found-as-a-result-of-using-variable-before-it-E2-80-99s-set.4f95cb6df0-678.webp"}]},"/images/blog/how-to-prevent-typeerror-cannot-read-property-map-of-undefined/656903ddd887f1c1fd2b49ee_using-TypeScript-to-reduce-coding-errors.png":{width:678,height:460,isAnimated:!1,fallbackUrl:"/images/blog/how-to-prevent-typeerror-cannot-read-property-map-of-undefined/656903ddd887f1c1fd2b49ee_using-TypeScript-to-reduce-coding-errors.png",avif:[{width:400,url:"/images/generated/656903ddd887f1c1fd2b49ee_using-TypeScript-to-reduce-coding-errors.735c751479-400.avif"},{width:678,url:"/images/generated/656903ddd887f1c1fd2b49ee_using-TypeScript-to-reduce-coding-errors.735c751479-678.avif"}],webp:[{width:400,url:"/images/generated/656903ddd887f1c1fd2b49ee_using-TypeScript-to-reduce-coding-errors.735c751479-400.webp"},{width:678,url:"/images/generated/656903ddd887f1c1fd2b49ee_using-TypeScript-to-reduce-coding-errors.735c751479-678.webp"}]},"/images/blog/how-to-prevent-typeerror-cannot-read-property-map-of-undefined/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.png":{width:2025,height:1164,isAnimated:!1,fallbackUrl:"/images/blog/how-to-prevent-typeerror-cannot-read-property-map-of-undefined/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.png",avif:[{width:400,url:"/images/generated/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.d1586d28aa-400.avif"},{width:800,url:"/images/generated/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.d1586d28aa-800.avif"},{width:1200,url:"/images/generated/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.d1586d28aa-1200.avif"},{width:1600,url:"/images/generated/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.d1586d28aa-1600.avif"},{width:2e3,url:"/images/generated/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.d1586d28aa-2000.avif"},{width:2025,url:"/images/generated/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.d1586d28aa-2025.avif"}],webp:[{width:400,url:"/images/generated/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.d1586d28aa-400.webp"},{width:800,url:"/images/generated/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.d1586d28aa-800.webp"},{width:1200,url:"/images/generated/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.d1586d28aa-1200.webp"},{width:1600,url:"/images/generated/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.d1586d28aa-1600.webp"},{width:2e3,url:"/images/generated/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.d1586d28aa-2000.webp"},{width:2025,url:"/images/generated/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.d1586d28aa-2025.webp"}]},"/images/blog/how-to-set-up-visual-regression-testing/6569056a522396bb11419376_setting-up-snapshot-tests.png":{width:266,height:663,isAnimated:!1,fallbackUrl:"/images/blog/how-to-set-up-visual-regression-testing/6569056a522396bb11419376_setting-up-snapshot-tests.png",avif:[{width:266,url:"/images/generated/6569056a522396bb11419376_setting-up-snapshot-tests.c041d39d18-266.avif"}],webp:[{width:266,url:"/images/generated/6569056a522396bb11419376_setting-up-snapshot-tests.c041d39d18-266.webp"}]},"/images/blog/how-to-set-up-visual-regression-testing/656905e37bf3ee7f123c70de_testing-CI-workflow.png":{width:1999,height:1475,isAnimated:!1,fallbackUrl:"/images/blog/how-to-set-up-visual-regression-testing/656905e37bf3ee7f123c70de_testing-CI-workflow.png",avif:[{width:400,url:"/images/generated/656905e37bf3ee7f123c70de_testing-CI-workflow.7a4913828a-400.avif"},{width:800,url:"/images/generated/656905e37bf3ee7f123c70de_testing-CI-workflow.7a4913828a-800.avif"},{width:1200,url:"/images/generated/656905e37bf3ee7f123c70de_testing-CI-workflow.7a4913828a-1200.avif"},{width:1600,url:"/images/generated/656905e37bf3ee7f123c70de_testing-CI-workflow.7a4913828a-1600.avif"},{width:1999,url:"/images/generated/656905e37bf3ee7f123c70de_testing-CI-workflow.7a4913828a-1999.avif"}],webp:[{width:400,url:"/images/generated/656905e37bf3ee7f123c70de_testing-CI-workflow.7a4913828a-400.webp"},{width:800,url:"/images/generated/656905e37bf3ee7f123c70de_testing-CI-workflow.7a4913828a-800.webp"},{width:1200,url:"/images/generated/656905e37bf3ee7f123c70de_testing-CI-workflow.7a4913828a-1200.webp"},{width:1600,url:"/images/generated/656905e37bf3ee7f123c70de_testing-CI-workflow.7a4913828a-1600.webp"},{width:1999,url:"/images/generated/656905e37bf3ee7f123c70de_testing-CI-workflow.7a4913828a-1999.webp"}]},"/images/blog/how-to-set-up-visual-regression-testing/656905e39c90ec66afec51fe_writing-and-running-snapsh
29ot-tests.png":{width:590,height:390,isAnimated:!1,fallbackUrl:"/images/blog/how-to-set-up-visual-regression-testing/656905e39c90ec66afec51fe_writing-and-running-snapshot-tests.png",avif:[{width:400,url:"/images/generated/656905e39c90ec66afec51fe_writing-and-running-snapshot-tests.2b87d635dd-400.avif"},{width:590,url:"/images/generated/656905e39c90ec66afec51fe_writing-and-running-snapshot-tests.2b87d635dd-590.avif"}],webp:[{width:400,url:"/images/generated/656905e39c90ec66afec51fe_writing-and-running-snapshot-tests.2b87d635dd-400.webp"},{width:590,url:"/images/generated/656905e39c90ec66afec51fe_writing-and-running-snapshot-tests.2b87d635dd-590.webp"}]},"/images/blog/how-to-set-up-visual-regression-testing/65690607f0b1d59da905d6e6_example-of-failing-snapshot-test.png":{width:1999,height:1387,isAnimated:!1,fallbackUrl:"/images/blog/how-to-set-up-visual-regression-testing/65690607f0b1d59da905d6e6_example-of-failing-snapshot-test.png",avif:[{width:400,url:"/images/generated/65690607f0b1d59da905d6e6_example-of-failing-snapshot-test.bfae4bb7f5-400.avif"},{width:800,url:"/images/generated/65690607f0b1d59da905d6e6_example-of-failing-snapshot-test.bfae4bb7f5-800.avif"},{width:1200,url:"/images/generated/65690607f0b1d59da905d6e6_example-of-failing-snapshot-test.bfae4bb7f5-1200.avif"},{width:1600,url:"/images/generated/65690607f0b1d59da905d6e6_example-of-failing-snapshot-test.bfae4bb7f5-1600.avif"},{width:1999,url:"/images/generated/65690607f0b1d59da905d6e6_example-of-failing-snapshot-test.bfae4bb7f5-1999.avif"}],webp:[{width:400,url:"/images/generated/65690607f0b1d59da905d6e6_example-of-failing-snapshot-test.bfae4bb7f5-400.webp"},{width:800,url:"/images/generated/65690607f0b1d59da905d6e6_example-of-failing-snapshot-test.bfae4bb7f5-800.webp"},{width:1200,url:"/images/generated/65690607f0b1d59da905d6e6_example-of-failing-snapshot-test.bfae4bb7f5-1200.webp"},{width:1600,url:"/images/generated/65690607f0b1d59da905d6e6_example-of-failing-snapshot-test.bfae4bb7f5-1600.webp"},{width:1999,url:"/images/generated/65690607f0b1d59da905d6e6_example-of-failing-snapshot-test.bfae4bb7f5-1999.webp"}]},"/images/blog/how-to-set-up-visual-regression-testing/656906794908530b2b34919a_visual-regression-testing-examples.png":{width:1999,height:664,isAnimated:!1,fallbackUrl:"/images/blog/how-to-set-up-visual-regression-testing/656906794908530b2b34919a_visual-regression-testing-examples.png",avif:[{width:400,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-400.avif"},{width:800,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-800.avif"},{width:1200,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-1200.avif"},{width:1600,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-1600.avif"},{width:1999,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-1999.avif"}],webp:[{width:400,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-400.webp"},{width:800,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-800.webp"},{width:1200,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-1200.webp"},{width:1600,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-1600.webp"},{width:1999,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-1999.webp"}]},"/images/blog/how-to-set-up-visual-regression-testing/6569067962fb5eadd033c67f_example-of-screen-test-with-the-yarn-test-visual-command.png":{width:1558,height:1138,isAnimated:!1,fallbackUrl:"/images/blog/how-to-set-up-visual-regression-testing/6569067962fb5eadd033c67f_example-of-screen-test-with-the-yarn-test-visual-command.png",avif:[{width:400,url:"/images/generated/6569067962fb5eadd033c67f_example-of-screen-test-with-the-yarn-test-visual-command.770210f5d5-400.avif"}
29,{width:800,url:"/images/generated/6569067962fb5eadd033c67f_example-of-screen-test-with-the-yarn-test-visual-command.770210f5d5-800.avif"},{width:1200,url:"/images/generated/6569067962fb5eadd033c67f_example-of-screen-test-with-the-yarn-test-visual-command.770210f5d5-1200.avif"},{width:1558,url:"/images/generated/6569067962fb5eadd033c67f_example-of-screen-test-with-the-yarn-test-visual-command.770210f5d5-1558.avif"}],webp:[{width:400,url:"/images/generated/6569067962fb5eadd033c67f_example-of-screen-test-with-the-yarn-test-visual-command.770210f5d5-400.webp"},{width:800,url:"/images/generated/6569067962fb5eadd033c67f_example-of-screen-test-with-the-yarn-test-visual-command.770210f5d5-800.webp"},{width:1200,url:"/images/generated/6569067962fb5eadd033c67f_example-of-screen-test-with-the-yarn-test-visual-command.770210f5d5-1200.webp"},{width:1558,url:"/images/generated/6569067962fb5eadd033c67f_example-of-screen-test-with-the-yarn-test-visual-command.770210f5d5-1558.webp"}]},"/images/blog/how-to-set-up-visual-regression-testing/6569067971ee05239185da13_example-of-failing-screen-test.png":{width:1999,height:1004,isAnimated:!1,fallbackUrl:"/images/blog/how-to-set-up-visual-regression-testing/6569067971ee05239185da13_example-of-failing-screen-test.png",avif:[{width:400,url:"/images/generated/6569067971ee05239185da13_example-of-failing-screen-test.901d1d691a-400.avif"},{width:800,url:"/images/generated/6569067971ee05239185da13_example-of-failing-screen-test.901d1d691a-800.avif"},{width:1200,url:"/images/generated/6569067971ee05239185da13_example-of-failing-screen-test.901d1d691a-1200.avif"},{width:1600,url:"/images/generated/6569067971ee05239185da13_example-of-failing-screen-test.901d1d691a-1600.avif"},{width:1999,url:"/images/generated/6569067971ee05239185da13_example-of-failing-screen-test.901d1d691a-1999.avif"}],webp:[{width:400,url:"/images/generated/6569067971ee05239185da13_example-of-failing-screen-test.901d1d691a-400.webp"},{width:800,url:"/images/generated/6569067971ee05239185da13_example-of-failing-screen-test.901d1d691a-800.webp"},{width:1200,url:"/images/generated/6569067971ee05239185da13_example-of-failing-screen-test.901d1d691a-1200.webp"},{width:1600,url:"/images/generated/6569067971ee05239185da13_example-of-failing-screen-test.901d1d691a-1600.webp"},{width:1999,url:"/images/generated/6569067971ee05239185da13_example-of-failing-screen-test.901d1d691a-1999.webp"}]},"/images/blog/how-to-set-up-visual-regression-testing/65690679f0b1d59da906291f_testing-a-workflow.png":{width:1999,height:1114,isAnimated:!1,fallbackUrl:"/images/blog/how-to-set-up-visual-regression-testing/65690679f0b1d59da906291f_testing-a-workflow.png",avif:[{width:400,url:"/images/generated/65690679f0b1d59da906291f_testing-a-workflow.a60ada8fff-400.avif"},{width:800,url:"/images/generated/65690679f0b1d59da906291f_testing-a-workflow.a60ada8fff-800.avif"},{width:1200,url:"/images/generated/65690679f0b1d59da906291f_testing-a-workflow.a60ada8fff-1200.avif"},{width:1600,url:"/images/generated/65690679f0b1d59da906291f_testing-a-workflow.a60ada8fff-1600.avif"},{width:1999,url:"/images/generated/65690679f0b1d59da906291f_testing-a-workflow.a60ada8fff-1999.avif"}],webp:[{width:400,url:"/images/generated/65690679f0b1d59da906291f_testing-a-workflow.a60ada8fff-400.webp"},{width:800,url:"/images/generated/65690679f0b1d59da906291f_testing-a-workflow.a60ada8fff-800.webp"},{width:1200,url:"/images/generated/65690679f0b1d59da906291f_testing-a-workflow.a60ada8fff-1200.webp"},{width:1600,url:"/images/generated/65690679f0b1d59da906291f_testing-a-workflow.a60ada8fff-1600.webp"},{width:1999,url:"/images/generated/65690679f0b1d59da906291f_testing-a-workflow.a60ada8fff-1999.webp"}]},"/images/blog/how-to-set-up-visual-regression-testing/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.png":{width:4317,height:2484,isAnimated:!1,fallbackUrl:"/images/blog/how-to-set-up-visual-regression-testing/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.png",avif:[{width:400,url:"/images/generated/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.7bce3ec5c3-400.avif"},{width:800,url:"/images/generated/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.7bce3ec5c3-800.avif"},{width:1200,url:"/images/generated/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.7bce3ec5c3-1200.avif"},{width:1600,url:"/images/generated/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.7bce3ec5c3-1600.avif"},{width:2e3,url:"/images/generated/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.7bce3ec5c3-2000.avif"},{width:4317,url:"/images/generated/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.7bce3ec5c3-4317.avif"}],webp:[{width:400,url:"/images/generated/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.7bce3ec5c3-400.webp"},{width:800,url:"/images/generated/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.7bce3ec5c3-800.webp"},{width:1200,url:"/images/generated/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.7bce3ec5c3-1200.webp"},{width:1600,url:"/images/generated/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.7bce3ec5c3-1600.webp"},{width:2e3,url:"/images/generated/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.7bce3ec5c3-2000.webp"},{width:4317,url:"/images/generated/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.7bce3ec5c3-4317.webp"}]},"/images/blog/how-to-use-jest-spyon/656907b90c4f529ecded0cfa_popularity-of-Jest-in-2021.jpeg":{width:650,height:455,isAnimated:!1,fallbackUrl:"/images/blog/how-to-use-jest-spyon/656907b90c4f529ecded0cfa_popularity-of-Jest-in-2021.jpeg",avif:[{width:400,url:"/images/generated/656907b90c4f529ecded0cfa_popularity-of-Jest-in-2021.6ffa458049-400.avif"}
29,{width:650,url:"/images/generated/656907b90c4f529ecded0cfa_popularity-of-Jest-in-2021.6ffa458049-650.avif"}],webp:[{width:400,url:"/images/generated/656907b90c4f529ecded0cfa_popularity-of-Jest-in-2021.6ffa458049-400.webp"},{width:650,url:"/images/generated/656907b90c4f529ecded0cfa_popularity-of-Jest-in-2021.6ffa458049-650.webp"}]},"/images/blog/how-to-use-jest-spyon/656907b9b19545b9d1dc41b8_React.js-app-used-to-guess-nationalities-of-a-given-name.gif":{width:1920,height:1138,isAnimated:!0,fallbackUrl:"/images/blog/how-to-use-jest-spyon/656907b9b19545b9d1dc41b8_React.js-app-used-to-guess-nationalities-of-a-given-name.gif",avif:[],webp:[{width:1920,url:"/images/generated/656907b9b19545b9d1dc41b8_React.js-app-used-to-guess-nationalities-of-a-given-name.e2064f6e36.webp"}]},"/images/blog/how-to-use-jest-spyon/65690814a090da6e57b5558d_testing-the-form-render.jpeg":{width:650,height:250,isAnimated:!1,fallbackUrl:"/images/blog/how-to-use-jest-spyon/65690814a090da6e57b5558d_testing-the-form-render.jpeg",avif:[{width:400,url:"/images/generated/65690814a090da6e57b5558d_testing-the-form-render.1067fd4674-400.avif"},{width:650,url:"/images/generated/65690814a090da6e57b5558d_testing-the-form-render.1067fd4674-650.avif"}],webp:[{width:400,url:"/images/generated/65690814a090da6e57b5558d_testing-the-form-render.1067fd4674-400.webp"},{width:650,url:"/images/generated/65690814a090da6e57b5558d_testing-the-form-render.1067fd4674-650.webp"}]},"/images/blog/how-to-use-jest-spyon/656908eb0c4f529ecdeddd79_using-jest-spyOn-with-console.log-to-spy-only.jpeg":{width:650,height:301,isAnimated:!1,fallbackUrl:"/images/blog/how-to-use-jest-spyon/656908eb0c4f529ecdeddd79_using-jest-spyOn-with-console.log-to-spy-only.jpeg",avif:[{width:400,url:"/images/generated/656908eb0c4f529ecdeddd79_using-jest-spyOn-with-console.log-to-spy-only.81255b3cb0-400.avif"},{width:650,url:"/images/generated/656908eb0c4f529ecdeddd79_using-jest-spyOn-with-console.log-to-spy-only.81255b3cb0-650.avif"}],webp:[{width:400,url:"/images/generated/656908eb0c4f529ecdeddd79_using-jest-spyOn-with-console.log-to-spy-only.81255b3cb0-400.webp"},{width:650,url:"/images/generated/656908eb0c4f529ecdeddd79_using-jest-spyOn-with-console.log-to-spy-only.81255b3cb0-650.webp"}]},"/images/blog/how-to-use-jest-spyon/656e25d938e7a058350b87d0_how-to-use-jest-spyon.webp":{width:2305,height:1324,isAnimated:!1,fallbackUrl:"/images/blog/how-to-use-jest-spyon/656e25d938e7a058350b87d0_how-to-use-jest-spyon.webp",avif:[{width:400,url:"/images/generated/656e25d938e7a058350b87d0_how-to-use-jest-spyon.2ddc8536a5-400.avif"},{width:800,url:"/images/generated/656e25d938e7a058350b87d0_how-to-use-jest-spyon.2ddc8536a5-800.avif"},{width:1200,url:"/images/generated/656e25d938e7a058350b87d0_how-to-use-jest-spyon.2ddc8536a5-1200.avif"},{width:1600,url:"/images/generated/656e25d938e7a058350b87d0_how-to-use-jest-spyon.2ddc8536a5-1600.avif"},{width:2e3,url:"/images/generated/656e25d938e7a058350b87d0_how-to-use-jest-spyon.2ddc8536a5-2000.avif"},{width:2305,url:"/images/generated/656e25d938e7a058350b87d0_how-to-use-jest-spyon.2ddc8536a5-2305.avif"}],webp:[{width:400,url:"/images/generated/656e25d938e7a058350b87d0_how-to-use-jest-spyon.2ddc8536a5-400.webp"},{width:800,url:"/images/generated/656e25d938e7a058350b87d0_how-to-use-jest-spyon.2ddc8536a5-800.webp"},{width:1200,url:"/images/generated/656e25d938e7a058350b87d0_how-to-use-jest-spyon.2ddc8536a5-1200.webp"},{width:1600,url:"/images/generated/656e25d938e7a058350b87d0_how-to-use-jest-spyon.2ddc8536a5-1600.webp"},{width:2e3,url:"/images/generated/656e25d938e7a058350b87d0_how-to-use-jest-spyon.2ddc8536a5-2000.webp"},{width:2305,url:"/images/generated/656e25d938e7a058350b87d0_how-to-use-jest-spyon.2ddc8536a5-2305.webp"}]},"/images/blog/how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide/65690a4aceeb3b440ac03ceb_react-testing-library-waitfor-example-app.gif":{width:2048,height:1518,isAnimated:!0,fallbackUrl:"/images/blog/how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide/65690a4aceeb3b440ac03ceb_react-testing-library-waitfor-example-app.gif",avif:[],webp:[{width:2048,url:"/images/generated/65690a4aceeb3b440ac03ceb_react-testing-library-waitfor-example-app.58ebf0231a.webp"}]},"/images/blog/how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide/656a25293744023d726dd7c0_how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.png":{width:1882,height:1080,isAnimated:!1,fallbackUrl:"/images/blog/how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide/656a25293744023d726dd7c0_how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.png",avif:[{width:400,url:"/images/generated/656a25293744023d726dd7c0_how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.f0824cc094-400.avif"},{width:800,url:"/images/generated/656a25293744023d726dd7c0_how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.f0824cc094-800.avif"},{width:1200,url:"/images/generated/656a25293744023d726dd7c0_how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.f0824cc094-1200.avif"},{width:1600,url:"/images/generated/656a25293744023d726dd7c0_how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.f0824cc094-1600.avif"},{width:1882,url:"/images/generated/656a25293744023d726dd7c0_how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.f0824cc094-1882.avif"}],webp:[{width:400,url:"/images/generated/656a25293744023d726dd7c0_how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.f0824cc094-400.webp"},{width:800,url:"/images/generated/656a25293744023d726dd7c0_how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.f0824cc094-800.webp"},{width:1200,url:"/images/generated/656a25293744023d726dd7c0_how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.f0824cc094-1200.webp"},{width:1600,url:"/images/generated/656a25293744023d726dd7c0_how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.f0824cc094-1600.webp"},{width:1882,url:"/images/generated/656a25293744023d726dd7c0_how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.f0824cc094-1882.webp"}]},"/images/blog/is-selenium-worth-it/65690bfe0c4f529ecdeff936_software-testing-pyramid.jpeg":{width:650,height:435,isAnimated:!1,fallbackUrl:"/images/blog/is-selenium-worth-it/65690bfe0c4f529ecdeff936_software-testing-pyramid.jpeg",avif:[{width:400,url:"/images/generated/65690bfe0c4f529ecdeff936_software-testing-pyramid.c67dea24cb-400.avif"}
29,{width:650,url:"/images/generated/65690bfe0c4f529ecdeff936_software-testing-pyramid.c67dea24cb-650.avif"}],webp:[{width:400,url:"/images/generated/65690bfe0c4f529ecdeff936_software-testing-pyramid.c67dea24cb-400.webp"},{width:650,url:"/images/generated/65690bfe0c4f529ecdeff936_software-testing-pyramid.c67dea24cb-650.webp"}]},"/images/blog/is-selenium-worth-it/656a184be362241a18e5fa84_is-selenium-worth-it.png":{width:1882,height:1082,isAnimated:!1,fallbackUrl:"/images/blog/is-selenium-worth-it/656a184be362241a18e5fa84_is-selenium-worth-it.png",avif:[{width:400,url:"/images/generated/656a184be362241a18e5fa84_is-selenium-worth-it.59608058db-400.avif"},{width:800,url:"/images/generated/656a184be362241a18e5fa84_is-selenium-worth-it.59608058db-800.avif"},{width:1200,url:"/images/generated/656a184be362241a18e5fa84_is-selenium-worth-it.59608058db-1200.avif"},{width:1600,url:"/images/generated/656a184be362241a18e5fa84_is-selenium-worth-it.59608058db-1600.avif"},{width:1882,url:"/images/generated/656a184be362241a18e5fa84_is-selenium-worth-it.59608058db-1882.avif"}],webp:[{width:400,url:"/images/generated/656a184be362241a18e5fa84_is-selenium-worth-it.59608058db-400.webp"},{width:800,url:"/images/generated/656a184be362241a18e5fa84_is-selenium-worth-it.59608058db-800.webp"},{width:1200,url:"/images/generated/656a184be362241a18e5fa84_is-selenium-worth-it.59608058db-1200.webp"},{width:1600,url:"/images/generated/656a184be362241a18e5fa84_is-selenium-worth-it.59608058db-1600.webp"},{width:1882,url:"/images/generated/656a184be362241a18e5fa84_is-selenium-worth-it.59608058db-1882.webp"}]},"/images/blog/javascript-reference-errors/65690cb409fa6cc6639ce639_javascript-reference-errors.webp":{width:1200,height:630,isAnimated:!1,fallbackUrl:"/images/blog/javascript-reference-errors/65690cb409fa6cc6639ce639_javascript-reference-errors.webp",avif:[{width:400,url:"/images/generated/65690cb409fa6cc6639ce639_javascript-reference-errors.abe2f48828-400.avif"},{width:800,url:"/images/generated/65690cb409fa6cc6639ce639_javascript-reference-errors.abe2f48828-800.avif"},{width:1200,url:"/images/generated/65690cb409fa6cc6639ce639_javascript-reference-errors.abe2f48828-1200.avif"}],webp:[{width:400,url:"/images/generated/65690cb409fa6cc6639ce639_javascript-reference-errors.abe2f48828-400.webp"},{width:800,url:"/images/generated/65690cb409fa6cc6639ce639_javascript-reference-errors.abe2f48828-800.webp"},{width:1200,url:"/images/generated/65690cb409fa6cc6639ce639_javascript-reference-errors.abe2f48828-1200.webp"}]},"/images/blog/javascript-regressions-and-how-to-prevent-them/656f161ba3e74485ffc5236f_javascript-regressions-and-how-to-prevent-them.png":{width:1993,height:1141,isAnimated:!1,fallbackUrl:"/images/blog/javascript-regressions-and-how-to-prevent-them/656f161ba3e74485ffc5236f_javascript-regressions-and-how-to-prevent-them.png",avif:[{width:400,url:"/images/generated/656f161ba3e74485ffc5236f_javascript-regressions-and-how-to-prevent-them.630c287bb5-400.avif"},{width:800,url:"/images/generated/656f161ba3e74485ffc5236f_javascript-regressions-and-how-to-prevent-them.630c287bb5-800.avif"},{width:1200,url:"/images/generated/656f161ba3e74485ffc5236f_javascript-regressions-and-how-to-prevent-them.630c287bb5-1200.avif"},{width:1600,url:"/images/generated/656f161ba3e74485ffc5236f_javascript-regressions-and-how-to-prevent-them.630c287bb5-1600.avif"},{width:1993,url:"/images/generated/656f161ba3e74485ffc5236f_javascript-regressions-and-how-to-prevent-them.630c287bb5-1993.avif"}],webp:[{width:400,url:"/images/generated/656f161ba3e74485ffc5236f_javascript-regressions-and-how-to-prevent-them.630c287bb5-400.webp"},{width:800,url:"/images/generated/656f161ba3e74485ffc5236f_javascript-regressions-and-how-to-prevent-them.630c287bb5-800.webp"},{width:1200,url:"/images/generated/656f161ba3e74485ffc5236f_javascript-regressions-and-how-to-prevent-them.630c287bb5-1200.webp"},{width:1600,url:"/images/generated/656f161ba3e74485ffc5236f_javascript-regressions-and-how-to-prevent-them.630c287bb5-1600.webp"},{width:1993,url:"/images/generated/656f161ba3e74485ffc5236f_javascript-regressions-and-how-to-prevent-them.630c287bb5-1993.webp"}]},"/images/blog/javascript-try-catch-guide/65690f3f9c90ec66aff3064a_javascript-try-catch-guide.webp":{width:1200,height:630,isAnimated:!1,fallbackUrl:"/images/blog/javascript-try-catch-guide/65690f3f9c90ec66aff3064a_javascript-try-catch-guide.webp",avif:[{width:400,url:"/images/generated/65690f3f9c90ec66aff3064a_javascript-try-catch-guide.a2e6ad1f22-400.avif"},{width:800,url:"/images/generated/65690f3f9c90ec66aff3064a_javascript-try-catch-guide.a2e6ad1f22-800.avif"},{width:1200,url:"/images/generated/65690f3f9c90ec66aff3064a_javascript-try-catch-guide.a2e6ad1f22-1200.avif"}],webp:[{width:400,url:"/images/generated/65690f3f9c90ec66aff3064a_javascript-try-catch-guide.a2e6ad1f22-400.webp"},{width:800,url:"/images/generated/65690f3f9c90ec66aff3064a_javascript-try-catch-guide.a2e6ad1f22-800.webp"},{width:1200,url:"/images/generated/65690f3f9c90ec66aff3064a_javascript-try-catch-guide.a2e6ad1f22-1200.webp"}]},"/images/blog/javascript-typeerrors-and-techniques-to-prevent-them/656911fc1db4feff57b8dbcd_IDE-showing-a-TypeError.png":{width:678,he
29ight:138,isAnimated:!1,fallbackUrl:"/images/blog/javascript-typeerrors-and-techniques-to-prevent-them/656911fc1db4feff57b8dbcd_IDE-showing-a-TypeError.png",avif:[{width:400,url:"/images/generated/656911fc1db4feff57b8dbcd_IDE-showing-a-TypeError.2991f45bf9-400.avif"},{width:678,url:"/images/generated/656911fc1db4feff57b8dbcd_IDE-showing-a-TypeError.2991f45bf9-678.avif"}],webp:[{width:400,url:"/images/generated/656911fc1db4feff57b8dbcd_IDE-showing-a-TypeError.2991f45bf9-400.webp"},{width:678,url:"/images/generated/656911fc1db4feff57b8dbcd_IDE-showing-a-TypeError.2991f45bf9-678.webp"}]},"/images/blog/javascript-typeerrors-and-techniques-to-prevent-them/656e241c74f1629eb3f38484_javascript-typeerrors-and-techniques-to-prevent-them.png":{width:1880,height:1080,isAnimated:!1,fallbackUrl:"/images/blog/javascript-typeerrors-and-techniques-to-prevent-them/656e241c74f1629eb3f38484_javascript-typeerrors-and-techniques-to-prevent-them.png",avif:[{width:400,url:"/images/generated/656e241c74f1629eb3f38484_javascript-typeerrors-and-techniques-to-prevent-them.9951e93870-400.avif"},{width:800,url:"/images/generated/656e241c74f1629eb3f38484_javascript-typeerrors-and-techniques-to-prevent-them.9951e93870-800.avif"},{width:1200,url:"/images/generated/656e241c74f1629eb3f38484_javascript-typeerrors-and-techniques-to-prevent-them.9951e93870-1200.avif"},{width:1600,url:"/images/generated/656e241c74f1629eb3f38484_javascript-typeerrors-and-techniques-to-prevent-them.9951e93870-1600.avif"},{width:1880,url:"/images/generated/656e241c74f1629eb3f38484_javascript-typeerrors-and-techniques-to-prevent-them.9951e93870-1880.avif"}],webp:[{width:400,url:"/images/generated/656e241c74f1629eb3f38484_javascript-typeerrors-and-techniques-to-prevent-them.9951e93870-400.webp"},{width:800,url:"/images/generated/656e241c74f1629eb3f38484_javascript-typeerrors-and-techniques-to-prevent-them.9951e93870-800.webp"},{width:1200,url:"/images/generated/656e241c74f1629eb3f38484_javascript-typeerrors-and-techniques-to-prevent-them.9951e93870-1200.webp"},{width:1600,url:"/images/generated/656e241c74f1629eb3f38484_javascript-typeerrors-and-techniques-to-prevent-them.9951e93870-1600.webp"},{width:1880,url:"/images/generated/656e241c74f1629eb3f38484_javascript-typeerrors-and-techniques-to-prevent-them.9951e93870-1880.webp"}]},"/images/blog/javascript-ui-testing-best-practices/656a54568f587f08f9a9b29f_javascript-ui-testing-best-practices.png":{width:1932,height:1106,isAnimated:!1,fallbackUrl:"/images/blog/javascript-ui-testing-best-practices/656a54568f587f08f9a9b29f_javascript-ui-testing-best-practices.png",avif:[{width:400,url:"/images/generated/656a54568f587f08f9a9b29f_javascript-ui-testing-best-practices.f73b23a5b1-400.avif"},{width:800,url:"/images/generated/656a54568f587f08f9a9b29f_javascript-ui-testing-best-practices.f73b23a5b1-800.avif"},{width:1200,url:"/images/generated/656a54568f587f08f9a9b29f_javascript-ui-testing-best-practices.f73b23a5b1-1200.avif"},{width:1600,url:"/images/generated/656a54568f587f08f9a9b29f_javascript-ui-testing-best-practices.f73b23a5b1-1600.avif"},{width:1932,url:"/images/generated/656a54568f587f08f9a9b29f_javascript-ui-testing-best-practices.f73b23a5b1-1932.avif"}],webp:[{width:400,url:"/images/generated/656a54568f587f08f9a9b29f_javascript-ui-testing-best-practices.f73b23a5b1-400.webp"},{width:800,url:"/images/generated/656a54568f587f08f9a9b29f_javascript-ui-testing-best-practices.f73b23a5b1-800.webp"},{width:1200,url:"/images/generated/656a54568f587f08f9a9b29f_javascript-ui-testing-best-practices.f73b23a5b1-1200.webp"},{width:1600,url:"/images/generated/656a54568f587f08f9a9b29f_javascript-ui-testing-best-practices.f73b23a5b1-1600.webp"},{width:1932,url:"/images/generated/656a54568f587f08f9a9b29f_javascript-ui-testing-best-practices.f73b23a5b1-1932.webp"}]},"/images/blog/javascript-unshift-complete-guide/656915dfd8fc9f2b38aefa44_javascript-unshift-complete-guide.webp":{width:1200,height:630,isAnimated:!1,fallbackUrl:"/images/blog/javascript-unshift-complete-guide/656915dfd8fc9f2b38aefa44_javascript-unshift-complete-guide.webp",avif:[{width:400,url:"/images/generated/656915dfd8fc9f2b38aefa44_javascript-unshift-complete-guide.274bb9cd24-400.avif"},{width:800,url:"/images/generated/656915dfd8fc9f2b38aefa44_javascript-unshift-complete-guide.274bb9cd24-800.avif"},{width:1200,url:"/images/generated/656915dfd8fc9f2b38aefa44_javascript-unshift-complete-guide.274bb9cd24-1200.avif"}],webp:[{width:400,url:"/images/generated/656915dfd8fc9f2b38aefa44_javascript-unshift-complete-guide.274bb9cd24-400.webp"},{width:800,url:"/images/generated/656915dfd8fc9f2b38aefa44_javascript-unshift-complete-guide.274bb9cd24-800.webp"},{width:1200,url:"/images/generated/656915dfd8fc9f2b38aefa44_javascript-unshift-complete-guide.274bb9cd24-1200.webp"}]},"/images/blog/jest-typescript/6568e468e6e36b1c79c4b9c0_the-output-of-the-quotes-api-built-using-expressjs-with-typescript.jpeg":{width:1160,height:572,isAnimated:!1,fallbackUrl:"/images/blog/jest-typescript/6568e468e6e36b1c79c4b9c0_the-output-of-the-quotes-api-built-using-expressjs-with-typescript.jpeg",avif:[{width:400,url:"/images/generated/6568e468e6e36b1c79c4b9c0_the-output-of-the-quotes-api-built-using-expressjs-with-typescript.d1563b85c4-400.avif"},{width:800,url:"/images/generated/6568e468e6e36b1c79c4b9c0_the-output-of-the-quotes-api-built-using-expressjs-with-typescript.d1563b85c4-800.avif"},{width:1160,url:"/images/generated/6568e468e6e36b1c79c4b9c0_the-output-of-the-quotes-api-built-using-expressjs-with-typescript.d1563b85c4-1160.avif"}],webp:[{width:400,url:"/images/generated/6568e468e6e36b1c79c4b9c0_the-output-of-the-quotes-api-built-using-expressjs-with-typescript.d1563b85c4-400.webp"},{width:800,url:"/images/generated/6568e468e6e36b1c79c4b9c0_the-output-of-the-quotes-api-built-using-expressjs-with-typescript.d1563b85c4-800.webp"},{width:1160,url:"/images/generated/6568e468e6e36b1c79c4b9c0_the-output-of-the-quotes-api-built-using-expressjs-with-typescript.d1563b85c4-1160.webp"}]},"/images/blog/jest-typescript/6568e469b382f40c957669a1_application-structure-for-the-quotes-api-built-using-expressjs-with-typescript.jpeg":{width:770,height:946,isAnimated:!1,fallbackUrl:"/images/blog/jest-typescript/6568e469b382f40c957669a1_application-structure-for-the-quotes-api-built-using-expressjs-with-typescript.jpeg",avif:[{width:400,url:"/images/generated/6568e469b382f40c957669a1_application-structure-for-the-quotes-api-built-using-expressjs-with-typescript.26a1960712-400.avif"},{width:770,url:"/images/generated/6568e469b382f40c957669a1_application-structure-for-the-quotes-api-built-using-expressjs-with-typescript.26a1960712-770.avif"}],webp:[{width:400,url:"/images/generated/6568e469b382f40c957669a1_application-structure-for-the-quotes-api-built-using-expressjs-with-typescript.26a1960712-400.webp"},{width:770,url:"/images/generated/6568e469b382f40c957669a1_application-structure-for-the-quotes-api-built-using-expressjs-with-typescript.26a1960712-770.webp"}]},"/images/blog/jest-typescript/6568e56835c91e047ebf1c2f_output-of-running-tests-for-the-quotes-controller-using-jest.jpeg":{width:1632,height:736,isAnimated:!1,fallbackUrl:"/images/blog/jest-typescript/6568e56835c91e047ebf1c2f_output-of-running-tests-for-the-quotes-controller-using-jest.jpeg",avif:[{width:400,url:"/images/generated/6568e56835c91e047ebf1c2f_output-of-running-tests-for-the-quotes-controller-using-jest.66d956cbab-400.avif"},{width:800,url:"/images/generated/6568e56835c91e047ebf1c2f_output-of-running-tests-for-the-quotes-controller-using-jest.66d956cbab-800.avif"},{width:1200,url:"/images/generated/6568e56835c91e047ebf1c2f_output-of-running-tests-for-the-quotes-controller-using-jest.66d956cbab-1200.avif"},{width:1600,url:"/images/generated/6568e56835c91e047ebf1c2f_output-of-running-tests-for-the-quotes-controller-using-jest.66d956cbab-1600.avif"},{width:1632,url:"/images/generated/6568e56835c91e047ebf1c2f_output-of-running-tests-for-the-quotes-controller-using-jest.66d956cbab-1632.avif"}],webp:[{width:400,url:"/images/generated/6568e56835c91e047ebf1c2f_output-of-running-tests-for-the-quotes-controller-using-jest.66d956cbab-400.webp"},{width:800,url:"/images/generated/6568e56835c91e047ebf1c2f_output-of-running-tests-for-the-quotes-controller-using-jest.66d956cbab-800.webp"},{width:1200,url:"/images/generated/6568e56835c91e047ebf1c2f_output-of-running-tests-for-the-quotes-controller-using-jest.66d956cbab-1200.webp"},{width:1600,url:"/images/generated/6568e56835c91e047ebf1c2f_output-of-running-tests-for-the-quotes-controller-using-jest.66d956cbab-1600.webp"},{width:1632,url:"/images/generated/6568e56835c91e047ebf1c2f_output-of-running-tests-for-the-quotes-controller-using-jest.66d956cbab-1632.webp"}]},"/images/blog/jest-typescript/6568e5b814b9409e63c45be7_showing-that-the-code-coverage-decreases-when-a-test-is-removed.jpeg":{width:1980,height:1008,isAnimated:!1,fallbackUrl:"/images/blog/jest-typescript/6568e5b814b9409e63c45be7_showing-that-the-code-coverage-decreases-when-a-test-is-removed.jpeg",avif:[{width:400,url:"/images/generated/6568e5b814b9409e63c45be7_showing-that-the-code-coverage-decreases-when-a-test-is-removed.d94179036a-400.avif"},{width:800,url:"/images/generated/6568e5b814b9409e63c45be7_showing-that-the-code-coverage-decreases-when-a-test-is-removed.d94179036a-800.avif"},{width:1200,url:"/images/generated/6568e5b814b9409e63c45be7_showing-that-the-code-coverage-decreases-when-a-test-is-removed.d94179036a-1200.avif"},{width:1600,url:"/images/generated/6568e5b814b9409e63c45be7_showing-that-the-code-coverage-decreases-when-a-test-is-removed.d94179036a-1600.avif"},{width:1980,url:"/images/generated/6568e5b814b9409e63c45be7_showing-that-the-code-coverage-decreases-when-a-test-is-removed.d94179036a-1980.avif"}],webp:[{width:400,url:"/images/generated/6568e5b814b9409e63c45be7_showing-that-the-code-coverage-decreases-when-a-test-is-removed.d94179036a-400.webp"},{width:800,url:"/images/generated/6568e5b814b9409e63c45be7_showing-that-the-code-coverage-decreases-when-a-test-is-removed.d94179036a-800.webp"},{width:1200,url:"/images/generated/6568e5b814b9409e63c45be7_showing-that-the-code-coverage-decreases-when-a-test-is-removed.d94179036a-1200.webp"},{width:1600,url:"/images/generated/6568e5b814b9409e63c45be7_showing-that-the-code-coverage-decreases-when-a-test-is-removed.d94179036a-1600.webp"},{width:1980,url:"/images/generated/6568e5b814b9409e63c45be7_showing-that-the-code-coverage-decreases-when-a-test-is-removed.d94179036a-1980.webp"}]},"/images/blog/jest-typescript/6568e5b85ed4aae6c64adc42_showing-test-code-coverage-running-tests-with-jest-for-the-typescript.jpeg":{width:1928,height:1004,isAnimated:!1,fallbackUrl:"/images/blog/jest-typescript/6568e5b85ed4aae6c64adc42_showing-test-code-coverage-running-tests-with-jest-for-the-typescript.jpeg",avif:[{width:400,url:"/images/generated/6568e5b85ed4aae6c64adc42_showing-test-code-coverage-running-tests-with-jest-for-the-typescript.4a32fa6f7e-400.avif"},{width:800,url:"/images/generated/6568e5b85ed4aae6c64adc42_showing-test-code-coverage-running-tests-with-jest-for-the-typescript.4a32fa6f7e-800.avif"},{width:1200,url:"/images/generated/6568e5b85ed4aae6c64adc42_showing-test-code-coverage-running-tests-with-jest-for-the-typescript.4a32fa6f7e-1200.avif"},{width:1600,url:"/images/generated/6568e5b85ed4aae6c64adc42_showing-test-code-coverage-running-tests-with-jest-for-the-typescript.4a32fa6f7e-1600.avif"},{width:1928,url:"/images/generated/6568e5b85ed4aae6c64adc42_showing-test-code-coverage-running-tests-with-jest-for-the-typescript.4a32fa6f7e-1928.avif"}],webp:[{width:400,url:"/images/generated/6568e5b85ed4aae6c64adc42_showing-test-code-coverage-running-tests-with-jest-for-the-typescript.4a32fa6f7e-400.webp"},{width:800,url:"/images/generated/6568e5b85ed4aae6c64adc42_showing-test-code-coverage-running-tests-with-jest-for-the-typescript.4a32fa6f7e-800.webp"},{width:1200,url:"/images/generated/6568e5b85ed4aae6c64adc42_showing-test-code-coverage-running-tests-with-jest-for-the-typescript.4a32fa6f7e-1200.webp"},{width:1600,url:"/images/generated/6568e5b85ed4aae6c64adc42_showing-test-code-coverage-running-tests-with-jest-for-the-typescript.4a32fa6f7e-1600.webp"},{width:1928,url:"/images/generated/6568e5b85ed4aae6c64adc42_showing-test-code-coverage-running-tests-with-jest-for-the-typescript.4a32fa6f7e-1928.webp"}]},"/images/blog/jest-typescript/6568e5b8c8469619c24d3e86_showing-code-coverage-from-the-html-file.jpeg":{width:1031,height:337,isAnimated:!1,fallbackUrl:"/images/blog/jest-typescript/6568e5b8c8469619c24d3e86_showing-code-coverage-from-the-html-file.jpeg",avif:[{width:400,url:"/images/generated/6568e5b8c8469619c24d3e86_showing-code-coverage-from-the-html-file.b6b40e3cdd-400.avif"},{width:800,url:"/images/generated/6568e5b8c8469619c24d3e86_showing-code-coverage-from-the-html-file.b6b40e3cdd-800.avif"},{width:1031,url:"/images/generated/6568e5b8c8469619c24d3e86_showing-code-coverage-from-the-html-file.b6b40e3cdd-1031.avif"}],webp:[{width:400,url:"/images/generated/6568e5b8c8469619c24d3e86_showing-code-coverage-from-the-html-file.b6b40e3cdd-400.webp"},{width:800,url:"/images/generated/6568e5b8c8469619c24d3e86_showing-code-coverage-from-the-html-file.b6b40e3cdd-800.webp"},{width:1031,url:"/images/generated/6568e5b8c8469619c24d3e86_showing-code-coverage-from-the-html-file.b6b40e3cdd-1031.webp"}]},"/images/blog/jest-typescript/6568e5b8fa163a58f103f0e7_output-of-running-tests-for-both-quotes-controller-and-quotes-service.jpeg":{width:1600,height:600,isAnimated:!1,fallbackUrl:"/images/blog/jest-typescript/6568e5b8fa163a58f103f0e7_output-of-running-tests-for-both-quotes-controller-and-quotes-service.jpeg",avif:[{width:400,url:"/images/generated/6568e5b8fa163a58f103f0e7_output-of-running-tests-for-both-quotes-controller-and-quotes-service.3f2401e72c-400.avif"},{width:800,url:"/images/generated/6568e5b8fa163a58f103f0e7_output-of-running-tests-for-both-quotes-controller-and-quotes-service.3f2401e72c-800.avif"},{width:1200,url:"/images/generated/6568e5b8fa163a58f103f0e7_output-of-running-tests-for-both-quotes-controller-and-quotes-service.3f2401e72c-1200.avif"},{width:1600,url:"/images/generated/6568e5b8fa163a58f103f0e7_output-of-running-tests-for-both-quotes-controller-and-quotes-service.3f2401e72c-1600.avif"}],webp:[{width:400,url:"/images/generated/6568e5b8fa163a58f103f0e7_output-of-running-tests-for-both-quotes-controller-and-quotes-service.3f2401e72c-400.webp"},{width:800,url:"/images/generated/6568e5b8fa163a58f103f0e7_output-of-running-tests-for-both-quotes-controller-and-quotes-service.3f2401e72c-800.webp"},{width:1200,url:"/images/generated/6568e5b8fa163a58f103f0e7_output-of-running-tests-for-both-quotes-controller-and-quotes-service.3f2401e72c-1200.webp"},{width:1600,url:"/images/generated/6568e5b8fa163a58f103f0e7_output-of-running-tests-for-both-quotes-controller-and-quotes-service.3f2401e72c-1600.webp"}]},"/images/blog/jest-typescript/656a1916ec3f99329fd2ef53_jest-typescript.png":{width:1882,height:1080,isAnimated:!1,fallbackUrl:"/images/blog/jest-typescript/656a1916ec3f99329fd2ef53_jest-typescript.png",avif:[{width:400,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-400.avif"},{width:800,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-800.avif"},{width:1200,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-1200.avif"},{width:1600,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-1600.avif"},{width:1882,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-1882.avif"}],webp:[{width:400,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-400.webp"},{width:800,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-800.webp"},{width:1200,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-1200.webp"},{width:1600,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-1600.webp"},{width:1882,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-1882.webp"}]},"/images/blog/lessons-from-a-decade/659033b24b3e0306b46556bb_Frame-10.png":{width:1792,height:1024,isAnimated:!1,fallbackUrl:"/images/blog/lessons-from-a-decade/659033b24b3e0306b46556bb_Frame-10.png",avif:[{width:400,url:"/images/generated/659033b24b3e0306b46556bb_Frame-10.760c1b0637-400.avif
29"},{width:800,url:"/images/generated/659033b24b3e0306b46556bb_Frame-10.760c1b0637-800.avif"},{width:1200,url:"/images/generated/659033b24b3e0306b46556bb_Frame-10.760c1b0637-1200.avif"},{width:1600,url:"/images/generated/659033b24b3e0306b46556bb_Frame-10.760c1b0637-1600.avif"},{width:1792,url:"/images/generated/659033b24b3e0306b46556bb_Frame-10.760c1b0637-1792.avif"}],webp:[{width:400,url:"/images/generated/659033b24b3e0306b46556bb_Frame-10.760c1b0637-400.webp"},{width:800,url:"/images/generated/659033b24b3e0306b46556bb_Frame-10.760c1b0637-800.webp"},{width:1200,url:"/images/generated/659033b24b3e0306b46556bb_Frame-10.760c1b0637-1200.webp"},{width:1600,url:"/images/generated/659033b24b3e0306b46556bb_Frame-10.760c1b0637-1600.webp"},{width:1792,url:"/images/generated/659033b24b3e0306b46556bb_Frame-10.760c1b0637-1792.webp"}]},"/images/blog/lessons-from-a-decade/67910626bcafbac4661ac0e7_65904cad11f274f3b2155cde_maintenance-cost.png":{width:1502,height:520,isAnimated:!1,fallbackUrl:"/images/blog/lessons-from-a-decade/67910626bcafbac4661ac0e7_65904cad11f274f3b2155cde_maintenance-cost.png",avif:[{width:400,url:"/images/generated/67910626bcafbac4661ac0e7_65904cad11f274f3b2155cde_maintenance-cost.08c344bd4f-400.avif"},{width:800,url:"/images/generated/67910626bcafbac4661ac0e7_65904cad11f274f3b2155cde_maintenance-cost.08c344bd4f-800.avif"},{width:1200,url:"/images/generated/67910626bcafbac4661ac0e7_65904cad11f274f3b2155cde_maintenance-cost.08c344bd4f-1200.avif"},{width:1502,url:"/images/generated/67910626bcafbac4661ac0e7_65904cad11f274f3b2155cde_maintenance-cost.08c344bd4f-1502.avif"}],webp:[{width:400,url:"/images/generated/67910626bcafbac4661ac0e7_65904cad11f274f3b2155cde_maintenance-cost.08c344bd4f-400.webp"},{width:800,url:"/images/generated/67910626bcafbac4661ac0e7_65904cad11f274f3b2155cde_maintenance-cost.08c344bd4f-800.webp"},{width:1200,url:"/images/generated/67910626bcafbac4661ac0e7_65904cad11f274f3b2155cde_maintenance-cost.08c344bd4f-1200.webp"},{width:1502,url:"/images/generated/67910626bcafbac4661ac0e7_65904cad11f274f3b2155cde_maintenance-cost.08c344bd4f-1502.webp"}]},"/images/blog/lessons-from-a-decade/67910626bcafbac4661ac0ea_659050e7720c2702e4af0800_pyramid.png":{width:416,height:274,isAnimated:!1,fallbackUrl:"/images/blog/lessons-from-a-decade/67910626bcafbac4661ac0ea_659050e7720c2702e4af0800_pyramid.png",avif:[{width:400,url:"/images/generated/67910626bcafbac4661ac0ea_659050e7720c2702e4af0800_pyramid.0fd7861c24-400.avif"},{width:416,url:"/images/generated/67910626bcafbac4661ac0ea_659050e7720c2702e4af0800_pyramid.0fd7861c24-416.avif"}],webp:[{width:416,url:"/images/generated/67910626bcafbac4661ac0ea_659050e7720c2702e4af0800_pyramid.0fd7861c24-416.webp"}]},"/images/blog/lessons-from-a-decade/67910626bcafbac4661ac0fc_6590494cdbb6f549d68735fe_graph-good-no-caption.png":{width:1398,height:648,isAnimated:!1,fallbackUrl:"/images/blog/lessons-from-a-decade/67910626bcafbac4661ac0fc_6590494cdbb6f549d68735fe_graph-good-no-caption.png",avif:[{width:400,url:"/images/generated/67910626bcafbac4661ac0fc_6590494cdbb6f549d68735fe_graph-good-no-caption.6907fdec92-400.avif"},{width:800,url:"/images/generated/67910626bcafbac4661ac0fc_6590494cdbb6f549d68735fe_graph-good-no-caption.6907fdec92-800.avif"},{width:1200,url:"/images/generated/67910626bcafbac4661ac0fc_6590494cdbb6f549d68735fe_graph-good-no-caption.6907fdec92-1200.avif"},{width:1398,url:"/images/generated/67910626bcafbac4661ac0fc_6590494cdbb6f549d68735fe_graph-good-no-caption.6907fdec92-1398.avif"}],webp:[{width:400,url:"/images/generated/67910626bcafbac4661ac0fc_6590494cdbb6f549d68735fe_graph-good-no-caption.6907fdec92-400.webp"},{width:800,url:"/images/generated/67910626bcafbac4661ac0fc_6590494cdbb6f549d68735fe_graph-good-no-caption.6907fdec92-800.webp"},{width:1200,url:"/images/generated/67910626bcafbac4661ac0fc_6590494cdbb6f549d68735fe_graph-good-no-caption.6907fdec92-1200.webp"},{width:1398,url:"/images/generated/67910626bcafbac4661ac0fc_6590494cdbb6f549d68735fe_graph-good-no-caption.6907fdec92-1398.webp"}]},"/images/blog/lessons-from-a-decade/67910626bcafbac4661ac0ff_659042d088fbae0751ed9c2f_graph.png":{width:1398,height:564,isAnimated:!1,fallbackUrl:"/images/blog/lessons-from-a-decade/67910626bcafbac4661ac0ff_659042d088fbae0751ed9c2f_graph.png",avif:[{width:400,url:"/images/generated/67910626bcafbac4661ac0ff_659042d088fbae0751ed9c2f_graph.9f2a58cd70-400.avif"},{width:800,url:"/images/generated/67910626bcafbac4661ac0ff_659042d088fbae0751ed9c2f_graph.9f2a58cd70-800.avif"},{width:1200,url:"/images/generated/67910626bcafbac4661ac0ff_659042d088fbae0751ed9c2f_graph.9f2a58cd70-1200.avif"},{width:1398,url:"/images/generated/67910626bcafbac4661ac0ff_659042d088fbae0751ed9c2f_graph.9f2a58cd70-1398.avif"}],webp:[{width:400,url:"/images/generated/67910626bcafbac4661ac0ff_659042d088fbae0751ed9c2f_graph.9f2a58cd70-400.webp"},{width:800,url:"/images/generated/67910626bcafbac4661ac0ff_659042d088fbae0751ed9c2f_graph.9f2a58cd70-800.webp"},{width:1200,url:"/images/generated/67910626bcafbac4661ac0ff_659042d088fbae0751ed9c2f_graph.9f2a58cd70-1200.webp"},{width:1398,url:"/images/generated/67910626bcafbac4661ac0ff_659042d088fbae0751ed9c2f_graph.9f2a58cd70-1398.webp"}]},"/images/blog/lessons-from-a-decade/67910626bcafbac4661ac102_659042ec582e5f01dc760e56_graph-bad.png":{width:1398,height:646,isAnimated:!1,fallbackUrl:"/images/blog/lessons-from-a-decade/67910626bcafbac4661ac102_659042ec582e5f01dc760e56_graph-bad.png",avif:[{width:400,url:"/images/generated/67910626bcafbac4661ac102_659042ec582e5f01dc760e56_graph-bad.c49001c8fb-400.avif"},{width:800,url:"/images/generated/67910626bcafbac4661ac102_659042ec582e5f01dc760e56_graph-bad.c49001c8fb-800.avif"},{width:1200,url:"/images/generated/67910626bcafbac4661ac102_659042ec582e5f01dc760e56_graph-bad.c49001c8fb-1200.avif"},{width:1398,url:"/images/generated/67910626bcafbac4661ac102_659042ec582e5f01dc760e56_graph-bad.c49001c8fb-1398.avif"}],webp:[{width:400,url:"/images/generated/67910626bcafbac4661ac102_659042ec582e5f01dc760e56_graph-bad.c49001c8fb-400.webp"},{width:800,url:"/images/generated/67910626bcafbac4661ac102_659042ec582e5f01dc760e56_graph-bad.c49001c8fb-800.webp"},{width:1200,url:"/images/generated/67910626bcafbac4661ac102_659042ec582e5f01dc760e56_graph-bad.c49001c8fb-1200.webp"},{width:1398,url:"/images/generated/67910626bcafbac4661ac102_659042ec582e5f01dc760e56_graph-bad.c49001c8fb-1398.webp"}]},"/images/blog/lessons-from-a-decade/67910626bcafbac4661ac10f_659042a47ab093299d225aa8_Untitled-201.png":{width:471,height:452,isAnimated:!1,fallbackUrl:"/images/blog/lessons-from-a-decade/67910626bcafbac4661ac10f_659042a47ab093299d225aa8_Untitled-201.png",avif:[{width:400,url:"/images/generated/67910626bcafbac4661ac10f_659042a47ab093299d225aa8_Untitled-201.db91d6c9b3-400.avif"},{width:471,url:"/images/generated/67910626bcafbac4661ac10f_659042a47ab093299d225aa8_Untitled-201.db91d6c9b3-471.avif"}],webp:[{width:400,url:"/images/generated/67910626bcafbac4661ac10f_659042a47ab093299d225aa8_Untitled-201.db91d6c9b3-400.webp"},{width:471,url:"/images/generated/67910626bcafbac4661ac10f_659042a47ab093299d225aa8_Untitled-201.db91d6c9b3-471.webp"}]},"/images/blog/let-users-write-tests-for-you/656916ea2967e28b96369f2d_let-users-write-tests-for-you.webp":{width:1200,height:630,isAnimated:!1,fallbackUrl:"/images/blog/let-users-write-tests-for-you/656916ea2967e28b96369f2d_let-users-write-tests-for-you.webp",avif:[{width:400,url:"/images/generated/656916ea2967e28b96369f2d_let-users-write-tests-for-you.4e1aa41ed4-400.avif"},{width:800,url:"/images/generated/656916ea2967e28b96369f2d_let-users-write-tests-for-you.4e1aa41ed4-800.avif"},{width:1200,url:"/images/generated/656916ea2967e28b96369f2d_let-users-write-tests-for-you.4e1aa41ed4-1200.avif"}],webp:[{width:400,url:"/images/generated/656916ea2967e28b96369f2d_let-users-write-tests-for-you.4e1aa41ed4-400.webp"},{width:800,url:"/images/generated/656916ea2967e28b96369f2d_let-users-write-tests-for-you.4e1aa41ed4-800.webp"},{width:1200,url:"/images/generated/656916ea2967e28b96369f2d_let-users-write-tests-for-you.4e1aa41ed4-1200.webp"}]},"/images/blog/let-users-write-tests-for-you/65691711938f0c44daf4f0a2_collapsing-building.gif":{width:250,height:265,isAnimated:!0,fallbackUrl:"/images/blog/let-users-write-tests-for-you/65691711938f0c44daf4f0a2_collapsing-building.gif",avif:[],webp:[{width:250,url:"/images/generated/65691711938f0c44daf4f0a2_collapsing-building.aaded747b9.webp"}]},"/images/blog/localstorage-complete-guide/65691790341bc3fa24842d44_localstorage-complete-guide.webp":{width:1200,height:630,isAnimated:!1,fallbackUrl:"/images/blog/localstorage-complete-guide/65691790341bc3fa24842d44_localstorage-complete-guide.webp",avif:[{width:400,url:"/images/generated/65691790341bc3fa24842d44_localstorage-complete-guide.e9819bfe56-400.avif"},{width:800,url:"/images/generated/65691790341bc3fa24842d44_localstorage-complete-guide.e9819bfe56-800.avif"},{width:1200,url:"/images/generated/65691790341bc3fa24842d44_localstorage-complete-guide.e9819bfe56-1200.avif"}],webp:[{width:400,url:"/images/generated/65691790341bc3fa24842d44_localstorage-complete-guide.e9819bfe56-400.webp"},{width:800,url:"/images/generated/65691790341bc3fa24842d44_localstorage-complete-guide.e9819bfe56-800.webp"},{width:1200,url:"/images/generated/65691790341bc3fa24842d44_localstorage-complete-guide.e9819bfe56-1200.webp"}]},"/images/blog/localstorage-complete-guide/656917a72367ea23ec8cfd3f_wordle-as-an-example-of-localStorage.jpeg":{width:1903,height:982,isAnimated:!1,fallbackUrl:"/images/blog/localstorage-complete-guide/656917a72367ea23ec8cfd3f_wordle-as-an-example-of-localStorage.jpeg",avif:[{width:400,url:"/images/generated/656917a72367ea23ec8cfd3f_wordle-as-an-example-of-localStorage.84eebd9089-400.avif"},{width:800,url:"/images/generated/656917a72367ea23ec8cfd3f_wordle-as-an-example-of-localStorage.84eebd9089-800.avif"},{width:1200,url:"/images/generated/656917a72367ea23ec8cfd3f_wordle-as-an-example-of-localStorage.84eebd9089-1200.avif"},{width:1600,url:"/images/generated/656917a72367ea23ec8cfd3f_wordle-as-an-example-of-localStorage.84eebd9089-1600.avif"},{width:1903,url:"/images/generated/656917a72367ea23ec8cfd3f_wordle-as-an-example-of-localStorage.84eebd9089-1903.avif"}],webp:[{width:400,url:"/images/generated/656917a72367ea23ec8cfd3f_wordle-as-an-example-of-localStorage.84eebd9089-400.webp"},{width:800,url:"/images/generated/656917a72367ea23ec8cfd3f_wordle-as-an-example-of-localStorage.84eebd9089-800.webp"},{width:1200,url:"/images/generated/656917a72367ea23ec8cfd3f_wordle-as-an-example-of-localStorage.84eebd9089-1200.webp"},{width:1600,url:"/images/generated/656917a72367ea23ec8cfd3f_wordle-as-an-example-of-localStorage.84eebd9089-1600.webp"},{width:1903,url:"/images/generated/656917a72367ea23ec8cfd3f_wordle-as-an-example-of-localStorage.84eebd9089-1903.webp"}]},"/images/blog/localstorage-complete-guide/656917a790dbf9088cd6dc6e_frontend-web-development.jpeg":{width:1423,height:947,isAnimated:!1,fallbackUrl:"/images/blog/localstorage-complete-guide/656917a790dbf9088cd6dc6e_frontend-web-development.jpeg",avif:[{width:400,url:"/images/generated/656917a790dbf9088cd6dc6e_frontend-web-development.0652b98e2b-400.avif"},{width:800,url:"/images/generated/656917a790dbf9088cd6dc6e_frontend-web-development.0652b98e2b-800.avif"},{width:1200,url:"/images/generated/656917a790dbf9088cd6dc6e_frontend-web-development.0652b98e2b-1200.avif"},{width:1423,url:"/images/generated/656917a790dbf9088cd6dc6e_frontend-web-development.0652b98e2b-1423.avif"}],webp:[{width:400,url:"/images/generated/656917a790dbf9088cd6dc6e_frontend-web-development.0652b98e2b-400.webp"},{width:800,url:"/images/generated/656917a790dbf9088cd6dc6e_frontend-web-development.0652b98e2b-800.webp"},{width:1200,url:"/images/generated/656917a790dbf9088cd6dc6e_frontend-web-development.0652b98e2b-1200.webp"},{width:1423,url:"/images/generated/656917a790dbf9088cd6dc6e_frontend-web-development.0652b98e2b-1423.webp"}]},"/images/blog/localstorage-complete-guide/656917caa237a90a968d44f4_devtools-storing-values-with-setItem--.gif":{width:1280,height:720,isAnimated:!0,fallbackUrl:"/images/blog/localstorage-complete-guide/656917caa237a90a968d44f4_devtools-storing-values-with-setItem--.gif",avif:[],webp:[]},"/images/blog/localstorage-complete-guide/656918026c73ea3ca982ccba_devtools-accessing-items-with-getItem--.gif":{width:1280,height:720,isAnimated:!0,fallbackUrl:"/images/blog/localstorage-complete-guide/656918026c73ea3ca982ccba_devtools-accessing-items-with-getItem--.gif",avif:[],webp:[]},"/images/blog/meticulous-announces-4m-seed-round/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.png":{width:4312,height:2479,isAnimated:!1,fallbackUrl:"/images/blog/meticulous-announces-4m-seed-round/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.png",avif:[{width:400,url:"/images/generated/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.53b3b602af-400.avif"},{width:800,url:"/images/generated/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.53b3b602af-800.avif"},{width:1200,url:"/images/generated/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.53b3b602af-1200.avif"},{width:1600,url:"/images/generated/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.53b3b602af-1600.avif"},{width:2e3,url:"/images/generated/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.53b3b602af-2000.avif"},{width:4312,url:"/images/generated/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.53b3b602af-4312.avif"}],webp:[{width:400,url:"/images/generated/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.53b3b602af-400.webp"},{width:800,url:"/images/generated/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.53b3b602af-800.webp"},{width:1200,url:"/images/generated/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.53b3b602af-1200.webp"},{width:1600,url:"/images/generated/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.53b3b602af-1600.webp"},{width:2e3,url:"/images/generated/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.53b3b602af-2000.webp"},{width:4312,url:"/images/generated/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.53b3b602af-4312.webp"}]},"/images/blog/mocking-a-javascript-class-with-jest-two-ways-to-make-it-easier/6569196e79dcea2163254cfa_dependency-injection-visualized-brph2.png":{width:936,height:588,isAnimated:!1,fallbackUrl:"/images/blog/mocking-a-javascript-class-with-jest-two-ways-to-make-it-easier/6569196e79dcea2163254cfa_dependency-injection-visualized-brph2.png",avif:[{width:400,url:"/images/generated/6569196e79dcea2163254cfa_dependency-injection-visualized-brph2.fd038a33a6-400.avif"},{width:800,url:"/images/generated/6569196e79dcea2163254cfa_dependency-injection-visualized-brph2.fd038a33a6-800.avif"},{width:936,url:"/images/generated/6569196e79dcea2163254cfa_dependency-injection-visualized-brph2.fd038a33a6-936.avif"}],webp:[{width:400,url:"/images/generated/6569196e79dcea2163254cfa_dependency-injection-visualized-brph2.fd038a33a6-400.webp"},{width:800,url:"/images/generated/6569196e79dcea2163254cfa_dependency-injection-visualized-brph2.fd038a33a6-800.webp"},{width:936,url:"/images/generated/6569196e79dcea2163254cfa_dependency-injection-visualized-brph2.fd038a33a6-936.webp"}]},"/images/blog/mocking-a-javascript-class-with-jest-two-ways-to-make-it-easier/656a51de168e1dff8f954229_mocking-a-javascript-class-with-jest.png":{width:1612,height:932,isAnimated:!1,fallbackUrl:"/images/blog/mocking-a-javascript-class-with-jest-two-ways-to-make-it-easier/656a51de168e1dff8f954229_mocking-a-javascript-class-with-jest.png",avif:[{width:400,url:"/images/generated/656a51de168e1dff8f954229_mocking-a-javascript-class-with-jest.3aef90b165-400.avif"},{width:800,url:"/images/generated/656a51de168e1dff8f954229_mocking-a-javascript-class-with-jest.3aef90b165-800.avif"},{width:1200,url:"/images/generated/656a51de168e1dff8f954229_mocking-a-javascript-class-with-jest.3aef90b165-1200.avif"},{width:1600,url:"/images/generated/656a51de168e1dff8f954229_mocking-a-javascript-class-with-jest.3aef90b165-1600.avif"},{width:1612,url:"/images/generated/656a51de168e1dff8f954229_mocking-a-javascript-class-with-jest.3aef90b165-1612.avif"}],webp:[{width:400,url:"/images/generated/656a51de168e1dff8f954229_mocking-a-javascript-class-with-jest.3aef90b165-400.webp"},{width:800,url:"/imag
29es/generated/656a51de168e1dff8f954229_mocking-a-javascript-class-with-jest.3aef90b165-800.webp"},{width:1200,url:"/images/generated/656a51de168e1dff8f954229_mocking-a-javascript-class-with-jest.3aef90b165-1200.webp"},{width:1600,url:"/images/generated/656a51de168e1dff8f954229_mocking-a-javascript-class-with-jest.3aef90b165-1600.webp"},{width:1612,url:"/images/generated/656a51de168e1dff8f954229_mocking-a-javascript-class-with-jest.3aef90b165-1612.webp"}]},"/images/blog/power-meticulous-case-study/65fc793430d60c2932345f46_power-meticulous-case-study.png":{width:3657,height:2094,isAnimated:!1,fallbackUrl:"/images/blog/power-meticulous-case-study/65fc793430d60c2932345f46_power-meticulous-case-study.png",avif:[{width:400,url:"/images/generated/65fc793430d60c2932345f46_power-meticulous-case-study.986eabe5c9-400.avif"},{width:800,url:"/images/generated/65fc793430d60c2932345f46_power-meticulous-case-study.986eabe5c9-800.avif"},{width:1200,url:"/images/generated/65fc793430d60c2932345f46_power-meticulous-case-study.986eabe5c9-1200.avif"},{width:1600,url:"/images/generated/65fc793430d60c2932345f46_power-meticulous-case-study.986eabe5c9-1600.avif"},{width:2e3,url:"/images/generated/65fc793430d60c2932345f46_power-meticulous-case-study.986eabe5c9-2000.avif"},{width:3657,url:"/images/generated/65fc793430d60c2932345f46_power-meticulous-case-study.986eabe5c9-3657.avif"}],webp:[{width:400,url:"/images/generated/65fc793430d60c2932345f46_power-meticulous-case-study.986eabe5c9-400.webp"},{width:800,url:"/images/generated/65fc793430d60c2932345f46_power-meticulous-case-study.986eabe5c9-800.webp"},{width:1200,url:"/images/generated/65fc793430d60c2932345f46_power-meticulous-case-study.986eabe5c9-1200.webp"},{width:1600,url:"/images/generated/65fc793430d60c2932345f46_power-meticulous-case-study.986eabe5c9-1600.webp"},{width:2e3,url:"/images/generated/65fc793430d60c2932345f46_power-meticulous-case-study.986eabe5c9-2000.webp"},{width:3657,url:"/images/generated/65fc793430d60c2932345f46_power-meticulous-case-study.986eabe5c9-3657.webp"}]},"/images/blog/rainforest-qa-alternatives/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.png":{width:2e3,height:1295,isAnimated:!1,fallbackUrl:"/images/blog/rainforest-qa-alternatives/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.png",avif:[{width:400,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-400.avif"},{width:800,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-800.avif"},{width:1200,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-1200.avif"},{width:1600,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-1600.avif"},{width:2e3,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-2000.avif"}],webp:[{width:400,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-400.webp"},{width:800,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-800.webp"},{width:1200,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-1200.webp"},{width:1600,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-1600.webp"},{width:2e3,url:"/images/generated/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.292859fbd8-2000.webp"}]},"/images/blog/rainforest-qa-alternatives/6568e03d3a443a9259457eba_sauce-labs-testing.png":{width:2e3,height:588,isAnimated:!1,fallbackUrl:"/images/blog/rainforest-qa-alternatives/6568e03d3a443a9259457eba_sauce-labs-testing.png",avif:[{width:400,url:"/images/generated/6568e03d3a443a9259457eba_sauce-labs-testing.e3f75b9574-400.avif"},{width:800,url:"/images/generated/6568e03d3a443a9259457eba_sauce-labs-testing.e3f75b9574-800.avif"},{width:1200,url:"/images/generated/6568e03d3a443a9259457eba_sauce-labs-testing.e3f75b9574-1200.avif"},{width:1600,url:"/images/generated/6568e03d3a443a9259457eba_sauce-labs-testing.e3f75b9574-1600.avif"},{width:2e3,url:"/images/generated/6568e03d3a443a9259457eba_sauce-labs-testing.e3f75b9574-2000.avif"}],webp:[{width:400,url:"/images/generated/6568e03d3a443a9259457eba_sauce-labs-testing.e3f75b9574-400.webp"},{width:800,url:"/images/generated/6568e03d3a443a9259457eba_sauce-labs-testing.e3f75b9574-800.webp"},{width:1200,url:"/images/generated/6568e03d3a443a9259457eba_sauce-labs-testing.e3f75b9574-1200.webp"},{width:1600,url:"/images/generated/6568e03d3a443a9259457eba_sauce-labs-testing.e3f75b9574-1600.webp"},{width:2e3,url:"/images/generated/6568e03d3a443a9259457eba_sauce-labs-testing.e3f75b9574-2000.webp"}]},"/images/blog/rainforest-qa-alternatives/6568e03d3c03637732084b36_test-io-website.png":{width:2e3,height:1254,isAnimated:!1,fallbackUrl:"/images/blog/rainforest-qa-alternatives/6568e03d3c03637732084b36_test-io-website.png",avif:[{width:400,url:"/images/generated/6568e03d3c03637732084b36_test-io-website.aaa009cfff-400.avif"},{width:800,url:"/images/generated/6568e03d3c03637732084b36_test-io-website.aaa009cfff-800.avif"},{width:1200,url:"/images/generated/6568e03d3c03637732084b36_test-io-website.aaa009cfff-1200.avif"},{width:1600,url:"/images/generated/6568e03d3c03637732084b36_test-io-website.aaa009cfff-1600.avif"},{width:2e3,url:"/images/generated/6568e03d3c03637732084b36_test-io-website.aaa009cfff-2000.avif"}],webp:[{width:400,url:"/images/generated/6568e03d3c03637732084b36_test-io-website.aaa009cfff-400.webp"},{width:800,url:"/images/generated/6568e03d3c03637732084b36_test-io-website.aaa009cfff-800.webp"},{width:1200,url:"/images/generated/6568e03d3c03637732084b36_test-io-website.aaa009cfff-1200.webp"},{width:1600,url:"/images/generated/6568e03d3c03637732084b36_test-io-website.aaa009cfff-1600.webp"},{width:2e3,url:"/images/generated/6568e03d3c03637732084b36_test-io-website.aaa009cfff-2000.webp"}]},"/images/blog/rainforest-qa-alternatives/6568e03db076ff0e9cc17263_meticulous-testimonials.png":{width:2e3,height:721,isAnimated:!1,fallbackUrl:"/images/blog/rainforest-qa-alternatives/6568e03db076ff0e9cc17263_meticulous-testimonials.png",avif:[{width:400,url:"/images/generated/6568e03db076ff0e9cc17263_meticulous-testimonials.812b5204c6-400.avif"},{width:800,url:"/images/generated/6568e03db076ff0e9cc17263_meticulous-testimonials.812b5204c6-800.avif"},{width:1200,url:"/images/generated/6568e03db076ff0e9cc17263_meticulous-testimonials.812b5204c6-1200.avif"},{width:1600,url:"/images/generated/6568e03db076ff0e9cc17263_meticulous-testimonials.812b5204c6-1600.avif"},{width:2e3,url:"/images/generated/6568e03db076ff0e9cc17263_meticulous-testimonials.812b5204c6-2000.avif"}],webp:[{width:400,url:"/images/generated/6568e03db076ff0e9cc17263_meticulous-testimonials.812b5204c6-400.webp"},{width:800,url:"/images/generated/6568e03db076ff0e9cc17263_meticulous-testimonials.812b5204c6-800.webp"},{width:1200,url:"/images/generated/6568e03db076ff0e9cc17263_meticulous-testimonials.812b5204c6-1200.webp"},{width:1600,url:"/images/generated/6568e03db076ff0e9cc17263_meticulous-testimonials.812b5204c6-1600.webp"},{width:2e3,url:"/images/generated/6568e03db076ff0e9cc17263_meticulous-testimonials.812b5204c6-2000.webp"}]},"/images/blog/rainforest-qa-alternatives/6568e0d2abc4df6a9ec45615_cypress-test-runner-ui.png":{width:2e3,height:1132,isAnimated:!1,fallbackUrl:"/images/blog/rainforest-qa-alternatives/6568e0d2abc4df6a9ec45615_cypress-test-runner-ui.png",avif:[{width:400,url:"/images/generated/6568e0d2abc4df6a9ec45615_cypress-test-runner-ui.5d72a3731e-400.avif"},{width:800,url:"/images/generated/6568e0d2abc4df6a9ec45615_cypress-test-runner-ui.5d72a3731e-800.avif"},{width:1200,url:"/images/generated/6568e0d2abc4df6a9ec45615_cypress-test-runner-ui.5d72a3731e-1200.avif"},{width:1600,url:"/images/generated/6568e0d2abc4df6a9ec45615_cypress-test-runner-ui.5d72a3731e-1600.avif"},{width:2e3,url:"/images/generated/6568e0d2abc4df6a9ec45615_cypress-test-runner-ui.5d72a3731e-2000.avif"}],webp:[{width:400,url:"/images/generated/6568e0d2abc4df6a9ec45615_cypress-test-runner-ui.5d72a3731e-400.webp"},{width:800,url:"/images/generated/6568e0d2abc4df6a9ec45615_cypress-test-runner-ui.5d72a3731e-800.webp"},{width:1200,url:"/images/generated/6568e0d2abc4df6a9ec45615_cypress-test-runner-ui.5d72a3731e-1200.webp"},{width:1600,url:"/images/generated/6568e0d2abc4df6a9ec45615_cypress-test-runner-ui.5d72a3731e-1600.webp"},{width:2e3,url:"/images/generated/6568e0d2abc4df6a9ec45615_cypress-test-runner-ui.5d72a3731e-2000.webp"}]},"/images/blog/rainforest-qa-alternatives/6568e0d2ebfcbace373f5c31_cypress-testing.png":{width:2e3,height:1140,isAnimated:!1,fallbackUrl:"/images/blog/rainforest-qa-alternatives/6568e0d2ebfcbace373f5c31_cypress-testing.png",avif:[{width:400,url:"/images/generated/6568e0d2ebfcbace373f5c31_cypress-testing.de90246179-400.avif"},{width:800,url:"/images/generated/6568e0d2ebfcbace373f5c31_cypress-testing.de90246179-800.avif"},{width:1200,url:"/images/generated/6568e0d2ebfcbace373f5c31_cypress-testing.de90246179-1200.avif"},{width:1600,url:"/images/generated/6568e0d2ebfcbace373f5c31_cypress-testing.de90246179-1600.avif"},{width:2e3,url:"/images/generated/6568e0d2ebfcbace373f5c31_cypress-testing.de90246179-2000.avif"}],webp:[{width:400,url:"/images/generated/6568e0d2ebfcbace373f5c31_cypress-testing.de90246179-400.webp"},{width:800,url:"/images/generated/6568e0d2ebfcbace373f5c31_cypress-testing.de90246179-800.webp"},{width:1200,url:"/images/generated/6568e0d2ebfcbace373f5c31_cypress-testing.de90246179-1200.webp"},{width:1600,url:"/images/generated/6568e0d2ebfcbace373f5c31_cypress-testing.de90246179-1600.webp"},{width:2e3,url:"/images/generated/6568e0d2ebfcbace373f5c31_cypress-testing.de90246179-2000.webp"}]},"/images/blog/rainforest-qa-alternatives/6568e199b995b7283ddb3bb3_browserstack-testing.png":{width:2e3,height:1590,isAnimated:!1,fallbackUrl:"/images/blog/rainforest-qa-alternatives/6568e199b995b7283ddb3bb3_browserstack-testing.png",avif:[{width:400,url:"/images/generated/6568e199b995b7283ddb3bb3_browserstack-testing.fc0347bb18-400.avif"},{width:800,url:"/images/generated/6568e199b995b7283ddb3bb3_browserstack-testing.fc0347bb18-800.avif"},{width:1200,url:"/images/generated/6568e199b995b7283ddb3bb3_browserstack-testing.fc0347bb18-1200.avif"},{width:1600,url:"/images/generated/6568e199b995b7283ddb3bb3_browserstack-testing.fc0347bb18-1600.avif"},{width:2e3,url:"/images/generated/6568e199b995b7283ddb3bb3_browserstack-testing.fc0347bb18-2000.avif"}],webp:[{width:400,url:"/images/generated/6568e199b995b7283ddb3bb3_browserstack-testing.fc0347bb18-400.webp"},{width:800,url:"/images/generated/6568e199b995b7283ddb3bb3_browserstack-testing.fc0347bb18-800.webp"},{width:1200,url:"/images/generated/6568e199b995b7283ddb3bb3_browserstack-testing.fc0347bb18-1200.webp"},{width:1600,url:"/images/generated/6568e199b995b7283ddb3bb3_browserstack-testing.fc0347bb18-1600.webp"},{width:2e3,url:"/images/generated/6568e199b995b7283ddb3bb3_browserstack-testing.fc0347bb18-2000.webp"}]},"/images/blog/rainforest-qa-alternatives/656a1afd825962cfb448524a_rainforest-qa-alternatives.png":{width:1880,height:1068,isAnimated:!1,fallbackUrl:"/images/blog/rainforest-qa-alternatives/656a1afd825962cfb448524a_rainforest-qa-alternatives.png",avif:[{width:400,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-400.avif"},{width:800,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-800.avif"},{width:1200,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-1200.avif"},{width:1600,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-1600.avif"},{width:1880,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-1880.avif"}],webp:[{width:400,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-400.webp"},{width:800,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-800.webp"},{width:1200,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-1200.webp"},{width:1600,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-1600.webp"},{width:1880,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-1880.webp"}]},"/images/blog/range-errors-and-how-to-prevent-them/65691ad51147e20f0fe69de0_range-errors-and-how-to-prevent-them.webp":{width:1200,height:630,isAnimated:!1,fallbackUrl:"/images/blog/range-errors-and-how-to-prevent-them/65691ad51147e20f0fe69de0_range-errors-and-how-to-prevent-them.webp",avif:[{width:400,url:"/images/generated/65691ad51147e20f0fe69de0_range-errors-and-how-to-prevent-them.72ec616879-400.avif"},{width:800,url:"/images/generated/65691ad51147e20f0fe69de0_range-errors-and-how-to-prevent-them.72ec616879-800.avif"},{width:1200,url:"/images/generated/65691ad51147e20f0fe69de0_range-errors-and-how-to-prevent-them.72ec616879-1200.avif"}],webp:[{width:400,url:"/images/generated/65691ad51147e20f0fe69de0_range-errors-and-how-to-prevent-them.72ec616879-400.webp"},{width:800,url:"/images/generated/65691ad51147e20f0fe69de0_range-errors-and-how-to-prevent-them.72ec616879-800.webp"},{width:1200,url:"/images/generated/65691ad51147e20f0fe69de0_range-errors-and-how-to-prevent-them.72ec616879-1200.webp"}]},"/images/blog/react-error-boundaries-complete-guide/65691c2fce8ceff434cd6921_react-error-boundaries-complete-guide.webp":{width:1920,height:1015,isAnimated:!1,fallbackUrl:"/images/blog/react-error-boundaries-complete-guide/65691c2fce8ceff434cd6921_react-error-boundaries-complete-guide.webp",avif:[{width:400,url:"/images/generated/65691c2fce8ceff434cd6921_react-error-boundaries-complete-guide.b0bba30ee7-400.avif"},{width:800,url:"/images/generated/65691c2fce8ceff434cd6921_react-error-boundaries-complete-guide.b0bba30ee7-800.avif"},{width:1200,url:"/images/generated/65691c2fce8ceff434cd6921_react-error-boundaries-complete-guide.b0bba30ee7-1200.avif"},{width:1600,url:"/images/generated/65691c2fce8ceff434cd6921_react-error-boundaries-complete-guide.b0bba30ee7-1600.avif"},{width:1920,url:"/images/generated/65691c2fce8ceff434cd6921_react-error-boundaries-complete-guide.b0bba30ee7-1920.avif"}],webp:[{width:400,url:"/images/generated/65691c2fce8ceff434cd6921_react-error-boundaries-complete-guide.b0bba30ee7-400.webp"},{width:800,url:"/images/generated/65691c2fce8ceff434cd6921_react-error-boundaries-complete-guide.b0bba30ee7-800.webp"},{width:1200,url:"/images/generated/65691c2fce8ceff434cd6921_react-error-boundaries-complete-guide.b0bba30ee7-1200.webp"},{width:1600,url:"/images/generated/65691c2fce8ceff434cd6921_react-error-boundaries-complete-guide.b0bba30ee7-1600.webp"},{width:1920,url:"/images/generated/65691c2fce8ceff434cd6921_react-error-boundaries-complete-guide.b0bba30ee7-1920.webp"}]},"/images/blog/react-error-boundaries-complete-guide/65691c4a56d4bdc384b8ccfd_react-errors-bubbling.png":{width:667,height:495,isAnimated:!1,fallbackUrl:"/images/blog/react-error-boundaries-complete-guide/65691c4a56d4bdc384b8ccfd_react-errors-bubbling.png",avif:[{width:400,url:"/images/generated/65691c4a56d4bdc384b8ccfd_react-errors-bubbling.3eb8cc1b65-400.avif"},{width:667,url:"/images/generated/65691c4a56d4bdc384b8ccfd_react-errors-bubbling.3eb8cc1b65-667.avif"}],webp:[{width:400,url:"/images/generated/65691c4a56d4bdc384b8ccfd_react-errors-bubbling.3eb8cc1b65-400.webp"},{width:667,url:"/images/generated/65691c4a56d4bdc384b8ccfd_react-errors-bubbling.3eb8cc1b65-667.webp"}]},"/images/blog/react-error-boundaries-complete-guide/65691c4afdb26ce826751528_react-errors-bubbling-with-error-boundary.png":{width:667,height:495,isAnimated:!1,fallbackUrl:"/images/blog/react-error-boundaries-complete-guide/65691c4afdb26ce826751528_react-errors-bubbling-with-error-boundary.png",avif:[{width:400,url:"/images/generated/65691c4afdb26ce826751528_react-errors-bubbling-with-error-boundary.efa76a05d8-400.avif"},{width:667,url:"/images/generated/65691c4afdb26ce826751528_react-errors-bubbling-with-error-boundary.efa76a05d8-667.avif"}],webp:[{width:400,url:"/images/generated/65691c4afdb26ce826751528_react-errors-bubbling-with-error-boundary.efa76a05d8-400.webp"},{width:667,url:"/images/generated/65691c4afdb26ce826751528_react-errors-bubbling-with-error-boundary.efa76a05d8-667.webp"}]},"/images
29/blog/reflect-alternatives/656a1afd825962cfb448524a_rainforest-qa-alternatives.png":{width:1880,height:1068,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/656a1afd825962cfb448524a_rainforest-qa-alternatives.png",avif:[{width:400,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-400.avif"},{width:800,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-800.avif"},{width:1200,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-1200.avif"},{width:1600,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-1600.avif"},{width:1880,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-1880.avif"}],webp:[{width:400,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-400.webp"},{width:800,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-800.webp"},{width:1200,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-1200.webp"},{width:1600,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-1600.webp"},{width:1880,url:"/images/generated/656a1afd825962cfb448524a_rainforest-qa-alternatives.dc42f4dfb1-1880.webp"}]},"/images/blog/reflect-alternatives/671793faf25617e2c132a07e_659c2cfa36f85270571c0cf8_pricing-page-of-percy.png":{width:582,height:382,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793faf25617e2c132a07e_659c2cfa36f85270571c0cf8_pricing-page-of-percy.png",avif:[{width:400,url:"/images/generated/671793faf25617e2c132a07e_659c2cfa36f85270571c0cf8_pricing-page-of-percy.4aa72a494e-400.avif"},{width:582,url:"/images/generated/671793faf25617e2c132a07e_659c2cfa36f85270571c0cf8_pricing-page-of-percy.4aa72a494e-582.avif"}],webp:[{width:400,url:"/images/generated/671793faf25617e2c132a07e_659c2cfa36f85270571c0cf8_pricing-page-of-percy.4aa72a494e-400.webp"},{width:582,url:"/images/generated/671793faf25617e2c132a07e_659c2cfa36f85270571c0cf8_pricing-page-of-percy.4aa72a494e-582.webp"}]},"/images/blog/reflect-alternatives/671793fbf25617e2c132a083_659c2b558167aa6d2cfc7681_lambdatest-pricing.png":{width:576,height:344,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793fbf25617e2c132a083_659c2b558167aa6d2cfc7681_lambdatest-pricing.png",avif:[{width:400,url:"/images/generated/671793fbf25617e2c132a083_659c2b558167aa6d2cfc7681_lambdatest-pricing.b168f8051b-400.avif"},{width:576,url:"/images/generated/671793fbf25617e2c132a083_659c2b558167aa6d2cfc7681_lambdatest-pricing.b168f8051b-576.avif"}],webp:[{width:400,url:"/images/generated/671793fbf25617e2c132a083_659c2b558167aa6d2cfc7681_lambdatest-pricing.b168f8051b-400.webp"},{width:576,url:"/images/generated/671793fbf25617e2c132a083_659c2b558167aa6d2cfc7681_lambdatest-pricing.b168f8051b-576.webp"}]},"/images/blog/reflect-alternatives/671793fbf25617e2c132a086_659c2bba36d0ec5e92c08b8e_lambda-test-web-and-mobile-browser-on-real-device-plan.png":{width:1854,height:300,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793fbf25617e2c132a086_659c2bba36d0ec5e92c08b8e_lambda-test-web-and-mobile-browser-on-real-device-plan.png",avif:[{width:400,url:"/images/generated/671793fbf25617e2c132a086_659c2bba36d0ec5e92c08b8e_lambda-test-web-and-mobile-browser-on-real-device-plan.8ba991b877-400.avif"},{width:800,url:"/images/generated/671793fbf25617e2c132a086_659c2bba36d0ec5e92c08b8e_lambda-test-web-and-mobile-browser-on-real-device-plan.8ba991b877-800.avif"},{width:1200,url:"/images/generated/671793fbf25617e2c132a086_659c2bba36d0ec5e92c08b8e_lambda-test-web-and-mobile-browser-on-real-device-plan.8ba991b877-1200.avif"},{width:1600,url:"/images/generated/671793fbf25617e2c132a086_659c2bba36d0ec5e92c08b8e_lambda-test-web-and-mobile-browser-on-real-device-plan.8ba991b877-1600.avif"},{width:1854,url:"/images/generated/671793fbf25617e2c132a086_659c2bba36d0ec5e92c08b8e_lambda-test-web-and-mobile-browser-on-real-device-plan.8ba991b877-1854.avif"}],webp:[{width:400,url:"/images/generated/671793fbf25617e2c132a086_659c2bba36d0ec5e92c08b8e_lambda-test-web-and-mobile-browser-on-real-device-plan.8ba991b877-400.webp"},{width:800,url:"/images/generated/671793fbf25617e2c132a086_659c2bba36d0ec5e92c08b8e_lambda-test-web-and-mobile-browser-on-real-device-plan.8ba991b877-800.webp"},{width:1200,url:"/images/generated/671793fbf25617e2c132a086_659c2bba36d0ec5e92c08b8e_lambda-test-web-and-mobile-browser-on-real-device-plan.8ba991b877-1200.webp"},{width:1600,url:"/images/generated/671793fbf25617e2c132a086_659c2bba36d0ec5e92c08b8e_lambda-test-web-and-mobile-browser-on-real-device-plan.8ba991b877-1600.webp"},{width:1854,url:"/images/generated/671793fbf25617e2c132a086_659c2bba36d0ec5e92c08b8e_lambda-test-web-and-mobile-browser-on-real-device-plan.8ba991b877-1854.webp"}]},"/images/blog/reflect-alternatives/671793fbf25617e2c132a089_659c2a58be45672b92e8f1e9_reflect-build-at-the-speed-of-thought.png":{width:990,height:438,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793fbf25617e2c132a089_659c2a58be45672b92e8f1e9_reflect-build-at-the-speed-of-thought.png",avif:[{width:400,url:"/images/generated/671793fbf25617e2c132a089_659c2a58be45672b92e8f1e9_reflect-build-at-the-speed-of-thought.77893fbfe5-400.avif"},{width:800,url:"/images/generated/671793fbf25617e2c132a089_659c2a58be45672b92e8f1e9_reflect-build-at-the-speed-of-thought.77893fbfe5-800.avif"},{width:990,url:"/images/generated/671793fbf25617e2c132a089_659c2a58be45672b92e8f1e9_reflect-build-at-the-speed-of-thought.77893fbfe5-990.av
29if"}],webp:[{width:400,url:"/images/generated/671793fbf25617e2c132a089_659c2a58be45672b92e8f1e9_reflect-build-at-the-speed-of-thought.77893fbfe5-400.webp"},{width:800,url:"/images/generated/671793fbf25617e2c132a089_659c2a58be45672b92e8f1e9_reflect-build-at-the-speed-of-thought.77893fbfe5-800.webp"},{width:990,url:"/images/generated/671793fbf25617e2c132a089_659c2a58be45672b92e8f1e9_reflect-build-at-the-speed-of-thought.77893fbfe5-990.webp"}]},"/images/blog/reflect-alternatives/671793fbf25617e2c132a08c_659c2c194d12e2b23b43f604_browserstack-features.png":{width:927,height:312,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793fbf25617e2c132a08c_659c2c194d12e2b23b43f604_browserstack-features.png",avif:[{width:400,url:"/images/generated/671793fbf25617e2c132a08c_659c2c194d12e2b23b43f604_browserstack-features.3b775a5602-400.avif"},{width:800,url:"/images/generated/671793fbf25617e2c132a08c_659c2c194d12e2b23b43f604_browserstack-features.3b775a5602-800.avif"},{width:927,url:"/images/generated/671793fbf25617e2c132a08c_659c2c194d12e2b23b43f604_browserstack-features.3b775a5602-927.avif"}],webp:[{width:400,url:"/images/generated/671793fbf25617e2c132a08c_659c2c194d12e2b23b43f604_browserstack-features.3b775a5602-400.webp"},{width:800,url:"/images/generated/671793fbf25617e2c132a08c_659c2c194d12e2b23b43f604_browserstack-features.3b775a5602-800.webp"},{width:927,url:"/images/generated/671793fbf25617e2c132a08c_659c2c194d12e2b23b43f604_browserstack-features.3b775a5602-927.webp"}]},"/images/blog/reflect-alternatives/671793fbf25617e2c132a08f_659c2d5fdda0c588eb95221d_pricing-page-of-saucelabs.png":{width:987,height:580,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793fbf25617e2c132a08f_659c2d5fdda0c588eb95221d_pricing-page-of-saucelabs.png",avif:[{width:400,url:"/images/generated/671793fbf25617e2c132a08f_659c2d5fdda0c588eb95221d_pricing-page-of-saucelabs.e00eb60d4b-400.avif"},{width:800,url:"/images/generated/671793fbf25617e2c132a08f_659c2d5fdda0c588eb95221d_pricing-page-of-saucelabs.e00eb60d4b-800.avif"},{width:987,url:"/images/generated/671793fbf25617e2c132a08f_659c2d5fdda0c588eb95221d_pricing-page-of-saucelabs.e00eb60d4b-987.avif"}],webp:[{width:400,url:"/images/generated/671793fbf25617e2c132a08f_659c2d5fdda0c588eb95221d_pricing-page-of-saucelabs.e00eb60d4b-400.webp"},{width:800,url:"/images/generated/671793fbf25617e2c132a08f_659c2d5fdda0c588eb95221d_pricing-page-of-saucelabs.e00eb60d4b-800.webp"},{width:987,url:"/images/generated/671793fbf25617e2c132a08f_659c2d5fdda0c588eb95221d_pricing-page-of-saucelabs.e00eb60d4b-987.webp"}]},"/images/blog/reflect-alternatives/671793fbf25617e2c132a09f_659c29
29c1f139e9976338ea47_screenshot-of-reflect.png":{width:1193,height:739,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793fbf25617e2c132a09f_659c29c1f139e9976338ea47_screenshot-of-reflect.png",avif:[{width:400,url:"/images/generated/671793fbf25617e2c132a09f_659c29c1f139e9976338ea47_screenshot-of-reflect.73206a6ba4-400.avif"},{width:800,url:"/images/generated/671793fbf25617e2c132a09f_659c29c1f139e9976338ea47_screenshot-of-reflect.73206a6ba4-800.avif"},{width:1193,url:"/images/generated/671793fbf25617e2c132a09f_659c29c1f139e9976338ea47_screenshot-of-reflect.73206a6ba4-1193.avif"}],webp:[{width:400,url:"/images/generated/671793fbf25617e2c132a09f_659c29c1f139e9976338ea47_screenshot-of-reflect.73206a6ba4-400.webp"},{width:800,url:"/images/generated/671793fbf25617e2c132a09f_659c29c1f139e9976338ea47_screenshot-of-reflect.73206a6ba4-800.webp"},{width:1193,url:"/images/generated/671793fbf25617e2c132a09f_659c29c1f139e9976338ea47_screenshot-of-reflect.73206a6ba4-1193.webp"}]},"/images/blog/reflect-alternatives/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.png":{width:2156,height:1138,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.png",avif:[{width:400,url:"/images/generated/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.00a3b4fcdd-400.avif"},{width:800,url:"/images/generated/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.00a3b4fcdd-800.avif"},{width:1200,url:"/images/generated/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.00a3b4fcdd-1200.avif"},{width:1600,url:"/images/generated/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.00a3b4fcdd-1600.avif"},{width:2e3,url:"/images/generated/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.00a3b4fcdd-2000.avif"},{width:2156,url:"/images/generated/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.00a3b4fcdd-2156.avif"}],webp:[{width:400,url:"/images/generated/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.00a3b4fcdd-400.webp"},{width:800,url:"/images/generated/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.00a3b4fcdd-800.webp"},{width:1200,url:"/images/generated/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.00a3b4fcdd-1200.webp"},{width:1600,url:"/images/generated/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.00a3b4fcdd-1600.webp"},{width:2e3,url:"/images/generated/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.00a3b4fcdd-2000.webp"},{width:2156,url:"/images/generated/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.00a3b4fcdd-2156.webp"}]},"/images/blog/reflect-alternatives/671793fcf25617e2c132a0b1_659c2d901d398de59a297112_screenshot-of-cypress.png":{width:2e3,height:1140,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793fcf25617e2c132a0b1_659c2d901d398de59a297112_screenshot-of-cypress.png",avif:[{width:400,url:"/images/generated/671793fcf25617e2c132a0b1_659c2d901d398de59a297112_screenshot-of-cypress.316fa0df6a-400.avif"},{width:800,url:"/images/generated/671793fcf25617e2c132a0b1_659c2d901d398de59a297112_screenshot-of-cypress.316fa0df6a-800.avif"},{width:1200,url:"/images/generated/671793fcf25617e2c132a0b1_659c2d901d398de59a297112_screenshot-of-cypress.316fa0df6a-1200.avif"},{width:1600,url:"/images/generated/671793fcf25617e2c132a0b1_659c2d901d398de59a297112_screenshot-of-cypress.316fa0df6a-1600.avif"},{width:2e3,url:"/images/generated/671793fcf25617e2c132a0b1_659c2d901d398de59a297112_screenshot-of-cypress.316fa0df6a-2000.avif"}],webp:[{width:400,url:"/images/generated/671793fcf25617e2c132a0b1_659c2d901d398de59a297112_screenshot-of-cypress.316fa0df6a-400.webp"},{width:800,url:"/images/generated/671793fcf25617e2c132a0b1_659c2d901d398de59a297112_screenshot-of-cypress.316fa0df6a-800.webp"},{width:1200,url:"/images/generated/671793fcf25617e2c132a0b1_659c2d901d398de59a297112_screenshot-of-cypress.316fa0df6a-1200.webp"},{width:1600,url:"/images/generated/671793fcf25617e2c132a0b1_659c2d901d398de59a297112_screenshot-of-cypress.316fa0df6a-1600.webp"},{width:2e3,url:"/images/generated/671793fcf25617e2c132a0b1_659c2d901d398de59a297112_screenshot-of-cypress.316fa0df6a-2000.webp"}]},"/images/blog/reflect-alternatives/671793fcf25617e2c132a0b4_659c2cbb46d3ad6372c1f36c_untitled.png":{width:2e3,height:1056,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793fcf25617e2c132a0b4_659c2cbb46d3ad6372c1f36c_untitled.png",avif:[{width:400,url:"/images/generated/671793fcf25617e2c132a0b4_659c2cbb46d3ad6372c1f36c_untitled.609737af08-400.avif"},{width:800,url:"/images/generated/671793fcf25617e2c132a0b4_659c2cbb46d3ad6372c1f36c_untitled.609737af08-800.avif"},{width:1200,url:"/images/generated/671793fcf25617e2c132a0b4_659c2cbb46d3ad6372c1f36c_untitled.609737af08-1200.avif"},{width:1600,url:"/images/generated/671793fcf25617e2c132a0b4_659c2cbb46d3ad6372c1f36c_untitled.609737af08-1600.avif"},{width:2e3,url:"/images/generated/671793fcf25617e2c132a0b4_659c2cbb46d3ad6372c1f36c_untitled.609737af08-2000.avif"}],webp:[{width:400,url:"/images/generated/671793fcf25617e2c132a0b4_659c2cbb46d3ad6372c1f36c_untitled.609737af08-400.webp"},{width:800,url:"/images/generated/671793fcf25617e2c132a0b4_659c2cbb46d3ad6372c1f36c_untitled.609737af08-800.webp"},{width:1200,url:"/images/generated/671793fcf25617e2c132a0b4_659c2cbb46d3ad6372c1f36c_untitled.609737af08-1200.webp"},{width:1600,url:"/images/generated/671793fcf25617e2c132a0b4_659c2cbb46d3ad6372c1f36c_untitled.609737af08-1600.webp"},{width:2e3,url:"/images/generated/671793fcf25617e2c132a0b4_659c2cbb46d3ad6372c1f36c_untitled.609737af08-2000.webp"}]},"/images/blog/reflect-alternatives/671793fcf25617e2c132a0b7_659c2d323662aaee1a594bbc_screenshot-of-saucelabs.png":{width:2e3,height:1094,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793fcf25617e2c132a0b7_659c2d323662aaee1a594bbc_screenshot-of-saucelabs.png",avif:[{width:400,url:"/images/generated/671793fcf25617e2c132a0b7_659c2d323662aaee1a594bbc_screenshot-of-saucelabs.b19c534c38-400.avif"},{width:800,url:"/images/generated/671793fcf25617e2c132a0b7_659c2d323662aaee1a594bbc_screenshot-of-saucelabs.b19c534c38-800.avif"},{width:1200,url:"/images/generated/671793fcf25617e2c132a0b7_659c2d323662aaee1a594bbc_screenshot-of-saucelabs.b19c534c38-1200.avif"},{width:1600,url:"/images/generated/671793fcf25617e2c132a0b7_659c2d323662aaee1a594bbc_screenshot-of-saucelabs.b19c534c38-1600.avif"},{width:2e3,url:"/images/generated/671793fcf25617e2c132a0b7_659c2d323662aaee1a594bbc_screenshot-of-saucelabs.b19c534c38-2000.avif"}],webp:[{width:400,url:"/images/generated/671793fcf25617e2c132a0b7_659c2d323662aaee1a594bbc_screenshot-of-saucelabs.b19c534c38-400.webp"},{width:800,url:"/images/generated/671793fcf25617e2c132a0b7_659c2d323662aaee1a594bbc_screenshot-of-saucelabs.b19c534c38-800.webp"},{width:1200,url:"/images/generated/671793fcf25617e2c132a0b7_659c2d323662aaee1a594bbc_screenshot-of-saucelabs.b19c534c38-1200.webp"},{width:1600,url:"/images/generated/671793fcf25617e2c132a0b7_659c2d323662aaee1a594bbc_screenshot-of-saucelabs.b19c534c38-1600.webp"},{width:2e3,url:"/images/generated/671793fcf25617e2c132a0b7_659c2d323662aaee1a594bbc_screenshot-of-saucelabs.b19c534c38-2000.webp"}]},"/images/blog/reflect-alternatives/671793fcf25617e2c132a0d6_659c2be3f5321d84e225827d_screenshot-of-browserstack.png":{width:2e3,height:1358,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793fcf25617e2c132a0d6_659c2be3f5321d84e225827d_screenshot-of-browserstack.png",avif:[{width:400,url:"/images/generated/671793fcf25617e2c132a0d6_659c2be3f5321d84e225827d_screenshot-of-browserstack.e4e8567aba-400.avif"},{width:800,url:"/images/generated/671793fcf25617e2c132a0d6_659c2be3f5321d84e225827d_screenshot-of-browserstack.e4e8567aba-800.avif"},{width:1200,url:"/images/generated/671793fcf25617e2c132a0d6_659c2be3f5321d84e225827d_screenshot-of-browserstack.e4e8567aba-1200.avif"},{width:1600,url:"/images/generated/671793fcf25617e2c132a0d6_659c2be3f5321d84e225827d_screenshot-of-browserstack.e4e8567aba-1600.avif"},{width:2e3,url:"/images/generated/671793fcf25617e2c132a0d6_659c2be3f5321d84e225827d_screenshot-of-browserstack.e4e8567aba-2000.avif"}],webp:[{width:400,url:"/images/generated/671793fcf25617e2c132a0d6_659c2be3f5321d84e225827d_screenshot-of-browserstack.e4e8567aba-400.webp"},{width:800,url:"/images/generated/671793fcf25617e2c132a0d6_659c2be3f5321d84e225827d_screenshot-of-browserstack.e4e8567aba-800.webp"},{width:1200,url:"/images/generated/671793fcf25617e2c132a0d6_659c2be3f5321d84e225827d_screenshot-of-browserstack.e4e8567aba-1200.webp"},{width:1600,url:"/images/generated/671793fcf25617e2c132a0d6_659c2be3f5321d84e225827d_screenshot-of-browserstack.e4e8567aba-1600.webp"},{width:2e3,url:"/images/generated/671793fcf25617e2c132a0d6_659c2be3f5321d84e225827d_screenshot-of-browserstack.e4e8567aba-2000.webp"}]},"/images/blog/reflect-alternatives/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.png":{width:2840,height:1596,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.png",avif:[{width:400,url:"/images/generated/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.1305e599c3-400.avif"},{width:800,url:"/images/generated/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.1305e599c3-800.avif"},{width:1200,url:"/images/generated/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.1305e599c3-1200.avif"},{width:1600,url:"/images/generated/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.1305e599c3-1600.avif"},{width:2e3,url:"/images/generated/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.1305e599c3-2000.avif"},{width:2840,url:"/images/generated/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.1305e599c3-2840.avif"}],webp:[{width:400,url:"/images/generated/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.1305e599c3-400.webp"},{width:800,url:"/images/generated/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.1305e599c3-800.webp"},{width:1200,url:"/images/generated/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.1305e599c3-1200.webp"},{width:1600,url:"/images/generated/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.1305e599c3-1600.webp"},{width:2e3,url:"/images/generated/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.1305e599c3-2000.webp"},{width:2840,url:"/images/generated/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.1305e599c3-2840.webp"}]},"/images/blog/reflect-alternatives/671793fcf25617e2c132a0e7_659c2b05e0d35a70fefb16a5_screenshot-of-lambdatest.png":{width:2e3,height:1255,isAnimated:!1,fallbackUrl:"/images/blog/reflect-alternatives/671793fcf25617e2c132a0e7_659c2b05e0d35a70fefb16a5_screenshot-of-lambdatest.png",avif:[{width:400,url:"/images/generated/671793fcf25617e2c132a0e7_659c2b05e0d35a70fefb16a5_screenshot-of-lambdatest.f239a8ee80-400.avif"},{width:800,url:"/images/generated/671793fcf25617e2c132a0e7_659c2b05e0d35a70fefb16a5_screenshot-of-lambdatest.f239a8ee80-800.avif"},{width:1200,url:"/images/generated/671793fcf25617e2c132a0e7_659c2b05e0d35a70fefb16a5_screenshot-of-lambdatest.f239a8ee80-1200.avif"},{width:1600,url:"/images/generated/671793fcf25617e2c132a0e7_659c2b05e0d35a70fefb16a5_screenshot-of-lambdatest.f239a8ee80-1600.avif"},{width:2e3,url:"/images/generated/671793fcf25617e2c132a0e7_659c2b05e0d35a70fefb16a5_screenshot-of-lambdatest.f239a8ee80-2000.avif"}],webp:[{width:400,url:"/images/generated/671793fcf25617e2c132a0e7_659c2b05e0d35a70fefb16a5_screenshot-of-lambdatest.f239a8ee80-400.webp"},{width:800,url:"/images/generated/671793fcf25617e2c132a0e7_659c2b05e0d35a70fefb16a5_screenshot-of-lambdatest.f239a8ee80-800.webp"},{width:1200,url:"/images/generated/671793fcf25617e2c132a0e7_659c2b05e0d35a70fefb16a5_screenshot-of-lambdatest.f239a8ee80-1200.webp"},{width:1600,url:"/images/generated/671793fcf25617e2c132a0e7_659c2b05e0d35a70fefb16a5_screenshot-of-lambdatest.f239a8ee80-1600.webp"},{width:2e3,url:"/images/generated/671793fcf25617e2c132a0e7_659c2b05e0d35a70fefb16a5_screenshot-of-lambdatest.f239a8ee80-2000.webp"}]},"/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e861efa94e4ab833c201_image-of-an-error-in-IDE-after-appl
29ying-exhaustive-guard-function.jpeg":{width:936,height:278,isAnimated:!1,fallbackUrl:"/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e861efa94e4ab833c201_image-of-an-error-in-IDE-after-applying-exhaustive-guard-function.jpeg",avif:[{width:400,url:"/images/generated/6568e861efa94e4ab833c201_image-of-an-error-in-IDE-after-applying-exhaustive-guard-function.6d46f1b99c-400.avif"},{width:800,url:"/images/generated/6568e861efa94e4ab833c201_image-of-an-error-in-IDE-after-applying-exhaustive-guard-function.6d46f1b99c-800.avif"},{width:936,url:"/images/generated/6568e861efa94e4ab833c201_image-of-an-error-in-IDE-after-applying-exhaustive-guard-function.6d46f1b99c-936.avif"}],webp:[{width:400,url:"/images/generated/6568e861efa94e4ab833c201_image-of-an-error-in-IDE-after-applying-exhaustive-guard-function.6d46f1b99c-400.webp"},{width:800,url:"/images/generated/6568e861efa94e4ab833c201_image-of-an-error-in-IDE-after-applying-exhaustive-guard-function.6d46f1b99c-800.webp"},{width:936,url:"/images/generated/6568e861efa94e4ab833c201_image-of-an-error-in-IDE-after-applying-exhaustive-guard-function.6d46f1b99c-936.webp"}]},"/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e88527e4347591f9d51a_image-of-an-IDE-after-applying-exhaustive-guard-function.jpeg":{width:936,height:414,isAnimated:!1,fallbackUrl:"/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e88527e4347591f9d51a_image-of-an-IDE-after-applying-exhaustive-guard-function.jpeg",avif:[{width:400,url:"/images/generated/6568e88527e4347591f9d51a_image-of-an-IDE-after-applying-exhaustive-guard-function.5cd9379008-400.avif"},{width:800,url:"/images/generated/6568e88527e4347591f9d51a_image-of-an-IDE-after-applying-exhaustive-guard-function.5cd9379008-800.avif"},{width:936,url:"/images/generated/6568e88527e4347591f9d51a_image-of-an-IDE-after-applying-exhaustive-guard-function.5cd9379008-936.avif"}],webp:[{width:400,url:"/images/generated/6568e88527e4347591f9d51a_image-of-an-IDE-after-applying-exhaustive-guard-function.5cd9379008-400.webp"},{width:800,url:"/images/generated/6568e88527e4347591f9d51a_image-of-an-IDE-after-applying-exhaustive-guard-function.5cd9379008-800.webp"},{width:936,url:"/images/generated/6568e88527e4347591f9d51a_image-of-an-IDE-after-applying-exhaustive-guard-function.5cd9379008-936.webp"}]},"/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e88557c23c2bf31794dd_image-of-an-IDE-using-ESlint-rule-9c1ab.jpeg":{width:936,height:438,isAnimated:!1,fallbackUrl:"/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e88557c23c2bf31794dd_image-of-an-IDE-using-ESlint-rule-9c1ab.jpeg",avif:[{width:400,url:"/images/generated/6568e88557c23c2bf31794dd_image-of-an-IDE-using-ESlint-rule-9c1ab.5359ff8ec6-400.avif"},{width:800,url:"/images/generated/6568e88557c23c2bf31794dd_image-of-an-IDE-using-ESlint-rule-9c1ab.5359ff8ec6-800.avif"},{width:936,url:"/images/generated/6568e88557c23c2bf31794dd_image-of-an-IDE-using-ESlint-rule-9c1ab.5359ff8ec6-936.avif"}],webp:[{width:400,url:"/images/generated/6568e88557c23c2bf31794dd_image-of-an-IDE-using-ESlint-rule-9c1ab.5359ff8ec6-400.webp"},{width:800,url:"/images/generated/6568e88557c23c2bf31794dd_image-of-an-IDE-using-ESlint-rule-9c1ab.5359ff8ec6-800.webp"},{width:936,url:"/images/generated/6568e88557c23c2bf31794dd_image-of-an-IDE-using-ESlint-rule-9c1ab.5359ff8ec6-936.webp"}]},"/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e8859a207665bc8b873a_image-of-an-IDE-when-noImplicitReturns-compiler-is-enabled-c0ji9.jpeg":{width:936,height:428,isAnimated:!1,fallbackUrl:"/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e8859a207665bc8b873a_image-of-an-IDE-when-noImplicitReturns-compiler-is-enabled-c0ji9.jpeg",avif:[{width:400,url:"/images/generated/6568e8859a207665bc8b873a_image-of-an-IDE-when-noImplicitReturns-compiler-is-enabled-c0ji9.034be20491-400.avif"},{width:800,url:"/images/generated/6568e8859a207665bc8b873a_image-of-an-IDE-when-noImplicitReturns-compiler-is-enabled-c0ji9.034be20491-800.avif"},{width:936,url:"/images/generated/6568e8859a207665bc8b873a_image-of-an-IDE-when-noImplicitReturns-compiler-is-enabled-c0ji9.034be20491-936.avif"}],webp:[{width:400,url:"/images/generated/6568e8859a207665bc8b873a_image-of-an-IDE-when-noImplicitReturns-compiler-is-enabled-c0ji9.034be20491-400.webp"},{width:800,url:"/images/generated/6568e8859a207665bc8b873a_image-of-an-IDE-when-noImplicitReturns-compiler-is-enabled-c0ji9.034be20491-800.webp"},{width:936,url:"/images/generated/6568e8859a207665bc8b873a_image-of-an-IDE-when-noImplicitReturns-compiler-is-enabled-c0ji9.034be20491-936.webp"}]},"/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e885bc03f39375be966b_image-of-an-IDE-with-a-general-compilation-error-mvzpc.jpeg":{width:936,height:546,isAnimated:!1,fallbackUrl:"/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e885bc03f39375be966b_image-of-an-IDE-with-a-general-compilation-error-mvzpc.jpeg",avif:[{width:400,url:"/images/generated/6568e885bc03f39375be966b_image-of-an-IDE-with-a-general-compilation-error-mvzpc.b75c39c6b3-400.avif"},{width:800,url:"/images/generated/6568e885bc03f39375be966b_image-of-an-IDE-with-a-general-compilation-error-mvzpc.b75c39c6b3-800.avif"},{width:936,url:"/images/generated/6568e885bc03f39375be966b_image-of-an-IDE-with-a-general-compilation-error-mvzpc.b75c39c6b3-936.avif"}],webp:[{width:400,url:"/images/generated/6568e885bc03f39375be966b_image-of-an-IDE-with-a-general-compilation-error-mvzpc.b75c39c6b3-400.webp"},{width:800,url:"/images/generated/6568e885bc03f39375be966b_image-of-an-IDE-with-a-general-compilation-error-mvzpc.b75c39c6b3-800.webp"},{width:936,url:"/images/generated/6568e885bc03f39375be966b_image-of-an-IDE-with-a-general-compilation-error-mvzpc.b75c39c6b3-936.webp"}]},"/images/blog/safer-exhaustive-switch-statements-in-typescript/656a1934d09a3c1cc575f9b0_safer-exhaustive-switch-statements-in-typescript.png":{width:1880,height:1080,isAnimated:!1,fallbackUrl:"/images/blog/safer-exhaustive-switch-statements-in-typescript/656a1934d09a3c1cc575f9b0_safer-exhaustive-switch-statements-in-typescript.png",avif:[{width:400,url:"/images/generated/656a1934d09a3c1cc575f9b0_safer-exhaustive-switch-statements-in-typescript.59b0ef698a-400.avif"},{width:800,url:"/images/generated/656a1934d09a3c1cc575f9b0_safer-exhaustive-switch-statements-in-typescript.59b0ef698a-800.avif"},{width:1200,url:"/images/generated/656a1934d09a3c1cc575f9b0_safer-exhaustive-switch-statements-in-typescript.59b0ef698a-1200.avif"},{width:1600,url:"/images/generated/656a1934d09a3c1cc575f9b0_safer-exhaustive-switch-statements-in-typescript.59b0ef698a-1600.avif"},{width:1880,url:"/images/generated/656a1934d09a3c1cc575f9b0_safer-exhaustive-switch-statements-in-typescript.59b0ef698a-1880.avif"}],webp:[{width:400,url:"/images/generated/656a1934d09a3c1cc575f9b0_safer-exhaustive-switch-statements-in-typescript.59b0ef698a-400.webp"},{width:800,url:"/images/generated/656a1934d09a3c1cc575f9b0_safer-exhaustive-switch-statements-in-typescript.59b0ef698a-800.webp"},{width:1200,url:"/images/generated/656a1934d09a3c1cc575f9b0_safer-exhaustive-switch-statements-in-typescript.59b0ef698a-1200.webp"},{width:1600,url:"/images/generated/656a1934d09a3c1cc575f9b0_safer-exhaustive-switch-statements-in-typescript.59b0ef698a-1600.webp"},{width:1880,url:"/images/generated/656a1934d09a3c1cc575f9b0_safer-exhaustive-switch-statements-in-typescript.59b0ef698a-1880.webp"}]},"/images/blog/saucelabs-alternatives/65675603ffedcc5b58a56f2d_meticulous-testing-app.png":{width:2e3,height:1295,isAnimated:!1,fallbackUrl:"/images/blog/saucelabs-alternatives/65675603ffedcc5b58a56f2d_meticulous-testing-app.png",avif:[{width:400,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-400.avif"},{width:800,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-800.avif"},{width:1200,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-1200.avif"},{width:1600,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-1600.avif"},{width:2e3,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-2000.avif"}],webp:[{width:400,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-400.webp"},{width:800,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-800.webp"},{width:1200,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-1200.webp"},{width:1600,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-1600.webp"},{width:2e3,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-2000.webp"}]},"/images/blog/saucelabs-alternatives/6568b28da09f13dbb5abaaee_saucelabs-features-and-pricing.png":{width:2e3,height:870,isAnimated:!1,fallbackUrl:"/images/blog/saucelabs-alternatives/6568b28da09f13dbb5abaaee_saucelabs-features-and-pricing.png",avif:[{width:400,url:"/images/generated/6568b28da09f13dbb5abaaee_saucelabs-features-and-pricing.7e32613dde-400.avif"},{width:800,url:"/images/generated/6568b28da09f13dbb5abaaee_saucelabs-features-and-pricing.7e32613dde-800.avif"},{width:1200,url:"/images/generated/6568b28da09f13dbb5abaaee_saucelabs-features-and-pricing.7e32613dde-1200.avif"},{width:1600,url:"/images/generated/6568b28da09f13dbb5abaaee_saucelabs-features-and-pricing.7e32613dde-1600.avif"},{width:2e3,url:"/images/generated/6568b28da09f13dbb5abaaee_saucelabs-features-and-pricing.7e32613dde-2000.avif"}],webp:[{width:400,url:"/images/generated/6568b28da09f13dbb5abaaee_saucelabs-features-and-pricing.7e32613dde-400.webp"},{width:800,url:"/images/generated/6568b28da09f13dbb5abaaee_saucelabs-features-and-pricing.7e32613dde-800.webp"},{width:1200,url:"/images/generated/6568b28da09f13dbb5abaaee_saucelabs-features-and-pricing.7e32613dde-1200.webp"},{width:1600,url:"/images/generated/6568b28da09f13dbb5abaaee_saucelabs-features-and-pricing.7e32613dde-1600.webp"},{width:2e3,url:"/images/generated/6568b28da09f13dbb5abaaee_saucelabs-features-and-pricing.7e32613dde-2000.webp"}]},"/images/blog/saucelabs-alternatives/6568b28e32c9755f582f1724_lambatest-testing-capabilities.png":{width:2e3,height:1255,isAnimated:!1,fallbackUrl:"/images/blog/saucelabs-alternatives/6568b28e32c9755f582f1724_lambatest-testing-capabilities.png",avif:[{width:400,url:"/images/generated/6568b28e32c9755f582f1724_lambatest-testing-capabilities.821808731a-400.avif"},{width:800,url:"/images/generated/6568b28e32c9755f582f1724_lambatest-testing-capabilities.821808731a-800.avif"},{width:1200,url:"/images/generated/6568b28e32c9755f582f1724_lambatest-testing-capabilities.821808731a-1200.avif"},{width:1600,url:"/images/generated/6568b28e32c9755f582f1724_lambatest-testing-capabilities.821808731a-1600.avif"},{width:2e3,url:"/images/generated/6568b28e32c9755f582f1724_lambatest-testing-capabilities.821808731a-2000.avif"}],webp:[{width:400,url:"/images/generated/6568b28e32c9755f582f1724_lambatest-testing-capabilities.821808731a-400.webp"},{width:800,url:"/images/generated/6568b28e32c9755f582f1724_lambatest-testing-capabilities.821808731a-800.webp"},{width:1200,url:"/images/generated/6568b28e32c9755f582f1724_lambatest-testing-capabilities.821808731a-1200.webp"},{width:1600,url:"/images/generated/6568b28e32c9755f582f1724_lambatest-testing-capabilities.821808731a-1600.webp"},{width:2e3,url:"/images/generated/6568b28e32c9755f582f1724_lambatest-testing-capabilities.821808731a-2000.webp"}]},"/images/blog/saucelabs-alternatives/6568b28e3bf5da4e516877aa_testcomplete-testing-tool.png":{width:2e3,height:1030,isAnimated:!1,fallbackUrl:"/images/blog/saucelabs-alternatives/6568b28e3bf5da4e516877aa_testcomplete-testing-tool.png",avif:[{width:400,url:"/images/generated/6568b28e3bf5da4e516877aa_testcomplete-testing-tool.99d55cbdf8-400.avif"},{width:800,url:"/images/generated/6568b28e3bf5da4e516877aa_testcomplete-testing-tool.99d55cbdf8-800.avif"},{width:1200,url:"/images/generated/6568b28e3bf5da4e516877aa_testcomplete-testing-tool.99d55cbdf8-1200.avif"},{width:1600,url:"/images/generated/6568b28e3bf5da4e516877aa_testcomplete-testing-tool.99d55cbdf8-1600.avif"},{width:2e3,url:"/images/generated/6568b28e3bf5da4e516877aa_testcomplete-testing-tool.99d55cbdf8-2000.avif"}],webp:[{width:400,url:"/images/generated/6568b28e3bf5da4e516877aa_testcomplete-testing-tool.99d55cbdf8-400.webp"},{width:800,url:"/images/generated/6568b28e3bf5da4e516877aa_testcomplete-testing-tool.99d55cbdf8-800.webp"},{width:1200,url:"/images/generated/6568b28e3bf5da4e516877aa_testcomplete-testing-tool.99d55cbdf8-1200.webp"},{width:1600,url:"/images/generated/6568b28e3bf5da4e516877aa_testcomplete-testing-tool.99d55cbdf8-1600.webp"},{width:2e3,url:"/images/generated/6568b28e3bf5da4e516877aa_testcomplete-testing-tool.99d55cbdf8-2000.webp"}]},"/images/blog/saucelabs-alternatives/6568b28e4021fb690413c201_saucelabs-testing-capabilities.png":{width:2e3,height:1094,isAnimated:!1,fallbackUrl:"/images/blog/saucelabs-alternatives/6568b28e4021fb690413c201_saucelabs-testing-capabilities.png",avif:[{width:400,url:"/images/generated/6568b28e4021fb690413c201_saucelabs-testing-capabilities.0b2afbc0d8
29-400.avif"},{width:800,url:"/images/generated/6568b28e4021fb690413c201_saucelabs-testing-capabilities.0b2afbc0d8-800.avif"},{width:1200,url:"/images/generated/6568b28e4021fb690413c201_saucelabs-testing-capabilities.0b2afbc0d8-1200.avif"},{width:1600,url:"/images/generated/6568b28e4021fb690413c201_saucelabs-testing-capabilities.0b2afbc0d8-1600.avif"},{width:2e3,url:"/images/generated/6568b28e4021fb690413c201_saucelabs-testing-capabilities.0b2afbc0d8-2000.avif"}],webp:[{width:400,url:"/images/generated/6568b28e4021fb690413c201_saucelabs-testing-capabilities.0b2afbc0d8-400.webp"},{width:800,url:"/images/generated/6568b28e4021fb690413c201_saucelabs-testing-capabilities.0b2afbc0d8-800.webp"},{width:1200,url:"/images/generated/6568b28e4021fb690413c201_saucelabs-testing-capabilities.0b2afbc0d8-1200.webp"},{width:1600,url:"/images/generated/6568b28e4021fb690413c201_saucelabs-testing-capabilities.0b2afbc0d8-1600.webp"},{width:2e3,url:"/images/generated/6568b28e4021fb690413c201_saucelabs-testing-capabilities.0b2afbc0d8-2000.webp"}]},"/images/blog/saucelabs-alternatives/6568b28e688649424b1923fc_perfecto-mobile-testing.png":{width:2e3,height:1039,isAnimated:!1,fallbackUrl:"/images/blog/saucelabs-alternatives/6568b28e688649424b1923fc_perfecto-mobile-testing.png",avif:[{width:400,url:"/images/generated/6568b28e688649424b1923fc_perfecto-mobile-testing.2f606ce4f3-400.avif"},{width:800,url:"/images/generated/6568b28e688649424b1923fc_perfecto-mobile-testing.2f606ce4f3-800.avif"},{width:1200,url:"/images/generated/6568b28e688649424b1923fc_perfecto-mobile-testing.2f606ce4f3-1200.avif"},{width:1600,url:"/images/generated/6568b28e688649424b1923fc_perfecto-mobile-testing.2f606ce4f3-1600.avif"},{width:2e3,url:"/images/generated/6568b28e688649424b1923fc_perfecto-mobile-testing.2f606ce4f3-2000.avif"}],webp:[{width:400,url:"/images/generated/6568b28e688649424b1923fc_perfecto-mobile-testing.2f606ce4f3-400.webp"},{width:800,url:"/images/generated/6568b28e688649424b1923fc_perfecto-mobile-testing.2f606ce4f3-800.webp"},{width:1200,url:"/images/generated/6568b28e688649424b1923fc_perfecto-mobile-testing.2f606ce4f3-1200.webp"},{width:1600,url:"/images/generated/6568b28e688649424b1923fc_perfecto-mobile-testing.2f606ce4f3-1600.webp"},{width:2e3,url:"/images/generated/6568b28e688649424b1923fc_perfecto-mobile-testing.2f606ce4f3-2000.webp"}]},"/images/blog/saucelabs-alternatives/6568b28ea9051e969944026e_browserstack-testing-app.png":{width:2e3,height:1358,isAnimated:!1,fallbackUrl:"/images/blog/saucelabs-alternatives/6568b28ea9051e969944026e_browserstack-testing-app.png",avif:[{width:400,url:"/images/generated/6568b28ea9051e969944026e_browserstack-testing-app.c746042d0b-400.avif"},{width:800,url:"/images/generated/6568b28ea9051e969944026e_browserstack-testing-app.c746042d0b-800.avif"},{width:1200,url:"/images/generated/6568b28ea9051e969944026e_browserstack-testing-app.c746042d0b-1200.avif"},{width:1600,url:"/images/generated/6568b28ea9051e969944026e_browserstack-testing-app.c746042d0b-1600.avif"},{width:2e3,url:"/images/generated/6568b28ea9051e969944026e_browserstack-testing-app.c746042d0b-2000.avif"}],webp:[{width:400,url:"/images/generated/6568b28ea9051e969944026e_browserstack-testing-app.c746042d0b-400.webp"},{width:800,url:"/images/generated/6568b28ea9051e969944026e_browserstack-testing-app.c746042d0b-800.webp"},{width:1200,url:"/images/generated/6568b28ea9051e969944026e_browserstack-testing-app.c746042d0b-1200.webp"},{width:1600,url:"/images/generated/6568b28ea9051e969944026e_browserstack-testing-app.c746042d0b-1600.webp"},{width:2e3,url:"/images/generated/6568b28ea9051e969944026e_browserstack-testing-app.c746042d0b-2000.webp"}]},"/images/blog/saucelabs-alternatives/6568b28ef44b50a9172518a3_feedback-on-meticulous.png":{width:2e3,height:729,isAnimated:!1,fallbackUrl:"/images/blog/saucelabs-alternatives/6568b28ef44b50a9172518a3_feedback-on-meticulous.png",avif:[{width:400,url:"/images/generated/6568b28ef44b50a9172518a3_feedback-on-meticulous.2c346839af-400.avif"},{width:800,url:"/images/generated/6568b28ef44b50a9172518a3_feedback-on-meticulous.2c346839af-800.avif"},{width:1200,url:"/images/generated/6568b28ef44b50a9172518a3_feedback-on-meticulous.2c346839af-1200.avif"},{width:1600,url:"/images/generated/6568b28ef44b50a9172518a3_feedback-on-meticulous.2c346839af-1600.avif"},{width:2e3,url:"/images/generated/6568b28ef44b50a9172518a3_feedback-on-meticulous.2c346839af-2000.avif"}],webp:[{width:400,url:"/images/generated/6568b28ef44b50a9172518a3_feedback-on-meticulous.2c346839af-400.webp"},{width:800,url:"/images/generated/6568b28ef44b50a9172518a3_feedback-on-meticulous.2c346839af-800.webp"},{width:1200,url:"/images/generated/6568b28ef44b50a9172518a3_feedback-on-meticulous.2c346839af-1200.webp"},{width:1600,url:"/images/generated/6568b28ef44b50a9172518a3_feedback-on-meticulous.2c346839af-1600.webp"},{width:2e3,url:"/images/generated/6568b28ef44b50a9172518a3_feedback-on-meticulous.2c346839af-2000.webp"}]},"/images/blog/saucelabs-alternatives/656a0097cf5de148a77a9eae_saucelabs-alternatives.png":{width:940,height:540,isAnimated:!1,fallbackUrl:"/images/blog/saucelabs-alternatives/656a0097cf5de148a77a9eae_saucelabs-alternatives.png",avif:[{width:400,url:"/images/generated/656a0097cf5de148a77a9eae_saucelabs-alternatives.433adce3a3-400.avif"},{width:800,url:"/images/generated/656a0097cf5de148a77a9eae_saucelabs-alternatives.433adce3a3-800.avif"},{width:940,url:"/images/generated/656a0097cf5de148a77a9eae_saucelabs-alternatives.433adce3a3-940.avif"}],webp:[{width:400,url:"/images/generated/656a0097cf5de148a77a9eae_saucelabs-alternatives.433adce3a3-400.webp"},{width:800,url:"/images/generated/656a0097cf5de148a77a9eae_saucelabs-alternatives.433adce3a3-800.webp"},{width:940,url:"/images/generated/656a0097cf5de148a77a9eae_saucelabs-alternatives.433adce3a3-940.webp"}]},"/images/blog/series-a/6a573faa128fb0c4eacd01d8_Frame-2--1-.png":{width:1200,height:627,isAnimated:!1,fallbackUrl:"/images/blog/series-a/6a573faa128fb0c4eacd01d8_Frame-2--1-.png",avif:[{width:400,url:"/images/generated/6a573faa128fb0c4eacd01d8_Frame-2--1-.498cf0d84c-400.avif"},{width:800,url:"/images/generated/6a573faa128fb0c4eacd01d8_Frame-2--1-.498cf0d84c-800.avif"},{width:1200,url:"/images/generated/6a573faa128fb0c4eacd01d8_Frame-2--1-.498cf0d84c-1200.avif"}],webp:[{width:400,url:"/images/generated/6a573faa128fb0c4eacd01d8_Frame-2--1-.498cf0d84c-400.webp"},{width:800,url:"/images/generated/6a573faa128fb0c4eacd01d8_Frame-2--1-.498cf0d84c-800.webp"},{width:1200,url:"/images/generated/6a573faa128fb0c4eacd01d8_Frame-2--1-.498cf0d84c-1200.webp"}]},"/images/blog/smoke-testing/655db87c8fb904aae00d6ad0_smoke-testting-main.png":{width:944,height:542,isAnimated:!1,fallbackUrl:"/images/blog/smoke-testing/655db87c8fb904aae00d6ad0_smoke-testting-main.png",avif:[{width:400,url:"/images/generated/655db87c8fb904aae00d6ad0_smoke-testting-main.3eb84c3485-400.avif"},{width:800,url:"/images/generated/655db87c8fb904aae00d6ad0_smoke-testting-main.3eb84c3485-800.avif"},{width:944,url:"/images/generated/655db87c8fb904aae00d6ad0_smoke-testting-main.3eb84c3485-944.avif"}],webp:[{width:400,url:"/images/generated/655db87c8fb904aae00d6ad0_smoke-testting-main.3eb84c3485-400.webp"},{width:800,url:"/images/generated/655db87c8fb904aae00d6ad0_smoke-testting-main.3eb84c3485-800.webp"},{width:944,url:"/images/generated/655db87c8fb904aae00d6ad0_smoke-testting-main.3eb84c3485-944.webp"}]},"/images/blog/smoke-testing/65675603ffedcc5b58a56f2d_meticulous-testing-app.png":{width:2e3,height:1295,isAnimated:!1,fallbackUrl:"/images/blog/smoke-testing/65675603ffedcc5b58a56f2d_meticulous-testing-app.png",avif:[{width:400,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-400.avif"},{width:800,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-800.avif"},{width:1200,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-1200.avif"},{width:1600,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-1600.avif"},{width:2e3,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-2000.avif"}],webp:[{width:400,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-400.webp"},{width:800,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-800.webp"},{width:1200,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-1200.webp"},{width:1600,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-1600.webp"},{width:2e3,url:"/images/generated/65675603ffedcc5b58a56f2d_meticulous-testing-app.f82dc105a5-2000.webp"}]},"/images/blog/smoke-testing/65675604ba11c5bb11af8f8a_automating-a-task.png":{width:1140,height:926,isAnimated:!1,fallbackUrl:"/images/blog/smoke-testing/65675604ba11c5bb11af8f8a_automating-a-task.png",avif:[{width:400,url:"/images/generated/65675604ba11c5bb11af8f8a_automating-a-task.51860a6679-400.avif"},{width:800,url:"/images/generated/65675604ba11c5bb11af8f8a_automating-a-task.51860a6679-800.avif"},{width:1140,url:"/images/generated/65675604ba11c5bb11af8f8a_automating-a-task.51860a6679-1140.avif"}],webp:[{width:400,url:"/images/generated/65675604ba11c5bb11af8f8a_automating-a-task.51860a6679-400.webp"},{width:800,url:"/images/generated/65675604ba11c5bb11af8f8a_automating-a-task.51860a6679-800.webp"},{width:1140,url:"/images/generated/65675604ba11c5bb11af8f8a_automating-a-task.51860a6679-1140.webp"}]},"/images/blog/smoke-testing/65675605baad647a9398a057_visual-diffs-on-a-meticulous-app.png":{width:2e3,height:1125,isAnimated:!1,fallbackUrl:"/images/blog/smoke-testing/65675605baad647a9398a057_visual-diffs-on-a-meticulous-app.png",avif:[{width:400,url:"/images/generated/65675605baad647a9398a057_visual-diffs-on-a-meticulous-app.d152043a5f-400.avif"},{width:800,url:"/images/generated/65675605baad647a9398a057_visual-diffs-on-a-meticulous-app.d152043a5f-800.avif"},{width:1200,url:"/images/generated/65675605baad647a9398a057_visual-diffs-on-a-meticulous-app.d152043a5f-1200.avif"},{width:1600,url:"/images/generated/65675605baad647a9398a057_visual-diffs-on-a-meticulous-app.d152043a5f-1600.avif"},{width:2e3,url:"/images/generated/65675605baad647a9398a057_visual-diffs-on-a-meticulous-app.d152043a5f-2000.avif"}],webp:[{width:400,url:"/images/generated/65675605baad647a9398a057_visual-diffs-on-a-meticulous-app.d152043a5f-400.webp"},{width:800,url:"/images/generated/65675605baad647a9398a057_visual-diffs-on-a-meticulous-app.d152043a5f-800.webp"},{width:1200,url:"/images/generated/65675605baad647a9398a057_visual-diffs-on-a-meticulous-app.d152043a5f-1200.webp"},{width:1600,url:"/images/generated/65675605baad647a9398a057_visual-diffs-on-a-meticulous-app.d152043a5f-1600.webp"},{width:2e3,url:"/images/generated/65675605baad647a9398a057_visual-diffs-on-a-meticulous-app.d152043a5f-2000.webp"}]},"/images/blog/speed-up-your-frontend-monorepo-build/6568d8b608eb1ebee8035709_235961689-70570278-397c-474b-8e5d-51eff764805d.png":{width:1676,height:1294,isAnimated:!1,fallbackUrl:"/images/blog/speed-up-your-frontend-monorepo-build/6568d8b608eb1ebee8035709_235961689-70570278-397c-474b-8e5d-51eff764805d.png",avif:[{width:400,url:"/images/generated/6568d8b608eb1ebee8035709_235961689-70570278-397c-474b-8e5d-51eff764805d.86b6a72e6c-400.avif"},{width:800,url:"/images/generated/6568d8b608eb1ebee8035709_235961689-70570278-397c-474b-8e5d-51eff764805d.86b6a72e6c-800.avif"},{width:1200,url:"/images/generated/6568d8b608eb1ebee8035709_235961689-70570278-397c-474b-8e5d-51eff764805d.86b6a72e6c-1200.avif"},{width:1600,url:"/images/generated/6568d8b608eb1ebee8035709_235961689-70570278-397c-474b-8e5d-51eff764805d.86b6a72e6c-1600.avif"},{width:1676,url:"/images/generated/6568d8b608eb1ebee8035709_235961689-70570278-397c-474b-8e5d-51eff764805d.86b6a72e6c-1676.avif"}],webp:[{width:400,url:"/images/generated/6568d8b608eb1ebee8035709_235961689-70570278-397c-474b-8e5d-51eff764805d.86b6a72e6c-400.webp"},{width:800,url:"/images/generated/6568d8b608eb1ebee8035709_235961689-70570278-397c-474b-8e5d-51eff764805d.86b6a72e6c-800.webp"},{width:1200,url:"/images/generated/6568d8b608eb1ebee8035709_235961689-70570278-397c-474b-8e5d-51eff764805d.86b6a72e6c-1200.webp"},{width:1600,url:"/images/generated/6568d8b608eb1ebee8035709_235961689-70570278-397c-474b-8e5d-51eff764805d.86b6a72e6c-1600.webp"},{width:1676,url:"/images/generated/6568d8b608eb1ebee8035709_235961689-70570278-397c-474b-8e5d-51eff764805d.86b6a72e6c-1676.webp"}]},"/images/blog/speed-up-your-frontend-monorepo-build/656a186285f6674a84d471c4_speed-up-your-frontend-monorepo-build.png":{width:1880,height:1082,isAnimated:!1,fallbackUrl:"/images/blog/speed-up-your-frontend-monorepo-build/656a186285f6674a84d471c4_speed-up-your-frontend-monorepo-build.png",avif:[{width:400,url:"/images/generated/656a186285f6674a84d471c4_speed-up-your-frontend-monorepo-build.ad3c35bf2d-400.avif"},{width:800,url:"/images/generated/656a186285f6674a84d471c4_speed-up-your-frontend-monorepo-build.ad3c35bf2d-800.avif"},{width:1200,url:"/images/generated/656a186285f6674a84d471c4_speed-up-your-frontend-monorepo-build.ad3c35bf2d-1200.avif"},{width:1600,url:"/images/generated/656a186285f6674a84d471c4_speed-up-your-frontend-monorepo-build.ad3c35bf2d-1600.avif"},{width:1880,url:"/images/generated/656a186285f6674a84d471c4_speed-up-your-frontend-monorepo-build.ad3c35bf2d-1880.avif"}],webp:[{width:400,url:"/images/generated/656a186285f6674a84d471c4_speed-up-your-frontend-monorepo-build.ad3c35bf2d-400.webp"},{width:800,url:"/images/generated/656a186285f6674a84d471c4_speed-up-your-frontend-monorepo-build.ad3c35bf2d-800.webp"},{width:1200,url:"/images/generated/656a186285f6674a84d471c4_speed-up-your-frontend-monorepo-build.ad3c35bf2d-1200.webp"},{width:1600,url:"/images/generated/656a186285f6674a84d471c4_speed-up-your-frontend-monorepo-build.ad3c35bf2d-1600.webp"},{width:1880,url:"/images/generated/656a186285f6674a84d471c4_speed-up-your-frontend-monorepo-build.ad3c35bf2d-1880.webp"}]},"/images/blog/testing-pyramid-for-frontend/65691d32cd8a0c688b0e2e64_testing-pyramid-for-frontend.webp":{width:1200,height:630,isAnimated:!1,fallbackUrl:"/images/blog/testing-pyramid-for-frontend/65691d32cd8a0c688b0e2e64_testing-pyramid-for-frontend.webp",avif:[{width:400,url:"/images/generated/65691d32cd8a0c688b0e2e64_testing-pyramid-for-frontend.63b7c8e1a1-400.avif"},{width:800,url:"/images/generated/65691d32cd8a0c688b0e2e64_testing-pyramid-for-frontend.63b7c8e1a1-800.avif"},{width:1200,url:"/images/generated/65691d32cd8a0c688b0e2e64_testing-pyramid-for-frontend.63b7c8e1a1-1200.avif"}],webp:[{width:400,url:"/images/generated/65691d32cd8a0c688b0e2e64_testing-pyramid-for-frontend.63b7c8e1a1-400.webp"},{width:800,url:"/images/generated/65691d32cd8a0c688b0e2e64_testing-pyramid-for-frontend.63b7c8e1a1-800.webp"},{width:1200,url:"/images/generated/65691d32cd8a0c688b0e2e64_testing-pyramid-for-frontend.63b7c8e1a1-1200.webp"}]},"/images/blog/testing-pyramid-for-frontend/65691d48627ed3e15f2d4569_testing-pyramid.jpeg":{width:650,height:444,isAnimated:!1,fallbackUrl:"/images/blog/testing-pyramid-for-frontend/65691d48627ed3e15f2d4569_testing-pyramid.jpeg",avif:[{width:400,url:"/images/generated/65691d48627ed3e15f2d4569_testing-pyramid.8d3e3cd6d7-400.avif"}
29,{width:650,url:"/images/generated/65691d48627ed3e15f2d4569_testing-pyramid.8d3e3cd6d7-650.avif"}],webp:[{width:400,url:"/images/generated/65691d48627ed3e15f2d4569_testing-pyramid.8d3e3cd6d7-400.webp"},{width:650,url:"/images/generated/65691d48627ed3e15f2d4569_testing-pyramid.8d3e3cd6d7-650.webp"}]},"/images/blog/testing-react-components-with-storybook/65691ed0908a038c2f08e339_testing-react-components-with-storybook.webp":{width:1200,height:630,isAnimated:!1,fallbackUrl:"/images/blog/testing-react-components-with-storybook/65691ed0908a038c2f08e339_testing-react-components-with-storybook.webp",avif:[{width:400,url:"/images/generated/65691ed0908a038c2f08e339_testing-react-components-with-storybook.cda624a109-400.avif"},{width:800,url:"/images/generated/65691ed0908a038c2f08e339_testing-react-components-with-storybook.cda624a109-800.avif"},{width:1200,url:"/images/generated/65691ed0908a038c2f08e339_testing-react-components-with-storybook.cda624a109-1200.avif"}],webp:[{width:400,url:"/images/generated/65691ed0908a038c2f08e339_testing-react-components-with-storybook.cda624a109-400.webp"},{width:800,url:"/images/generated/65691ed0908a038c2f08e339_testing-react-components-with-storybook.cda624a109-800.webp"},{width:1200,url:"/images/generated/65691ed0908a038c2f08e339_testing-react-components-with-storybook.cda624a109-1200.webp"}]},"/images/blog/testing-react-components-with-storybook/65691ee2282dd37a09d1c47f_repository.gif":{width:600,height:338,isAnimated:!0,fallbackUrl:"/images/blog/testing-react-components-with-storybook/65691ee2282dd37a09d1c47f_repository.gif",avif:[],webp:[{width:600,url:"/images/generated/65691ee2282dd37a09d1c47f_repository.caf7b6bb19.webp"}]},"/images/blog/testing-react-components-with-storybook/65691f102c3ab90e368d0ef1_storybook-directory-containing-some-configuration-files.png":{width:253,height:428,isAnimated:!1,fallbackUrl:"/images/blog/testing-react-components-with-storybook/65691f102c3ab90e368d0ef1_storybook-directory-containing-some-configuration-files.png",avif:[{width:253,url:"/images/generated/65691f102c3ab90e368d0ef1_storybook-directory-containing-some-configuration-files.fc61d091cb-253.avif"}],webp:[{width:253,url:"/images/generated/65691f102c3ab90e368d0ef1_storybook-directory-containing-some-configuration-files.fc61d091cb-253.webp"}]},"/images/blog/testing-react-components-with-storybook/65691f2e6c73ea3ca9889094_storybook.png":{width:650,height:287,isAnimated:!1,fallbackUrl:"/images/blog/testing-react-components-with-storybook/65691f2e6c73ea3ca9889094_storybook.png",avif:[{width:400,url:"/images/generated/65691f2e6c73ea3ca9889094_storybook.dee0658330-400.avif"},{width:650,url:"/images/generated/65691f2e6c73ea3ca9889094_storybook.dee0658330-650.avif"}],webp:[{width:400,url:"/images/generated/65691f2e6c73ea3ca9889094_storybook.dee0658330-400.webp"},{width:650,url:"/images/generated/65691f2e6c73ea3ca9889094_storybook.dee0658330-650.webp"}]},"/images/blog/testing-react-components-with-storybook/65691f61326d6e8f8b8f2d28_stories--20the-building-blocks-of-storybook.png":{width:650,height:326,isAnimated:!1,fallbackUrl:"/images/blog/testing-react-components-with-storybook/65691f61326d6e8f8b8f2d28_stories--20the-building-blocks-of-storybook.png",avif:[{width:400,url:"/images/generated/65691f61326d6e8f8b8f2d28_stories--20the-building-blocks-of-storybook.7b8f874ea8-400.avif"},{width:650,url:"/images/generated/65691f61326d6e8f8b8f2d28_stories--20the-building-blocks-of-storybook.7b8f874ea8-650.avif"}],webp:[{width:400,url:"/images/generated/65691f61326d6e8f8b8f2d28_stories--20the-building-blocks-of-storybook.7b8f874ea8-400.webp"},{width:650,url:"/images/generated/65691f61326d6e8f8b8f2d28_stories--20the-building-blocks-of-storybook.7b8f874ea8-650.webp"}]},"/images/blog/testing-react-components-with-storybook/65691fb7080bcad1469888ae_storybook-controls.png":{width:650,height:481,isAnimated:!1,fallbackUrl:"/images/blog/testing-react-components-with-storybook/65691fb7080bcad1469888ae_storybook-controls.png",avif:[{width:400,url:"/images/generated/65691fb7080bcad1469888ae_storybook-controls.e9a9bd2baa-400.avif"}
29,{width:650,url:"/images/generated/65691fb7080bcad1469888ae_storybook-controls.e9a9bd2baa-650.avif"}],webp:[{width:400,url:"/images/generated/65691fb7080bcad1469888ae_storybook-controls.e9a9bd2baa-400.webp"},{width:650,url:"/images/generated/65691fb7080bcad1469888ae_storybook-controls.e9a9bd2baa-650.webp"}]},"/images/blog/testing-react-components-with-storybook/65691fec81f4a6c477f1389e_storybook-docs.png":{width:650,height:206,isAnimated:!1,fallbackUrl:"/images/blog/testing-react-components-with-storybook/65691fec81f4a6c477f1389e_storybook-docs.png",avif:[{width:400,url:"/images/generated/65691fec81f4a6c477f1389e_storybook-docs.4114ad2c0a-400.avif"},{width:650,url:"/images/generated/65691fec81f4a6c477f1389e_storybook-docs.4114ad2c0a-650.avif"}],webp:[{width:400,url:"/images/generated/65691fec81f4a6c477f1389e_storybook-docs.4114ad2c0a-400.webp"},{width:650,url:"/images/generated/65691fec81f4a6c477f1389e_storybook-docs.4114ad2c0a-650.webp"}]},"/images/blog/the-beginner-guide-to-visual-regression-testing/6568e7175b6250bf2de936f0_visual-difference-caught-by-visual-regression-test.png":{width:936,height:422,isAnimated:!1,fallbackUrl:"/images/blog/the-beginner-guide-to-visual-regression-testing/6568e7175b6250bf2de936f0_visual-difference-caught-by-visual-regression-test.png",avif:[{width:400,url:"/images/generated/6568e7175b6250bf2de936f0_visual-difference-caught-by-visual-regression-test.4bf87ef10e-400.avif"},{width:800,url:"/images/generated/6568e7175b6250bf2de936f0_visual-difference-caught-by-visual-regression-test.4bf87ef10e-800.avif"},{width:936,url:"/images/generated/6568e7175b6250bf2de936f0_visual-difference-caught-by-visual-regression-test.4bf87ef10e-936.avif"}],webp:[{width:400,url:"/images/generated/6568e7175b6250bf2de936f0_visual-difference-caught-by-visual-regression-test.4bf87ef10e-400.webp"},{width:800,url:"/images/generated/6568e7175b6250bf2de936f0_visual-difference-caught-by-visual-regression-test.4bf87ef10e-800.webp"},{width:936,url:"/images/generated/6568e7175b6250bf2de936f0_visual-difference-caught-by-visual-regression-test.4bf87ef10e-936.webp"}]},"/images/blog/the-beginner-guide-to-visual-regression-testing/6568e717627d73bb56b40040_american-airlines-homepage.png":{width:630,height:1224,isAnimated:!1,fallbackUrl:"/images/blog/the-beginner-guide-to-visual-regression-testing/6568e717627d73bb56b40040_american-airlines-homepage.png",avif:[{width:400,url:"/images/generated/6568e717627d73bb56b40040_american-airlines-homepage.0b965cad98-400.avif"},{width:630,url:"/images/generated/6568e717627d73bb56b40040_american-airlines-homepage.0b965cad98-630.avif"}],webp:[{width:400,url:"/images/generated/6568e717627d73bb56b40040_american-airlines-homepage.0b965cad98-400.webp"},{width:630,url:"/images/generated/6568e717627d73bb56b40040_american-airlines-homepage.0b965cad98-630.webp"}]},"/images/blog/the-beginner-guide-to-visual-regression-testing/656a2ae55bc91e9b6399c2af_visual-regression-testing-guide.png":{width:1880,height:1080,isAnimated:!1,fallbackUrl:"/images/blog/the-beginner-guide-to-visual-regression-testing/656a2ae55bc91e9b6399c2af_visual-regression-testing-guide.png",avif:[{width:400,url:"/images/generated/656a2ae55bc91e9b6399c2af_visual-regression-testing-guide.7f9a611759-400.avif"},{width:800,url:"/images/generated/656a2ae55bc91e9b6399c2af_visual-regression-testing-guide.7f9a611759-800.avif"},{width:1200,url:"/images/generated/656a2ae55bc91e9b6399c2af_visual-regression-testing-guide.7f9a611759-1200.avif"},{width:1600,url:"/images/generated/656a2ae55bc91e9b6399c2af_visual-regression-testing-guide.7f9a611759-1600.avif"},{width:1880,url:"/images/generated/656a2ae55bc91e9b6399c2af_visual-regression-testing-guide.7f9a611759-1880.avif"}],webp:[{width:400,url:"/images/generated/656a2ae55bc91e9b6399c2af_visual-regression-testing-guide.7f9a611759-400.webp"},{width:800,url:"/images/generated/656a2ae55bc91e9b6399c2af_visual-regression-testing-guide.7f9a611759-800.webp"},{width:1200,url:"/images/generated/656a2ae55bc91e9b6399c2af_visual-regression-testing-guide.7f9a611759-1200.webp"},{width:1600,url:"/images/generated/656a2ae55bc91e9b6399c2af_visual-regression-testing-guide.7f9a611759-1600.webp"},{width:1880,url:"/images/generated/656a2ae55bc91e9b6399c2af_visual-regression-testing-guide.7f9a611759-1880.webp"}]},"/images/blog/typescript-unit-tests-with-debugging/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.png":{width:2140,height:480,isAnimated:!1,fallbackUrl:"/images/blog/typescript-unit-tests-with-debugging/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.png",avif:[{width:400,url:"/images/generated/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.896d814615-400.avif"},{width:800,url:"/images/generated/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.896d814615-800.avif"},{width:1200,url:"/images/generated/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.896d814615-1200.avif"},{width:1600,url:"/images/generated/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.896d814615-1600.avif"},{width:2e3,url:"/images/generated/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.896d814615-2000.avif"},{width:2140,url:"/images/generated/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.896d814615-2140.avif"}],webp:[{width:400,url:"/images/generated/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.896d814615-400.webp"},{width:800,url:"/images/generated/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.896d814615-800.webp"},{width:1200,url:"/images/generated/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.896d814615-1200.webp"},{width:1600,url:"/images/generated/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.896d814615-1600.webp"},{width:2e3,url:"/images/generated/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.896d814615-2000.webp"},{width:2140,url:"/images/generated/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.896d814615-2140.webp"}]},"/images/blog/typescript-unit-tests-with-debugging/656921821be83eca40ab3746_screenshot-jest-test-explorer.png":{width:2140,height:406,isAnimated:!1,fallbackUrl:"/images/blog/typescript-unit-tests-with-debugging/656921821be83eca40ab3746_screenshot-jest-test-explorer.png",avif:[{width:400,url:"/images/generated/656921821be83eca40ab3746_screenshot-jest-test-explorer.97256f85b8-400.avif"},{width:800,url:"/images/generated/656921821be83eca40ab3746_screenshot-jest-test-explorer.97256f85b8-800.avif"},{width:1200,url:"/images/generated/656921821be83eca40ab3746_screenshot-jest-test-explorer.97256f85b8-1200.avif"},{width:1600,url:"/images/generated/656921821be83eca40ab3746_screenshot-jest-test-explorer.97256f85b8-1600.avif"},{width:2e3,url:"/images/generated/656921821be83eca40ab3746_screenshot-jest-test-explorer.97256f85b8-2000.avif"},{width:2140,url:"/images/generated/656921821be83eca40ab3746_screenshot-jest-test-explorer.97256f85b8-2140.av
29if"}],webp:[{width:400,url:"/images/generated/656921821be83eca40ab3746_screenshot-jest-test-explorer.97256f85b8-400.webp"},{width:800,url:"/images/generated/656921821be83eca40ab3746_screenshot-jest-test-explorer.97256f85b8-800.webp"},{width:1200,url:"/images/generated/656921821be83eca40ab3746_screenshot-jest-test-explorer.97256f85b8-1200.webp"},{width:1600,url:"/images/generated/656921821be83eca40ab3746_screenshot-jest-test-explorer.97256f85b8-1600.webp"},{width:2e3,url:"/images/generated/656921821be83eca40ab3746_screenshot-jest-test-explorer.97256f85b8-2000.webp"},{width:2140,url:"/images/generated/656921821be83eca40ab3746_screenshot-jest-test-explorer.97256f85b8-2140.webp"}]},"/images/blog/typescript-unit-tests-with-debugging/6569218253363e298b084f8d_screenshot-of-tests-passing.png":{width:866,height:370,isAnimated:!1,fallbackUrl:"/images/blog/typescript-unit-tests-with-debugging/6569218253363e298b084f8d_screenshot-of-tests-passing.png",avif:[{width:400,url:"/images/generated/6569218253363e298b084f8d_screenshot-of-tests-passing.7750619781-400.avif"},{width:866,url:"/images/generated/6569218253363e298b084f8d_screenshot-of-tests-passing.7750619781-866.avif"}],webp:[{width:400,url:"/images/generated/6569218253363e298b084f8d_screenshot-of-tests-passing.7750619781-400.webp"}]},"/images/blog/typescript-unit-tests-with-debugging/656921825de0eb8b8d3edd7e_screenshot-jest-start-all-runners.png":{width:1192,height:350,isAnimated:!1,fallbackUrl:"/images/blog/typescript-unit-tests-with-debugging/656921825de0eb8b8d3edd7e_screenshot-jest-start-all-runners.png",avif:[{width:400,url:"/images/generated/656921825de0eb8b8d3edd7e_screenshot-jest-start-all-runners.a8aaf10dbe-400.avif"},{width:800,url:"/images/generated/656921825de0eb8b8d3edd7e_screenshot-jest-start-all-runners.a8aaf10dbe-800.avif"},{width:1192,url:"/images/generated/656921825de0eb8b8d3edd7e_screenshot-jest-start-all-runners.a8aaf10dbe-1192.avif"}],webp:[{width:400,url:"/images/generated/656921825de0eb8b8d3edd7e_screenshot-jest-start-all-runners.a8aaf10dbe-400.webp"},{width:800,url:"/images/generated/656921825de0eb8b8d3edd7e_screenshot-jest-start-all-runners.a8aaf10dbe-800.webp"}]},"/images/blog/typescript-unit-tests-with-debugging/65692182a0e399077eb26033_screenshot-jest-debug-test.png":{width:2480,height:480,isAnimated:!1,fallbackUrl:"/images/blog/typescript-unit-tests-with-debugging/65692182a0e399077eb26033_screenshot-jest-debug-test.png",avif:[{width:400,url:"/images/generated/65692182a0e399077eb26033_screenshot-jest-debug-test.7ab60fc916-400.avif"},{width:800,url:"/images/generated/65692182a0e399077eb26033_screenshot-jest-debug-test.7ab60fc916-800.avif"},{width:1200,url:"/images/generated/65692182a0e399077eb26033_screenshot-jest-debug-test.7ab60fc916-1200.avif"},{width:1600,url:"/images/generated/65692182a0e399077eb26033_screenshot-jest-debug-test.7ab60fc916-1600.avif"},{width:2e3,url:"/images/generated/65692182a0e399077eb26033_screenshot-jest-debug-test.7ab60fc916-2000.avif"},{width:2480,url:"/images/generated/65692182a0e399077eb26033_screenshot-jest-debug-test.7ab60fc916-2480.avif"}],webp:[{width:400,url:"/images/generated/65692182a0e399077eb26033_screenshot-jest-debug-test.7ab60fc916-400.webp"},{width:800,url:"/images/generated/65692182a0e399077eb26033_screenshot-jest-debug-test.7ab60fc916-800.webp"},{width:1200,url:"/images/generated/65692182a0e399077eb26033_screenshot-jest-debug-test.7ab60fc916-1200.webp"},{width:1600,url:"/images/generated/65692182a0e399077eb26033_screenshot-jest-debug-test.7ab60fc916-1600.webp"},{width:2e3,url:"/images/generated/65692182a0e399077eb26033_screenshot-jest-debug-test.7ab60fc916-2000.webp"},{width:2480,url:"/images/generated/65692182a0e399077eb26033_screenshot-jest-debug-test.7ab60fc916-2480.webp"}]},"/images/blog/typescript-unit-tests-with-debugging/65692182d4036d9bcea8e2b6_screenshot-of-vscode-jest.png":{width:906,height:352,isAnimated:!1,fallbackUrl:"/images/blog/typescript-unit-tests-with-debugging/65692182d4036d9bcea8e2b6_screenshot-of-vscode-jest.png",avif:[{width:400,url:"/images/generated/65692182d4036d9bcea8e2b6_screenshot-of-vscode-jest.9b3c90c8b7-400.avif"},{width:800,url:"/images/generated/65692182d4036d9bcea8e2b6_screenshot-of-vscode-jest.9b3c90c8b7-800.avif"},{width:906,url:"/images/generated/65692182d4036d9bcea8e2b6_screenshot-of-vscode-jest.9b3c90c8b7-906.avif"}],webp:[{width:400,url:"/images/generated/65692182d4036d9bcea8e2b6_screenshot-of-vscode-jest.9b3c90c8b7-400.webp"},{width:800,url:"/images/generated/65692182d4036d9bcea8e2b6_screenshot-of-vscode-jest.9b3c90c8b7-800.webp"},{width:906,url:"/images/generated/65692182d4036d9bcea8e2b6_screenshot-of-vscode-jest.9b3c90c8b7-906.webp"}]},"/images/blog/typescript-unit-tests-with-debugging/656a1916ec3f99329fd2ef53_jest-typescript.png":{width:1882,height:1080,isAnimated:!1,fallbackUrl:"/images/blog/typescript-unit-tests-with-debugging/656a1916ec3f99329fd2ef53_jest-typescript.png",avif:[{width:400,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-400.avif"},{width:800,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-800.avif"},{width:1200,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-1200.avif"},{width:1600,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-1600.avif"},{width:1882,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-1882.avif"}],webp:[{width:400,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-400.webp"},{width:800,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-800.webp"},{width:1200,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-1200.webp"},{width:1600,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-1600.webp"},{width:1882,url:"/images/generated/656a1916ec3f99329fd2ef53_jest-typescript.e691e7d1a0-1882.webp"}]},"/images/blog/visual-regression-testing-tools/6568e66d4f55fc65a5a12c48_applitools-website.jpeg":{width:936,height:580,isAnimated:!1,fallbackUrl:"/images/blog/visual-regression-testing-tools/6568e66d4f55fc65a5a12c48_applitools-website.jpeg",avif:[{width:400,url:"/images/generated/6568e66d4f55fc65a5a12c48_applitools-website.ad4cff8ca4-400.avif"},{width:800,url:"/images/generated/6568e66d4f55fc65a5a12c48_applitools-website.ad4cff8ca4-800.avif"},{width:936,url:"/images/generated/6568e66d4f55fc65a5a12c48_applitools-website.ad4cff8ca4-936.avif"}],webp:[{width:400,url:"/images/generated/6568e66d4f55fc65a5a12c48_applitools-website.ad4cff8ca4-400.webp"},{width:800,url:"/images/generated/6568e66d4f55fc65a5a12c48_applitools-website.ad4cff8ca4-800.webp"},{width:936,url:"/images/generated/6568e66d4f55fc65a5a12c48_applitools-website.ad4cff8ca4-936.webp"}]},"/images/blog/visual-regression-testing-tools/6568e66d8815fbded00efeed_selenium-website.jpeg":{width:936,height:578,isAnimated:!1,fallbackUrl:"/images/blog/visual-regression-testing-tools/6568e66d8815fbded00efeed_selenium-website.jpeg",avif:[{width:400,url:"/images/generated/6568e66d8815fbded00efeed_selenium-website.724c0eb75d-400.avif"},{width:800,url:"/images/generated/6568e66d8815fbded00efeed_selenium-website.724c0eb75d-800.avif"},{width:936,url:"/images/generated/6568e66d8815fbded00efeed_selenium-website.724c0eb75d-936.avif"}],webp:[{width:400,url:"/images/generated/6568e66d8815fbded00efeed_selenium-website.724c0eb75d-400.webp"},{width:800,url:"/images/generated/6568e66d8815fbded00efeed_selenium-website.724c0eb75d-800.webp"},{width:936,url:"/images/generated/6568e66d8815fbded00efeed_selenium-website.724c0eb75d-936.webp"}]},"/images/blog/visual-regression-testing-tools/6568e66dd87e81eeff576386_playwright-website.jpeg":{width:936,height:580,isAnimated:!1,fallbackUrl:"/images/blog/visual-regression-testing-tools/6568e66dd87e81eeff576386_playwright-website.jpeg",avif:[{width:400,url:"/images/generated/6568e66dd87e81eeff576386_playwright-website.c3f3a4adb6-400.avif"},{width:800,url:"/images/generated/6568e66dd87e81eeff576386_playwright-website.c3f3a4adb6-800.avif"},{width:936,url:"/images/generated/6568e66dd87e81eeff576386_playwright-website.c3f3a4adb6-936.avif"}],webp:[{width:400,url:"/images/generated/6568e66dd87e81eeff576386_playwright-website.c3f3a4adb6-400.webp"},{width:800,url:"/images/generated/6568e66dd87e81eeff576386_playwright-website.c3f3a4adb6-800.webp"},{width:936,url:"/images/generated/6568e66dd87e81eeff576386_playwright-website.c3f3a4adb6-936.webp"}]},"/images/blog/visual-regression-testing-tools/6568e66de6e36b1c79c62618_percy-website.jpeg":{width:936,height:596,isAnimated:!1,fallbackUrl:"/images/blog/visual-regression-testing-tools/6568e66de6e36b1c79c62618_percy-website.jpeg",avif:[{width:400,url:"/images/generated/6568e66de6e36b1c79c62618_percy-website.8878f1bc5c-400.avif"},{width:800,url:"/images/generated/6568e66de6e36b1c79c62618_percy-website.8878f1bc5c-800.avif"},{width:936,url:"/images/generated/6568e66de6e36b1c79c62618_percy-website.8878f1bc5c-936.avif"}],webp:[{width:400,url:"/images/generated/6568e66de6e36b1c79c62618_percy-website.8878f1bc5c-400.webp"},{width:800,url:"/images/generated/6568e66de6e36b1c79c62618_percy-website.8878f1bc5c-800.webp"},{width:936,url:"/images/generated/6568e66de6e36b1c79c62618_percy-website.8878f1bc5c-936.webp"}]},"/images/blog/visual-regression-testing-tools/6568e66e0d89b7585c751d93_cypress-website.jpeg":{width:936,height:594,isAnimated:!1,fallbackUrl:"/images/blog/visual-regression-testing-tools/6568e66e0d89b7585c751d93_cypress-website.jpeg",avif:[{width:400,url:"/images/generated/6568e66e0d89b7585c751d93_cypress-website.903fcb48ec-400.avif"},{width:800,url:"/images/generated/6568e66e0d89b7585c751d93_cypress-website.903fcb48ec-800.avif"},{width:936,url:"/images/generated/6568e66e0d89b7585c751d93_cypress-website.903fcb48ec-936.avif"}],webp:[{width:400,url:"/images/generated/6568e66e0d89b7585c751d93_cypress-website.903fcb48ec-400.webp"},{width:800,url:"/images/generated/6568e66e0d89b7585c751d93_cypress-website.903fcb48ec-800.webp"},{width:936,url:"/images/generated/6568e66e0d89b7585c751d93_cypress-website.903fcb48ec-936.webp"}]},"/images/blog/visual-regression-testing-tools/656a1c5a262bc9e761f0d471_visual-regression-testing-tools.png":{width:1882,height:1082,isAnimated:!1,fallbackUrl:"/images/blog/visual-regression-testing-tools/656a1c5a262bc9e761f0d471_visual-regression-testing-tools.png",avif:[{width:400,url:"/images/generated/656a1c5a262bc9e761f0d471_visual-regression-testing-tools.e6b658016f-400.avif"},{width:800,url:"/images/generated/656a1c5a262bc9e761f0d471_visual-regression-testing-tools.e6b658016f-800.avif"},{width:1200,url:"/images/generated/656a1c5a262bc9e761f0d471_visual-regression-testing-tools.e6b658016f-1200.avif"},{width:1600,url:"/images/generated/656a1c5a262bc9e761f0d471_visual-regression-testing-tools.e6b658016f-1600.avif"},{width:1882,url:"/images/generated/656a1c5a262bc9e761f0d471_visual-regression-testing-tools.e6b658016f-1882.avif"}],webp:[{width:400,url:"/images/generated/656a1c5a262bc9e761f0d471_visual-regression-testing-tools.e6b658016f-400.webp"},{width:800,url:"/images/generated/656a1c5a262bc9e761f0d471_visual-regression-testing-tools.e6b658016f-800.webp"},{width:1200,url:"/images/generated/656a1c5a262bc9e761f0d471_visual-regression-testing-tools.e6b658016f-1200.webp"},{width:1600,url:"/images/generated/656a1c5a262bc9e761f0d471_visual-regression-testing-tools.e6b658016f-1600.webp"},{width:1882,url:"/images/generated/656a1c5a262bc9e761f0d471_visual-regression-testing-tools.e6b658016f-1882.webp"}]}
29,"/images/blog/visual-regression-testing-tools/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.png":{width:2860,height:1610,isAnimated:!1,fallbackUrl:"/images/blog/visual-regression-testing-tools/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.png",avif:[{width:400,url:"/images/generated/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.c02d99a934-400.avif"},{width:800,url:"/images/generated/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.c02d99a934-800.avif"},{width:1200,url:"/images/generated/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.c02d99a934-1200.avif"},{width:1600,url:"/images/generated/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.c02d99a934-1600.avif"},{width:2e3,url:"/images/generated/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.c02d99a934-2000.avif"},{width:2860,url:"/images/generated/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.c02d99a934-2860.avif"}],webp:[{width:400,url:"/images/generated/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.c02d99a934-400.webp"},{width:800,url:"/images/generated/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.c02d99a934-800.webp"},{width:1200,url:"/images/generated/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.c02d99a934-1200.webp"},{width:1600,url:"/images/generated/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.c02d99a934-1600.webp"},{width:2e3,url:"/images/generated/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.c02d99a934-2000.webp"},{width:2860,url:"/images/generated/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.c02d99a934-2860.webp"}]},"/images/blog/visual-regressions-and-how-to-prevent-them/6569056a522396bb11419376_setting-up-snapshot-tests.png":{width:266,height:663,isAnimated:!1,fallbackUrl:"/images/blog/visual-regressions-and-how-to-prevent-them/6569056a522396bb11419376_setting-up-snapshot-tests.png",avif:[{width:266,url:"/images/generated/6569056a522396bb11419376_setting-up-snapshot-tests.c041d39d18-266.avif"}],webp:[{width:266,url:"/images/generated/6569056a522396bb11419376_setting-up-snapshot-tests.c041d39d18-266.webp"}]},"/images/blog/visual-regressions-and-how-to-prevent-them/656906794908530b2b34919a_visual-regression-testing-examples.png":{width:1999,height:664,isAnimated:!1,fallbackUrl:"/images/blog/visual-regressions-and-how-to-prevent-them/656906794908530b2b34919a_visual-regression-testing-examples.png",avif:[{width:400,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-400.avif"},{width:800,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-800.avif"},{width:1200,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-1200.avif"},{width:1600,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-1600.avif"},{width:1999,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-1999.avif"}],webp:[{width:400,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-400.webp"},{width:800,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-800.webp"},{width:1200,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-1200.webp"},{width:1600,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-1600.webp"},{width:1999,url:"/images/generated/656906794908530b2b34919a_visual-regression-testing-examples.e4b67b222b-1999.webp"}]},"/images/blog/visual-regressions-and-how-to-prevent-them/656922ba1baf9f0a659075e0_pyramid-of-tests.png":{width:315,height:240,isAnimated:!1,fallbackUrl:"/images/blog/visual-regressions-and-how-to-prevent-them/656922ba1baf9f0a659075e0_pyramid-of-tests.png",avif:[{width:315,url:"/images/generated/656922ba1baf9f0a659075e0_pyramid-of-tests.8fe3ad28c9-315.avif"}],webp:[{width:315,url:"/images/generated/656922ba1baf9f0a659075e0_pyramid-of-tests.8fe3ad28c9-315.webp"}]},"/images/blog/visual-regressions-and-how-to-prevent-them/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.png":{width:2832,height:1728,isAnimated:!1,fallbackUrl:"/images/blog/visual-regressions-and-how-to-prevent-them/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.png",avif:[{width:400,url:"/images/generated/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.2a08952cce-400.avif"},{width:800,url:"/images/generated/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.2a08952cce-800.avif"},{width:1200,url:"/images/generated/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.2a08952cce-1200.avif"},{width:1600,url:"/images/generated/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.2a08952cce-1600.avif"},{width:2e3,url:"/images/generated/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.2a08952cce-2000.avif"},{width:2832,url:"/images/generated/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.2a08952cce-2832.avif"}],webp:[{width:400,url:"/images/generated/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.2a08952cce-400.webp"},{width:800,url:"/images/generated/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.2a08952cce-800.webp"},{width:1200,url:"/images/generated/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.2a08952cce-1200.webp"},{width:1600,url:"/images/generated/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.2a08952cce-1600.webp"},{width:2e3,url:"/images/generated/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.2a08952cce-2000.webp"},{width:2832,url:"/images/generated/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.2a08952cce-2832.webp"}]}
29,"/images/card-bg.webp":{width:882,height:411,isAnimated:!1,fallbackUrl:"/images/card-bg.webp",avif:[{width:400,url:"/images/generated/card-bg.ccfe5a5e9b-400.avif"},{width:800,url:"/images/generated/card-bg.ccfe5a5e9b-800.avif"},{width:882,url:"/images/generated/card-bg.ccfe5a5e9b-882.avif"}],webp:[{width:400,url:"/images/generated/card-bg.ccfe5a5e9b-400.webp"},{width:800,url:"/images/generated/card-bg.ccfe5a5e9b-800.webp"},{width:882,url:"/images/generated/card-bg.ccfe5a5e9b-882.webp"}]},"/images/careers-social-image.png":{width:2400,height:1260,isAnimated:!1,fallbackUrl:"/images/careers-social-image.png",avif:[{width:400,url:"/images/generated/careers-social-image.0225cf6475-400.avif"},{width:800,url:"/images/generated/careers-social-image.0225cf6475-800.avif"},{width:1200,url:"/images/generated/careers-social-image.0225cf6475-1200.avif"},{width:1600,url:"/images/generated/careers-social-image.0225cf6475-1600.avif"},{width:2e3,url:"/images/generated/careers-social-image.0225cf6475-2000.avif"},{width:2400,url:"/images/generated/careers-social-image.0225cf6475-2400.avif"}],webp:[{width:400,url:"/images/generated/careers-social-image.0225cf6475-400.webp"},{width:800,url:"/images/generated/careers-social-image.0225cf6475-800.webp"},{width:1200,url:"/images/generated/careers-social-image.0225cf6475-1200.webp"},{width:1600,url:"/images/generated/careers-social-image.0225cf6475-1600.webp"},{width:2e3,url:"/images/generated/careers-social-image.0225cf6475-2000.webp"},{width:2400,url:"/images/generated/careers-social-image.0225cf6475-2400.webp"}]},"/images/case-card-bg-1.webp":{width:942,height:503,isAnimated:!1,fallbackUrl:"/images/case-card-bg-1.webp",avif:[{width:400,url:"/images/generated/case-card-bg-1.9b938a4317-400.avif"},{width:800,url:"/images/generated/case-card-bg-1.9b938a4317-800.avif"},{width:942,url:"/images/generated/case-card-bg-1.9b938a4317-942.avif"}],webp:[{width:400,url:"/images/generated/case-card-bg-1.9b938a4317-400.webp"},{width:800,url:"/images/generated/case-card-bg-1.9b938a4317-800.webp"},{width:942,url:"/images/generated/case-card-bg-1.9b938a4317-942.webp"}]},"/images/cases-placeholder.png":{width:1523,height:839,isAnimated:!1,fallbackUrl:"/images/cases-placeholder.png",avif:[{width:400,url:"/images/generated/cases-placeholder.2ed106d7dc-400.avif"},{width:800,url:"/images/generated/cases-placeholder.2ed106d7dc-800.avif"},{width:1200,url:"/images/generated/cases-placeholder.2ed106d7dc-1200.avif"},{width:1523,url:"/images/generated/cases-placeholder.2ed106d7dc-1523.avif"}],webp:[{width:400,url:"/images/generated/cases-placeholder.2ed106d7dc-400.webp"},{width:800,url:"/images/generated/cases-placeholder.2ed106d7dc-800.webp"},{width:1200,url:"/images/generated/cases-placeholder.2ed106d7dc-1200.webp"},{width:1523,url:"/images/generated/cases-placeholder.2ed106d7dc-1523.webp"}]},"/images/cases_bg-2-small.webp":{width:1523,height:485,isAnimated:!1,fallbackUrl:"/images/cases_bg-2-small.webp",avif:[{width:400,url:"/images/generated/cases_bg-2-small.02a5718b07-400.avif"},{width:800,url:"/images/generated/cases_bg-2-small.02a5718b07-800.avif"},{width:1200,url:"/images/generated/cases_bg-2-small.02a5718b07-1200.avif"},{width:1523,url:"/images/generated/cases_bg-2-small.02a5718b07-1523.avif"}],webp:[{width:400,url:"/images/generated/cases_bg-2-small.02a5718b07-400.webp"},{width:800,url:"/images/generated/cases_bg-2-small.02a5718b07-800.webp"},{width:1200,url:"/images/generated/cases_bg-2-small.02a5718b07-1200.webp"},{width:1523,url:"/images/generated/cases_bg-2-small.02a5718b07-1523.webp"}]},"/images/cases_bg-small.webp":{width:1523,height:498,isAnimated:!1,fallbackUrl:"/images/cases_bg-small.webp",avif:[{width:400,url:"/images/generated/cases_bg-small.e189fa50d7-400.avif"},{width:800,url:"/images/generated/cases_bg-small.e189fa50d7-800.avif"},{width:1200,url:"/images/generated/cases_bg-small.e189fa50d7-1200.avif"},{width:1523,url:"/images/generated/cases_bg-small.e189fa50d7-1523.avif"}],webp:[{width:400,url:"/images/generated/cases_bg-small.e189fa50d7-400.webp"},{width:800,url:"/images/generated/cases_bg-small.e189fa50d7-800.webp"},{width:1200,url:"/images/generated/cases_bg-small.e189fa50d7-1200.webp"},{width:1523,url:"/images/generated/cases_bg-small.e189fa50d7-1523.webp"}]},"/images/chromium.png":{width:944,height:540,isAnimated:!1,fallbackUrl:"/images/chromium.png",avif:[{width:400,url:"/images/generated/chromium.6af1706f40-400.avif"},{width:800,url:"/images/generated/chromium.6af1706f40-800.avif"},{width:944,url:"/images/generated/chromium.6af1706f40-944.avif"}],webp:[{width:400,url:"/images/generated/chromium.6af1706f40-400.webp"},{width:800,url:"/images/generated/chromium.6af1706f40-800.webp"},{width:944,url:"/images/generated/chromium.6af1706f40-944.webp"}]},"/images/ci-install.png":{width:1118,height:1099,isAnimated:!1,fallbackUrl:"/images/ci-install.png",avif:[{width:400,url:"/images/generated/ci-install.b949822ac9-400.avif"},{width:800,url:"/images/generated/ci-install.b949822ac9-800.avif"},{width:1118,url:"/images/generated/ci-install.b949822ac9-1118.avif"}],webp:[{width:400,url:"/images/generated/ci-install.b949822ac9-400.webp"},{width:800,url:"/images/generated/ci-install.b949822ac9-800.webp"},{width:1118,url:"/images/generated/ci-install.b949822ac9-1118.webp"}]},"/images/combination.png":{width:1200,height:708,isAnimated:!1,fallbackUrl:"/images/combination.png",avif:[{width:400,url:"/images/generated/combination.f33a19fcba-400.avif"},{width:800,url:"/images/generated/combination.f33a19fcba-800.avif"},{width:1200,url:"/images/generated/combination.f33a19fcba-1200.avif"}],webp:[{width:400,url:"/images/generated/combination.f33a19fcba-400.webp"},{width:800,url:"/images/generated/combination.f33a19fcba-800.webp"},{width:1200,url:"/images/generated/combination.f33a19fcba-1200.webp"}]},"/images/core.png":{width:1200,height:184,isAnimated:!1,fallbackUrl:"/images/core.png",avif:[{width:400,url:"/images/generated/core.21fa1e9782-400.avif"},{width:800,url:"/images/generated/core.21fa1e9782-800.avif"},{width:1200,url:"/images/generated/core.21fa1e9782-1200.avif"}],webp:[{width:400,url:"/images/generated/core.21fa1e9782-400.webp"},{width:800,url:"/images/generated/core.21fa1e9782-800.webp"},{width:1200,url:"/images/generated/core.21fa1e9782-1200.webp"}]},"/images/cta-bg.png":{width:808,height:128,isAnimated:!1,fallbackUrl:"/images/cta-bg.png",avif:[{width:400,url:"/images/generated/cta-bg.3576f290d0-400.avif"},{width:800,url:"/images/generated/cta-bg.3576f290d0-800.avif"},{width:808,url:"/images/generated/cta-bg.3576f290d0-808.avif"}],webp:[{width:400,url:"/images/generated/cta-bg.3576f290d0-400.webp"},{width:800,url:"/images/generated/cta-bg.3576f290d0-800.webp"},{width:808,url:"/images/generated/cta-bg.3576f290d0-808.webp"}]},"/images/dashboard_1.webp":{width:2474,height:1512,isAnimated:!1,fallbackUrl:"/images/dashboard_1.webp",avif:[{width:400,url:"/images/generated/dashboard_1.eb6b3c9394-400.avif"},{width:800,url:"/images/generated/dashboard_1.eb6b3c9394-800.avif"},{width:1200,url:"/images/generated/dashboard_1.eb6b3c9394-1200.avif"},{width:1600,url:"/images/generated/dashboard_1.eb6b3c9394-1600.avif"},{width:2e3,url:"/images/generated/dashboard_1.eb6b3c9394-2000.avif"},{width:2474,url:"/images/generated/dashboard_1.eb6b3c9394-2474.avif"}],webp:[{width:400,url:"/images/generated/dashboard_1.eb6b3c9394-400.webp"},{width:800,url:"/images/generated/dashboard_1.eb6b3c9394-800.webp"},{width:1200,url:"/images/generated/dashboard_1.eb6b3c9394-1200.webp"},{width:1600,url:"/images/generated/dashboard_1.eb6b3c9394-1600.webp"},{width:2e3,url:"/images/generated/dashboard_1.eb6b3c9394-2000.webp"},{width:2474,url:"/images/generated/dashboard_1.eb6b3c9394-2474.webp"}]},"/images/dashboard_1dashboard.webp":{width:2e3,height:1222,isAnimated:!1,fallbackUrl:"/images/dashboard_1dashboard.webp",avif:[{width:400,url:"/images/generated/dashboard_1dashboard.ab1522b179-400.avif"},{width:800,url:"/images/generated/dashboard_1dashboard.ab1522b179-800.avif"},{width:1200,url:"/images/generated/dashboard_1dashboard.ab1522b179-1200.avif"},{width:1600,url:"/images/generated/dashboard_1dashboard.ab1522b179-1600.avif"},{width:2e3,url:"/images/generated/dashboard_1dashboard.ab1522b179-2000.avif"}],webp:[{width:400,url:"/images/generated/dashboard_1dashboard.ab1522b179-400.webp"},{width:800,url:"/images/generated/dashboard_1dashboard.ab1522b179-800.webp"},{width:1200,url:"/images/generated/dashboard_1dashboard.ab1522b179-1200.webp"},{width:1600,url:"/images/generated/dashboard_1dashboard.ab1522b179-1600.webp"},{width:2e3,url:"/images/generated/dashboard_1dashboard.ab1522b179-2000.webp"}]},"/images/derive-algorithms.png":{width:944,height:540,isAnimated:!1,fallbackUrl:"/images/derive-algorithms.png",avif:[{width:400,url:"/images/generated/derive-algorithms.1d52c435e1-400.avif"},{width:800,url:"/images/generated/derive-algorithms.1d52c435e1-800.avif"},{width:944,url:"/images/generated/derive-algorithms.1d52c435e1-944.avif"}],webp:[{width:400,url:"/images/generated/derive-algorithms.1d52c435e1-400.webp"},{width:800,url:"/images/generated/derive-algorithms.1d52c435e1-800.webp"},{width:944,url:"/images/generated/derive-algorithms.1d52c435e1-944.webp"}]},"/images/dropbox_icon.png":{width:2048,height:2048,isAnimated:!1,fallbackUrl:"/images/dropbox_icon.png",avif:[{width:400,url:"/images/generated/dropbox_icon.9f81669389-400.avif"},{width:800,url:"/images/generated/dropbox_icon.9f81669389-800.avif"},{width:1200,url:"/images/generated/dropbox_icon.9f81669389-1200.avif"},{width:1600,url:"/images/generated/dropbox_icon.9f81669389-1600.avif"},{width:2e3,url:"/images/generated/dropbox_icon.9f81669389-2000.avif"},{width:2048,url:"/images/generated/dropbox_icon.9f81669389-2048.avif"}],webp:[{width:400,url:"/images/generated/dropbox_icon.9f81669389-400.webp"},{width:800,url:"/images/generated/dropbox_icon.9f81669389-800.webp"},{width:1200,url:"/images/generated/dropbox_icon.9f81669389-1200.webp"},{width:1600,url:"/images/generated/dropbox_icon.9f81669389-1600.webp"},{width:2e3,url:"/images/generated/dropbox_icon.9f81669389-2000.webp"},{width:2048,url:"/images/generated/dropbox_icon.9f81669389-2048.webp"}]},"/images/elevenlabs-logo-black.png":{width:2082,height:270,isAnimated:!1,fallbackUrl:"/images/elevenlabs-logo-black.png",avif:[{width:400,url:"/images/generated/elevenlabs-logo-black.7a8df6758c-400.avif"},{width:800,url:"/images/generated/elevenlabs-logo-black.7a8df6758c-800.avif"},{width:1200,url:"/images/generated/elevenlabs-logo-black.7a8df6758c-1200.avif"},{width:1600,url:"/images/generated/elevenlabs-logo-black.7a8df6758c-1600.avif"},{width:2e3,url:"/images/generated/elevenlabs-logo-black.7a8df6758c-2000.avif"},{width:2082,url:"/images/generated/elevenlabs-logo-black.7a8df6758c-2082.avif"}],webp:[{width:400,url:"/images/generated/elevenlabs-logo-black.7a8df6758c-400.webp"},{width:800,url:"/images/generated/elevenlabs-logo-black.7a8df6758c-800.webp"},{width:1200,url:"/images/generated/elevenlabs-logo-black.7a8df6758c-1200.webp"},{width:1600,url:"/images/generated/elevenlabs-logo-black.7a8df6758c-1600.webp"},{width:2082,url:"/images/generated/elevenlabs-logo-black.7a8df6758c-2082.webp"}]},"/images/eliminate.png":{width:1070,height:656,isAnimated:!1,fallbackUrl:"/images/eliminate.png",avif:[{width:400,url:"/images/generated/eliminate.d859f83174-400.avif"},{width:800,url:"/images/generated/eliminate.d859f83174-800.avif"},{width:1070,url:"/images/generated/eliminate.d859f83174-1070.avif"}],webp:[{width:400,url:"/images/generated/eliminate.d859f83174-400.webp"},{width:800,url:"/images/generated/eliminate.d859f83174-800.webp"},{width:1070,url:"/images/generated/eliminate.d859f83174-1070.webp"}]},"/images/engine-logo.png":{width:600,height:229,isAnimated:!1,fallbackUrl:"/images/engine-logo.png",avif:[{width:400,url:"/images/generated/engine-logo.e893b76032-400.avif"},{width:600,url:"/images/generated/engine-logo.e893b76032-600.avif"}],webp:[{width:400,url:"/images/generated/engine-logo.e893b76032-400.webp"},{width:600,url:"/images/generated/engine-logo.e893b76032-600.webp"}]},"/images/erdem.webp":{width:480,height:402,isAnimated:!1,fallbackUrl:"/images/erdem.webp",avif:[{width:400,url:"/images/generated/erdem.348b8240cd-400.avif"},{width:480,url:"/images/generated/erdem.348b8240cd-480.avif"}],webp:[{width:400,url:"/images/generated/erdem.348b8240cd-400.webp"},{width:480,url:"/images/generated/erdem.348b8240cd-480.webp"}]},"/images/execute.png":{width:1200,height:1020,isAnimated:!1,fallbackUrl:"/images/execute.png",avif:[{width:400,url:"/images/generated/execute.de76786688-400.avif"},{width:800,url:"/images/generated/execute.de76786688-800.avif"},{width:1200,url:"/images/generated/execute.de76786688-1200.avif"}],webp:[{width:400,url:"/images/generated/execute.de76786688-400.webp"},{width:800,url:"/images/generated/execute.de76786688-800.webp"},{width:1200,url:"/images/generated/execute.de76786688-1200.webp"}]},"/images/fast-scale.png":{width:1416,height:1361,isAnimated:!1,fallbackUrl:"/images/fast-scale.png",avif:[{width:400,url:"/images/generated/fast-scale.46db936179-400.avif"},{width:800,url:"/images/generated/fast-scale.46db936179-800.avif"},{width:1200,url:"/images/generated/fast-scale.46db936179-1200.avif"},{width:1416,url:"/images/generated/fast-scale.46db936179-1416.avif"}],webp:[{width:400,url:"/images/generated/fast-scale.46db936179-400.webp"},{width:800,url:"/images/generated/fast-scale.46db936179-800.webp"},{width:1200,url:"/images/generated/fast-scale.46db936179-1200.webp"},{width:1416,url:"/images/generated/fast-scale.46db936179-1416.webp"}]},"/images/getstart-texture-new.webp":{width:1668,height:601,isAnimated:!1,fallbackUrl:"/images/getstart-texture-new.webp",avif:[{width:400,url:"/images/generated/getstart-texture-new.fda1cd56af-400.avif"},{width:800,url:"/images/generated/getstart-texture-new.fda1cd56af-800.avif"},{width:1200,url:"/images/generated/getstart-texture-new.fda1cd56af-1200.avif"},{width:1600,url:"/images/generated/getstart-texture-new.fda1cd56af-1600.avif"},{width:1668,url:"/images/generated/getstart-texture-new.fda1cd56af-1668.avif"}],webp:[{width:400,url:"/images/generated/getstart-texture-new.fda1cd56af-400.webp"},{width:800,url:"/images/generated/getstart-texture-new.fda1cd56af-800.webp"},{width:1200,url:"/images/generated/getstart-texture-new.fda1cd56af-1200.webp"},{width:1600,url:"/images/generated/getstart-texture-new.fda1cd56af-1600.webp"},{width:1668,url:"/images/generated/getstart-texture-new.fda1cd56af-1668.webp"}]},"/images/hero-bg-black.webp":{width:2201,height:1496,isAnimated:!1,fallbackUrl:"/images/hero-bg-black.webp",avif:[{width:400,url:"/images/generated/hero-bg-black.7d052c73c8-400.avif"},{width:800,url:"/images/generated/hero-bg-black.7d052c73c8-800.avif"},{width:1200,url:"/images/generated/hero-bg-black.7d052c73c8-1200.avif"},{width:1600,url:"/images/generated/hero-bg-black.7d052c73c8-1600.avif"},{width:2e3,url:"/images/generated/hero-bg-black.7d052c73c8-2000.avif"},{width:2201,url:"/images/generated/hero-bg-black.7d052c73c8-2201.avif"}],webp:[{width:400,url:"/images/generated/hero-bg-black.7d052c73c8-400.webp"},{width:800,url:"/images/generated/hero-bg-black.7d052c73c8-800.webp"},{width:1200,url:"/images/generated/hero-bg-black.7d052c73c8-1200.webp"},{width:1600,url:"/images/generated/hero-bg-black.7d052c73c8-1600.webp"},{width:2e3,url:"/images/generated/hero-bg-black.7d052c73c8-2000.webp"},{width:2201,url:"/images/generated/hero-bg-black.7d052c73c8-2201.webp"}]},"/images/image-1.png":{width:2400,height:1260,isAnimated:!1,fallbackUrl:"/images/image-1.png",avif:[{width:400,url:"/images/generated/image-1.0225cf6475-400.avif"},{width:800,url:"/images/generated/image-1.0225cf6475-800.avif"},{width:1200,url:"/images/generated/image-1.0225cf6475-1200.avif"},{width:1600,url:"/images/generated/image-1.0225cf6475-1600.avif"},{width:2e3,url:"/images/generated/image-1.0225cf6475-2000.avif"},{width:2400,url:"/images/generated/image-1.0225cf6475-2400.avif"}],webp:[{width:400,url:"/images/generated/image-1.0225cf6475-400.webp"},{width:800,url:"/images/generated/image-1.0225cf6475-800.webp"},{width:1200,url:"/images/generated/image-1.0225cf6475-1200.webp"},{width:1600,url:"/images/generated/image-1.0225cf6475-1600.webp"},{width:2e3,url:"/images/generated/image-1.0225cf6475-2000.webp"},{width:2400,url:"/images/generated/image-1.0225cf6475-2400.webp"}]},"/images/jared-frie
29dman.png":{width:112,height:112,isAnimated:!1,fallbackUrl:"/images/jared-friedman.png",avif:[{width:112,url:"/images/generated/jared-friedman.90fa0dd620-112.avif"}],webp:[{width:112,url:"/images/generated/jared-friedman.90fa0dd620-112.webp"}]},"/images/joel-hollingsmith.png":{width:180,height:180,isAnimated:!1,fallbackUrl:"/images/joel-hollingsmith.png",avif:[{width:180,url:"/images/generated/joel-hollingsmith.525557829a-180.avif"}],webp:[{width:180,url:"/images/generated/joel-hollingsmith.525557829a-180.webp"}]},"/images/launchdarkly-Logo.png":{width:1842,height:284,isAnimated:!1,fallbackUrl:"/images/launchdarkly-Logo.png",avif:[{width:400,url:"/images/generated/launchdarkly-Logo.e227fd20f7-400.avif"},{width:800,url:"/images/generated/launchdarkly-Logo.e227fd20f7-800.avif"},{width:1200,url:"/images/generated/launchdarkly-Logo.e227fd20f7-1200.avif"},{width:1842,url:"/images/generated/launchdarkly-Logo.e227fd20f7-1842.avif"}],webp:[{width:400,url:"/images/generated/launchdarkly-Logo.e227fd20f7-400.webp"},{width:1842,url:"/images/generated/launchdarkly-Logo.e227fd20f7-1842.webp"}]},"/images/mercor.webp":{width:780,height:159,isAnimated:!1,fallbackUrl:"/images/mercor.webp",avif:[{width:400,url:"/images/generated/mercor.663f58e617-400.avif"},{width:780,url:"/images/generated/mercor.663f58e617-780.avif"}],webp:[{width:400,url:"/images/generated/mercor.663f58e617-400.webp"}]},"/images/meticulous-256x256-1.png":{width:256,height:256,isAnimated:!1,fallbackUrl:"/images/meticulous-256x256-1.png",avif:[{width:256,url:"/images/generated/meticulous-256x256-1.660b46e2b9-256.avif"}],webp:[{width:256,url:"/images/generated/meticulous-256x256-1.660b46e2b9-256.webp"}]},"/images/meticulous-256x256.png":{width:256,height:256,isAnimated:!1,fallbackUrl:"/images/meticulous-256x256.png",avif:[{width:256,url:"/images/generated/meticulous-256x256.660b46e2b9-256.avif"}],webp:[{width:256,url:"/images/generated/meticulous-256x256.660b46e2b9-256.webp"}]},"/images/meticulous-snapshots.png":{width:1200,height:840,isAnimated:!1,fallbackUrl:"/images/meticulous-snapshots.png",avif:[{width:400,url:"/images/generated/meticulous-snapshots.abc7deb7f0-400.avif"},{width:800,url:"/images/generated/meticulous-snapshots.abc7deb7f0-800.avif"},{width:1200,url:"/images/generated/meticulous-snapshots.abc7deb7f0-1200.avif"}],webp:[{width:400,url:"/images/generated/meticulous-snapshots.abc7deb7f0-400.webp"},{width:800,url:"/images/generated/meticulous-snapshots.abc7deb7f0-800.webp"},{width:1200,url:"/images/generated/meticulous-snapshots.abc7deb7f0-1200.webp"}]},"/images/network-report.png":{width:1200,height:840,isAnimated:!1,fallbackUrl:"/images/network-report.png",avif:[{width:400,url:"/images/generated/network-report.021d859b3a-400.avif"},{width:800,url:"/images/generated/network-report.021d859b3a-800.avif"},{width:1200,url:"/images/generated/network-report.021d859b3a-1200.avif"}],webp:[{width:400,url:"/images/generated/network-report.021d859b3a-400.webp"},{width:800,url:"/images/generated/network-report.021d859b3a-800.webp"},{width:1200,url:"/images/generated/network-report.021d859b3a-1200.webp"}]},"/images/notion-thumb.webp":{width:3348,height:1664,isAnimated:!1,fallbackUrl:"/images/notion-thumb.webp",avif:[{width:400,url:"/images/generated/notion-thumb.c49ed7d3ec-400.avif"},{width:800,url:"/images/generated/notion-thumb.c49ed7d3ec-800.avif"},{width:1200,url:"/images/generated/notion-thumb.c49ed7d3ec-1200.avif"},{width:1600,url:"/images/generated/notion-thumb.c49ed7d3ec-1600.avif"},{width:2e3,url:"/images/generated/notion-thumb.c49ed7d3ec-2000.avif"},{width:3348,url:"/images/generated/notion-thumb.c49ed7d3ec-3348.avif"}],webp:[{width:400,url:"/images/generated/notion-thumb.c49ed7d3ec-400.webp"},{width:800,url:"/images/generated/notion-thumb.c49ed7d3ec-800.webp"},{width:1200,url:"/images/generated/notion-thumb.c49ed7d3ec-1200.webp"},{width:1600,url:"/images/generated/notion-thumb.c49ed7d3ec-1600.webp"},{width:2e3,url:"/images/generated/notion-thumb.c49ed7d3ec-2000.webp"},{width:3348,url:"/images/generated/notion-thumb.c49ed7d3ec-3348.webp"}]},"/images/notion-white.png":{width:252,height:72,isAnimated:!1,fallbackUrl:"/images/notion-white.png",avif:[{width:252,url:"/images/generated/notion-white.7d3f8347fb-252.avif"}],webp:[{width:252,url:"/images/generated/notion-white.7d3f8347fb-252.webp"}]},"/images/patchwork.png":{width:192,height:192,isAnimated:!1,fallbackUrl:"/images/patchwork.png",avif:[{width:192,url:"/images/generated/patchwork.1d6afcb211-192.avif"}],webp:[{width:192,url:"/images/generated/patchwork.1d6afcb211-192.webp"}]},"/images/placeholder.webp":{width:1015,height:573,isAnimated:!1,fallbackUrl:"/images/placeholder.webp",avif:[{width:400,url:"/images/generated/placeholder.bb43210e61-400.avif"},{width:800,url:"/images/generated/placeholder.bb43210e61-800.avif"},{width:1015,url:"/images/generated/placeholder.bb43210e61-1015.avif"}],webp:[{width:400,url:"/images/generated/placeholder.bb43210e61-400.webp"},{width:800,url:"/images/generated/placeholder.bb43210e61-800.webp"},{width:1015,url:"/images/generated/placeholder.bb43210e61-1015.webp"}]},"/images/pull-request.png":{width:1112,height:1242,isAnimated:!1,fallbackUrl:"/images/pull-request.png",avif:[{width:400,url:"/images/generated/pull-request.4b57f53c47-400.avif"},{width:800,url:"/images/generated/pull-request.4b57f53c47-800.avif"},{width:1112,url:"/images/generated/pull-request.4b57f53c47-1112.avif"}],webp:[{width:400,url:"/images/generated/pull-request.4b57f53c47-400.webp"},{width:800,url:"/images/generated/pull-request.4b57f53c47-800.webp"},{width:1112,url:"/images/generated/pull-request.4b57f53c47-1112.webp"}]},"/images/replit-logo.png":{width:540,height:128,isAnimated:!1,fallbackUrl:"/images/replit-logo.png",avif:[{width:400,url:"/images/generated/replit-logo.22934c655d-400.avif"},{width:540,url:"/images/generated/replit-logo.22934c655d-540.av
29if"}],webp:[{width:400,url:"/images/generated/replit-logo.22934c655d-400.webp"},{width:540,url:"/images/generated/replit-logo.22934c655d-540.webp"}]},"/images/snippet-img.png":{width:1196,height:70,isAnimated:!1,fallbackUrl:"/images/snippet-img.png",avif:[{width:400,url:"/images/generated/snippet-img.be6dc60fa5-400.avif"},{width:800,url:"/images/generated/snippet-img.be6dc60fa5-800.avif"},{width:1196,url:"/images/generated/snippet-img.be6dc60fa5-1196.avif"}],webp:[{width:400,url:"/images/generated/snippet-img.be6dc60fa5-400.webp"},{width:800,url:"/images/generated/snippet-img.be6dc60fa5-800.webp"},{width:1196,url:"/images/generated/snippet-img.be6dc60fa5-1196.webp"}]},"/images/social-graph-image.png":{width:1200,height:630,isAnimated:!1,fallbackUrl:"/images/social-graph-image.png",avif:[{width:400,url:"/images/generated/social-graph-image.b804bd1e6d-400.avif"},{width:800,url:"/images/generated/social-graph-image.b804bd1e6d-800.avif"},{width:1200,url:"/images/generated/social-graph-image.b804bd1e6d-1200.avif"}],webp:[{width:400,url:"/images/generated/social-graph-image.b804bd1e6d-400.webp"},{width:800,url:"/images/generated/social-graph-image.b804bd1e6d-800.webp"},{width:1200,url:"/images/generated/social-graph-image.b804bd1e6d-1200.webp"}]},"/images/step-code-coverage.png":{width:1222,height:1126,isAnimated:!1,fallbackUrl:"/images/step-code-coverage.png",avif:[{width:400,url:"/images/generated/step-code-coverage.73fd2a6840-400.avif"},{width:800,url:"/images/generated/step-code-coverage.73fd2a6840-800.avif"},{width:1200,url:"/images/generated/step-code-coverage.73fd2a6840-1200.avif"},{width:1222,url:"/images/generated/step-code-coverage.73fd2a6840-1222.avif"}],webp:[{width:400,url:"/images/generated/step-code-coverage.73fd2a6840-400.webp"},{width:800,url:"/images/generated/step-code-coverage.73fd2a6840-800.webp"},{width:1200,url:"/images/generated/step-code-coverage.73fd2a6840-1200.webp"},{width:1222,url:"/images/generated/step-code-coverage.73fd2a6840-1222.webp"}]},"/images/step-monitors.png":{width:1210,height:952,isAnimated:!1,fallbackUrl:"/images/step-monitors.png",avif:[{width:400,url:"/images/generated/step-monitors.d3fc88e031-400.avif"},{width:800,url:"/images/generated/step-monitors.d3fc88e031-800.avif"},{width:1200,url:"/images/generated/step-monitors.d3fc88e031-1200.avif"},{width:1210,url:"/images/generated/step-monitors.d3fc88e031-1210.avif"}],webp:[{width:400,url:"/images/generated/step-monitors.d3fc88e031-400.webp"},{width:800,url:"/images/generated/step-monitors.d3fc88e031-800.webp"},{width:1200,url:"/images/generated/step-monitors.d3fc88e031-1200.webp"},{width:1210,url:"/images/generated/step-monitors.d3fc88e031-1210.webp"}]},"/images/step-pull-request.png":{width:1112,height:1216,isAnimated:!1,fallbackUrl:"/images/step-pull-request.png",avif:[{width:400,url:"/images/generated/step-pull-request.d4bdec187c-400.avif"},{width:800,url:"/images/generated/step-pull-request.d4bdec187c-800.avif"},{width:1112,url:"/images/generated/step-pull-request.d4bdec187c-1112.avif"}],webp:[{width:400,url:"/images/generated/step-pull-request.d4bdec187c-400.webp"},{width:800,url:"/images/generated/step-pull-request.d4bdec187c-800.webp"},{width:1112,url:"/images/generated/step-pull-request.d4bdec187c-1112.webp"}]},"/images/system-result.png":{width:940,height:540,isAnimated:!1,fallbackUrl:"/images/system-result.png",avif:[{width:400,url:"/images/generated/system-result.01980e856f-400.avif"},{width:800,url:"/images/generated/system-result.01980e856f-800.avif"},{width:940,url:"/images/generated/system-result.01980e856f-940.avif"}],webp:[{width:400,url:"/images/generated/system-result.01980e856f-400.webp"},{width:800,url:"/images/generated/system-result.01980e856f-800.webp"},{width:940,url:"/images/generated/system-result.01980e856f-940.webp"}]},"/images/tony-xu.webp":{width:768,height:768,isAnimated:!1,fallbackUrl:"/images/tony-xu.webp",avif:[{width:400,url:"/images/generated/tony-xu.6051f982f9-400.avif"},{width:768,url:"/images/generated/tony-xu.6051f982f9-768.avif"}],webp:[{width:400,url:"/images/generated/tony-xu.6051f982f9-400.webp"},{width:768,url:"/images/generated/tony-xu.6051f982f9-768.webp"}]},"/images/velocity.png":{width:1279,height:1358,isAnimated:!1,fallbackUrl:"/images/velocity.png",avif:[{width:400,url:"/images/generated/velocity.e8be93ddc2-400.avif"},{width:800,url:"/images/generated/velocity.e8be93ddc2-800.avif"},{width:1200,url:"/images/generated/velocity.e8be93ddc2-1200.avif"},{width:1279,url:"/images/generated/velocity.e8be93ddc2-1279.avif"}],webp:[{width:400,url:"/images/generated/velocity.e8be93ddc2-400.webp"},{width:800,url:"/images/generated/velocity.e8be93ddc2-800.webp"},{width:1200,url:"/images/generated/velocity.e8be93ddc2-1200.webp"},{width:1279,url:"/images/generated/velocity.e8be93ddc2-1279.webp"}]},"/images/video-snapshot.png":{width:1920,height:1080,isAnimated:!1,fallbackUrl:"/images/video-snapshot.png",avif:[{width:400,url:"/images/generated/video-snapshot.a92e482769-400.avif"},{width:800,url:"/images/generated/video-snapshot.a92e482769-800.avif"},{width:1200,url:"/images/generated/video-snapshot.a92e482769-1200.avif"},{width:1600,url:"/images/generated/video-snapshot.a92e482769-1600.avif"},{width:1920,url:"/images/generated/video-snapshot.a92e482769-1920.avif"}],webp:[{width:400,url:"/images/generated/video-snapshot.a92e482769-400.webp"},{width:800,url:"/images/generated/video-snapshot.a92e482769-800.webp"},{width:1200,url:"/images/generated/video-snapshot.a92e482769-1200.webp"},{width:1600,url:"/images/generated/video-snapshot.a92e482769-1600.webp"},{width:1920,url:"/images/generated/video-snapshot.a92e482769-1920.webp"}]},"/images/video-texture-action.png":{width:750,height:822,isAnimated:!1,fallbackUrl:"/images/video-texture-action.png",avif:[{width:400,url:"/images/generated/video-texture-action.bb74349caa-400.avif"},{width:750,url:"/images/generated/video-texture-action.bb74349caa-750.avif"}],webp:[{width:400,url:"/images/generated/video-texture-action.bb74349caa-400.webp"},{width:750,url:"/images/generated/video-texture-action.bb74349caa-750.webp"}]},"/images/video-texture.png":{width:1668,height:1328,isAnimated:!1,fallbackUrl:"/images/video-texture.png",avif:[{width:400,url:"/images/generated/video-texture.d057b55d31-400.avif"},{width:800,url:"/images/generated/video-texture.d057b55d31-800.avif"},{width:1200,url:"/images/generated/video-texture.d057b55d31-1200.avif"},{width:1600,url:"/images/generated/video-texture.d057b55d31-1600.avif"},{width:1668,url:"/images/generated/video-texture.d057b55d31-1668.avif"}],webp:[{width:400,url:"/images/generated/video-texture.d057b55d31-400.webp"},{width:800,url:"/images/generated/video-texture.d057b55d31-800.webp"},{width:1200,url:"/images/generated/video-texture.d057b55d31-1200.webp"},{width:1600,url:"/images/generated/video-texture.d057b55d31-1600.webp"},{width:1668,url:"/images/generated/video-texture.d057b55d31-1668.webp"}]},"/images/wandb-logo.png":{width:1200,height:364,isAnimated:!1,fallbackUrl:"/images/wandb-logo.png",avif:[{width:400,url:"/images/generated/wandb-logo.1fa59140b2-400.avif"},{width:800,url:"/images/generated/wandb-logo.1fa59140b2-800.avif"},{width:1200,url:"/images/generated/wandb-logo.1fa59140b2-1200.avif"}],webp:[{width:400,url:"/images/generated/wandb-logo.1fa59140b2-400.webp"},{width:800,url:"/images/generated/wandb-logo.1fa59140b2-800.webp"},{width:1200,url:"/images/generated/wandb-logo.1fa59140b2-1200.webp"}]},"/images/webclip.png":{width:256,height:256,isAnimated:!1,fallbackUrl:"/images/webclip.png",avif:[{width:256,url:"/images/generated/webclip.660b46e2b9-256.avif"}],webp:[{width:256,url:"/images/generated/webclip.660b46e2b9-256.webp"}]},"/images/wiz.webp":{width:500,height:249,isAnimated:!1,fallbackUrl:"/images/wiz.webp",avif:[{width:400,url:"/images/generated/wiz.6cd0846986-400.avif"}
29,{width:500,url:"/images/generated/wiz.6cd0846986-500.avif"}],webp:[{width:500,url:"/images/generated/wiz.6cd0846986-500.webp"}]}},v9=Object.freeze(Object.defineProperty({__proto__:null,default:y9},Symbol.toStringTag,{value:"Module"})),_9=Object.assign({"../../generated/image-manifest.json":v9}),k9=()=>{const[a]=Object.values(_9);return a?.default??{}},x9=k9(),T9=a=>x9[a],za="https://www.meticulous.ai",S9="Meticulous auto-generates and auto-maintains visual frontend browser tests, providing a level of coverage that is unattainable with manually written tests.",A9=`${za}/images/social-graph-image.png`,Qe=({title:a,description:i=S9,ogImage:s=A9,canonicalPath:r})=>{const c=`${za}${r}`;return{meta:[{name:"description",content:i},{property:"og:title",content:a},{property:"og:description",content:i},{property:"og:image",content:s},{property:"og:type",content:"website"},{property:"og:url",content:c},{name:"twitter:title",content:a},{name:"twitter:description",content:i},{name:"twitter:image",content:s},{name:"twitter:card",content:"summary_large_image"},{title:a}],links:[{rel:"canonical",href:c}]}},E9=(a,i)=>{const s=T9(a);if(!(s===void 0||s.webp.length===0))return{rel:"preload",as:"image",href:s.fallbackUrl,imagesrcset:w9(s.webp),imagesizes:i,fetchpriority:"high"}},E7=({title:a,description:i,canonicalPath:s,datePublished:r,author:c,image:u})=>({"@context":"https://schema.org","@type":"BlogPosting",headline:a,description:i,url:`${za}${s}`,datePublished:r,author:{"@type":"Person",name:c},publisher:{"@type":"Organization",name:"Meticulous AI"},...u===void 0?{}:{image:`${za}${u}`}}),j9={"@context":"https://schema.org","@type":"Organization",name:"Meticulous AI",url:za,logo:`${za}/images/meticulous-256x256.png`,foundingDate:"2021-06-01",contactPoint:{"@type":"ContactPoint",contactType:"customer support",email:"[email protected]"},address:{"@type":"PostalAddress",streetAddress:"Albert House, 256-260 Old St",addressLocality:"London",addressRegion:"Greater London",postalCode:"EC1V 9DD",addressCountry:"GB"},sameAs:["https://www.linkedin.com/company/alwaysmeticulous","https://x.com/meticulousgabe","https://github.com/alwaysmeticulous"],publishes:{"@type":"SoftwareApplication",name:"Meticulous",applicationCategory:["DeveloperApplication","TestingSoftware"],description:"Automated frontend testing platform that records user sessions and catches visual regressions.",operatingSystem:"Web",url:za}},C9="/assets/styles-D-4j5tXq.css",et=n8({head:()=>({meta:[{charSet:"utf-8"},{name:"viewport",content:"width=device-width, initial-scale=1"},{name:"generator",content:"TanStack Start"}],links:[{rel:"llms-txt",href:"/llms.txt"},{rel:"stylesheet",href:C9},{rel:"preload",href:"/fonts/noto-sans/noto-sans-latin.woff2",as:"font",type:"font/woff2",crossOrigin:"anonymous"},{rel:"preload",href:"/fonts/PPNeueMachina-PlainLight.woff2",as:"font",type:"font/woff2",crossOrigin:"anonymous"},{rel:"shortcut icon",href:"/images/favicon.png",type:"image/x-icon"},{rel:"apple-touch-icon",href:"/images/webclip.png"}]}),shellComponent:R9,notFoundComponent:g9}),I9=new Set(["/401","/404"]);function R9({children:a}){const i=vu({select:u=>u.location.pathname}),s=I9.has(i)?void 0:"body",r=c9(),c=m9();return Q.jsxs("html",{lang:"en",children:[Q.jsxs("head",{children:[Q.jsx("script",{"data-recording-token":c,"data-is-production-environment":String(b9()),src:p9}),Q.jsx("script",{dangerouslySetInnerHTML:{__html:l9}}),Q.jsx("script",{id:"cookieyes",src:h9}),r===void 0?null:Q.jsx("script",{dangerouslySetInnerHTML:{__html:r}}),Q.jsx("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(j9)}}),Q.jsx(y8,{})]}),Q.jsxs("body",{className:s,children:[a,Q.jsx(f9,{}),Q.jsx(_8,{})]})]})}const O9="modulepreload",M9=function(a){return"/"+a},Em={},lt=function(i,s,r){let c=Promise.resolve();if(s&&s.length>0){let m=function(g){return Promise.all(g.map(w=>Promise.resolve(w).then(b=>({status:"fulfilled",value:b}),b=>({status:"rejected",reason:b}))))};document.getElementsByTagName("link");const h=document.querySelector("meta[property=csp-nonce]"),f=h?.nonce||h?.getAttribute("nonce");c=m(s.map(g=>{if(g=M9(g),g in Em)return;Em[g]=!0;const w=g.endsWith(".css"),b=w?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${g}"]${b}`))return;const v=document.createElement("link");if(v.rel=w?"stylesheet":O9,w||(v.as="script"),v.crossOrigin="",v.href=g,f&&v.setAttribute("nonce",f),document.head.appendChild(v),w)return new Promise((_,T)=>{v.addEventListener("load",_),v.addEventListener("error",()=>T(new Error(`Unable to preload CSS for ${g}`)))})}))}function u(h){const f=new Event("vite:preloadError",{cancelable:!0});if(f.payload=h,window.dispatchEvent(f),!f.defaultPrevented)throw h}return c.then(h=>{for(const f of h||[])f.status==="rejected"&&u(f.reason);return i().catch(u)})},U9=()=>lt(()=>import("./index-DhAS6qGo.js"),__vite__mapDeps([0,1,2,3])),D9=ot()({head:()=>Qe({title:"Meticulous AI - Automated Frontend Testing Without Writing Tests",canonicalPath:"/"}),component:rt(U9,"component")}),B9=()=>lt(()=>import("./401-CaRJpCiW.js"),[]),L9=ot()({head:()=>Qe({title:"Protected page",canonicalPath:"/401"}),component:rt(B9,"component")}),q9=()=>lt(()=>import("./404-BklnvoNM.js"),[]),z9=ot()({head:()=>Qe({title:"Not Found",canonicalPath:"/404"}),component:rt(q9,"component")}),N9=()=>lt(()=>import("./about-us-F_ftiKXA.js"),__vite__mapDeps([4,1])),H9=ot()({head:()=>Qe({title:"About Us",canonicalPath:"/about-us"}),component:rt(N9,"component")}),P9=()=>lt(()=>import("./book-a-demo-CkLIhBy1.js"),__vite__mapDeps([5,1])),F9=ot()({head:()=>Qe({title:"Book a Demo",canonicalPath:"/book-a-demo"}),component:rt(P9,"component")}),J9=()=>lt(()=>import("./case-studies-Dlw1ASa7.js"),__vite__mapDeps([6,1,2])),G9=ot()({head:()=>Qe({title:"Case Studies",canonicalPath:"/case-studies"}),component:rt(J9,"component")}),Y9=()=>lt(()=>
vendor: 36,134 bytes, lines 29-84
29import("./how-it-works-D8jH6MjO.js"),__vite__mapDeps([7,3,1])),V9=ot()({head:()=>Qe({title:"How it Works",canonicalPath:"/how-it-works"}),component:rt(Y9,"component")}),W9=()=>lt(()=>import("./privacy-policy-4HSC5mFr.js"),__vite__mapDeps([8,1])),Q9=ot()({head:()=>Qe({title:"Privacy Policy",canonicalPath:"/privacy-policy"}),component:rt(W9,"component")}),$9=()=>lt(()=>import("./terms-conditions-BmRqwKjQ.js"),__vite__mapDeps([9,1])),X9=ot()({head:()=>Qe({title:"Terms & Conditions",canonicalPath:"/terms-conditions"}),component:rt($9,"component")});function xu(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}let Ya=xu();function b0(a){Ya=a}const w0=/[&<>"']/,K9=new RegExp(w0.source,"g"),y0=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,Z9=new RegExp(y0.source,"g"),e4={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},jm=a=>e4[a];function Ot(a,i){if(i){if(w0.test(a))return a.replace(K9,jm)}else if(y0.test(a))return a.replace(Z9,jm);return a}const t4=/(^|[^\[])\^/g;function Me(a,i){let s=typeof a=="string"?a:a.source;i=i||"";const r={replace:(c,u)=>{let h=typeof u=="string"?u:u.source;return h=h.replace(t4,"$1"),s=s.replace(c,h),r},getRegex:()=>new RegExp(s,i)};return r}function Cm(a){try{a=encodeURI(a).replace(/%25/g,"%")}catch{return null}return a}const Ns={exec:()=>null};function Im(a,i){const s=a.replace(/\|/g,(u,h,f)=>{let m=!1,g=h;for(;--g>=0&&f[g]==="\\";)m=!m;return m?"|":" |"}),r=s.split(/ \|/);let c=0;if(r[0].trim()||r.shift(),r.length>0&&!r[r.length-1].trim()&&r.pop(),i)if(r.length>i)r.splice(i);else for(;r.length<i;)r.push("");for(;c<r.length;c++)r[c]=r[c].trim().replace(/\\\|/g,"|");return r}function Bs(a,i,s){const r=a.length;if(r===0)return"";let c=0;for(;c<r&&a.charAt(r-c-1)===i;)c++;return a.slice(0,r-c)}function n4(a,i){if(a.indexOf(i[1])===-1)return-1;let s=0;for(let r=0;r<a.length;r++)if(a[r]==="\\")r++;else if(a[r]===i[0])s++;else if(a[r]===i[1]&&(s--,s<0))return r;return-1}function Rm(a,i,s,r){const c=i.href,u=i.title?Ot(i.title):null,h=a[1].replace(/\\([\[\]])/g,"$1");if(a[0].charAt(0)!=="!"){r.state.inLink=!0;const f={type:"link",raw:s,href:c,title:u,text:h,tokens:r.inlineTokens(h)};return r.state.inLink=!1,f}return{type:"image",raw:s,href:c,title:u,text:Ot(h)}}function a4(a,i){const s=a.match(/^(\s+)(?:```)/);if(s===null)return i;const r=s[1];return i.split(`
30`).map(c=>{const u=c.match(/^\s+/);if(u===null)return c;const[h]=u;return h.length>=r.length?c.slice(r.length):c}).join(`
31`)}class Hr{options;rules;lexer;constructor(i){this.options=i||Ya}space(i){const s=this.rules.block.newline.exec(i);if(s&&s[0].length>0)return{type:"space",raw:s[0]}}code(i){const s=this.rules.block.code.exec(i);if(s){const r=s[0].replace(/^ {1,4}/gm,"");return{type:"code",raw:s[0],codeBlockStyle:"indented",text:this.options.pedantic?r:Bs(r,`
32`)}}}fences(i){const s=this.rules.block.fences.exec(i);if(s){const r=s[0],c=a4(r,s[3]||"");return{type:"code",raw:r,lang:s[2]?s[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):s[2],text:c}}}heading(i){const s=this.rules.block.heading.exec(i);if(s){let r=s[2].trim();if(/#$/.test(r)){const c=Bs(r,"#");(this.options.pedantic||!c||/ $/.test(c))&&(r=c.trim())}return{type:"heading",raw:s[0],depth:s[1].length,text:r,tokens:this.lexer.inline(r)}}}hr(i){const s=this.rules.block.hr.exec(i);if(s)return{type:"hr",raw:Bs(s[0],`
33`)}}blockquote(i){const s=this.rules.block.blockquote.exec(i);if(s){let r=Bs(s[0],`
34`).split(`
35`),c="",u="";const h=[];for(;r.length>0;){let f=!1;const m=[];let g;for(g=0;g<r.length;g++)if(/^ {0,3}>/.test(r[g]))m.push(r[g]),f=!0;else if(!f)m.push(r[g]);else break;r=r.slice(g);const w=m.join(`
36`),b=w.replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,`
37    $1`).replace(/^ {0,3}>[ \t]?/gm,"");c=c?`${c}
38${w}`:w,u=u?`${u}
39${b}`:b;const v=this.lexer.state.top;if(this.lexer.state.top=!0,this.lexer.blockTokens(b,h,!0),this.lexer.state.top=v,r.length===0)break;const _=h[h.length-1];if(_?.type==="code")break;if(_?.type==="blockquote"){const T=_,x=T.raw+`
40`+r.join(`
41`),I=this.blockquote(x);h[h.length-1]=I,c=c.substring(0,c.length-T.raw.length)+I.raw,u=u.substring(0,u.length-T.text.length)+I.text;break}else if(_?.type==="list"){const T=_,x=T.raw+`
42`+r.join(`
43`),I=this.list(x);h[h.length-1]=I,c=c.substring(0,c.length-_.raw.length)+I.raw,u=u.substring(0,u.length-T.raw.length)+I.raw,r=x.substring(h[h.length-1].raw.length).split(`
44`);continue}}return{type:"blockquote",raw:c,tokens:h,text:u}}}list(i){let s=this.rules.block.list.exec(i);if(s){let r=s[1].trim();const c=r.length>1,u={type:"list",raw:"",ordered:c,start:c?+r.slice(0,-1):"",loose:!1,items:[]};r=c?`\\d{1,9}\\${r.slice(-1)}`:`\\${r}`,this.options.pedantic&&(r=c?r:"[*+-]");const h=new RegExp(`^( {0,3}${r})((?:[	 ][^\\n]*)?(?:\\n|$))`);let f=!1;for(;i;){let m=!1,g="",w="";if(!(s=h.exec(i))||this.rules.block.hr.test(i))break;g=s[0],i=i.substring(g.length);let b=s[2].split(`
45`,1)[0].replace(/^\t+/,U=>" ".repeat(3*U.length)),v=i.split(`
46`,1)[0],_=!b.trim(),T=0;if(this.options.pedantic?(T=2,w=b.trimStart()):_?T=s[1].length+1:(T=s[2].search(/[^ ]/),T=T>4?1:T,w=b.slice(T),T+=s[1].length),_&&/^ *$/.test(v)&&(g+=v+`
47`,i=i.substring(v.length+1),m=!0),!m){const U=new RegExp(`^ {0,${Math.min(3,T-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ 	][^\\n]*)?(?:\\n|$))`),N=new RegExp(`^ {0,${Math.min(3,T-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),L=new RegExp(`^ {0,${Math.min(3,T-1)}}(?:\`\`\`|~~~)`),G=new RegExp(`^ {0,${Math.min(3,T-1)}}#`);for(;i;){const Z=i.split(`
48`,1)[0];if(v=Z,this.options.pedantic&&(v=v.replace(/^ {1,4}(?=( {4})*[^ ])/g,"  ")),L.test(v)||G.test(v)||U.test(v)||N.test(i))break;if(v.search(/[^ ]/)>=T||!v.trim())w+=`
49`+v.slice(T);else{if(_||b.search(/[^ ]/)>=4||L.test(b)||G.test(b)||N.test(b))break;w+=`
50`+v}!_&&!v.trim()&&(_=!0),g+=Z+`
51`,i=i.substring(Z.length+1),b=v.slice(T)}}u.loose||(f?u.loose=!0:/\n *\n *$/.test(g)&&(f=!0));let x=null,I;this.options.gfm&&(x=/^\[[ xX]\] /.exec(w),x&&(I=x[0]!=="[ ] ",w=w.replace(/^\[[ xX]\] +/,""))),u.items.push({type:"list_item",raw:g,task:!!x,checked:I,loose:!1,text:w,tokens:[]}),u.raw+=g}u.items[u.items.length-1].raw=u.items[u.items.length-1].raw.trimEnd(),u.items[u.items.length-1].text=u.items[u.items.length-1].text.trimEnd(),u.raw=u.raw.trimEnd();for(let m=0;m<u.items.length;m++)if(this.lexer.state.top=!1,u.items[m].tokens=this.lexer.blockTokens(u.items[m].text,[]),!u.loose){const g=u.items[m].tokens.filter(b=>b.type==="space"),w=g.length>0&&g.some(b=>/\n.*\n/.test(b.raw));u.loose=w}if(u.loose)for(let m=0;m<u.items.length;m++)u.items[m].loose=!0;return u}}html(i){const s=this.rules.block.html.exec(i);if(s)return{type:"html",block:!0,raw:s[0],pre:s[1]==="pre"||s[1]==="script"||s[1]==="style",text:s[0]}}def(i){const s=this.rules.block.def.exec(i);if(s){const r=s[1].toLowerCase().replace(/\s+/g," "),c=s[2]?s[2].replace(/^<(.*)>$/,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",u=s[3]?s[3].substring(1,s[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):s[3];return{type:"def",tag:r,raw:s[0],href:c,title:u}}}table(i){const s=this.rules.block.table.exec(i);if(!s||!/[:|]/.test(s[2]))return;const r=Im(s[1]),c=s[2].replace(/^\||\| *$/g,"").split("|"),u=s[3]&&s[3].trim()?s[3].replace(/\n[ \t]*$/,"").split(`
52`):[],h={type:"table",raw:s[0],header:[],align:[],rows:[]};if(r.length===c.length){for(const f of c)/^ *-+: *$/.test(f)?h.align.push("right"):/^ *:-+: *$/.test(f)?h.align.push("center"):/^ *:-+ *$/.test(f)?h.align.push("left"):h.align.push(null);for(let f=0;f<r.length;f++)h.header.push({text:r[f],tokens:this.lexer.inline(r[f]),header:!0,align:h.align[f]});for(const f of u)h.rows.push(Im(f,h.header.length).map((m,g)=>({text:m,tokens:this.lexer.inline(m),header:!1,align:h.align[g]})));return h}}lheading(i){const s=this.rules.block.lheading.exec(i);if(s)return{type:"heading",raw:s[0],depth:s[2].charAt(0)==="="?1:2,text:s[1],tokens:this.lexer.inline(s[1])}}paragraph(i){const s=this.rules.block.paragraph.exec(i);if(s){const r=s[1].charAt(s[1].length-1)===`
53`?s[1].slice(0,-1):s[1];return{type:"paragraph",raw:s[0],text:r,tokens:this.lexer.inline(r)}}}text(i){const s=this.rules.block.text.exec(i);if(s)return{type:"text",raw:s[0],text:s[0],tokens:this.lexer.inline(s[0])}}escape(i){const s=this.rules.inline.escape.exec(i);if(s)return{type:"escape",raw:s[0],text:Ot(s[1])}}tag(i){const s=this.rules.inline.tag.exec(i);if(s)return!this.lexer.state.inLink&&/^<a /i.test(s[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&/^<\/a>/i.test(s[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(s[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(s[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:s[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:s[0]}}link(i){const s=this.rules.inline.link.exec(i);if(s){const r=s[2].trim();if(!this.options.pedantic&&/^</.test(r)){if(!/>$/.test(r))return;const h=Bs(r.slice(0,-1),"\\");if((r.length-h.length)%2===0)return}else{const h=n4(s[2],"()");if(h>-1){const m=(s[0].indexOf("!")===0?5:4)+s[1].length+h;s[2]=s[2].substring(0,h),s[0]=s[0].substring(0,m).trim(),s[3]=""}}let c=s[2],u="";if(this.options.pedantic){const h=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(c);h&&(c=h[1],u=h[3])}else u=s[3]?s[3].slice(1,-1):"";return c=c.trim(),/^</.test(c)&&(this.options.pedantic&&!/>$/.test(r)?c=c.slice(1):c=c.slice(1,-1)),Rm(s,{href:c&&c.replace(this.rules.inline.anyPunctuation,"$1"),title:u&&u.replace(this.rules.inline.anyPunctuation,"$1")},s[0],this.lexer)}}reflink(i,s){let r;if((r=this.rules.inline.reflink.exec(i))||(r=this.rules.inline.nolink.exec(i))){const c=(r[2]||r[1]).replace(/\s+/g," "),u=s[c.toLowerCase()];if(!u){const h=r[0].charAt(0);return{type:"text",raw:h,text:h}}return Rm(r,u,r[0],this.lexer)}}emStrong(i,s,r=""){let c=this.rules.inline.emStrongLDelim.exec(i);if(!c||c[3]&&r.match(/[\p{L}\p{N}]/u))return;if(!(c[1]||c[2]||"")||!r||this.rules.inline.punctuation.exec(r)){const h=[...c[0]].length-1;let f,m,g=h,w=0;const b=c[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(b.lastIndex=0,s=s.slice(-1*i.length+h);(c=b.exec(s))!=null;){if(f=c[1]||c[2]||c[3]||c[4]||c[5]||c[6],!f)continue;if(m=[...f].length,c[3]||c[4]){g+=m;continue}else if((c[5]||c[6])&&h%3&&!((h+m)%3)){w+=m;continue}if(g-=m,g>0)continue;m=Math.min(m,m+g+w);const v=[...c[0]][0].length,_=i.slice(0,h+c.index+v+m);if(Math.min(h,m)%2){const x=_.slice(1,-1);return{type:"em",raw:_,text:x,tokens:this.lexer.inlineTokens(x)}}const T=_.slice(2,-2);return{type:"strong",raw:_,text:T,tokens:this.lexer.inlineTokens(T)}}}}codespan(i){const s=this.rules.inline.code.exec(i);if(s){let r=s[2].replace(/\n/g," ");const c=/[^ ]/.test(r),u=/^ /.test(r)&&/ $/.test(r);return c&&u&&(r=r.substring(1,r.length-1)),r=Ot(r,!0),{type:"codespan",raw:s[0],text:r}}}br(i){const s=this.rules.inline.br.exec(i);if(s)return{type:"br",raw:s[0]}}del(i){const s=this.rules.inline.del.exec(i);if(s)return{type:"del",raw:s[0],text:s[2],tokens:this.lexer.inlineTokens(s[2])}}autolink(i){const s=this.rules.inline.autolink.exec(i);if(s){let r,c;return s[2]==="@"?(r=Ot(s[1]),c="mailto:"+r):(r=Ot(s[1]),c=r),{type:"link",raw:s[0],text:r,href:c,tokens:[{type:"text",raw:r,text:r}]}}}url(i){let s;if(s=this.rules.inline.url.exec(i)){let r,c;if(s[2]==="@")r=Ot(s[0]),c="mailto:"+r;else{let u;do u=s[0],s[0]=this.rules.inline._backpedal.exec(s[0])?.[0]??"";while(u!==s[0]);r=Ot(s[0]),s[1]==="www."?c="http://"+s[0]:c=s[0]}return{type:"link",raw:s[0],text:r,href:c,tokens:[{type:"text",raw:r,text:r}]}}}inlineText(i){const s=this.rules.inline.text.exec(i);if(s){let r;return this.lexer.state.inRawBlock?r=s[0]:r=Ot(s[0]),{type:"text",raw:s[0],text:r}}}}const i4=/^(?: *(?:\n|$))+/,s4=/^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,o4=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,Xs=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,r4=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,v0=/(?:[*+-]|\d{1,9}[.)])/,_0=Me(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/).replace(/bull/g,v0).replace(/blockCode/g,/ {4}/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).getRegex(),Tu=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,l4=/^[^\n]+/,Su=/(?!\s*\])(?:\\.|[^\[\]\\])+/,c4=Me(/^ {0,3}\[(label)\]: *(?:\n *)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/).replace("label",Su).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),d4=Me(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,v0).getRegex(),tl="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",Au=/<!--(?:-?>|[\s\S]*?(?:-->|$))/,u4=Me("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n *)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$))","i").replace("comment",Au).replace("tag",tl).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),k0=Me(Tu).replace("hr",Xs).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",tl).getRegex(),h4=Me(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",k0).getRegex(),Eu={blockquote:h4,code:s4,def:c4,fences:o4,heading:r4,hr:Xs,html:u4,lheading:_0,list:d4,newline:i4,paragraph:k0,table:Ns,text:l4},Om=Me("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",Xs).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",tl).getRegex(),f4={...Eu,table:Om,paragraph:Me(Tu).replace("hr",Xs).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",Om).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",tl).getRegex()},g4={...Eu,html:Me(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment",Au).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:Ns,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:Me(Tu).replace("hr",Xs).replace("heading",` *#{1,6} *[^
54]`).replace("lheading",_0).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},x0=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,p4=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,T0=/^( {2,}|\\)\n(?!\s*$)/,m4=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,Ks="\\p{P}\\p{S}",b4=Me(/^((?![*_])[\spunctuation])/,"u").replace(/punctuation/g,Ks).getRegex(),w4=/\[[^[\]]*?\]\([^\(\)]*?\)|`[^`]*?`|<[^<>]*?>/g,y4=Me(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/,"u").replace(/punct/g,Ks).getRegex(),v4=Me("^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)[punct](\\*+)(?=[\\s]|$)|[^punct\\s](\\*+)(?!\\*)(?=[punct\\s]|$)|(?!\\*)[punct\\s](\\*+)(?=[^punct\\s])|[\\s](\\*+)(?!\\*)(?=[punct])|(?!\\*)[punct](\\*+)(?!\\*)(?=[punct])|[^punct\\s](\\*+)(?=[^punct\\s])","gu").replace(/punct/g,Ks).getRegex(),_4=Me("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)[punct](_+)(?=[\\s]|$)|[^punct\\s](_+)(?!_)(?=[punct\\s]|$)|(?!_)[punct\\s](_+)(?=[^punct\\s])|[\\s](_+)(?!_)(?=[punct])|(?!_)[punct](_+)(?!_)(?=[punct])","gu").replace(/punct/g,Ks).getRegex(),k4=Me(/\\([punct])/,"gu").replace(/punct/g,Ks).getRegex(),x4=Me(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),T4=Me(Au).replace("(?:-->|$)","-->").getRegex(),S4=Me("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment",T4).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),Pr=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,A4=Me(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label",Pr).replace("href",/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),S0=Me(/^!?\[(label)\]\[(ref)\]/).replace("label",Pr).replace("ref",Su).getRegex(),A0=Me(/^!?\[(ref)\](?:\[\])?/).replace("ref",Su).getRegex(),E4=Me("reflink|nolink(?!\\()","g").replace("reflink",S0).replace("nolink",A0).getRegex(),ju={_backpedal:Ns,anyPunctuation:k4,autolink:x4,blockSkip:w4,br:T0,code:p4,del:Ns,emStrongLDelim:y4,emStrongRDelimAst:v4,emStrongRDelimUnd:_4,escape:x0,link:A4,nolink:A0,punctuation:b4,reflink:S0,reflinkSearch:E4,tag:S4,text:m4,url:Ns},j4={...ju,link:Me(/^!?\[(label)\]\((.*?)\)/).replace("label",Pr).getRegex(),reflink:Me(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",Pr).getRegex()},Kd={...ju,escape:Me(x0).replace("])","~|])").getRegex(),url:Me(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/},C4={...Kd,br:Me(T0).replace("{2,}","*").getRegex(),text:Me(Kd.text).replace("\\b_","\\b_| {2,}\\n").replace(/\{2,\}/g,"*").getRegex()},Ar={normal:Eu,gfm:f4,pedantic:g4},Ls={normal:ju,gfm:Kd,breaks:C4,pedantic:j4};class rn{tokens;options;state;tokenizer;inlineQueue;constructor(i){this.tokens=[],this.tokens.links=Object.create(null),this.options=i||Ya,this.options.tokenizer=this.options.tokenizer||new Hr,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};const s={block:Ar.normal,inline:Ls.normal};this.options.pedantic?(s.block=Ar.pedantic,s.inline=Ls.pedantic):this.options.gfm&&(s.block=Ar.gfm,this.options.breaks?s.inline=Ls.breaks:s.inline=Ls.gfm),this.tokenizer.rules=s}static get rules(){return{block:Ar,inline:Ls}}static lex(i,s){return new rn(s).lex(i)}static lexInline(i,s){return new rn(s).inlineTokens(i)}lex(i){i=i.replace(/\r\n|\r/g,`
55`),this.blockTokens(i,this.tokens);for(let s=0;s<this.inlineQueue.length;s++){const r=this.inlineQueue[s];this.inlineTokens(r.src,r.tokens)}return this.inlineQueue=[],this.tokens}blockTokens(i,s=[],r=!1){this.options.pedantic?i=i.replace(/\t/g,"    ").replace(/^ +$/gm,""):i=i.replace(/^( *)(\t+)/gm,(f,m,g)=>m+"    ".repeat(g.length));let c,u,h;for(;i;)if(!(this.options.extensions&&this.options.extensions.block&&this.options.extensions.block.some(f=>(c=f.call({lexer:this},i,s))?(i=i.substring(c.raw.length),s.push(c),!0):!1))){if(c=this.tokenizer.space(i)){i=i.substring(c.raw.length),c.raw.length===1&&s.length>0?s[s.length-1].raw+=`
56`:s.push(c);continue}if(c=this.tokenizer.code(i)){i=i.substring(c.raw.length),u=s[s.length-1],u&&(u.type==="paragraph"||u.type==="text")?(u.raw+=`
57`+c.raw,u.text+=`
58`+c.text,this.inlineQueue[this.inlineQueue.length-1].src=u.text):s.push(c);continue}if(c=this.tokenizer.fences(i)){i=i.substring(c.raw.length),s.push(c);continue}if(c=this.tokenizer.heading(i)){i=i.substring(c.raw.length),s.push(c);continue}if(c=this.tokenizer.hr(i)){i=i.substring(c.raw.length),s.push(c);continue}if(c=this.tokenizer.blockquote(i)){i=i.substring(c.raw.length),s.push(c);continue}if(c=this.tokenizer.list(i)){i=i.substring(c.raw.length),s.push(c);continue}if(c=this.tokenizer.html(i)){i=i.substring(c.raw.length),s.push(c);continue}if(c=this.tokenizer.def(i)){i=i.substring(c.raw.length),u=s[s.length-1],u&&(u.type==="paragraph"||u.type==="text")?(u.raw+=`
59`+c.raw,u.text+=`
60`+c.raw,this.inlineQueue[this.inlineQueue.length-1].src=u.text):this.tokens.links[c.tag]||(this.tokens.links[c.tag]={href:c.href,title:c.title});continue}if(c=this.tokenizer.table(i)){i=i.substring(c.raw.length),s.push(c);continue}if(c=this.tokenizer.lheading(i)){i=i.substring(c.raw.length),s.push(c);continue}if(h=i,this.options.extensions&&this.options.extensions.startBlock){let f=1/0;const m=i.slice(1);let g;this.options.extensions.startBlock.forEach(w=>{g=w.call({lexer:this},m),typeof g=="number"&&g>=0&&(f=Math.min(f,g))}),f<1/0&&f>=0&&(h=i.substring(0,f+1))}if(this.state.top&&(c=this.tokenizer.paragraph(h))){u=s[s.length-1],r&&u?.type==="paragraph"?(u.raw+=`
61`+c.raw,u.text+=`
62`+c.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=u.text):s.push(c),r=h.length!==i.length,i=i.substring(c.raw.length);continue}if(c=this.tokenizer.text(i)){i=i.substring(c.raw.length),u=s[s.length-1],u&&u.type==="text"?(u.raw+=`
63`+c.raw,u.text+=`
64`+c.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=u.text):s.push(c);continue}if(i){const f="Infinite loop on byte: "+i.charCodeAt(0);if(this.options.silent){console.error(f);break}else throw new Error(f)}}return this.state.top=!0,s}inline(i,s=[]){return this.inlineQueue.push({src:i,tokens:s}),s}inlineTokens(i,s=[]){let r,c,u,h=i,f,m,g;if(this.tokens.links){const w=Object.keys(this.tokens.links);if(w.length>0)for(;(f=this.tokenizer.rules.inline.reflinkSearch.exec(h))!=null;)w.includes(f[0].slice(f[0].lastIndexOf("[")+1,-1))&&(h=h.slice(0,f.index)+"["+"a".repeat(f[0].length-2)+"]"+h.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(f=this.tokenizer.rules.inline.blockSkip.exec(h))!=null;)h=h.slice(0,f.index)+"["+"a".repeat(f[0].length-2)+"]"+h.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);for(;(f=this.tokenizer.rules.inline.anyPunctuation.exec(h))!=null;)h=h.slice(0,f.index)+"++"+h.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;i;)if(m||(g=""),m=!1,!(this.options.extensions&&this.options.extensions.inline&&this.options.extensions.inline.some(w=>(r=w.call({lexer:this},i,s))?(i=i.substring(r.raw.length),s.push(r),!0):!1))){if(r=this.tokenizer.escape(i)){i=i.substring(r.raw.length),s.push(r);continue}if(r=this.tokenizer.tag(i)){i=i.substring(r.raw.length),c=s[s.length-1],c&&r.type==="text"&&c.type==="text"?(c.raw+=r.raw,c.text+=r.text):s.push(r);continue}if(r=this.tokenizer.link(i)){i=i.substring(r.raw.length),s.push(r);continue}if(r=this.tokenizer.reflink(i,this.tokens.links)){i=i.substring(r.raw.length),c=s[s.length-1],c&&r.type==="text"&&c.type==="text"?(c.raw+=r.raw,c.text+=r.text):s.push(r);continue}if(r=this.tokenizer.emStrong(i,h,g)){i=i.substring(r.raw.length),s.push(r);continue}if(r=this.tokenizer.codespan(i)){i=i.substring(r.raw.length),s.push(r);continue}if(r=this.tokenizer.br(i)){i=i.substring(r.raw.length),s.push(r);continue}if(r=this.tokenizer.del(i)){i=i.substring(r.raw.length),s.push(r);continue}if(r=this.tokenizer.autolink(i)){i=i.substring(r.raw.length),s.push(r);continue}if(!this.state.inLink&&(r=this.tokenizer.url(i))){i=i.substring(r.raw.length),s.push(r);continue}if(u=i,this.options.extensions&&this.options.extensions.startInline){let w=1/0;const b=i.slice(1);let v;this.options.extensions.startInline.forEach(_=>{v=_.call({lexer:this},b),typeof v=="number"&&v>=0&&(w=Math.min(w,v))}),w<1/0&&w>=0&&(u=i.substring(0,w+1))}if(r=this.tokenizer.inlineText(u)){i=i.substring(r.raw.length),r.raw.slice(-1)!=="_"&&(g=r.raw.slice(-1)),m=!0,c=s[s.length-1],c&&c.type==="text"?(c.raw+=r.raw,c.text+=r.text):s.push(r);continue}if(i){const w="Infinite loop on byte: "+i.charCodeAt(0);if(this.options.silent){console.error(w);break}else throw new Error(w)}}return s}}class Fr{options;parser;constructor(i){this.options=i||Ya}space(i){return""}code({text:i,lang:s,escaped:r}){const c=(s||"").match(/^\S*/)?.[0],u=i.replace(/\n$/,"")+`
65`;return c?'<pre><code class="language-'+Ot(c)+'">'+(r?u:Ot(u,!0))+`</code></pre>
66`:"<pre><code>"+(r?u:Ot(u,!0))+`</code></pre>
67`}blockquote({tokens:i}){return`<blockquote>
68${this.parser.parse(i)}</blockquote>
69`}html({text:i}){return i}heading({tokens:i,depth:s}){return`<h${s}>${this.parser.parseInline(i)}</h${s}>
70`}hr(i){return`<hr>
71`}list(i){const s=i.ordered,r=i.start;let c="";for(let f=0;f<i.items.length;f++){const m=i.items[f];c+=this.listitem(m)}const u=s?"ol":"ul",h=s&&r!==1?' start="'+r+'"':"";return"<"+u+h+`>
72`+c+"</"+u+`>
73`}listitem(i){let s="";if(i.task){const r=this.checkbox({checked:!!i.checked});i.loose?i.tokens.length>0&&i.tokens[0].type==="paragraph"?(i.tokens[0].text=r+" "+i.tokens[0].text,i.tokens[0].tokens&&i.tokens[0].tokens.length>0&&i.tokens[0].tokens[0].type==="text"&&(i.tokens[0].tokens[0].text=r+" "+i.tokens[0].tokens[0].text)):i.tokens.unshift({type:"text",raw:r+" ",text:r+" "}):s+=r+" "}return s+=this.parser.parse(i.tokens,!!i.loose),`<li>${s}</li>
74`}checkbox({checked:i}){return"<input "+(i?'checked="" ':"")+'disabled="" type="checkbox">'}paragraph({tokens:i}){return`<p>${this.parser.parseInline(i)}</p>
75`}table(i){let s="",r="";for(let u=0;u<i.header.length;u++)r+=this.tablecell(i.header[u]);s+=this.tablerow({text:r});let c="";for(let u=0;u<i.rows.length;u++){const h=i.rows[u];r="";for(let f=0;f<h.length;f++)r+=this.tablecell(h[f]);c+=this.tablerow({text:r})}return c&&(c=`<tbody>${c}</tbody>`),`<table>
76<thead>
77`+s+`</thead>
78`+c+`</table>
79`}tablerow({text:i}){return`<tr>
80${i}</tr>
81`}tablecell(i){const s=this.parser.parseInline(i.tokens),r=i.header?"th":"td";return(i.align?`<${r} align="${i.align}">`:`<${r}>`)+s+`</${r}>
82`}strong({tokens:i}){return`<strong>${this.parser.parseInline(i)}</strong>`}em({tokens:i}){return`<em>${this.parser.parseInline(i)}</em>`}codespan({text:i}){return`<code>${i}</code>`}br(i){return"<br>"}del({tokens:i}){return`<del>${this.parser.parseInline(i)}</del>`}link({href:i,title:s,tokens:r}){const c=this.parser.parseInline(r),u=Cm(i);if(u===null)return c;i=u;let h='<a href="'+i+'"';return s&&(h+=' title="'+s+'"'),h+=">"+c+"</a>",h}image({href:i,title:s,text:r}){const c=Cm(i);if(c===null)return r;i=c;let u=`<img src="${i}" alt="${r}"`;return s&&(u+=` title="${s}"`),u+=">",u}text(i){return"tokens"in i&&i.tokens?this.parser.parseInline(i.tokens):i.text}}class nl{strong({text:i}){return i}em({text:i}){return i}codespan({text:i}){return i}del({text:i}){return i}html({text:i}){return i}text({text:i}){return i}link({text:i}){return""+i}image({text:i}){return""+i}br(){return""}}class Qt{options;renderer;textRenderer;constructor(i){this.options=i||Ya,this.options.renderer=this.options.renderer||new Fr,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new nl}static parse(i,s){return new Qt(s).parse(i)}static parseInline(i,s){return new Qt(s).parseInline(i)}parse(i,s=!0){let r="";for(let c=0;c<i.length;c++){const u=i[c];if(this.options.extensions&&this.options.extensions.renderers&&this.options.extensions.renderers[u.type]){const f=u,m=this.options.extensions.renderers[f.type].call({parser:this},f);if(m!==!1||!["space","hr","heading","code","table","blockquote","list","html","paragraph","text"].includes(f.type)){r+=m||"";continue}}const h=u;switch(h.type){case"space":{r+=this.renderer.space(h);continue}case"hr":{r+=this.renderer.hr(h);continue}case"heading":{r+=this.renderer.heading(h);continue}case"code":{r+=this.renderer.code(h);continue}case"table":{r+=this.renderer.table(h);continue}case"blockquote":{r+=this.renderer.blockquote(h);continue}case"list":{r+=this.renderer.list(h);continue}case"html":{r+=this.renderer.html(h);continue}case"paragraph":{r+=this.renderer.paragraph(h);continue}case"text":{let f=h,m=this.renderer.text(f);for(;c+1<i.length&&i[c+1].type==="text";)f=i[++c],m+=`
83`+this.renderer.text(f);s?r+=this.renderer.paragraph({type:"paragraph",raw:m,text:m,tokens:[{type:"text",raw:m,text:m}]}):r+=m;continue}default:{const f='Token with "'+h.type+'" type was not found.';if(this.options.silent)return console.error(f),"";throw new Error(f)}}}return r}parseInline(i,s){s=s||this.renderer;let r="";for(let c=0;c<i.length;c++){const u=i[c];if(this.options.extensions&&this.options.extensions.renderers&&this.options.extensions.renderers[u.type]){const f=this.options.extensions.renderers[u.type].call({parser:this},u);if(f!==!1||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(u.type)){r+=f||"";continue}}const h=u;switch(h.type){case"escape":{r+=s.text(h);break}case"html":{r+=s.html(h);break}case"link":{r+=s.link(h);break}case"image":{r+=s.image(h);break}case"strong":{r+=s.strong(h);break}case"em":{r+=s.em(h);break}case"codespan":{r+=s.codespan(h);break}case"br":{r+=s.br(h);break}case"del":{r+=s.del(h);break}case"text":{r+=s.text(h);break}default:{const f='Token with "'+h.type+'" type was not found.';if(this.options.silent)return console.error(f),"";throw new Error(f)}}}return r}}class Ur{options;constructor(i){this.options=i||Ya}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(i){return i}postprocess(i){return i}processAllTokens(i){return i}}class I4{defaults=xu();options=this.setOptions;parse=this.parseMarkdown(rn.lex,Qt.parse);parseInline=this.parseMarkdown(rn.lexInline,Qt.parseInline);Parser=Qt;Renderer=Fr;TextRenderer=nl;Lexer=rn;Tokenizer=Hr;Hooks=Ur;constructor(...i){this.use(...i)}walkTokens(i,s){let r=[];for(const c of i)switch(r=r.concat(s.call(this,c)),c.type){case"table":{const u=c;for(const h of u.header)r=r.concat(this.walkTokens(h.tokens,s));for(const h of u.rows)for(const f of h)r=r.concat(this.walkTokens(f.tokens,s));break}case"list":{const u=c;r=r.concat(this.walkTokens(u.items,s));break}default:{const u=c;this.defaults.extensions?.childTokens?.[u.type]?this.defaults.extensions.childTokens[u.type].forEach(h=>{const f=u[h].flat(1/0);r=r.concat(this.walkTokens(f,s))}):u.tokens&&(r=r.concat(this.walkTokens(u.tokens,s)))}}return r}use(...i){const s=this.defaults.extensions||{renderers:{},childTokens:{}};return i.forEach(r=>{const c={...r};if(c.async=this.defaults.async||c.async||!1,r.extensions&&(r.extensions.forEach(u=>{if(!u.name)throw new Error("extension name required");if("renderer"in u){const h=s.renderers[u.name];h?s.renderers[u.name]=function(...f){let m=u.renderer.apply(this,f);return m===!1&&(m=h.apply(this,f)),m}:s.renderers[u.name]=u.renderer}if("tokenizer"in u){if(!u.level||u.level!=="block"&&u.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");const h=s[u.level];h?h.unshift(u.tokenizer):s[u.level]=[u.tokenizer],u.start&&(u.level==="block"?s.startBlock?s.startBlock.push(u.start):s.startBlock=[u.start]:u.level==="inline"&&(s.startInline?s.startInline.push(u.start):s.startInline=[u.start]))}"childTokens"in u&&u.childTokens&&(s.childTokens[u.name]=u.childTokens)}),c.extensions=s),r.renderer){const u=this.defaults.renderer||new Fr(this.defaults);for(const h in r.renderer){if(!(h in u))throw new Error(`renderer '${h}' does not exist`);if(["options","parser"].includes(h))continue;const f=h,m=r.renderer[f],g=u[f];u[f]=(...w)=>{let b=m.apply(u,w);return b===!1&&(b=g.apply(u,w)),b||""}}c.renderer=u}if(r.tokenizer){const u=this.defaults.tokenizer||new Hr(this.defaults);for(const h in r.tokenizer){if(!(h in u))throw new Error(`tokenizer '${h}' does not exist`);if(["options","rules","lexer"].includes(h))continue;const f=h,m=r.tokenizer[f],g=u[f];u[f]=(...w)=>{let b=m.apply(u,w);return b===!1&&(b=g.apply(u,w)),b}}c.tokenizer=u}if(r.hooks){const u=this.defaults.hooks||new Ur;for(const h in r.hooks){if(!(h in u))throw new Error(`hook '${h}' does not exist`);if(h==="options")continue;const f=h,m=r.hooks[f],g=u[f];Ur.passThroughHooks.has(h)?u[f]=w=>{if(this.defaults.async)return Promise.resolve(m.call(u,w)).then(v=>g.call(u,v));const b=m.call(u,w);return g.call(u,b)}:u[f]=(...w)=>{let b=m.apply(u,w);return b===!1&&(b=g.apply(u,w)),b}}c.hooks=u}if(r.walkTokens){const u=this.defaults.walkTokens,h=r.walkTokens;c.walkTokens=function(f){let m=[];return m.push(h.call(this,f)),u&&(m=m.concat(u.call(this,f))),m}}this.defaults={...this.defaults,...c}}),this}setOptions(i){return this.defaults={...this.defaults,...i},this}lexer(i,s){return rn.lex(i,s??this.defaults)}parser(i,s){return Qt.parse(i,s??this.defaults)}parseMarkdown(i,s){return(c,u)=>{const h={...u},f={...this.defaults,...h},m=this.onError(!!f.silent,!!f.async);if(this.defaults.async===!0&&h.async===!1)return m(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof c>"u"||c===null)return m(new Error("marked(): input parameter is undefined or null"));if(typeof c!="string")return m(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(c)+", string expected"));if(f.hooks&&(f.hooks.options=f),f.async)return Promise.resolve(f.hooks?f.hooks.preprocess(c):c).then(g=>i(g,f)).then(g=>f.hooks?f.hooks.processAllTokens(g):g).then(g=>f.walkTokens?Promise.all(this.walkTokens(g,f.walkTokens)).then(()=>g):g).then(g=>s(g,f)).then(g=>f.hooks?f.hooks.postprocess(g):g).catch(m);try{f.hooks&&(c=f.hooks.preprocess(c));let g=i(c,f);f.hooks&&(g=f.hooks.processAllTokens(g)),f.walkTokens&&this.walkTokens(g,f.walkTokens);let w=s(g,f);return f.hooks&&(w=f.hooks.postprocess(w)),w}catch(g){return m(g)}}}onError(i,s){return r=>{if(r.message+=`
84Please report this to https://github.com/markedjs/marked.`,i){const c="<p>An error occurred:</p><pre>"+Ot(r.message+"",!0)+"</pre>";return s?Promise.resolve(c):c}if(s)return Promise.reject(r);throw r}}}const Ha=new I4;function Oe(a,i){return Ha.parse(a,i)}Oe.options=Oe.setOptions=function(a){return Ha.setOptions(a),Oe.defaults=Ha.defaults,b0(Oe.defaults),Oe};Oe.getDefaults=xu;Oe.defaults=Ya;Oe.use=function(...a){return Ha.use(...a),Oe.defaults=Ha.defaults,b0(Oe.defaults),Oe};Oe.walkTokens=function(a,i){return Ha.walkTokens(a,i)};Oe.parseInline=Ha.parseInline;Oe.Parser=Qt;Oe.parser=Qt.parse;Oe.Renderer=Fr;Oe.TextRenderer=nl;Oe.Lexer=rn;Oe.lexer=rn.lex;Oe.Tokenizer=Hr;Oe.Hooks=Ur;Oe.parse=Oe;Oe.options;Oe.setOptions;Oe.use;Oe.walkTokens;Oe.parseInline;Qt.parse;rn.lex;const R4=new Qt,O4=new nl,j7=a=>R4.parseInline(a,O4),C7=()=>{const a=new Map;return i=>{const s=i.toLowerCase().trim().replace(/[^\p{Letter}\p{Number}
84]+/gu,"-").replace(/^-|-$/g,"")||"section",r=a.get(s)??0;return a.set(s,r+1),r===0?s:`${s}-${r+1}`}},al=a=>a.trim().toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g,"").replace(/['’]/g,"").replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,""),M4=`---
85title: "Debugging React Testing Library Tests | A Beginner’s Guide in 2024"
86slug: "a-beginners-guide-to-debugging-react-testing-library-tests"
87date: "2024-01-16"
88description: "A beginner’s guide to debugging React testing library tests, with a code walkthrough of how to use react-testing-library to debug tests."
89author: "Geshan Manandhar"
90authorImage: "/images/blog/authors/6568d05a61cacbddd7afcac0_geshan-manandhar.jpeg"
91heroImage: "/images/blog/a-beginners-guide-to-debugging-react-testing-library-tests/6568e96a667dc66d07f3d6a7_debugging-react.webp"
92heroImageAlt: "Debugging React Testing Library Tests | A Beginner’s Guide in 2024"
93categories: ["Testing", "Resources", "React"]
94showTableOfContents: true
95showPromo: true
96---
97
98Automated testing is crucial for the success of medium and large projects, which are often maintained for years. It is more scalable than manual testing. [Visual regression testing](https://www.meticulous.ai/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial) is a category of automated testing that helps you to make sure that all the UI elements work as expected after any changes.
99
100[React](https://www.meticulous.ai/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial).js is one of the most popular JavaScript frontend libraries and React testing library is a lightweight solution for testing React.js components. If you use create react app to create your project, react testing library is included out of the box. In this post, you will learn about the importance of automated testing, reasons to do visual regression testing, and ways to debug tests written with React Testing Libary - let’s get going!
101
102## Automated Testing is Important
103
104Having some form of automated testing is crucial for the long-term maintainability of a project. [Unit tests](https://www.meticulous.ai/blog/frontend-unit-testing-best-practices) are the most predictable, repeatable, and fastest breed of [automated tests](https://www.meticulous.ai/blog/testing-pyramid-for-frontend). You write tests so that you don’t need to debug any pesky issues in the future. However, the test code is code too, so you will also need to debug the test code!
105
106Writing tests for your code is important, the other more important aspect is writing testable code. Writing code in a testable way is a forcing function for writing clean code with best practices like dependency injection baked in. Then, adding tests on code that is already well designed makes it more maintainable and adaptable for future changes. The success of a large project that will be operated for years by multiple software engineers will rely heavily on having useful and carefully designed automated tests.
107
108## Why do Visual Regression Testing
109
110For [front-end](https://www.meticulous.ai/blog/frontend-unit-testing-best-practices) applications, like the ones built with React.js, testing is paramount. As the frontend application is the gateway between the user and your data, the User Interface (UI) elements always need to look and behave as intended. This is where visual regression testing becomes even more crucial.
111
112The problem of visual regression exacerbates as the size of the codebase grows with time. For a smaller codebase, most relations are easier to remember. In smaller codebases, the visual regression is relatively manageable. The full codebase and its intricacies are easily visible. But, as the size of the codebase increases, the complexity of the relationship between components also increases. It is more likely to see visual regression problems in large and legacy code bases compared to smaller and newer micro frontend projects.
113
114[Visual regression testing](https://www.meticulous.ai/blog/the-beginner-guide-to-visual-regression-testing) is a way to verify that changes do not negatively impact the visual appearance of the existing user interface of the application. For instance, an engineer changes a text box for the search form but as the text box is used in the login form too, the change breaks the login form. This is the pressing reason to do visual regression testing so that changes in one part or lower level primitive do not adversely affect other parts of the application.
115
116Given test code is also code, it also needs to be debugged so that tests can be written easily and accurately. In the next section, you will learn about an example app to debug tests written with React testing library.
117
118## Example App - Name to Nationality Percent
119
120To debug some React testing library tests you will use the Name to Nationality guessing application. If you are interested in how the application is built, please read the post on [Jest SpyOn](https://meticulous.ai/blog/how-to-use-jest-spyon/). It is a simple yet useful app where the first name of a person is entered and the app tries to guess the nationality percent of the given name using the Nationalize.io API. For instance, with the name chris the app works like the below:
121
122This application code is available on [GitHub](https://github.com/geshan/name-nationality) if you want to fiddle around with the code. You can quickly try your own name as the app is hosted on [Netlify](https://name-nationality.netlify.app/). Give it a shot. Next, you will learn how to debug the existing test of the flags being displayed correctly for the React testing library test.
123
124## Example Test - Get Nationality Flag
125
126The app already has some [test cases](https://github.com/geshan/name-nationality/blob/master/src/App.test.js) to verify that it works as expected. One of those tests is named should get nationalities for a name [checks](https://github.com/geshan/name-nationality/blob/master/src/App.test.js#L26) that the happy path works fine. You will be using this test as an example to debug a react testing library test. For the getRoles part, you will use the render function on the App component as it makes more sense. In the next section, you will learn about screen.debug, which is very useful to view the current HTML output of a component.
127
128## Using screen.debug to Peek into Rendered HTML
129
130There are a few tests in the example application. One of them is the React testing library test to check the happy path of the flags loading when a Name is entered. This test looks like the below:
131
132\`\`\`
133test('should get nationalities for a name', async() => {
134  render(<App />);
135
136  //simulate filling up the textbox
137  const personNameInput = screen.getByRole('textbox');
138  fireEvent.change(personNameInput, {target: {value: 'john'}}
138)
139  expect(personNameInput.value).toBe('john');
140
141  //click the button
142   const getNationalitiesBtn = screen.getByRole('button', { name: 'Get Nationalities' });
143   expect(getNationalitiesBtn).not.toBeDisabled();
144   userEvent.click(getNationalitiesBtn);
145
146   //Verify percent and flag images are displayed
147   expect(await screen.findByText('3 guess(es) found')).toBeVisible();
148
149   expect(windowFetchSpy).toHaveBeenCalled();
150   expect(windowFetchSpy).toHaveBeenCalledWith('https://api.nationalize.io/?name=john');
151   expect(screen.getByText('US - 4.84%')).toBeVisible();
152   expect(screen.getByText('IM - 4.44%')).toBeVisible();
153   expect(screen.getByText('IE - 4.21%')).toBeVisible();
154
155   const flagImages = screen.getAllByRole('img');
156   expect(flagImages).toHaveLength(3);
157   expect(flagImages[0]).toHaveAccessibleName('US flag');
158   expect(flagImages[1]).toHaveAccessibleName('IM flag');
159   expect(flagImages[2]).toHaveAccessibleName('IE flag');
160});
161\`\`\`
162
163Let’s suppose you did not know that there were 3 flags or the country names being rendered. It could be debugged with debug method on the screen object as follows:
164
165\`\`\`
166const flagImages = screen.getAllByRole('img');
167//console.log(flagImages);
168screen.debug(flagImages);
169
170expect(flagImages).toHaveLength(3);
171expect(flagImages[0]).toHaveAccessibleName('US flag');
172expect(flagImages[1]).toHaveAccessibleName('IM flag');
173expect(flagImages[2]).toHaveAccessibleName('IE flag');
174\`\`\`
175
176Like any other debug process, your first tool in the debugging toolbox would be the trustworthy console.log. If the above console.log was not commented the output would have been something like:
177
178\`\`\`
179console.log
180    [
181       HTMLImageElement {
182        '__reactFiber$spdd5vndmz9': FiberNode {
183          tag: 5,
184          key: null,
185          elementType: 'img',
186          type: 'img',
187          stateNode: [Circular *1],
188          return: [FiberNode],
189          child: null,
190          sibling: null,
191          index: 2,
192          ref: null,
193          pendingProps: [Object],
194          memoizedProps: [Object],
195          updateQueue: null,
196          memoizedState: null,
197          dependencies: null,
198          mode: 1,
199          flags: 1048580,
200          subtreeFlags: 0,
201          deletions: null,
202          lanes: 0,
203          childLanes: 0,
204          alternate: null,
205          actualDuration: 0,
206          actualStartTime: -1,
207          selfBaseDuration: 0,
208          treeBaseDuration: 0,
209          _debugSource: [Object],
210          _debugOwner: [FiberNode],
211          _debugNeedsRemount: false,
212          _debugHookTypes: null
213        },
214        '__reactProps$spdd5vndmz9': {
215          src: 'https://flagcdn.com/w160/us.jpg',
216          alt: 'US flag',
217          style: [Object]
218        },
219        '__reactEvents$spdd5vndmz9': Set(2) { 'error__bubble', 'load__bubble' },
220        [Symbol(SameObject caches)]: [Object: null prototype] {
221          style: [CSSStyleDeclaration],
222          childNodes: NodeList {}
223        }
224      },
225…
226]
227\`\`\`
228
229As seen above the console.log output for a getAllByRole call on screen is cryptic, to say the least. Rather than helping to debug it is making the process more difficult. Thereby, the generally useful console.log is not very useful in this particular case. On the other hand the output of the helpful screen.debug call is as follows:
230
231\`\`\`
232console.log
233
234
235      at logDOM (node_modules/@testing-library/dom/dist/pretty-dom.js:90:13)
236          at Array.forEach ()
237
238console.log
239
240
241      at logDOM (node_modules/@testing-library/dom/dist/pretty-dom.js:90:13)
242          at Array.forEach ()
243
244console.log
245
246
247      at logDOM (node_modules/@testing-library/dom/dist/pretty-dom.js:90:13)
248          at Array.forEach ()
249\`\`\`
250
251Now, it is much easier for you to know that there are 3 elements in the flagImages array and each of those is a simple img tag. The next step is to check the alt text which can be tested by using toHaveAccessibleName and using the string value like US flag.
252
253Now it is clear that the screen.debug method allows you to see the rendered HTML of the component which makes writing tests much easier. Without the debug method it might feel like shooting in the dark, especially if the component renders a complicated HTML. Next up you will learn about the magic sauce behind the debug method is prettyDOM.
254
255## Debugging DOM Elements with prettyDOM
256
257[PrettyDOM](https://testing-library.com/docs/dom-testing-library/api-debugging/#prettydom) is built on top of [pretty-format](https://github.com/facebook/jest/tree/main/packages/pretty-format). The prettyDOM function is used to print out a readable representation of the DOM tree of a node.
258
259The method used in the previous section screen.debug is a shorthand for writing console.log(prettyDOM(baseElement)). In this console.log call with prettyDOM, the baseElement is the DOM node where your React Element is rendered. You can use prettyDOM in the above happy path test case as:
260
261\`\`\`
262const flagImages = screen.getAllByRole('img');
263let index = 1;
264for(const flagImage of flagImages) {
265  console.log(\`flag image \${index}: \`, prettyDOM(flagImage));
266  index++;
267}
268
269expect(flagImages).toHaveLength(3);
270expect(flagImages[0]).toHaveAccessibleName('US flag');
271expect(flagImages[1]).toHaveAccessibleName('IM flag');
272expect(flagImages[2]).toHaveAccessibleName('IE flag');
273\`\`\`
274
275Notice here, that you have put a for loop explicitly to repeat through each flag image. In place of screen.debug the underlying prettyDOM function has been called in this case. PrettyDOM as the name suggests will print out the DOM elements in a neat and clean format. You can also use the pretty-format [options](https://github.com/facebook/jest/tree/main/packages/pretty-format#usage-with-options) to leverage the full powers of prettyDOM. It will give an output as follows when you run the above test:
276
277\`\`\`
278console.log
279    flag image 1:
280
281      at Object. (src/App.test.js:77:13)
282
283console.log
284    flag image 2:
285
286      at Object. (src/App.test.js:77:13)
287
288console.log
289    flag image 3:
290
291      at Object. (src/App.test.js:77:13)
292\`\`\`
293
294The above output is pretty similar to screen.debug. In this instance, as console.log( flag Image \${index}:, prettyDOM(flagImage)); is used in a loop you have more control over how to show the output. For example, if you want to turn off the code highlight it can be done with prettyDOM(flagImage, null, {highlight:false}). The second parameter is the maxLength which is sent as null and the third parameter is the [options](https://github.com/facebook/jest/tree/main/packages/pretty-format#usage-with-options) object. The options object is the same as the options for the pretty-format package. In this particular call, you have set it turn of code highlighting with highlight: false.
295
296Hence, the magic sauce of screen.debug is that it uses prettyDOM to do the heavy lifting. screen.debug uses prettyDOM with the default options, if you want to customize it you can call prettyDOM directly as seen in the above example. In the next part, you will learn about logRoles for easier querying of the DOM elements.
297
298## Finding Ways to Query DOM Elements using logRoles
299
300The logRoles helper function is another useful function. It is utilized to print out a list of all the implicit [ARIA roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) within a tree of DOM nodes. It lists all the nodes which match that role. This is very handy to find out the ways to query the DOM being testing. Sounds a bit confusing, but the code below will clear your confusion with the app component’s roles rendered with logRoles at the start of the test as seen below:
301
302\`\`\`
303const { container } = render();
304logRoles(container);
305\`\`\`
306
307This will output all the roles/HTML elements when the App component is initially loaded which yields:
308
309\`\`\`
310console.log
311banner:
312
313Name "":
314<header
315  class="App-header"
316/>
317
318--------------------------------------------------
319heading:
320
321Name "Check Name's Nationalities percent":
322<h2 />
323
324--------------------------------------------------
325form:
326
327Name "":
328<form
329  name="nationalities-form"
330/>
331
332--------------------------------------------------
333textbox:
334
335Name "":
336<input
337  name="personName"
338  placeholder="Enter a person's name"
339  type="text"
340  value=""
341/>
342
343--------------------------------------------------
344button:
345
346Name "Get Nationalities":
347<button />
348
349--------------------------------------------------
350
351  at logRoles (node_modules/@testing-library/dom/dist/role-helpers.js:234:20)
352\`\`\`
353
354This reveals there are multiple elements like one H2 with the name “Check Name's Nationalities percent”, a button element with the name “Get Nationalities” and others. With the above information, it makes it much easier to write a test like:
355
356\`\`\`
357const getNationalitiesBtn = screen.getByRole('button', { name: 'Get Nationalities' });
358expect(getNationalitiesBtn).not.toBeDisabled();
359userEvent.click(getNationalitiesBtn);
360\`\`\`
361
362As you know there is a button element and it has the name “Get Nationalities”, another test can be to check that the button is not disabled. Selecting the button by role was much easier as you could see the role printed on the console with the logRoles helper function with the container from the rendered App component.
363
364Whenever your React testing library test fails, these are 3 amazingly useful functions you now know about to help you debug and fix the broken or failing tests.
365
366## Conclusion
367
368In this post to debug React testing library tests, you witnessed how to use screen.debug instead of the usual console.log to easily see the rendered HTML for your react component. You also learned that the screen.debug actually uses prettyDOM helper function in the background. Finally, you became aware of the logRoles helper function that prints all the roles available for a given React component which makes it much easier to query Nodes by roles to write further tests. Keep on writing those amazing tests!
369
370## Meticulous
371
372Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
373
374This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
375
376This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
377`,U4=`---
378title: "Browserstack vs SauceLabs: App & Browser Testing"
379slug: "browserstack-vs-saucelabs"
380date: "2024-01-16"
381description: "This article provides a detailed comparison of two popular cloud-based device testing tools, BrowserStack and Sauce Labs. It outlines their features, benefits, and pricing structures so you can choose the tool that will best meet your needs, and it introduces an alternative tool called Meticulous."
382author: "Kasper Siig"
383authorImage: "/images/blog/authors/6556f52006849f153f151c05_kasper-siig.png"
384heroImage: "/images/blog/browserstack-vs-saucelabs/656a24f7b14bc510a964b55d_browserstack-vs-saucelabs.png"
385heroImageAlt: "Browserstack vs SauceLabs: App & Browser Testing"
386categories: ["Resources"]
387showTableOfContents: true
388showPromo: true
389---
390
391From smartphones to tablets, desktops, and even wearables, technological progress has transformed the testing environment, as well as the features required from testing tools. [BrowserStack](https://www.browserstack.com/) and [Sauce Labs](https://saucelabs.com/) are two popular tools for cloud-based device testing, and in this article, we’ll provide the pros and cons of each to help you decide which tool to incorporate into your tool stack. We’ll also introduce an exciting alternative for you to consider.
392
393## What Is Cloud-Based Device Testing?
394
395For developers, ensuring consistent software functionality across a vast array of devices, operating systems, and browsers — often termed "[device diversity](https://www.splashtop.com/blog/how-it-teams-are-navigating-device-diversity)" — is a monumental task. But with cloud-based device testing, developers can access a virtual collection of real devices, all hosted in the cloud — and this has a number of benefits:
396
397- Unparalleled Coverage: Software is tested across a wide range of devices, browsers, and operating systems, to ensure a user experience that's consistent, irrespective of the device or platform.
398- Lower Costs: Cloud-based testing offers testers access to the latest devices, eliminating the need for a costly in-house device lab.
399- Accelerated Testing: Development teams can run cloud-based tests concurrently across multiple devices, reducing testing time and speeding up product launches.
400- Mimicking Reality: Either through emulation or by having real devices placed throughout the world, cloud-based tools can replicate or simulate real-world conditions.
401- Improved Collaboration: Given that teams might include people in different cities or or even on different c
401ontinents, cloud-based testing platforms act as a central hub, allowing teams to test, share insights, and debug together.
402- Future-Ready Scalability: As software evolves, cloud-based testing platforms adapt, ensuring that increasing testing needs are consistently met.
403
404## The Challenges Solved by Cloud-Based Testing
405
406When you’re developing a new application or even just a new feature, one of the main challenges is to ensure that the app's user interface remains consistent across different screen sizes and resolutions. Moreover, functionalities like push notifications and real-time messaging must work seamlessly across various OS versions. This is where cloud-based testing platforms come in. With them, you can access a range of real devices, conduct simultaneous tests, and obtain immediate feedback, guaranteeing a uniform experience for all users.
407
408However, each approach comes with its own set of unique challenges:
409
410.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #eceff1; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
411
412| Testing Method | Challenges |
413| -------------- | ---------- |
414
415| On-Premises Testing  
416 | Significant investments required for infrastructure setup. Regular upgrades needed for equipment, machines, and software. Both virtual and physical storage areas may be required, leading to higher costs |
417| Cloud Testing  
418 | Potential issues with network latency affecting response times. Concerns about data safety and security. Integration challenges with different cloud providers. Potential for increased expenses if not managed properly |
419| In-House Device Labs |
420
421Limited coverage due to a restricted range of devices, browsers, or OS versions. Geographical limitations preventing testing from various locations. Time-consuming and resource-intensive setup and maintenance. High costs associated with constant device updates and lab maintenance
422
423|
424
425Cloud-based device testing is a key component of software development. It's not merely about ensuring software functionality; it's about ensuring that every user, regardless of the device they’re on, receives a consistent, premium experience.
426
427## An Overview of Browserstack
428
429![Screenshot of Browserstack’s website](/images/blog/browserstack-vs-saucelabs/6568db8e80d256f17b587010_browserstack-website.png)
430
431BrowserStack has a clear and compelling mission: "empower developers to build amazing experiences." The company remains steadfast in its dedication to providing premier software testing tools and ensuring that developers and QA teams can consistently deliver outstanding user experiences. With a goal of becoming the "testing infrastructure of the Internet," BrowserStack is set to reshape quality assurance standards.
432
433### Browserstack Features: An Overview
434
435![Overview of Browserstack’s features](/images/blog/browserstack-vs-saucelabs/6568db90af7766b254ad6888_browserstack-features.png)
436
437- [Manual Cross-Browser Testing (Live)](https://www.browserstack.com/live) allows developers and testers to manually assess their websites across various browsers and devices in real time, ensuring that websites render and function consistently and provide a seamless user experience.
438- [Browser Automation Grid (Automate)](https://www.browserstack.com/automate) provides a robust foundation for running automated browser tests across numerous browsers and operating systems, streamlining the testing process and leading to quicker feedback and releases.
439- [Visual Testing and Review (Percy)](https://www.browserstack.com/percy) is a tool that compares web pages across different browsers and devices, identifies visual discrepancies, and ensures a consistent visual narrative.
440- [Manual Real Device Testing (App Live)](https://www.browserstack.com/app-live) reflects BrowserStack's dedication to authenticity — this feature facilitates hands-on testing of mobile apps on actual devices, replicating genuine user interactions.
441- [Real Device Automation Grid (App Automate)](https://www.browserstack.com/app-automate) allows automated testing of mobile apps on real devices, capturing an authentic portrayal of app performance.
442
443In addition to specific tools, there are some traits that can be found across BrowserStack’s entire suite of features.
444
445- [Extensive Coverage](https://www.browserstack.com/list-of-browsers-and-platforms/app_automate): One of BrowserStack's hallmark features is its comprehensive coverage. It grants instant access to more than 3,000 browsers and real iOS and Android devices, ensuring thorough testing across myriad devices, browsers, and OS versions.
446- [Unwavering Security](https://www.browserstack.com/security): In today's climate of frequent data breaches, BrowserStack's commitment to security is paramount. Being [SOC 2 Type 2](https://www.accessowl.io/blog/6-misconceptions-iso27001-soc2/) compliant, it guarantees that testing processes and data are shielded from threats, alleviating concerns about cloud data security.
447- [Integration with Leading Tools](https://www.browserstack.com/integrations/app-automate): To optimize the software development lifecycle, BrowserStack is compatible with renowned tools such as Selenium, Jenkins, Slack, and Appium, so it integrates seamlessly into existing workflows. 
448
449In summary, BrowserStack's offerings underscore its dedication to quality, security, and efficie
449ncy. By presenting a range of tools tailored to diverse testing requirements, BrowserStack has established itself as an invaluable partner for developers and QA teams.
450
451### Pricing & Reviews
452
453![Browserstack pricing page](/images/blog/browserstack-vs-saucelabs/6568db8ef4bb378dc6c5fa2d_browserstack-pricing-page.png)
454
455BrowserStack offers a diverse range of plans tailored to specific needs. The plan you should select largely hinges on the desired feature set. For people interested in the Live feature, the following plans are available:
456
457.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #eceff1; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
458
459| Plan | Price (billed annually) | Key Features |
460| ---- | ----------------------- | ------------ |
461
462| Desktop  
463 | $29/month | 3000+ desktop browsers. Unlimited testing minutes. Localhost, staging, private website testing |
464| Desktop & Mobile  
465 | $39/month | All features of Live. 20,000+ real iOS & Android devices. Mobile browsers |
466| Team |
467
468$150/month (for 5 users)
469
470| All features of Desktop & Mobile. Collaborative debugging. Geolocation testing |
471| Team Pro | $199/month (for 5 users) | All features of Team. 2FA with OTP/SMS. Test payments via Apple Pay and Google Pay |
472| Enterprise | Custom pricing | All features of Team Pro. Advanced access control. Single sign-on. IP whitelisting |
473| Freelancer | $12.50/month | Standard features |
474| Open Source | Free | Standard features |
475
476Reviews for BrowserStack on [TrustRadius](https://www.trustradius.com/products/browserstack/reviews) are predominantly positive. However, some users have pointed out areas for improvement, particularly in performance, support, and configurability.
477
478.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #eceff1; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
479
480| Criteria | Rating (out of 10) |
481| -------- | ------------------ |
482
483| Likelihood to Renew  
484 | 9.2 |
485| Usability  
486 | 9.3 |
487| Availability |
488
4899.1
490
491|
492| Performance | 7.9 |
493| Support Rating | 8 |
494| Implementation Rating | 9.1 |
495| Configurability | 8.2 |
496| Contract Terms and Pricing Model | 9.1 |
497| Product Scalability | 9.1 |
498| Professional Services | 9.1 |
499| Vendor post-sale | 9.1 |
500| Vendor pre-sale | 9.1 |
501
502## SauceLabs' Testing Capabilities
503
504![Saucelabs test toolchain](/images/blog/browserstack-vs-saucelabs/6568dcff3f304b534014ae09_saucelabs-test-toolchain.png)
505
506Sauce Labs has carved out a niche for itself through unwavering determination and technological innovation. Initially, it focused on testing for the Internet Explorer browser, addressing a significant challenge for developers globally. As the digital realm evolved, so did Sauce Labs. In 2013, Adobe's decision to shut down its BrowserLab testing platform [and point users toward Sauce Labs](https://finance.yahoo.com/news/sauce-labs-rescues-adobe-browserlab-212248090.html?guce_referrer=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8&guce_referrer_sig=AQAAAElNLdC6M6uySQtmh449Ksex0dVL8STMUMkk18kntKFQtjio8ticDvbeodK_sr7_dlctBZZHZSlRkisbcKksBaScXB_d_Wcc27UOLGVDFM11kn3pzkLHCUB1LB3zd1sFA4Otz_0pTjeQr45BU4ZbIhBYRfO7aS4TF6-vOYGkz9jQ&guccounter=2) underscored SauceLabs’ growing prominence in the testing arena.
507
508The accolades didn't end there. Sauce Labs took the lead in supporting [Automated Testing for Microsoft Edge](https://saucelabs.com/resources/blog/testing-on-microsoft-edge) and promptly extended its support to the [Firebug plug-in for Mozilla Firefox](https://cloud-computing-today.com/2013/03/17/13067/). The firm experienced an uptick in investments during 2015 and 2016, and this financial boost facilitated the [acquisition of TestObject](https://saucelabs.com/company/news/sauce-labs-acquires-testobject-to-expand-real-device-mobile-app-testing-platform), a distinguished real device mobile app testing platform, in December 2016.
509
510Further solidifying its industry standing, Sauce Labs launched the [Sauce plugin for Microsoft Visual Studio Team Services (VSTS)](https://docs.saucelabs.com/basics/integrations/azure/) and unveiled the innovative Sauce Labs Test Analytics. The company's unwavering commitment to excellence garnered it multiple accolades, including a spot among the [top 100 fastest-growing private companies](https://saucelabs.com/company/news/sauce-labs-named-to-san-francisco-business-times-top-100-fastest-growing-private-companies-list-for-eighth-consecutive-year) in the Bay Area in 2015 and 2017. Additionally, [Forrester Research hailed Sauce Labs](https://saucelabs.com/resources/white-paper/the-forrester-wave-continuous-functional-test-automation-suites-q2-2020) as the "developers’ choice for cross-browser and mobile testing in the cloud," an endorsement that underscores its prowess.
511
512### SauceLabs Features: An Overview
513
514![Overview of Saucelabs’ Features](/images/blog/browserstack-vs-saucelabs/6568dcff8d9b73c476d2e291_saucelabs-features.png)
515
516- [Comprehensive Coverage](https://docs.saucelabs.com/insights/coverage/): Sauce Labs distinguishes itself with its extensive range of testing environments. It ensures that applications and websites maintain compatibility across the latest browser-OS comb
516inations, mobile emulators, simulators, and real devices. This expansive coverage allows developers to test across diverse scenarios, mirroring a majority of user interactions.
517- [Scalability for CI/CD](https://docs.saucelabs.com/ci/): Aligned with contemporary development paradigms, Sauce Labs is crafted with continuous integration and continuous delivery (CI/CD) workflows at its core. It promotes swift feedback, empowering development teams to deftly manage multiple pull requests daily, and guaranteeing both rapid and dependable software delivery.
518- [Enhanced Efficiency with Debugging Tools](https://docs.saucelabs.com/insights/debug/): In today's accelerated development cycles, efficiency is paramount. Sauce Labs boasts a range of debugging tools, encompassing videos, screenshots, and log files. It also provides in-depth analytics, live testing environments, and Sauce Headless for headless browser testing. These tools empower developers to swiftly pinpoint and address issues, accelerating iterations and elevating product quality.
519- [Enterprise Security and Support](https://saucelabs.com/company/security): In an era when digital security is paramount, Sauce Labs delivers top-notch protection. With single-use VMs and real devices subjected to stringent post-testing cleaning, data integrity remains uncompromised. Moreover, Sauce Connect offers a secure tunneling protocol, ensuring that enterprise-level security concerns are thoroughly addressed.
520- History and Background: While not a concrete feature, Sauce Labs' storied history offers insights into its evolution and unwavering commitment to excellence. From its initial emphasis on Internet Explorer testing to accolades from industry stalwarts and its ongoing innovation, Sauce Labs has consistently demonstrated its dedication to redefining the limits of cloud-based testing.
521
522Sauce Labs has become an indispensable ally for developers and QA teams worldwide. Its expansive coverage ensures optimal compatibility across a vast array of browser-OS combinations, mobile emulators, simulators, and real devices, reflecting the diverse interactions of users.
523
524### Pricing & Reviews
525
526![Saucelabs pricing page](/images/blog/browserstack-vs-saucelabs/6568dcffb076ff0e9cbf3848_saucelabs-pricing-page.png)
527
528Compared with BrowserStack’s, Sauce Labs' credit-based pricing structure is refreshingly clear.
529
530.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #eceff1; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
531
532| Plan | Price | Key Features |
533| ---- | ----- | ------------ |
534
535| Free  
536 | $0 | 2,000 free credits/week. Test on up to 10 devices in parallel |
537| Monthly  
538 | $50/month | 25,000 credits/month. Test on up to 25 devices in parallel |
539| Annual |
540
541$600/year
542
543| 330,000 credits/year (including 30,000 bonus credits). Test on up to 50 devices in parallel |
544| Enterprise  
545 | Custom pricing | Unlimited automated testing minutes. Private device cloud. Single sign-on (SSO) |
546| Mobile Beta Testing | $199/month (billed annually) | 50 apps, 500 beta testers. 180 days data retention. 10,000 video sessions/month |
547| Open Source Projects | Free | Standard features |
548
549While the [TrustRadius page for Sauce Labs](https://www.trustradius.com/products/sauce-labs/reviews) doesn't offer scores as detailed as BrowserStack’s, it indicates general user satisfaction with the product. However, it's noteworthy that there's a slight discrepancy between the modest 7.4 rating and the "key insights."
550
551.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #eceff1; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
552
553| Attribute | Insight |
554| --------- | ------- |
555
556| Overall Rating  
557 | 7.3 out of 10 |
558| Total Reviews  
559 | 286 Reviews and Ratings  
560 |
561| Would Buy Again |
562
56391% of users would repurchase
564
565|
566| Value for Price  
567 | 100% believe it offers good value for the price |
568| Feature Set Satisfaction | 95% are satisfied with the feature set |
569| Sales and Marketing Promises | 100% affirm it met sales and marketing promises |
570| Implementation Satisfaction  
571 | 90% felt the implementation met expectations |
572
573## Comparing Browserstack and SauceLabs
574
575Both platforms offer robust testing capabilities, but selecting the right one requires careful consideration of specific needs and constraints.
576
577### Budgetary Constraints
578
579For many organizations, the cost of services significantly influences decisions. At first glance, BrowserStack's Freelancer plan, priced at $12.50 per month, seems reasonable. However, its scope may 
579not fully address the comprehensive testing demands of an expanding startup. The Desktop & Mobile plan, at $39 per month, offers a more encompassing testing environment, spanning both desktop and mobile platforms.
580
581And while Sauce Labs’ Free plan’s 2,000 credits per week are enticing, the plan may prove inadequate for startups with rigorous testing needs. Their Monthly plan, priced slightly higher at $50 per month, grants a substantial 25,000 credits, potentially aligning more closely with the expected testing frequency of a growing company.
582
583### Specific Testing Needs
584
585There’s no such thing as a one-size-fits-all solution; choosing a tool requires careful consideration of a given tool’s feature set and whether it can meet your needs. For companies grappling with the following issues, the decision may involve answering these questions:
586
587- Geolocation Testing: Does the application deliver content tailored to the user's location?
588
589BrowserStack offers geolocation testing in its Team plan and higher tiers.
590
591Sauce Labs doesn't provide clear information about geolocation testing in its listed plans.
592
593- Mobile App Testing: Is there a need to develop a mobile version of the application?
594
595BrowserStack supports testing on real iOS & Android devices within its Desktop & Mobile plan.
596
597Sauce Labs includes mobile testing, but further investigation would be needed to understand the specific devices and configurations available.
598
599- Collaborative Debugging: Is team collaboration on debugging sessions a common practice?
600
601BrowserStack includes this feature in its Team package.
602
603Sauce Labs doesn't say whether collaborative debugging is part of its offerings.
604
605### The Verdict
606
607In the startup realm, decisions are seldom easily cut-and-dried. Theoretically, BrowserStack's Desktop & Mobile plan, priced at $39 per month, aligns seamlessly with the prerequisites of geolocation and mobile app testing. The prospect of upgrading to the Team plan for enhanced collaborative debugging makes it even more appealing. 
608
609However, it’s wise to proceed with caution. Both platforms offer trial periods, and you should take advantage of that. Hands-on experience often reveals nuances that mere specifications can’t provide.
610
611### The Visual Testing Wildcard: Meticulous
612
613![Meticulous for visual regression testing](/images/blog/browserstack-vs-saucelabs/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.png)
614
615Although BrowserStack and [Sauce Labs](https://www.meticulous.ai/blog/saucelabs-alternatives) may be among the best options for cloud-based device testing, there are many other aspects of visual testing to consider. For instance, nearly all tools on the market — even no-code tools — require some amount of maintenance. As long as your test suite requires you to configure tests in some capacity, maintenance is inevitable.
616
617The only way of solving this is by having instrumentation be the only aspect of configuration — enter Meticulous. This tool requires only that you install a simple Javascript snippet, after which Meticulous will record real user sessions. Then it analyzes screenshots with pixel depth, to catch any and all visual discrepancies.
618
619All of this — combined with additional features found within Meticulous — lays the foundation for an efficient [visual regression testing tool](https://www.meticulous.ai/blog/visual-regression-testing-tools).
620
621## What’s the Right Choice for You?
622
623Both BrowserStack and Sauce Labs present a suite of tools tailored to streamline and elevate the testing experience for developers and QA professionals. However, picking one over the other requires a thorough understanding of their features, similarities, and distinctions.
624
625.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #eceff1; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
626
627| Feature | BrowserStack | SauceLabs |
628| ------- | ------------ | --------- |
629
630| Environment  
631 | Extensive range of devices, browsers, and OS | Extensive range of devices, browsers, and OS |
632| Hosting  
633 | Hosted on the cloud  
634 | Hosted on the cloud |
635| Integration |
636
637Compatible with renowned CI/CD tools
638
639| Compatible with renowned CI/CD tools |
640| Mobile Testing  
641 | Facilitates mobile testing on actual devices | Facilitates mobile testing on actual devices |
642| Pricing Model | Transparent monthly pricing | Operates on a credit-based system |
643| Geolocation Testing | Accessible in premium plans | Not specifically highlighted |
644| Collaborative Debugging  
645 | Featured in the "Team" package | Not specifically highlighted |
646| Visual Testing | Not a central offering | Prioritizes visual testing (notably post-acquisition of Percy) |
647
648Different platforms resonate with different user segments. Startups might gravitate toward BrowserStack's Desktop & Mobile package, given its balance of cost-effectiveness and exhaustive testing features. In contrast, larger corporations might favor SauceLabs for its adaptability, focus on visual testing, and seamless integrations. Solo developers or independent contractors may be inclined toward BrowserStack for its intuitive interface and the Freelancer package catering to fundamental testing requirements.
649
650## Meticulous
651
652Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
653
654This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way 
654we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
655
656This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
657`,D4=`---
658title: 'How to Prevent "Cannot Read Property Split of Undefined"'
659slug: "cannot-read-property-split-of-undefined"
660date: "2024-01-16"
661description: "An article about the error 'cannot read property split of undefined' and various techniques to prevent it."
662author: "Oluseun Falodun"
663authorImage: "/images/blog/authors/6568ed0d46a96d27bd953794_oluseun-falodun.webp"
664heroImage: "/images/blog/cannot-read-property-split-of-undefined/656a18f4e9738d8ac141281d_cannot-read-property-split-of-undefined.png"
665heroImageAlt: 'How to Prevent "Cannot Read Property Split of Undefined"'
666categories: ["Testing"]
667showTableOfContents: true
668showPromo: true
669---
670
671Split() is a JavaScript string method that splits a string into an array of substrings without mutating the original string. As a JavaScript developer, there's a good chance that you've used the split() method and encountered this error: "Cannot read property split of undefined".
672
673This error is a [TypeError](https://www.meticulous.ai/blog/javascript-typeerrors-and-techniques-to-prevent-them), and it occurs when calling a method, operand, or argument on the wrong data type, or when calling the split() method on a variable that returns undefined. A TypeError is one of the most common [errors in JavaScript](https://www.meticulous.ai/blog/how-to-prevent-javascript-syntax-errors) and so it's important to understand why it happens and how to avoid it.
674
675In this article, you'll take a closer look at the meaning of the error "Cannot read property split of undefined", what causes it, some examples, and techniques to prevent it.
676
677## Why is it Important to Understand this Error
678
679As a developer, you're constantly dealing with user inputs as strings. Understanding this error will enable you to work with strings more effectively and will help prevent your users from having a bad experience with your application.
680
681## What Causes "Cannot Read Property Split of Undefined"
682
683The split() method divides a string into an array of substrings based on the separator argument passed in. Let's look at a few code examples below:
684
685\`\`\`
686const sampleString = "This is a sample string";
687\`\`\`
688
689Call the split() method on the string:
690
691\`\`\`
692sampleString.split(' ');
693\`\`\`
694
695After running this code in your console, you'll see the split() method in action:
696
697\`\`\`
698['This', 'is', 'a', 'sample', 'string']
699\`\`\`
700
701The split() method can be a very useful tool in algorithms involving string data types. For example, you could use it when you need to reverse a string, or remove spaces from within a string.
702
703The error "Cannot read property split of undefined" can occur when performing any of these types of operations if the variable is undefined. Furthermore, when you are working on a project, there are several other scenarios where this error can occur, including the following:
704
705- If different values are assigned to a variable several times, calling the split() method on the variable when it is set to undefined will result in the error. For example, a variable is defined as a string in a global scope and set to undefined in the function scope. In this case calling the split () method on the variable in the function scope will result in an error.
706- In cases where the return value of a variable could be a string or undefined, the function could return undefined, and thus calling split on the output might result in an error.
707
708Let's look at a concrete example. Suppose you'd like to return a message depending on the value of n. The variable, message, is initialized on the first line. When n is greater than five, the message is ‘This number is greater than five’, and the Split() method is called on the message.
709
710\`\`\`
711function greaterThanFive(n) {
712    let message;
713    if (n > 5) {
714        message = 'This number is greater than five';
715        return message.split(' ')
716    } else {
717        return message.split(' ');
718    }
719}
720
721console.log(greaterThanFive(6));
722\`\`\`
723
724The output will be:
725
726\`\`\`
727[ 'This', 'number', 'is', 'greater', 'than', 'five' ]
728\`\`\`
729
730The value of the message when n is less than five is not defined. As such, when n is less than five, the split function will be called on the undefined message, which will return an error. For example, calling greaterThanFive(4) will throw an error, as can be seen in the screenshot:
731
732- If you're dealing with [JavaScript promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) and you are trying to access the response from the promise without awaiting it, if the split() method is called on this response, this error would be generated. This is because when the response is accessed, the promise is unresolved and as such the response will be undefined.
733
734## How Can you Fix this Error
735
736Now that you understand why this error occurs and its potential impact on your work, how can you avoid it? There are several ways of doing this.
737
738## Check Data Types
739
740Check the data type of your variable before proceeding to call the split() method. If the data type is a string, then you can proceed with your splitting operation. You can check a data type using the typeof() method, then call the split() method using conditional statements like if and else, as shown in the following code:
741
742\`\`\`
743const sampleString = "This is a sample text";
744if (typeof(sampleString) === 'string') {
745    return sampleString.split('');
746} else {
747    return sampleString;
748}
749\`\`\`
750
751The first line in the code snippet defines the sample variable you want to check with the conditional statement in the next line. If the conditional if statement is true, then the sample variable is split; if it returns false, then the algorithm returns the original variable. This is a simple, easily added check to catch and address the Cannot read property split of undefined error.
752
753Another way to implement more thorough type checks in your code is to use TypeScript. [TypeScript](https://www.meticulous.ai/blog/safer-exhaustive-switch-statements-in-typescript) is a strongly typed superset of JavaScript that allows you to catch type errors at build time rather than at runtime. For example, a function written in TypeScript might look like this:
754
755\`\`\`
756// note the \`string\` type specified for the input parameter
757const splitString = (input: string) => {
758    return input.split('');
759}
760
761console.log(splitString('foo')); // [ 'f', 'o', 'o' ]
762
763console.log(splitString(undefined)); // Argument of type 'undefined' is not assignable to parameter of type 'string'.
764\`\`\`
765
766When using TypeScript, you're able to annotate your code with types the way you would in other strongly typed languages. This enables deeper levels of IntelliSense and in-editor errors and hints. In this case, assuming you're using a code editor with TypeScript support, the second call to splitString will show you an error directly in your code editor when you try to invoke the function with an invalid type. This can save a lot of time and effort, as it drastically shortens the feedback loop when it comes to type-related errors, allowing you to find and fix them before your code ever runs.
767
768## Assign an Alternative Value
769
770If the variable the split() method is called on resolves to undefined, you can assign an alternative value to it.
771
772\`\`\`
773const sampleString = undefined;
774const result = sampleString || ‘ ’
775return result.split()
776\`\`\`
777
778Here, the sample variable is declared at undefined in line one. Rather than calling the split method directly on the variable, line two assigns an alternative value so the split operation will execute without error.
779
780## Optional Chaining
781
782[Optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) allows you to optionally call the split() method, depending on if a variable is defined or not. If the variable is defined, the function runs as expected; if it's undefined or null, it stops and returns \\\`\\\`undefined\\\`. This is especially useful in situations where the variable is optional, and may or may not be present.
783
784\`\`\`
785const person = {}; // empty object
786const personalDetails = person.location?.address.split('')
787return personalDetails // returns undefined
788\`\`\`
789
790Because the property location is not present in the person object, the code returns undefined.
791
792## try and catch Blocks
793
794Wrapping your code in a [try and catch block](https://meticulous.ai/blog/javascript-try-catch-guide/). While this method doesn't prevent the error from occurring, it does ensure that if it occurs, it's handled gracefully. This method is especially useful when there are edge cases that cannot be accounted for in your algorithm. You can read more about try-catch in this blog post [here](https://meticulous.ai/blog/javascript-try-catch-guide/).
795
796One of the benefits of the try and catch block is that it allows developers to handle errors seamlessly, and to customize your error messages. Putting your function in a try and catch block would ensure that a simple, easy-to-understand response is returned to the client if an error occurs.
797
798\`\`\`
799function convertString(sampleString) {
800  const result = sampleString.split(" ");
801  return result;
802}
803
804let invalidInput;
805
806try {
807  const parts = convertString(invalidInput);
808} catch (error) {
809  // handle error and display message here
810  console.log(error);
811}
812\`\`\`
813
814## Conclusion
815
816In this post, you have seen the many different scenarios that can result in a "Cannot read property split of undefined" error showing up in your application. You've also learned that carrying out data type checks, assigning alternative values to variables, and exception handling with a [try and catch](https://meticulous.ai/blog/javascript-try-catch-guide/) block are some of the best ways to handle the error in your project.
817
818Knowing how to handle errors is important, but a better approach is to avoid them altogether. Integrating [Meticulous](https://meticulous.ai/) into your CI flow will allow you to easily catch errors before they reach your customers.
819
820## Meticulous
821
822Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
823
824This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
825
826This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
827`,B4=`---
828title: "Automated Testing with Cypress: 5 Best Practices"
829slug: "cypress-testing"
830date: "2024-01-16"
831description: "This article will delve into the best practices for automated testing using Cypress, a popular front-end testing tool. It will provide insights into how these practices can be effectively implemented in real-life scenarios, thereby enhancing the efficiency and reliability of software testing processes."
832author: "Kasper Siig"
833authorImage: "/images/blog/authors/6556f52006849f153f151c05_kasper-siig.png"
834heroImage: "/images/blog/cypress-testing/655d9ff349fe9628bcf0b35a_cypress-main.png"
835heroImageAlt: "Automated Testing with Cypress: 5 Best Practices"
836categories: ["Resources", "Tools"]
837showTableOfContents: true
838showPromo: true
839---
840
841Automated testing has significantly improved efficiency, reduced human error, and accelerated time to market for many software companies, and this is particularly evident when it comes to [visual regression testing](https://www.meticulous.ai/blog/the-beginner-guide-to-visual-regression-testing), which aims to identify unintended visual changes — a critical factor in maintaining a good user experience. However, simply setting up automation is not enough; implementing it _efficiently_ is just as important.
842
843A popular tool in the realm of visual testing is [Cypress](https://www.cypress.io/), since it’s one of the few tools that allows developers to write automated tests directly in JavaScript. This guide will provide a comprehensive overview of the best practices to consider when you’re automating your visual testing with Cypress tests, as well as how those tests should be implemented.
844
845## Why Choose Cypress Tests for Automated Testing?
846
847![Screenshot of Cypress’ interface](/images/blog/cypress-testing/6567818cd5169dfe9cefc1a4_cypress-interface.png)
848
849The JavaScript-based nature of Cypress testing allows for frictionless integration with modern stacks, which are often based on a JavaScript framework such as [React](https://react.dev/) or [Angular](https://angular.io/).
850
851Cypress’s intuitive interface and real-time feedback mechanisms significantly simplify the process of writing and debugging tests for both developers and QA engineers, and they streamline the testing process, so Cypress is easier to use than most automated testing tools.
852
853### Reducing Flakiness
854
855One of the most critical aspects of any testing tool is its reliability. Cypress addresses this by offering built-in retry-ability features that aim to reduce test flakiness. By automatically retrying failed assertions, Cypress testing ensures more reliable test outcomes, thereby increasing the credibility of your testing suite. That being said, while retries are often a great way of reducing the [flake rate](https://docs.gitlab.com/ee/development/testing_guide/flaky_tests.html) of a given testing tool, _they don’t remove flakes._
856
857### Comparison Table: Cypress vs. Other Tools
858
859To fully grasp the features of Cypress and how it compares, it’s important to do a quick assessment of how it compares to other tools. The main Cypress competitors are:
860
861- [Meticulous](https://www.meticulous.ai/): Known for its focus on visual regression testing and minimal maintenance requirements.
862- [Test IO](https://test.io/): Utilizes a crowdtesting model, providing a diverse range of real-world testing scenarios.
863- [Sauce Labs](https://saucelabs.com/): Offers emulation-based device testing along with real-time feedback mechanisms.[‍](https://www.browserstack.com/)
864- [BrowserStack](https://www.browserstack.com/): Specializes in real-world device testing, offering a comprehensive solution for cross-platform compatibility.
865
866| Criteria         | Cypress          | Meticulous              | Test IO           | Sauce Labs            | Browserstack             |
867| ---------------- | ---------------- | ----------------------- | ----------------- | --------------------- | ------------------------ |
868| Test Automation  | Yes (JavaScript) | Yes (Visual Regression) | No (Crowdtesting) | Yes (Emulation-based) | Yes (Real-world devices) |
869| Language Support | JavaScript       | Multiple                | Multiple          | Multiple              | Multiple                 |
870| Ease of Use\\*    | High             | Moderate                | Low               | Moderate              | High                     |
871| Flake Rate\\*     | Moderate         | Low                     | High              | Moderate              | Low                      |
872
873\\* Subjective assessment based on reviews.
874
875## Cypress Testing Best Practices
876
877Following best practices isn’t just a matter of pleasing managers; it directly affects the efficiency and reliability of automated testing processes. Over time, this will help you reduce maintenance time, which is crucial in agile development environments where changes are frequent. Also, poorly designed tests can lead to misleading results, which can cause two common problems:
878
879- A bug slipping through the cracks and into production.
880- Having to spend time debugging a false positive, thus wasting engineering hours on 
880something that wasn’t faulty in the first place.
881
882Moreover, the adoption of best practices enhances team collaboration, making it easier to understand, modify, or extend test cases. This, in turn, fosters a more cohesive and productive development environment.
883
884When it comes to best practices related to _writing_ tests in Cypress, Cypress provides [fairly comprehensive documentation](https://docs.cypress.io/guides/references/best-practices) on this. As such, this blog post will focus more on how you can best utilize the different features offered by the tool.
885
886### Utilizing the Cypress Test Runner and Command Log and Debugging Features
887
888![Screenshot of Cypress’ features](/images/blog/cypress-testing/6567818c84a6aa6c1588ece2_cypress-test-runner-command-log-debugging-features.png)
889
890Cypress offers a robust Test Runner that serves as the core of its testing experience, allowing for real-time execution of tests, providing immediate feedback via a test report, and accelerating the debugging process. One of its most powerful features is the Command Log, which provides a detailed, chronological record of all test actions, assertions, and network requests.
891
892This allows developers to see the Application or Component Under Test (AUT/CUT) and explore its [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction) in real time. It’s not just a display; it’s a fully interactive application with developer tools for inspecting elements just like you would in a normal browser. This interactive element is often very helpful for debugging and understanding the behavior of the application under different test conditions.
893
894Command Log: Your Debugging Companion
895
896The Command Log is displayed on the left-hand side of the Cypress Test Runner, serving as a visual representation of your test suite. Clicking on any of the tests — which are neatly nested appropriately — reveals every Cypress command executed within that test's block. This includes commands executed in relevant [before, beforeEach, afterEach, and after hooks](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Hooks). The Command Log also offers a feature known as "[Time Traveling](https://docs.cypress.io/guides/core-concepts/cypress-app#Time-traveling)," which allows developers to hover over each command to see the exact state of the DOM at that moment.
897
898Best Practices for Utilizing Test Runner and Command Log
899
9001.  Real-Time Feedback: Make the most of the real-time execution feature for quicker identification and resolution of issues.
9012.  Command Log Interpretation: Learn to read the Command Log effectively. It provides a wealth of information that can help you debug tests faster.
9023.  Time Travel Feature: Use the Time Travel feature judiciously to understand the state changes in your application, for more effective debugging.
903
904### Scenarios Benefiting from Real-Time Execution and Command Log
905
906The real-time execution and Command Log features are particularly useful in debugging complex user flows that involve multiple steps or conditions. They are also invaluable in dynamic web applications where elements are frequently updated or changed. The detailed logging and real-time feedback simplify the process of updating and maintaining test cases, making these features especially beneficial in agile development environments.
907
908—---------------------
909
910Understanding and effectively utilizing Cypress's Test Runner and Command Log can significantly enhance your debugging capabilities. These features not only provide real-time feedback but also offer detailed insights into the execution flow and state changes in your application, making them indispensable tools in your testing arsenal.
911
912### Leveraging Cypress's Automatic Waiting Feature
913
914While it is possible to use a traditional .wait() statement in Cypress, there are a number of ways to avoid it. One way is to use the .should() statement, which allows you to pass a callback function that will run after a a given command. For instance, take a look at this example usage pulled from [Cypress’ official documentation](https://docs.cypress.io/api/commands/should):
915
916\`\`\`
917cy.get('p').should(($p) => {
918  // should have found 3 elements
919  expect($p).to.have.length(3)
920
921  // make sure the first contains some text content
922  expect($p.first()).to.contain('Hello World')
923
924  // use jquery's map to grab all of their classes
925  // jquery's map returns a new jquery object
926  const classes = $p.map((i, el) => {
927    return Cypress.$(el).attr('class')
928  })
929
930  // call classes.get() to make this a plain array
931  expect(classes.get()).to.deep.eq([
932    'text-primary',
933    'text-danger',
934    'text-default',
935  ])
936})
937\`\`\`
938
939‍
940
941In this example, Cypress makes sure to execute the code within the .should() callback function only after the .get('p') function has successfully executed and returned a value. Another example from Cypress’s documentation on the .wait() function shows how it’s not always necessary to input a specific time interval to wait; instead, you can tell Cypress tests to wait for a given alias to respond:
942
943\`\`\`
944// Wait for the alias 'getAccount' to respond
945// without changing or stubbing its response
946cy.intercept('/accounts/*').as('getAccount')
947cy.visit('/accounts/123')
948cy.wait('@getAccount').then((interception) => {
949  // we can now access the low level interception
950  // that contains the request body,
951  // response body, status, etc
952})
953\`\`\`
954
955Although there are some great ways of performing automatic waits within Cypress, it’s important to understand and be aware of the balance between time spent on optimizations and time spent on getting things done. If a simple .wait(2000) is going to increase the total run time of your tests by between one and two seconds but will work perfectly fine in 100% of cases, then you should likely just use the .wait(2000) statement. Automatic waits are great either when they’re easy to implement or when the required waiting time varies enough that a static .wait() statement will cause flakiness.
956
957### Using Spies, Stubs, and Clocks for Controlling Behavior
958
959Like many other testing tools, spies, stubs, and clocks are popular and powerful ways of handling application behavior during testing. With one simple line, you can define the behavior of a function to be exactly what you’d like it to be:
960
961\`\`\`
962// force obj.method() to return "foo"
963cy.stub(obj, 'method').returns('foo')
964
965// force obj.method() when called with "bar" argument to return "foo"
966cy.stub(obj, 'method').withArgs('bar').returns('foo')
967\`\`\`
968
969This is often useful when the part of the web application you’re testing relies on a function/method that is outside your test suite’s scope and therefore needs to be mocked. However, it’s important to exercise caution when using these tools, as manual customization of application behavior can lead to a [number of unwanted results](https://speedscale.com/blog/mock-apis-agile-environments/), like:
970
971- Increased development time due to maintenance, as mocks often have to be updated in order to reflect new behavior in the function being mocked.
972- Incorrect test results due to the misconfiguration of mocks.
973- Unrealistic test results due to manual configuration, such as the timing of a UI element loading.
974
975All in all, spies, stubs, and clocks are powerful ways of getting your tests to behave as you want them to, but it’s crucial to research and understand whether you can utilize [real-world traffic instead](https://speedscale.com/blog/build-mock-server/).
976
977### Implementing Network Traffic Control for Edge Case Testing
978
979Edge cases are testing scenarios that occur at the extreme ends of input boundaries and that may not always be present in real-world traffic captured from production. An edge case is a great example of a scenario where the use of stubs may in fact be necessary. While edge cases can happen at any point of an application, it’s very common to experience them when you’re testing interactions between multiple APIs; in this situation, it’s necessary to mock the edge case scenarios.
980
981While it is possible to use the .stub() method described in the previous section, Cypress testing does offer the .intercept() function as a specific solution to handling network requests:
982
983\`\`\`
984cy.intercept(
985  {
986    method: 'GET', // Route all GET requests
987    url: '/users/*', // that have a URL that matches '/users/*'
988  },
989  [] // and force the response to be: []
990).as('getUsers') // and assign an alias
991\`\`\`
992
993That being said, as with the .wait() function, this should be treated as an okay-but-non-preferred solution in specific use cases — for instance, when there’s a need for edge case testing. As stated in the previous section, adding manual mocks of any kind will inevitably increase the maintenance and reduce the reliability of testing suites, which is why you should always try to rely on real-world data where possible.
994
995### Ensuring Consistent Results with Cypress's Unique Architecture
996
997One of the most unique features of Cypress is its JavaScript-based architecture, which is also one of its most “dangerous” features. The possibility of writing tests in the same language you’re writing your application in allows for a lot of freedom, but without caution this can quickly become a drawback.
998
999For instance, a popular feature of JavaScript is the async/await feature, allowing you to effectively run multiple [“threads”](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) within the same runtime environment. But this can also easily result in a number of [race conditions](<https://betterways.dev/javascript-async-race-conditions#:~:text=It's%20extremely%20easy%20to%20create,(i.e.%20an%20await%20keyword).>). Typically, this isn’t too much of a worry, as there are a number of ways to work around it, but when it comes to testing — especially visual testing — this is an easy way to introduce flakiness into your test suite.
1000
1001All in all, Cypress’s unique architecture allows for amazing flexibility and freedom in the creation of test suites; however, it’s still crucial to keep your test suites as simple as possible in order to increase reliability and maintenance.
1002
1003## Enter Meticulous: Reducing Flakiness in Visual Regression Testing
1004
1005![Screenshot of the Meticulous app](/images/blog/cypress-testing/65675603ffedcc5b58a56f2d_meticulous-testing-app.png)
1006
1007While Cypress’s JavaScript-based approach will appeal to a large group of developers, it’s important to remember that there are important trade-offs. For instance, the concern of flaky tests has been raised multiple times in this post, as some using Cypress can result in unreliable tests and false positives.
1008
1009Another up-and-coming tool on the market has gone in the opposite direction from Cypress, opting for a solution relying on no code at all. To use Meticulous, you simply install a [recorder script](https://app.meticulous.ai/docs) on your website; this script will then capture all user interactions. And then, once you’ve developed a new visual feature, fixed a bug, or for some other reason made changes to the front-end, Meticulous will reenact a user’s behavior and capture screenshots of your application’s interface.
1010
1011These screenshots will be compared with the original ones recorded during normal user interactions, and by comparing each screenshot’s [pixel depth](https://support.esri.com/en-us/knowledge-base/faq-what-does-the-pixel-depth-mean-000006576), Meticulous can accurately determine and report any visual differences. Additionally, the recorded user traffic will also contain the responses of any third-party service being used (like a backend API), which will then automatically be mocked, essentially presenting you with a no-code, no-configuration, no-maintenance solution that almost eliminates the possibility of flakes.
1012
1013## Final Thoughts
1014
1015From its unique JavaScript-based architecture that ensures consistent and reliable test results to its robust features like real-time execution and debugging, automatic waiting, and network traffic control, Cypress stands out as a tool designed to enhance the efficiency and reliability of software testing processes.
1016
1017However, it’s crucial to avoid becoming too reliant on the efficacy of these features, as there are still a number of pitfalls you can run into, resulting in unreliable and/or flaky tests. Remember, any test you write yourself is subject to human error. By this logic, the most reliable tests will come from utilizing native features and automation as much as possible.
1018
1019While this is true for all code, it’s especially important when it comes to testing, as _your tests are supposed to catch human errors._
1020
1021## Meticulous
1022
1023Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
1024
1025This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
1026
1027This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
1028`,L4=`---
1029title: "Axios vs Fetch | Which is Best for Beginners?"
1030slug: "fetch-vs-axios"
1031date: "2024-01-16"
1032description: "A breakdown of the trade-offs and differences between these two different methods of making network requests in JavaScript."
1033author: "Ibas Majid"
1034authorImage: "/images/blog/authors/6568f0c1857f542a8026378a_ibas-majid.webp"
1035heroImage: "/images/blog/fetch-vs-axios/656a5b55303eec6586c2c166_fetch-vs-axios.png"
1036heroImageAlt: "Axios vs Fetch | Which is Best for Beginners?"
1037categories: ["Resources"]
1038showTableOfContents: true
1039showPromo: true
1040---
1041
1042When we build applications that require us to make a network request, either to our backend or a third-party API, we use HTTP clients like Axios and Fetch to perform such requests.
1043
1044In this guide, we introduce Axios and Fetch and compare them so that we can make an informed decision on which one to select.
1045
1046## A Quick Overview of Fetch and Axios
1047
1048The Fetch API is an interface that exposes a method called fetch() for making a network request. It is built into modern browsers and so no installation is required. It is also available as an [experimental feature in node.js](https://github.com/nodejs/node/pull/41749).
1049
1050Axios is a third-party library that we can add to our project either via a Content Distribution Network or CDN, or install it via a package manager, like npm or yarn. Axios can run within a browser or a node.js environment.
1051
1052Fetch and axios are both promise-based HTTP clients. This means that when we use them to make a network request, they return a promise that can either resolve or reject.
1053
1054## Installing Axios
1055
1056If we’re using Axios on a node.js environment, we can use one of the following installation methods:
1057
10581.  Install using NPM:
1059
1060\`\`\`
1061npm install axios
1062\`\`\`
1063
10642.  Install using Yarn:
1065
1066\`\`\`
1067yarn add axios
1068\`\`\`
1069
1070We must then import into our project:
1071
1072\`\`\`
1073import axios from "axios";
1074\`\`\`
1075
1076If we’re using Axios within a browser, we can use a CDN:
1077
1078\`\`\`
1079<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"><\/script>
1080\`\`\`
1081
1082You can read all the ways to install Axios [here](https://axios-http.com/docs/intro). Now that everything is installed, let’s compare them.
1083
1084## Comparing the Features of Fetch and Axios
1085
1086Let’s start with the syntax.
1087
1088## Syntax
1089
1090Fetch accepts two arguments. The first argument is the URL for the resource we want to fetch. The second is an optional argument which is an object c
1090ontaining the configuration options for making requests. Thus, the syntax is:
1091
1092Without the configuration option, the request will default to making a GET request:
1093
1094\`\`\`
1095fetch(url)
1096\`\`\`
1097
1098With the configuration option, we can define some custom settings for the request which include:
1099
1100\`\`\`
1101fetch(url, {
1102  method: 'GET', // other options: POST, PUT, DELETE, etc.
1103  headers: {
1104    'Content-Type': 'application/json'
1105  },
1106  body: JSON.stringify({}),
1107})
1108\`\`\`
1109
1110Axios syntax is similar, but there are many different ways of calling it:
1111
1112\`\`\`
1113axios(url, {
1114  // configuration options
1115})
1116\`\`\`
1117
1118We can also append the HTTP method like this:
1119
1120\`\`\`
1121axios.get(url, {
1122  // configuration options
1123})
1124\`\`\`
1125
1126Like the fetch method, we can ignore HTTP methods in axios to default to GET like so:
1127
1128\`\`\`
1129axios(url)
1130\`\`\`
1131
1132Likewise, we can define some custom settings for the request using the second argument:
1133
1134\`\`\`
1135axios(url, {
1136  method: 'get', // other options: post, put, delete, etc.
1137  headers: {},
1138  data: {},
1139})
1140\`\`\`
1141
1142We can also write it like this:
1143
1144\`\`\`
1145axios({
1146  method: 'get',
1147  url: url,
1148  headers: {},
1149  data: {},
1150})
1151\`\`\`
1152
1153Now let's examine the different way Axios and fetch handle responses.
1154
1155## Handling JSON Data
1156
1157In the example below, we perform a GET request to a [REST API called JSONPlaceholder](https://jsonplaceholder.typicode.com/) to fetch a list of todos items using both fetch and Axios and compare the differences.
1158
1159Using the Fetch API, our code looks like this:
1160
1161\`\`\`
1162const url = "https://jsonplaceholder.typicode.com/todos";
1163
1164fetch(url)
1165  .then(response => response.json())
1166  .then(console.log);
1167\`\`\`
1168
1169The result in our console looks like this:
1170
1171![Console showing JSON data](/images/blog/fetch-vs-axios/6568ef0d5295615c17bc2a0f_console-showing-JSON-data.png)
1172
1173The fetch() returns a promise whose response is handled by the .then() method. At this point, we do not have the JSON data format that we need, so we call the .json() method on the response object. This returns another promise that is resolved with the data in JSON form.So a typical fetch request contains two .then() calls.
1174
1175However, if we use Axios to perform the same fetch, we have the following code:
1176
1177\`\`\`
1178const url = "https://jsonplaceholder.typicode.com/todos";
1179
1180axios.get(url)
1181.then(response => console.log(response.data));
1182\`\`\`
1183
1184With Axios, the response data is available as JSON by default. The response data is always available on the data property of the response object.
1185
1186We can override the default JSON data type by specifying the responseType in the configuration option like so:
1187
1188\`\`\`
1189axios.get(url, {
1190  responseType: 'json' // options: 'arraybuffer', 'document', 'blob', 'text', 'stream'
1191})
1192\`\`\`
1193
1194## Auto Stringify
1195
1196Now let’s try sending data using the [JSONPlaceholder](https://jsonplaceholder.typicode.com/) API.
1197
1198We will need to serialize our data into a JSON string to do this. When we send JavaScript objects to the API using a POST method, Axios automatically stringifies the data.
1199
1200The following code performs a post request using Axios:
1201
1202\`\`\`
1203const url = "https://jsonplaceholder.typicode.com/todos";
1204
1205const todo = {
1206  title: "A new todo",
1207  completed: false
1208}
1209
1210axios.post(url, {
1211  headers: {
1212    'Content-Type': 'application/json',
1213  },
1214  data: todo
1215})
1216.then(console.log);
1217\`\`\`
1218
1219When we make a post request with axios, we assign the data to be sent as the request body to the data property. We can also set the content type header. By default, axios sets Content-Type to application/json.
1220
1221Let's take a look at the response object:
1222
1223![Response object](/images/blog/fetch-vs-axios/6568ef5e48713011713261cb_response-object.png)
1224
1225The response data is located on the response.data like so:
1226
1227\`\`\`
1228.then(response => console.log(response.data));
1229\`\`\`
1230
1231If we use the Fetch API, we have to manually stringify the object using JSON.stringify() and then assign it to the body of our request.
1232
1233\`\`\`
1234const url = "https://jsonplaceholder.typicode.com/todos";
1235
1236const todo = {
1237  title: "A new todo",
1238  completed: false
1239};
1240
1241fetch(url, {
1242  method: "post",
1243  headers: {
1244    "Content-Type": "application/json"
1245  },
1246  body: JSON.stringify(todo)
1247})
1248  .then((response) => response.json())
1249  .then((data) => console.log(data))
1250\`\`\`
1251
1252We must also explicitly set the Content-Type to application/json with Fetch.
1253
1254## Error Handling
1255
1256Both fetch and axios return a promise that is either resolved or rejected. When the promise rejects we can use .catch() to handle the error. How we handle errors with axios is more succinct when compared to the Fetch method.
1257
1258Starting with the axios, a typical error handling with .catch() looks like so:
1259
1260\`\`\`
1261const url = "https://jsonplaceholder.typicode.com/todos";
1262
1263axios.get(url)
1264  .then((response) => console.log(response.data))
1265  .catch((err) => {
1266    console.log(err.message);
1267  });
1268\`\`\`
1269
1270Axios’s promise will reject if the status code falls outside of the range of 2xx. We can determine more information about the error by checking whether the err object contains a response or a request property like so:
1271
1272\`\`\`
1273.catch((err) => {
1274  // handling error
1275  if (err.response) {
1276    // Request made and server responded
1277
1278    const { status, config } = err.response;
1279
1280    if (status === 404) {
1281      console.log(\`\${config.url} not found\`);
1282    }
1283    if (status === 500) {
1284      console.log("Server error");
1285    }
1286  } else if (err.request) {
1287    // Request made but no response from server
1288    console.log("Error", err.message);
1289  } else {
1290    // some other errors
1291    console.log("Error", err.message);
1292  }
1293});
1294\`\`\`
1295
1296A response property on the error object indicates that the client received an error response with a status code outside of the 2xx range. A request property on the error object indicates that a request was made, but the client did not receive a response. Otherwise, if there is no request or response property, an error occurred in setting up the network request.
1297
1298Fetch will not reject a promise if we get a 404 error or any other HTTP error. Fetch only rejects a promise when the network fails. So we have to manually handle HTTP errors within .then clauses.
1299
1300Let's take a look at the following code:
1301
1302\`\`\`
1303const url = "https://jsonplaceholder.typicode.com/todos";
1304
1305fetch(url)
1306  .then((response) => {
1307    if (!response.ok) {
1308      throw new Error(
1309        \`This is an HTTP error: The status is \${response.status}\`
1310      );
1311    }
1312    return response.json();
1313  })
1314  .then(console.log)
1315  .catch(err => {
1316    console.log(err.message);
1317  });
1318\`\`\`
1319
1320In the response block, we are checking if the response’s ok status is false, then we throw a custom error that is handled in the .catch block.
1321
1322We can take a look at the methods available on the response’s object like so:
1323
1324\`\`\`
1325.then(console.log)
1326\`\`\`
1327
1328![Response object's status](/images/blog/fetch-vs-axios/6568f00c34f7a16be9ecde2b_response-object-27s-status.png)
1329
1330The above screenshot is for a successful fetch. In a situation where we hit a wrong URL endpoint, the ok and status property will become false and 404 respectively, we throw an error and so the .catch() clause will display our custom error message.
1331
1332## Response Timeout/ Cancel Requests
1333
1334Let’s see how these HTTP clients handle response timeout for HTTP requests. With Axios, wecan add a timeout property in the configuration object and specify the time in milliseconds before the request terminates.
1335
1336In the following snippet, we aim to terminate the request if it takes longer than four seconds and then log an error in the console.
1337
1338\`\`\`
1339const url = "https://jsonplaceholder.typicode.com/todos";
1340
1341axios.get(url, {
1342  timeout: 4000, // default is \`0\` (no timeout)
1343})
1344  .then((response) => console.log(response.data))
1345  .catch((err) => {
1346    console.log(err.message);
1347  });
1348\`\`\`
1349
1350To cancel requests with Fetch, we can use the [AbortController interface](https://developer.mozilla.org/en-US/docs/Web/API/AbortController). See the usage below:
1351
1352\`\`\`
1353const url = "https://jsonplaceholder.typicode.com/todos";
1354
1355const controller = new AbortController();
1356const signal = controller.signal;
1357setTimeout(() => controller.abort(), 4000);
1358
1359fetch(url, {
1360  signal: signal
1361})
1362  .then((response) => response.json())
1363  .then(console.log)
1364  .catch((err) => {
1365    console.error(err.message);
1366  });
1367\`\`\`
1368
1369We started by creating a controller object and gained access to the signal object and the abort() method. We then passed the signal object to fetch() via the configuration option. With this, the fetch request will terminate whenever the abort method is called. As we can see, with the help of setTimeout function, the operation terminates if the server does not respond in four seconds.
1370
1371## Performance
1372
1373Since Fetch and axios are both promise-based, they should not cause any performance issues. However, we can still measure their performance using [measurethat.net](https://www.measurethat.net/).
1374
1375After successive testing using the online tool, we have [the following result](https://www.measurethat.net/Benchmarks/Show/17766/0/axios-vs-fetch):
1376
1377![Comparing performance of axios vs fetch](/images/blog/fetch-vs-axios/6568f0461075fe4b3629dd0d_comparing-performance-of-axios-vs-fetch.png)
1378
1379The graph:
1380
1381![Graph comparing performance of axios vs fetch](/images/blog/fetch-vs-axios/6568f04691810d1c386db009_graph-comparing-performance-of-axios-vs-fetch.png)
1382
1383As seen above, the native Fetch is slightly faster than axios. This is insignificant as both clients are asynchronous.
1384
1385## Browser Support
1386
1387Axios and Fetch are widely supported among modern browsers. For older environments like IE 11 that doesn’t support [ES6 Promises](https://caniuse.com/promises), we have to [use a polyfill](https://github.com/stefanpenner/es6-promise). Also for Fetch, specifically, we will add [another polyfill](https://github.com/github/fetch) to support implementations in [older browsers](https://caniuse.com/fetch).
1388
1389## Conclusion
1390
1391In this guide, we discussed Fetch and axios and practically compare them using real-world scenarios. In the end, what you select in your project depends on your personal preference and ease of use.
1392
1393## Meticulous
1394
1395Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
1396
1397This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way 
1397we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
1398
1399This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
1400`,q4=`---
1401title: "Frontend Unit Testing | Best Practices & Tutorial"
1402slug: "frontend-unit-testing-best-practices"
1403date: "2024-01-16"
1404description: "A guide on some of the best practices for frontend unit testing, including covering libraries like Jest with various code examples."
1405author: "Alex Langdon"
1406authorImage: "/images/blog/authors/6568e8c0efa94e4ab8341350_alex-langdon.jpeg"
1407heroImage: "/images/blog/frontend-unit-testing-best-practices/656a194b02a2a8e8bfe8aa11_frontend-unit-testing-best-practices.png"
1408heroImageAlt: "Frontend Unit Testing | Best Practices & Tutorial"
1409categories: ["Tutorial"]
1410showTableOfContents: true
1411showPromo: true
1412---
1413
1414In this guide we will present some common best practices for frontend unit testing. We will first outline some of the benefits and [rationale](https://www.meticulous.ai/blog/testing-pyramid-for-frontend) behind each recommendation, followed by examples of how each principle could be applied in practice to improve a set of test cases.
1415
1416Although the examples below will use [JavaScript](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices) and [Jest](https://www.meticulous.ai/blog/how-to-use-jest-spyon) testing framework, the principles discussed are broadly applicable to any language. However, it is worth noting these are best practices, not necessarily rules, so being mindful of exceptions is always best practice number zero.
1417
1418## 1\\. Using Linting Rules for Tests
1419
1420Linting or styling rules such as those provided by [eslint](https://eslint.org/) are commonly used as standard in most modern frontend code-bases. They help to automatically highlight errors in your IDE, such as test expectations that may never be reached and may lead to silently failing tests:
1421
1422![Using linting rules for tests](/images/blog/frontend-unit-testing-best-practices/6568f17dc69f48950b0e8004_using-linting-rules-for-tests.jpeg)
1423
1424It is well worth considering the linting rules available for your test framework and how they may help to avoid more common testing mistakes.
1425
1426### Why
1427
1428Linting can automatically highlight and suggest fixes to common mistakes.Ensure consistency across a code base and between many contributors.Integrations with most modern IDE’s can help in tracking and correcting linting errors as they are being written, before compile time.Can automatically run linter checks as part of a pre-commit hook (using tools like Husky). Ensuring that all test code passes linting rules before it is saved to version-control.
1429
1430### Example Code To Test
1431
1432\`\`\`
1433    // Example asynchronous GET request using Promises
1434
1435    function asyncRequest() {
1436      return fetch("some.api.com")
1437        .then(resp => resp.json())
1438        .catch(() => {
1439          throw new Error("Failed to fetch from some.api.com");
1440        })
1441    }
1442\`\`\`
1443
1444### Bad Test Example
1445
1446\`\`\`
1447// Bad - jest/valid-expect-in-promise lint error - The expect call may
1448// not be resolved if the async function never resolves.
1449
1450it("should resolve a successful fetch - bad", () => {
1451  asyncRequest().then((data) => {
1452    expect(data).toEqual({ id: 123 });
1453  });
1454});
1455
1456// Bad - jest/no-conditional-expect lint error - Expect call will not
1457// be reached if the async function does not throw an error.
1458
1459it("should catch fetch errors - bad", async () => {
1460  try {
1461    await asyncRequest();
1462  } catch (e) {
1463    expect(e).toEqual(new Error("Failed to fetch from some.api.com"));
1464  }
1465});
1466\`\`\`
1467
1468### Better Test Example
1469
1470\`\`\`
1471// Better - No linting error - Use async and await to ensure the
1472// expectation is called after the promise resolves.
1473
1474it('should resolve fetch - better', async () => {
1475  const result = await asyncRequest();
1476  expect(result).toBeDefined();
1477})
1478
1479// Better - No linting error - Use async and await to ensure the
1480// expectation is always called.
1481
1482it("should catch fetch errors - better", async () => {
1483  await expect(asyncRequest()).rejects.toThrow(
1484    new Error("Failed to fetch from some.api.com")
1485  );
1486});
1487\`\`\`
1488
1489## Further Notes
1490
1491Some recommended default linting rules for writing tests with Jest:
1492
1493- [no-focused-tests](https://github.com/jest-community/eslint-plugin-jest/blob/HEAD/docs/rules/no-focused-tests.md)
1494- [no-commented-out-tests](https://github.com/jest-community/eslint-plugin-jest/blob/HEAD/docs/rules/no-commented-out-tests.md)
1495- [valid-expect-in-promise](https://github.com/jest-community/eslint-plugin-jest/blob/HEAD/docs/rules/valid-expect-in-promise.md)
1496- [no-conditional-expect](https://github.com/jest-community/eslint-plugin-jest/blob/HEAD/docs/rules/no-conditional-expect.md)
1497
1498Consider adding other linting rules for popular frontend JS testing libraries:
1499
1500- [ESLint for Jest](https://www.npmjs.com/package/eslint-plugin-jest)
1501- [ESLint for Jest DOM](https://www.npmjs.com/package/eslint-plugin-jest-dom)
1502- [ESLint for Testing Library](https://www.npmjs.com/package/eslint-plugin-testing-library)
1503
1504How to set up eslint rules for a typical JS/[React](https://www.meticulous.ai/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial) project generated using [create-react-app](https://create-react-app.dev/):
1505
1506- In package.json, add the following eslintConfig section
1507- This will run the recommended eslint rules for all .jsx and .tsx files
1508
1509\`\`\`
1510"eslintConfig": {
1511    "extends": [
1512      "react-app",
1513      "react-app/jest",
1514      "eslint:recommended"
1515    ],
1516    "overrides": [
1517      {
1518        "files": ["**/*.js?(x)", "**/*.ts?(x)"],
1519        "plugins": ["jest"],
1520        "extends": ["plugin:jest/recommended"]
1521      }
1522    ]
1523  },
1524\`\`\`
1525
1526## 2\\. Do Not Repeat Yourself
1527
1528Use beforeEach/afterEach code blocks and utility functions to encapsulate logic that is repeated across multiple tests.
1529
1530### Why
1531
1532- Can easily contain and update shared logic if it is in one place.
1533- Less chance to forget common setup/teardown logic if it is automatically called within a beforeEach/afterEach code block.
1534- Reducing repetition can make tests shorter and more readable.
1535
1536### Example Code To Test
1537
1538\`\`\`
1539    // Example of a function for validating a hotel booking object
1540
1541    function validateBooking(booking) {
1542      const validationMessages = [];
1543
1544      if (booking.startDate >= booking.endDate) {
1545        validationMessages.push(
1546          "Error - Booking end date should be after the start date"
1547        );
1548      }
1549      if (!booking.guests) {
1550        validationMessages.push(
1551          "Error - Booking must have at least one guest"
1552        );
1553      }
1554
1555      return validationMessages;
1556    }
1557\`\`\`
1558
1559### Bad Test Example
1560
1561\`\`\`
1562// Bad - Repeating initialisation for a very similar booking object
1563
1564describe("ValidateBooking - Bad", () => {
1565  it("should return an error if the start and end date are the same", () => {
1566    const mockBooking = {
1567      id: "12345",
1568      userId: "67890",
1569      locationId: "ABCDE",
1570      guests: 2,
1571      startDate: new Date(2022, 10, 10),
1572      endDate: new Date(2022, 10, 10),
1573    };
1574
1575    expect(validateBooking(mockBooking)).toEqual([
1576      "Error - Booking end date should be after the start date",
1577    ]);
1578  });
1579
1580  it("should return an error if there are fewer than one guests", () => {
1581    const mockBooking = {
1582      id: "12345",
1583      userId: "67890",
1584      locationId: "ABCDE",
1585      guests: 0,
1586      startDate: new Date(2022, 10, 10),
1587      endDate: new Date(2022, 10, 12),
1588    };
1589
1590    expect(validateBooking(mockBooking)).toEqual([
1591      "Error - Booking must have at least one guest",
1592    ]);
1593  });
1594
1595  it("should return no errors if the booking is valid", () => {
1596    const mockBooking = {
1597      id: "12345",
1598      userId: "67890",
1599      locationId: "ABCDE",
1600      guests: 2,
1601      startDate: new Date(2022, 10, 10),
1602      endDate: new Date(2022, 10, 12),
1603    };
1604
1605    expect(validateBooking(mockBooking)).toEqual([]);
1606  });
1607});
1608\`\`\`
1609
1610### Better Test Example
1611
1612\`\`\`
1613// Better - Creation of a valid booking object is delegated to a
1614// reusable factory function createMockValidBooking.
1615
1616describe("ValidateBooking - Better", () => {
1617  function createMockValidBooking() {
1618    return {
1619      id: "12345",
1620      userId: "67890",
1621      locationId: "ABCDE",
1622      guests: 2,
1623      startDate: new Date(2022, 10, 10),
1624      endDate: new Date(2022, 10, 12),
1625    };
1626  }
1627
1628  it("should return an error if the start and end dates are the same", () => {
1629    const mockBooking = {
1630      ...createMockValidBooking(),
1631      startDate: new Date(2022, 10, 10),
1632      endDate: new Date(2022, 10, 10),
1633    };
1634
1635    expect(validateBooking(mockBooking)).toEqual([
1636      "Error - Booking end date should be after the start date",
1637    ]);
1638  });
1639
1640  it("should return an error if there are fewer than one guests", () => {
1641    const mockBooking = {
1642      ...createMockValidBooking(),
1643      guests: 0,
1644    };
1645
1646    expect(validateBooking(mockBooking)).toEqual([
1647      "Error - Booking must have at least one guest",
1648    ]);
1649  });
1650
1651  it("should return no errors if the booking is valid", () => {
1652    const mockBooking = createMockValidBooking();
1653
1654    expect(validateBooking(mockBooking)).toEqual([]);
1655  });
1656});
1657\`\`\`
1658
1659## 3\\. Group-related Tests in Describe Blocks
1660
1661### Why
1662
1663- Well titled describe blocks help to organise test files by separating related tests into groups.
1664- Easier to encapsulate setup/teardown logic specific to a subset of tests by adding a beforeEach/afterEach for a single describe block (see “Do not repeat yourself” rule above).
1665- Inner describe blocks can extend setup logic from the outer describe blocks.
1666
1667### Example Code To Test
1668
1669\`\`\`
1670    // Example of a simplified Stack data structure class
1671
1672    class Stack {
1673
1674      constructor() {
1675        this._items = [];
1676      }
1677
1678      push(item) {
1679        this._items.push(item);
1680      }
1681
1682      pop() {
1683        if(this.isEmpty()) {
1684          throw new Error("Error - Cannot pop from an empty stack");
1685        }
1686
1687        return this._items.pop();
1688      }
1689
1690      peek() {
1691        if(this.isEmpty()) {
1692          throw new Error("Error - Cannot peek an empty stack");
1693        }
1694
1695        return this._items[this._items.length-1];
1696      }
1697
1698      isEmpty() {
1699        return this._items.length === 0;
1700      }
1701
1702    }
1703\`\`\`
1704
1705### Bad Test Example
1706
1707\`\`\`
1708  // Bad - No inner "describe" blocks used to group tests.
1709    // Note the repeated use of "on an empty stack" or
1710    // "on a non-empty stack" in each test title.
1711
1712    describe("Stack - Bad", () => {
1713      it("should return isEmpty as true if the stack is empty", () => {
1714        const stack = new Stack();
1715
1716        expect(stack.isEmpty()).toBe(true);
1717      });
1718
1719      it("should return isEmpty as false if the stack is non-empty", () => {
1720        const stack = new Stack();
1721        stack.push(123);
1722
1723        expect(stack.isEmpty()).toBe(false);
1724      });
1725
1726      it("should throw error when peeking on an empty stack", () => {
1727        const stack = new Stack();
1728
1729        expect(() => stack.peek()).toThrowError(
1730          "Error - Cannot peek an empty stack"
1731        );
1732      });
1733
1734      it("should return the top item when peeking a non-empty stack", () => {
1735        const stack = new Stack();
1736        stack.push(123);
1737
1738        expect(stack.peek()).toEqual(123);
1739      });
1740
1741      it("should throw an error when popping from an empty stack", () => {
1742        const stack = new Stack();
1743
1744        expect(() => stack.pop()).toThrowError(
1745          "Error - Cannot pop from an empty stack"
1746        );
1747      });
1748
1749      it("should return the top item when popping a non-empty stack", () => {
1750        const stack = new Stack();
1751        stack.push(123);
1752
1753        expect(stack.pop()).toEqual(123);
1754      });
1755    });
1756\`\`\`
1757
1758### Better Test Example
1759
1760\`\`\`
1761    // Better - Using inner "describe" blocks to group related tests.
1762    // Also using beforeEach to reduce repeating initialization
1763    // within each test.
1764
1765    describe("Stack - Better", () => {
1766      let stack;
1767
1768      beforeEach(() => {
1769        stack = new Stack();
1770      });
1771
1772      describe("empty stack", () => {
1773        it("should return isEmpty as true", () => {
1774          expect(stack.isEmpty()).toBe(true);
1775        });
1776
1777        it("should throw error when peeking", () => {
1778          expect(() => stack.peek()).toThrowError(
1779            "Error - Cannot peek an empty stack"
1780          );
1781        });
1782
1783        it("should throw an error when popping", () => {
1784          expect(() => stack.pop()).toThrowError(
1785            "Error - Cannot pop from an empty stack"
1786          );
1787        });
1788      });
1789
1790      describe("non-empty stack", () => {
1791        beforeEach(() => {
1792          stack.push(123);
1793        });
1794
1795        it("should return isEmpty as false", () => {
1796          expect(stack.isEmpty()).toBe(false);
1797        });
1798
1799        it("should return the top item when peeking", () => {
1800          expect(stack.peek()).toEqual(123);
1801        });
1802
1803        it("should return the top item when popping", () => {
1804          expect(stack.pop()).toEqual(123);
1805        });
1806      });
1807    });
1808\`\`\`
1809
1810## 4\\. A Unit Test Should only Have one Reason to Fail
1811
1812### Why
1813
1814- As the name implies, a unit test should only test a single "unit" of code.
1815- If there is one reason to fail, there is less time spent identifying the root cause of the test failure.
1816- Follows the single-responsibility principle from [SOLID practices](https://en.wikipedia.org/wiki/SOLID).
1817- Encourages shorter and more readable unit tests.
1818
1819### Example Code To Test
1820
1821The Stack class detailed above will be reused for the examples in this section.
1822
1823### Bad Test Example
1824
1825\`\`\`
1826    // Bad - A single test is checking two separate logical branches
1827    // of the "pop" function.
1828    // Calling "stack.pop()" twice with two expectations.
1829
1830    describe("Stack - Bad", () => {
1831      let stack;
1832
1833      beforeEach(() => {
1834        stack = new Stack();
1835      });
1836
1837      it("should only allow popping when the stack is non-empty", () => {
1838        expect(() => stack.pop()).toThrowError();
1839
1840        stack.push(123);
1841        expect(stack.pop()).toEqual(123);
1842      });
1843    });
1844\`\`\`
1845
1846### Better Test Example
1847
1848\`\`\`
1849    // Better - Each test goes through a single logical branch
1850    // of the "pop" function.
1851    // A single "stack.pop()" call and a single "expect" call per test.
1852
1853    describe("Stack - Better", () => {
1854      let stack;
1855
1856      beforeEach(() => {
1857        stack = new Stack();
1858      });
1859
1860      it("should throw an error when popping from an empty stack", () => {
1861        expect(() => stack.pop()).toThrowError();
1862      });
1863
1864      it("should return the top item when popping a non-empty stack", () => {
1865        stack.push(123);
1866
1867        expect(stack.pop()).toEqual(123);
1868      });
1869    });
1870\`\`\`
1871
1872## 5\\. Keep Tests Independent
1873
1874### Why
1875
1876- The outcome of one test should not affect any other tests.
1877- Sharing state or mock instances between tests may lead to brittle or “flaky” tests that will pass erroneously.
1878- Dependent tests may fail unexpectedly if a new test is added or if test order changes.
1879
1880### Example Code To Test
1881
1882\`\`\`
1883    // Simple example of a function that accepts a callback
1884    // and executes it after a set number of milliseconds.
1885
1886    function callInFive(callback) {
1887      setTimeout(callback, 5000);
1888    }
1889\`\`\`
1890
1891### Bad Test Example
1892
1893\`\`\`
1894    // Bad - The tests below are NOT independent.
1895    // The mockCallback is not being reset between tests.
1896    // The timer from one test is not cleared before starting the next.
1897
1898    describe("callInFive - Bad", () => {
1899      beforeEach(() => {
1900        // Using jest library to control passage of time within each test
1901        jest.useFakeTimers();
1902      })
1903
1904      const mockCallback = jest.fn();
1905
1906      it("should not call callback before five seconds elapse", () => {
1907        callInFive(mockCallback);
1908
1909        jest.advanceTimersByTime(5000 - 1);
1910
1911        expect(mockCallback).not.toHaveBeenCalled();
1912      });
1913
1914      it("should call callback after five seconds elapse", () => {
1915        callInFive(mockCallback);
1916
1917        jest.advanceTimersByTime(5000);
1918
1919        expect(mockCallback).toHaveBeenCalled();
1920      });
1921    });
1922\`\`\`
1923
1924### Better Test Example
1925
1926\`\`\`
1927    // Better - We reset any ongoing timers and faked/mocked
1928    // functions between tests.
1929
1930    describe("callInFive - Better", () => {
1931
1932      beforeEach(() => {
1933        jest.useFakeTimers();
1934        // Remember to reset any mocks and spies before starting each test
1935        jest.resetAllMocks();
1936      })
1937
1938      // Remember to clear up any remaining pending timers and
1939      // restore native time functionality
1940      afterEach(() => {
1941        jest.runOnlyPendingTimers();
1942        jest.useRealTimers();
1943      })
1944
1945      const mockCallback = jest.fn();
1946
1947      it("should not call callback before five seconds elapse", () => {
1948        callInFive(mockCallback);
1949
1950        jest.advanceTimersByTime(5000 - 1);
1951
1952        expect(mockCallback).not.toHaveBeenCalled();
1953      });
1954
1955      it("should call callback after five seconds elapse", () => {
1956        callInFive(mockCallback);
1957
1958        jest.advanceTimersByTime(5000);
1959
1960        expect(mockCallback).toHaveBeenCalled();
1961      });
1962    });
1963\`\`\`
1964
1965### Further Notes
1966
1967- [You can configure Jest testing library](https://jestjs.io/docs/configuration#resetmocks-boolean) to automatically reset mocks before each test by adding resetMocks: true in your jest config file or alternatively adding the following to package.json
1968
1969\`\`\`
1970      "jest": {
1971        "resetMocks": true
1972      }
1973\`\`\`
1974
1975- Starting a React project with create-react-app will automatically add resetMocks: true to the built-in jest config ([see the docs](https://create-react-app.dev/docs/running-tests/#configuration)). Which is equivalent to automatically calling [jest.resetAllMocks()](https://jestjs.io/docs/jest-object#jestresetallmocks) before each test.
1976- Ensuring that your tests run in random order may help to identify cases where unit tests are not independent.
1977
1978## 6\\. Test a Variety of Input Parameters
1979
1980### Why
1981
1982- Ideally our tests should verify all possible code paths through the code under test.
1983- We want to be confident that our deployed code can handle the full range of inputs that a user can possibly enter.
1984- We may be biassed towards testing using inputs that trigger code paths we know to be correct. Testing a range of inputs can help verify edge cases we may not have considered.
1985
1986### Example Code To Test
1987
1988\`\`\`
1989    // Example of a function that creates an array of specific length
1990    // and filled with a given value.
1991
1992    function initArray(length, value) {
1993      // Note that there is a mistake in this if condition -
1994      // we throw an error if the length is zero.
1995      if (!length) {
1996       throw new Error(
1997        "Invalid parameter length - must be a number greater or equal to 0"
1998       );
1999      }
2000
2001      return new Array(length).fill().map(() => value);
2002    }
2003\`\`\`
2004
2005### Bad Test Example
2006
2007\`\`\`
2008// Bad - The below tests pass, and it may seem like these tests cover
2009// all code paths, but note that 0 is falsy in JS - and the below tests
2010// do not check the case of trying to create an array of length = 0
2011// or length = -1.
2012
2013describe("initArray - Bad", () => {
2014  it("should create an array of given size filled with the same value", () => {
2015    expect(initArray(3, { id: 123 })).toEqual([
2016      { id: 123 },
2017      { id: 123 },
2018      { id: 123 },
2019    ]);
2020  });
2021
2022  it("should throw an error if the array length parameter is invali
2022d", () => {
2023    expect(() => initArray(undefined, { id: 123 })).toThrowError();
2024  });
2025});
2026\`\`\`
2027
2028### Better Test Example
2029
2030\`\`\`
2031    // Better - Added tests for the edge case of creating an array of
2032    // length = 0 and length = -1
2033
2034    describe("initArray - Better", () => {
2035      it("should create an array of given size filled with the same value", () => {
2036        expect(initArray(3, { id: 123 })).toEqual([
2037          { id: 123 },
2038          { id: 123 },
2039          { id: 123 },
2040        ]);
2041      });
2042
2043      it("should handle an array length parameter of 0", () => {
2044        expect(initArray(0, { id: 123 })).toEqual([]);
2045      });
2046
2047      it("should throw an error if the array length parameter is -1", () => {
2048        expect(() => initArray(-1, { id: 123 })).toThrowError();
2049      });
2050
2051      it("should throw an error if the array length parameter is invalid", () => {
2052        expect(() => initArray(undefined, { id: 123 })).toThrowError();
2053      });
2054    });
2055\`\`\`
2056
2057### Further Notes
2058
2059We can also catch mistakes with unhandled inputs using a testing framework like [fast-check](https://github.com/dubzzz/fast-check). This will test our functions against a range of random values, which can automatically improve our code coverage and increase the chance of finding a bug.
2060
2061\`\`\`
2062    // Better - Can also use a framework like fast-check to generate
2063    // test cases against a range of inputs.
2064    // By default each assert will run 100 times with random input values.
2065
2066    describe("initArray - Better - Using fast-check", () => {
2067      it("should return an array of specified length", () =>
2068        fc.assert(
2069          fc.property(
2070            fc.integer({ min: 0, max: 100 }),
2071            fc.anything(),
2072            (length, value) => {
2073              expect(initArray(length, value).length).toEqual(length);
2074            }
2075          )
2076        ));
2077
2078      it("should throw an error if initialising array of length < 0", () =>
2079        fc.assert(
2080          fc.property(
2081            fc.integer({ max: -1 }),
2082            fc.anything(),
2083            (length, value) => {
2084              expect(() => initArray(length, value)).toThrowError();
2085            })
2086        ));
2087    });
2088\`\`\`
2089
2090## Conclusion
2091
2092To summarise, in this blog we have outlined the benefits for the following practices in frontend testing, and how they are applicable to example tests in JavaScript/Jest:
2093
20941.  Using linting rules for tests
20952.  Do not repeat yourself
20963.  Group related tests in describe blocks
20974.  A unit test should only have one reason to fail
20985.  Keep tests independent
20996.  Test a variety of input parameters
2100
2101These are just a few of the conventions to consider when writing tests, so if you are interested in learning more about frontend best practices you may find relevant blogs from Meticulous on [Frontend Testing Pyramid](https://meticulous.ai/blog/testing-pyramid-for-frontend/) or [JavaScript UI Test best practices](https://meticulous.ai/blog/javascript-ui-testing-best-practices/).
2102
2103Thank you for reading!
2104
2105## Meticulous
2106
2107Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
2108
2109This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
2110
2111This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
2112`,z4=`---
2113title: "IndexedDB | Guide to Getting Started, Tutorial, & Best Practices"
2114slug: "getting-started-with-indexeddb"
2115date: "2024-01-16"
2116description: "A tutorial on how to get started with IndexedDB and whether you should use it, with an example code walkthrough."
2117author: "Alex Merced"
2118authorImage: "/images/blog/authors/6568f9276e20a9ef23a6e08d_alex-merced.webp"
2119heroImage: "/images/blog/getting-started-with-indexeddb/6568f7fe3de1438cc155f581_getting-started-with-indexeddb.webp"
2120heroImageAlt: "IndexedDB | Guide to Getting Started, Tutorial, & Best Practices"
2121categories: ["Tutorial"]
2122showTableOfContents: true
2123showPromo: true
2124---
2125
2126This article will teach you about IndexedDB with a small tutorial, and compare IndexedDB to 
2126some of the other options available. IndexedDB is used for storing data in the browser and is particularly important for web application that need to work offline, like most progressive web applications.
2127
2128First, let’s cover some context of why you might need to store data in a web browser. Data is everywhere within web applications - user interactions create data, look up data, update data and delete data. Without a way to store this data you can’t allow user interactions to persist state across multiple uses of a web application.You’ll often use databases like MySQL, Postgres, MongoDB, Neo4j, ArangoDB and others to handle this storage, but what if you want your application to work offline?
2129
2130This is especially important within the growing trend of progressive web applications, applications that replicate the feel of a native app, but are in the browser. These progressive web applications must work offline, and so require a storage option. Luckily, there are several tools on how to store data in browser where it can be accessed online and offline.
2131
2132## Browser Storage Options
2133
2134Web standards provide you with three primary APIs on how to store data in the browser:
2135
2136- Cookies: This data is stored in the browser and cookies have a size limit of 4k. Often when a server responds to a request they may include a SET-COOKIE header which gives the browser a key and value to store. The client should then include this cookie in the headers of future requests, which will allow the server to recognize browser sessions and more. Often these cookies have an HTTP-Only attribute, which means the cookie cannot be accessed through a client-side script. This makes cookies a poor choice for holding offline data.
2137- LocalStorage/SessionStorage: Localstorage/Sessionstorage is a key value store built into the browser where each key has a size limit of 5mb. LocalStorage stores data until it is deleted while sessionStorage will clear itself when the browser closes. Otherwise, their APIs are the same. You can add a key-value pair with window.localStorage.setItem("Key", "Value") and retrieve a value with window.localStorage.getItem("Key"). Note that the LocalStorage API is synchronous and so using it does block other activity in the browser, which can be an issue. You can read more about LocalStorage [here](https://meticulous.ai/blog/localstorage-complete-guide/).
2138- IndexedDB: A full on document database built into the browser with no storage limits, it allows you to access the data asynchronously so great for preventing complex operations from blocking rendering and other activities. This is what we will dive into in-depth below.
2139
2140Within the context of these options, localStorage is a good choice for simple operations and storing data in small amounts. For more complex or regular operations IndexedDB may be the better option, particularly if you need to fetch data asynchronously.
2141
2142The IndexedDB API is more complicated than the LocalStorage API. So let’s build something with IndexedDB to give you a better feel for how it works!
2143
2144[\\[Repo with Code from the following exercise for reference\\]](https://github.com/AlexMercedCoder/indexeddb-sample/blob/main/index.html)
2145
2146## Tutorial Setup
2147
2148- Open your favorite IDE/Text Editor
2149- Create a new HTML file, let’s call it index.html
2150
2151In the index.html put the following starter code:
2152
2153\`\`\`
2154
2155
2156
2157
2158
2159    IndexedDB Todo List
2160
2161
2162
2163
2164        IndexedDB Todo-List
2165
2166
2167            Add Todo
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177        // Variables holding our inputs
2178        const textInput = document.querySelector("[type='text']")
2179        const button = document.querySelector("button")
2180        // Array to hold todos
2181        const todos = []
2182        // function to render todos
2183        function renderTodos(){
2184            const ul = document.querySelector("#todos ul")
2185            ul.innerHTML = ""
2186            for (todo of todos){
2187                ul.innerHTML += \`<li>\${todo}</li>\`
2188            }
2189        }
2190        renderTodos()
2191
2192
2193
2194        body {
2195            text-align: center;
2196        }
2197
2198        h1 {
2199            color: brown;
2200        }
2201
2202
2203
2204\`\`\`
2205
2206This sets up a basic shell for a todo list. Now we can start setting up IndexedDB. Open this file in your browser. If you’re using VScode, an extension like [liveserver](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) will be useful.
2207
2208## Getting Connected to IndexedDB
2209
2210IndexedDB support is pretty good but we’d still like to check if the browser supports an implementation of the API so you can add the following function to check.
2211
2212\`\`\`
2213      function getIndexDB() {
2214        const indexedDB =
2215          window.indexedDB ||
2216          window.mozIndexedDB ||
2217          window.webkitIndexedDB ||
2218          window.msIndexedDB ||
2219          window.shimIndexedDB;
2220          if (indexedDB){
2221              return indexedDB
2222          }
2223          console.error("indexedDB not supported by this browser")
2224          return null
2225      }
2226\`\`\`
2227
2228This function will either return the browser implementation of IndexedDB or log that it is not supported by the browser. You can log the result of calling getIndexDB in your browser to confirm that your browser supports IndexedDB. Below you can see the compatibility list of desktop web browsers from caniuse. You can find the [full list including mobile browsers here.](https://caniuse.com/indexeddb)
2229
2230![Can I use compatibility list of desktop web browsers](/images/blog/getting-started-with-indexeddb/6568f848e349504198a06f62_caniuse-27s-compatibility-list-of-desktop-web-browsers.png)
2231
2232Now let’s open a database with indexedDB.open("database name", 1). The first argument to .open is the name of the database and the second argument is the version of the database. You should increment the version number in the .open .open call if you wish to fire a onupgradeneeded. The .openmethod will return an object that has several properties, including onerror,onupgradeneeded, and onsuccess, each of which accepts a callback which executes when the relevant event occurs.
2233
2234\`\`\`
2235  const indexedDB = getIndexDB()
2236      // console.log(indexedDB)
2237      const request = indexedDB.open("todoDB", 1)
2238      console.log(request)
2239      renderTodos();
2240\`\`\`
2241
2242You should see a console.log that shows an IDBOpenDBRequest object being logged. IndexedDB is event-based, which fits it’s asynchronous model. Next let’s listen to the possible events that will occur as the database starts up. First we’ll listen for the request.onerror event in case there are any errors accessing the database.
2243
2244\`\`\`
2245 const indexedDB = getIndexDB()
2246      // console.log(indexedDB)
2247      const request = indexedDB.open("todoDB", 1)
2248      //console.log(request)
2249      //onerror handling
2250      request.onerror = (event) => console.error("IndexDB Error: ", event)
2251
2252      renderTodos();
2253\`\`\`
2254
2255The next event we will hook into is the request.onupgradeneeded event, which runs whenever an attempt is made to open a database with a version number higher than the database’s current version number. This is the function in which to create your stores/tables and their schemas. This function will only execute once per version number. So if you decide to change the onupgradeneeded allback to update your schema or create new stores, then the version number should also be incremented in the next .open call. A store is essentially the equivalent of a table in traditional databases.
2256
2257\`\`\`
2258const indexedDB = getIndexDB();
2259      // console.log(indexedDB)
2260      const request = indexedDB.open("todoDB", 1);
2261      //console.log(request)
2262      //onerror handling
2263      request.onerror = (event) => console.error("IndexDB Error: ", event);
2264      //onupgradeneeded
2265      request.onupgradeneeded = () => {
2266        // grab the database connection
2267        const db = request.result;
2268        // define a new store
2269        const store = db.createObjectStore("todos", {
2270          keyPath: "id",
2271          autoIncrement: true,
2272        });
2273        // specify a property as an index
2274        store.createIndex("todos_text", ["text"], {unique: false})
2275      };
2276
2277      renderTodos();
2278\`\`\`
2279
2280In the on upgrade needed call we do the following:
2281
2282- grab the database object (you know its available if the onupgradeneeded function is running)
2283- create a new store/table/collection called todos with a key of id that is an autoincrementing number (the unique identifier of records)
2284- specify todos_text as an index, which allows us to search the database by todos_text later. You don’t have to create an index if you don’t plan on searching by a particular property.
2285
2286Last you want to handle the request.onsuccess event that runs after the database connection and stores are all setup and configured. You will want to use this opportunity to pull the list of todos and inject them into our array. (yes, there is no todos, yet)
2287
2288\`\`\`
2289 //onsuccess
2290      request.onsuccess = () => {
2291          console.log("Database Connection Established")
2292          //grab the database connection
2293          const db = request.result
2294          // create a transaction object
2295          const tx = db.transaction("todos", "readwrite")
2296          //create a transaction with our store
2297          const todosStore = tx.objectStore("todos")
2298          //get all todos
2299          const query = todosStore.getAll()
2300          //use data from query
2301          query.onsuccess =  () => {
2302              console.log("All Todos: ", query.result)
2303              for (todo of query.result){
2304                  todos.push(todo.text)
2305              }
2306              renderTodos()
2307          }
2308      }
2309\`\`\`
2310
2311In our on success we do the following:
2312
2313- get the database connection
2314- create a transaction
2315- specify which store we are transacting on
2316- run a getAll query to get all documents/records in that store
2317- In the query specific onsuccess event we loop through the todos, push them into the todos array and call renderTodos() so they are rendered to the dom
2318
2319You should see a console.log with an empty array in your console.
2320
2321\\[Troubleshooting tip: If you are running a hot reloading web server like liveserver you may see an error that there are no stores. This is because the onupgradeneeded function executed prior to you finishing writing the function. Thus, it won’t execute again for that version number. The solution is to increment the table version number, which will create an onupgradeneeded, and the onupgradeneeded callback will execute on the next page refresh.\\]
2322
2323Now that we have the database setup with can follow this same pattern for any other events we want to happen. For example, let’s create an event when you click the button that will add a new todo not only to the dom but to the database so it’ll show up on page refreshes.
2324
2325\`\`\`
2326  // button event
2327      button.addEventListener("click", (event) => {
2328          // setup a transaction
2329          const db = request.result
2330          const tx = db.transaction("todos", "readwrite")
2331          const todosStore = tx.objectStore("todos")
2332          // add a todo
2333          const text = textInput.value
2334          todos.push(text) // add to todo array
2335          todosStore.put({text}) // add to indexedDB
2336          renderTodos() // update dom
2337      })
2338\`\`\`
2339
2340Now you can add todos and since you are using IndexedDB it will work whether you are online or offline as you can see in this gif.
2341
2342![Adding todos using IndexedDB](/images/blog/getting-started-with-indexeddb/6568f8c75295615c17c39fb6_adding-todos-using-IndexedDB.gif)
2343
2344Add some todos and when you refresh the page you shall see the todos persist. They will also show up in the console.log of the query result, with each todo having a unique ID. The full code up to this point should now look like so:
2345
2346\`\`\`
2347
2348
2349
2350
2351
2352    IndexedDB Todo List
2353
2354
2355
2356
2357      IndexedDB Todo-List
2358
2359
2360        Add Todo
2361
2362
2363
2364
2365
2366
2367
2368      // Variables holding our inputs
2369      const textInput = document.querySelector("[type='text']");
2370      const button = document.querySelector("button");
2371      // Array to hold todos
2372      const todos = [];
2373      // function to render todos
2374      function renderTodos() {
2375        const ul = document.querySelector("#todos ul");
2376        ul.innerHTML = "";
2377        for (todo of todos) {
2378          ul.innerHTML += \`<li>\${todo}</li>\`;
2379        }
2380      }
2381      // Function to check indexedDB implementation and return it
2382      function getIndexDB() {
2383        const indexedDB =
2384          window.indexedDB ||
2385          window.mozIndexedDB ||
2386          window.webkitIndexedDB ||
2387          window.msIndexedDB ||
2388          window.shimIndexedDB;
2389        if (indexedDB) {
2390          return indexedDB;
2391        }
2392        console.log("indexedDB not supported by this browser");
2393        return null;
2394      }
2395      const indexedDB = getIndexDB();
2396      // console.log(indexedDB)
2397      const request = indexedDB.open("todoDB", 2);
2398      //console.log(request)
2399      //onerror handling
2400      request.onerror = (event) => console.error("IndexDB Error: ", event);
2401      //onupgradeneeded
2402      request.onupgradeneeded = () => {
2403        // grab the database connection
2404        const db = request.result;
2405        // define a new store
2406        const store = db.createObjectStore("todos", {
2407          keyPath: "id",
2408          autoIncrement: true,
2409        });
2410        // specify a property as an index
2411        store.createIndex("todos_text", ["text"], {unique: false})
2412      };
2413      //onsuccess
2414      request.onsuccess = () => {
2415          console.log("Database Connection Established")
2416          //grab the database connection
2417          const db = request.result
2418          // create a transaction object
2419          const tx = db.transaction("todos", "readwrite")
2420          //create a transaction with our store
2421          const todosStore = tx.objectStore("todos")
2422          //get all todos
2423          const query = todosStore.getAll()
2424          //use data from query
2425          query.onsuccess =  () => {
2426              console.log("All Todos: ", query.result)
2427              for (todo of query.result){
2428                  todos.push(todo.text)
2429              }
2430              renderTodos()
2431          }
2432      }
2433      // button event
2434      button.addEventListener("click", (event) => {
2435          // setup a transaction
2436          const db = request.result
2437          const tx = db.transaction("todos", "readwrite")
2438          const todosStore = tx.objectStore("todos")
2439          // add a todo
2440          const text = textInput.value
2441          todos.push(text) // add to todo array
2442          todosStore.put({text}) // add to indexedDB
2443          renderTodos() // update dom
2444      })
2445
2446      renderTodos();
2447
2448
2449
2450      body {
2451        text-align: center;
2452      }
2453      h1 {
2454        color: brown;
2455      }
2456
2457
2458
2459\`\`\`
2460
2461Other methods on todosStore object that can be used for different types of transaction:
2462
2463- clear - deletes all documents/records in store
2464- add - insert a record with the given id (will error if it already exists)
2465- put - insert or update a record with given id (will update if already exists)
2466- get - get record with particular id
2467- getAll - get all record/documents from the store
2468- count - returns the count of records in the store
2469- createIndex - create object to query based on a declared index
2470- delete - delete document with the given id
2471
2472## Performance and other Considerations
2473
2474A couple of things you want to consider:
2475
2476- Storing files as blobs may not be supported in all browsers, you’ll find better support storing them as ArrayBuffers.
2477- Some browsers may not support writing to IndexedDB when in private browsing mode
2478- IndexedDB creates structured clones when writing your objects which does block the main thread so if you have large objects filled with even more nested objects this can cause some latency.
2479- If the user shuts down the browser there is a chance any incomplete transactions will be aborted.
2480- If another browser tab has the application open with a newer database version number, it will be blocked from upgrading till all older version tabs are closed/reloaded. Luckily, you can use the onblocked event to trigger alerts to notify the user they need to do this.
2481
2482You can find more limitations of [IndexedDB in the MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Basic_Terminology).
2483
2484Whilst indexedDB is great for making your app work offline, it should not be your main datastore. Once there in an internet connection you would probably want to sync indexedDB with your external database, so that the user’s information is not lost if they clear their browser data.
2485
2486## Conclusion
2487
2488IndexedDB gives you a powerful asynchronous document database in your browser. The IndexedDB API can be a little cumbersome but there are libraries like [Dexie](https://dexie.org/) that give you wrappers around IndexedDB which are much easier to use.
2489
2490## Meticulous
2491
2492Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
2493
2494This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The softw
2494are gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
2495
2496This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
2497`,N4=`---
2498title: "Logging in React | In-Depth Guide & Tutorial"
2499slug: "getting-started-with-react-logging"
2500date: "2024-01-16"
2501description: "How to effectively log info and error messages within a React application, and the best practices for doing this."
2502author: "Johann-Michael Thiebaut"
2503authorImage: "/images/blog/authors/6568fb928be7bcaa1d425ece_johann-michael-thiebaut.jpeg"
2504heroImage: "/images/blog/getting-started-with-react-logging/656a5d03303eec6586c3d56e_getting-started-with-react-logging.png"
2505heroImageAlt: "Logging in React | In-Depth Guide & Tutorial"
2506categories: ["React"]
2507showTableOfContents: true
2508showPromo: true
2509---
2510
2511We all start with writing a bunch of console.log statements in our work-in-progress React components. But then, you find yourself tracking these guys down because it’s not great to have users see all the console logs in a production app.
2512
2513Here, we’ll see that with a little structure we can start transforming a bad habit into a powerful best practice.
2514
2515![Log everything](/images/blog/getting-started-with-react-logging/6568f9f7f053c1165c4ac2bb_log-everything.jpeg)
2516
2517## 1\\. Our Little React Sandbox
2518
2519Let’s quickly create a React app to play with, using yarn, create-react-app and Typescript:
2520
2521\`\`\`
2522yarn create react-app --template typescript react-logging-start
2523cd react-logging-start
2524yarn start
2525\`\`\`
2526
2527We can now start playing around in src/App.tsx and add our usual console.log()statements.
2528
2529\`\`\`
2530import React from 'react';
2531import logo from './logo.svg';
2532import './App.css';
2533
2534function App() {
2535  console.log('Hello!');
2536  console.warn('Careful there!');
2537  console.error('Oh, no!');
2538
2539  return (
2540    <div className="App">
2541      <header className="App-header">
2542        <img src={logo} className="App-logo" alt="logo" />
2543        <p>
2544          Edit <code>src/App.tsx</code> and save to reload.
2545        </p>
2546        <a
2547          className="App-link"
2548          href="https://reactjs.org"
2549          target="_blank"
2550          rel="noopener noreferrer"
2551        >
2552          Learn React
2553        </a>
2554      </header>
2555    </div>
2556  );
2557}
2558
2559export default App;
2560\`\`\`
2561
2562These should appear in your browser’s developer console:
2563
2564![Developer Console Logs from App.tsx](/images/blog/getting-started-with-react-logging/6568fa2853dd376c63e93524_logging-ohno.png)
2565
2566In this case, it would be simple to remove or comment out the console.log() line before pushing the code to production, but this can become tedious as your application grows.
2567
2568So what should we do? Let’s see how we can keep the log statements but suppress the outputs by building a simple wrapper around the console object.
2569
2570## 2\\. Our Own Logging Wrapper
2571
2572Let’s start by simply replicating the console functions.
2573
2574\`\`\`
2575import React from 'react';
2576import './App.css';
2577import { ConsoleLogger } from './logger';
2578import logo from './logo.svg';
2579
2580function App() {
2581  const logger = new ConsoleLogger();
2582
2583  logger.log('Hello!');
2584  logger.warn('Careful there!');
2585  logger.error('Oh, no!');
2586
2587  return (
2588    <div className="App">
2589      <header className="App-header">
2590        <img src={logo} className="App-logo" alt="logo" />
2591        <p>
2592          Edit <code>src/App.tsx</code> and save to reload.
2593        </p>
2594        <a
2595          className="App-link"
2596          href="https://reactjs.org"
2597          target="_blank"
2598          rel="noopener noreferrer"
2599        >
2600          Learn React
2601        </a>
2602      </header>
2603    </div>
2604  );
2605}
2606
2607export default App;
2608\`\`\`
2609
2610\`\`\`
2611/** Signature of a logging function */
2612export interface LogFn {
2613  (message?: any, ...optionalParams: any[]): void;
2614}
2615
2616/** Basic logger interface */
2617export interface Logger {
2618  log: LogFn;
2619  warn: LogFn;
2620  error: LogFn;
2621}
2622
2623/** Logger which outputs to the browser console */
2624export class ConsoleLogger implements Logger {
2625  log(message?: any, ...optionalParams: any[]): void {
2626    console.log(message, ...optionalParams);
2627  }
2628
2629  warn(message?: any, ...optionalParams: any[]): void {
2630    console.warn(message, ...optionalParams);
2631  }
2632
2633  error(message?: any, ...optionalParams: any[]): void {
2634    console.error(message, ...optionalParams);
2635  }
2636}
2637\`\`\`
2638
2639Ah, but now we’ve lost the correct source file mapping in the browser console:
2640
2641![Developer Console Logs from App.tsx](/images/blog/getting-started-with-react-logging/6568fa56da43aff18ec14b6c_console.png)
2642
2643To preserve the correct source file lines, we can reuse the console functions themselves.
2644
2645\`\`\`
2646/** Logger which outputs to the browser console */
2647export class ConsoleLogger implements Logger {
2648  readonly log: LogFn;
2649  readonly warn: LogFn;
2650  readonly error: LogFn;
2651
2652  constructor() {
2653    this.log = console.log.bind(console);
2654    this.warn = console.warn.bind(console);
2655    this.error = console.error.bind(console);
2656  }
2657}/** Logger which outputs to the browser console */
2658export class ConsoleLogger implements Logger {
2659  readonly log: LogFn;
2660  readonly warn: LogFn;
2661  readonly error: LogFn;
2662
2663  constructor() {
2664    this.log = console.log.bind(console);
2665    this.warn = console.warn.bind(console);
2666    this.error = console.error.bind(console);
2667  }
2668}
2669\`\`\`
2670
2671Now, we’re back to where we started:
2672
2673![Developer console logs from App.tsx](/images/blog/getting-started-with-react-logging/6568fa2853dd376c63e93524_logging-ohno.png)
2674
2675Wait, wait, wait! There’s a reason we did all that work.
2676
2677Now, we can customize our own console logger to do what we need.
2678
2679## 3\\. Let’s Level Up Our Logging Game
2680
2681The first thing we can do is to make our ConsoleLogger configurable, we can tell it to output all logs, only the warn and error logs, or the error logs only.
2682
2683\`\`\`
2684/** Signature of a logging function */
2685export interface LogFn {
2686  (message?: any, ...optionalParams: any[]): void;
2687}
2688
2689/** Basic logger interface */
2690export interface Logger {
2691  log: LogFn;
2692  warn: LogFn;
2693  error: LogFn;
2694}
2695
2696/** Log levels */
2697export type LogLevel = 'log' | 'warn' | 'error';
2698
2699const NO_OP: LogFn = (message?: any, ...optionalParams: any[]) => {};
2700
2701/** Logger which outputs to the browser console */
2702export class ConsoleLogger implements Logger {
2703  readonly log: LogFn;
2704  readonly warn: LogFn;
2705  readonly error: LogFn;
2706
2707  constructor(options?: { level? : LogLevel }) {
2708    const { level } = options || {};
2709
2710    this.error = console.error.bind(console);
2711
2712    if (level === 'error') {
2713      this.warn = NO_OP;
2714      this.log = NO_OP;
2715
2716      return;
2717    }
2718
2719    this.warn = console.warn.bind(console);
2720
2721    if (level === 'warn') {
2722      this.log = NO_OP;
2723
2724      return;
2725    }
2726
2727    this.log = console.log.bind(console);
2728  }
2729}
2730\`\`\`
2731
2732We can see that setting the leveloption to error will make it drop log and warn messages while using the warn level will make it drop log messages only.
2733
2734The log level option is also optional, in which case the loglevel is used, i.e. all messages are outputted to the browser console.
2735
2736Now we can try it by editing src/App.tsx and setting the level option. Using the warn level, there should now be only two logs printed in the console:
2737
2738\`\`\`
2739const logger = new ConsoleLogger({ level: 'warn' });
2740\`\`\`
2741
2742“But I still need to find and replace all those ConsoleLogger thingies before pushing to production!”
2743
2744Ah, yes… But you can also create a global logger options object to help you:
2745
2746\`\`\`
2747import React from 'react';
2748import './App.css';
2749import { LOG_LEVEL } from './environment';
2750import { ConsoleLogger } from './logger';
2751import logo from './logo.svg';
2752
2753function App() {
2754  const logger = new ConsoleLogger({ level: LOG_LEVEL });
2755
2756  logger.log('Hello!');
2757  logger.warn('Careful there!');
2758  logger.error('Oh, no!');
2759
2760  return (
2761    <div className="App">
2762      <header className="App-header">
2763        <img src={logo} className="App-logo" alt="logo" />
2764        <p>
2765          Edit <code>src/App.tsx</code> and save to reload.
2766        </p>
2767        <a
2768          className="App-link"
2769          href="https://reactjs.org"
2770          target="_blank"
2771          rel="noopener noreferrer"
2772        >
2773          Learn React
2774        </a>
2775      </header>
2776    </div>
2777  );
2778}
2779
2780export default App;
2781\`\`\`
2782
2783\`\`\`
2784import { LogLevel } from "./logger";
2785
2786/** The App environment */
2787export type Environment = 'development' | 'production';
2788
2789export const APP_ENV: Environment = process.env.REACT_APP_APP_ENV === 'production' ? 'production' : 'development';
2790
2791export const LOG_LEVEL: LogLevel = APP_ENV === 'production' ? 'warn' : 'log';
2792\`\`\`
2793
2794Now, you can see the difference when running the app:
2795
2796\`\`\`
2797yarn start  # Will show all logs
2798REACT_APP_APP_ENV='production' yarn start  # Will show only 'warn' and
2799'error' logs
2800# Remember to build the production App with:
2801REACT_APP_APP_ENV='production' yarn build
2802\`\`\`
2803
2804If you’ve managed to stay on the ride with me up till now, you know you can already use logger.log() everywhere and know it will be removed in the production builds.
2805
2806Note: You can replace process.env.REACT_APP_APP_ENV with process.env.NODE_ENV to have log levels configured by create-react-app (see [docs](https://create-react-app.dev/docs/adding-custom-environment-variables/)).
2807
2808## 4\\. Exporting the Logger
2809
2810One last step we can do is to export the logger so that we can simply import it and reuse it in all the components of our React application.
2811
2812\`\`\`
2813import React from 'react';
2814import './App.css';
2815import { logger } from './logger';
2816import logo from './logo.svg';
2817
2818function App() {
2819  logger.log('Hello!');
2820  logger.warn('Careful there!');
2821  logger.error('Oh, no!');
2822
2823  return (
2824    <div className="App">
2825      <header className="App-header">
2826        <img src={logo} className="App-logo" alt="logo" />
2827        <p>
2828          Edit <code>src/App.tsx</code> and save to reload.
2829        </p>
2830        <a
2831          className="App-link"
2832          href="https://reactjs.org"
2833          target="_blank"
2834          rel="noopener noreferrer"
2835        >
2836          Learn React
2837        </a>
2838      </header>
2839    </div>
2840  );
2841}
2842
2843export default App;
2844\`\`\`
2845
2846\`\`\`
2847import { LogLevel } from "./logger";
2848
2849/** The App environment */
2850export type Environment = 'development' | 'production';
2851
2852export const APP_ENV: Environment = process.env.REACT_APP_APP_ENV === 'production' ? 'production' : 'development';
2853
2854export const LOG_LEVEL: LogLevel = APP_ENV === 'production' ? 'warn' : 'log';
2855\`\`\`
2856
2857\`\`\`
2858import { LOG_LEVEL } from "./environment";
2859
2860/** Signature of a logging function */
2861export interface LogFn {
2862  (message?: any, ...optionalParams: any[]): void;
2863}
2864
2865/** Basic logger interface */
2866export interface Logger {
2867  log: LogFn;
2868  warn: LogFn;
2869  error: LogFn;
2870}
2871
2872/** Log levels */
2873export type LogLevel = 'log' | 'warn' | 'error';
2874
2875const NO_OP: LogFn = (message?: any, ...optionalParams: any[]) => {};
2876
2877/** Logger which outputs to the browser console */
2878export class ConsoleLogger implements Logger {
2879  readonly log: LogFn;
2880  readonly warn: LogFn;
2881  readonly error: LogFn;
2882
2883  constructor(options?: { level? : LogLevel }) {
2884    const { level } = options || {};
2885
2886    this.error = console.error.bind(console);
2887
2888    if (level === 'error') {
2889      this.warn = NO_OP;
2890      this.log = NO_OP;
2891
2892      return;
2893    }
2894
2895    this.warn = console.warn.bind(console);
2896
2897    if (level === 'warn') {
2898      this.log = NO_OP;
2899
2900      return;
2901    }
2902
2903    this.log = console.log.bind(console);
2904  }
2905}
2906
2907export const logger = new ConsoleLogger({ level: LOG_LEVEL });
2908\`\`\`
2909
2910As you can see, we can now add import { logger } from './logger'; everywhere we want to use our logger, without the need to create a new instance each and every time.
2911
2912## Conclusion
2913
2914Wow, you made it!
2915
2916![Celebrate](/images/blog/getting-started-with-react-logging/6568fb46498487fdebc8b4e3_celebrate.gif)
2917
2918I understand your enthusiasm 😂️. While this may not look like much right now, it’s still a nice start towards good logging hygiene.
2919
2920With our quick setup we can now replace all the console.log() statements we have with logger.log() and we won’t have to track them down before building a production app. This encourages you to still use logging statements while developing.
2921
2922## Next Steps
2923
2924What can you do from here?
2925
2926- As your application grows, you may find that keeping all log() statements in development is becoming too verbose. You can disable the log level in development too and use it only in the components you’re working on (by creating a new logger instance).
2927- You can add logging to [Sentry](https://sentry.io/welcome/) or other monitoring platforms in production. This will allow you to capture warning and error events even when they don’t crash your application.
2928
2929## Meticulous
2930
2931Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
2932
2933This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
2934
2935This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
2936`,H4=`---
2937title: "Visual Regression Testing in a React App | Step-by-Step Tutorial"
2938slug: "how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial"
2939date: "2024-01-16"
2940description: "A guide on how to do visual regression testing in a React app - a step-by-step tutorial. This includes an introduction to visual regression testing, spinning up an example app and a step-by-step guide on creating some visual regression tests."
2941author: "Geshan Manandhar"
2942authorImage: "/images/blog/authors/6568d05a61cacbddd7afcac0_geshan-manandhar.jpeg"
2943heroImage: "/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656a5334ecfb394aeffc2d5b_visual-regression-testing-in-a-react-app.png"
2944heroImageAlt: "Visual Regression Testing in a React App | Step-by-Step Tutorial"
2945categories: ["Testing", "React", "Tutorial"]
2946showTableOfContents: true
2947showPromo: true
2948hidden: true
2949---
2950
2951[Visual regression testing](https://www.meticulous.ai/blog/the-beginner-guide-to-visual-regression-testing) in simple words is a mechanism to make sure that the changes made to a system, primarily code, do not adversely affect the visible appearance of the application’s user interface. In this post, you will learn how to do visual regression testing for a React.js application, let’s get going!
2952
2953## What is Visual Regression Testing
2954
2955Regression means a return to a former or less developed state. [Regression testing](https://smartbear.com/learn/automated-testing/what-is-regression-testing/) means to ensure that system changes in one part do not unintentionally break something that was working in another part of the system.
2956
2957Along the same lines, [visual regression testing](https://meticulous.ai/blog/visual-regressions-and-how-to-prevent-them/) means to verify that changes do not negatively impact the visual appearance of the existing user interface of the application. For example, an engineer changed a button used for the Login form but as the button was used for “Saving” things as well unintentionally broke the saving part.
2958
2959## Why do Visual Regression Testing
2960
2961Similar to any other form of testing like unit testing or [smoke testing](https://www.meticulous.ai/blog/smoke-testing), [setting up visual regression testing](https://meticulous.ai/blog/how-to-set-up-visual-regression-testing/) helps you to catch any visual discrepancies much earlier in the development phase. Visual regression testing is usually done by comparing two screenshots and figuring out which elements look different than the previous screenshot and if it is intended.
2962
2963A simple example can be, a profile picture that is expected to be 100x100 px on the desktop web browser, but due to an unintended change in CSS now the profile picture is 150x150 px. These kinds of visual issues can be caught and solved before it reaches the end users which is the main reason to use visual regression testing.
2964
2965## Prerequisites
2966
2967In the next section, you will start dabbling with some code. So to do that, some of the prerequisites are as follows:
2968
2969- Some experience with Node.js, React.js, and NPM CLI is expected.
2970- You should have a working knowledge of Git and GitHub including forking a repository, creating and switching branches in Git, and a general understanding of pull requests.
2971- Having some experience with Vercel will be good to have, it is not compulsary to use Vercel.
2972- A basic experience with React.js is expected to understand the React.js code snippet.
2973
2974Given you are aware of the skills needed to proceed, next the React web application used to demonstrate a visual regression test in a React.js app will be discussed.
2975
2976## Example App
2977
2978To show how to do visual regression testing on a React.js app, you will use the Name to Nationality guessing application. You can know more about how 
2978it is built in the [Jest SpyOn](https://meticulous.ai/blog/how-to-use-jest-spyon/) post. It is a simple app where given the first name it tries to guess the Nationality percent of that name using the Nationalize.io API. You can view a quick working example for the name chris below:
2979
2980![Visual regression testing on ReactJS app](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db7f2d8153a5b96948_visual-regression-testing-on-reactjs-app.gif)
2981
2982It is also hosted on [Vercel](https://name-nationality-meticulous.vercel.app/) if you want to play around with the app and the code is also available on [GitHub](https://github.com/geshan/name-nationality-meticulous). With that context, next, you will learn how to add visual regression tests on a React.js app with Meticulous. You should know that Meticulous is a useful [visual regression testing tool](https://www.meticulous.ai/blog/visual-regression-testing-tools) that enables you to add visual regression testing without the need to maintain any code. It also provides an advanced form of browser automation that records your actions and replays them to test for any visual differences catching unwanted visual regressions.
2983
2984## Visual Regression Testing with Meticulous
2985
2986Before you proceed further, you should clone the Name Nationality application from GitHub and make sure you are on the master branch. You can follow the [official guide](https://docs.github.com/en/get-started/quickstart/fork-a-repo) from GitHub to fork the repository to your GitHub account. Please also make sure that you have run npm install to install the packages. You can also execute npm start and check the application is running locally on port 3000.
2987
2988After that, you can register on Meticulous and follow along with this guide as shown in the next section.
2989
2990## Register on Meticulous
2991
2992As the next step, [register](https://app.meticulous.ai/signup) on Meticulous, use your Google account, GitHub or Email to do so on the screen below:
2993
2994![Meticulous sign up page](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbb28f6df423e2d749_meticulous-registration.jpeg)
2995
2996That should be easy to do, then you will be presented with the onboarding. Select an organization name:
2997
2998![Meticulous onboarding](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbf5a49ac6efa430d4_meticulous-onboarding.jpeg)
2999
3000In the above picture, it is Meticulous-try but you can choose it to be what you like as long as it is available. The consequent step in onboarding is to connect to GItHub or GitLab:
3001
3002![Meticulous GitHub GitLab integration](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db6211d254c828af8b_meticulous-github-gitlab-integration.jpeg)
3003
3004Choose GitHub and add the Meticulous app on GitHub after authenticating it as follows:
3005
3006![Meticulous installation](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db360fac73f7aecea6_meticulous-installation.jpeg)
3007
3008After that, use the name-nationality-meticulous repository that you had forked before registering on Meticulous:
3009
3010![Meticulous setup](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db64f0a2ada09bcff4_meticulous-setup.jpeg)
3011
3012The name for the project is chosen as name-nationality-meticulous same as the repo name as seen below: Then you can click “Create Project”.
3013
3014![Meticulous create project](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbd0335d980bc742e6_meticulous-project.jpeg)
3015
3016After that, you will see the page to select the framework, as the app is built with React.js (Create React App) choose the “Another Framework” option as you are using React.js and click “Next”:
3017
3018![Meticulous select framework](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbd0335d980bc742e0_meticulous-select-framework.jpeg)
3019
3020Then you will see the following screen to install the Meticulous recorder NPM package and add the script to load the Meticulous recorder. As you have forked and existing codebase, you don’t need to install the @alwaysmeticulous/recorder-loader NPM package. It is already included in the package.json [file](https://github.com/geshan/name-nationality-meticulous/blob/master/package.json#L6).
3021
3022You will only need to copy the projectId and set it as an environment variable named REACT_APP_METICULOUS_PROJECT_ID. For this copy the .env.example file to .env.local and replace the above variable in the new .env.local file.
3023
3024![Meticulous recorder script](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db189df99e4950a49d_meticulous-recorder-script.
3024jpeg)
3025
3026After you have set the project ID on your .env.local file, you can click the “Next” button on the page.
3027
3028![Meticulous recorder installation using script tag](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbd1cc922d75769941_meticulous-recorder-installation-using-script-tag.jpeg)
3029
3030At this point, you will need to run the app locally with npm start and search for the name “chris” on the app when it runs at https://localhost:3000. It should look something like the below:
3031
3032![Name nationalities app](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbf9ad18723691e538_name-nationalities-app.jpeg)
3033
3034Then stop the app by pressing Ctrl+C on the shell/CLI you opened the app in. This will stop the recording. Head back to the Meticulous page, you will see that your first session has been recorded:
3035
3036![Meticulous session recording](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbb2922d9e3a813c66_meticulous-session-recording.jpeg)
3037
3038In the next section, you will need to configure more things on Meticulous when you click “Next” on the above screen.
3039
3040## More Meticulous configurations
3041
3042After the first recording is added, you will need to configure how to execute your tests. On each new pull request, the above recording will be tested against any code change for any visual regression. When you see a screen like the one below, choose the “Run the tests against my existing preview URLs(recommended)” option. Then click “Next”.
3043
3044![Execute tests on Meticulous](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db68332f74985e70f6_meticulous-test-execution.jpeg)
3045
3046Then you will need to enable the settings page of the project, you can reach that page by clicking the “Go to projects page” button.
3047
3048![Meticulous projects page](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db7981669aeeb71b94_meticulous-projects-settings-page.jpeg)
3049
3050On the project overview page, you can click the “Settings” link and check the “Automatically run [Meticulous tests](https://www.meticulous.ai/blog/let-users-write-tests-for-you) whenever a new deployment id is ready” option.
3051
3052![Meticulous project overview settings](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbbdd2b122ad7a5ff1_meticulous-project-settings.jpeg)
3053
3054At this juncture, you will need to deploy your app to Vercel to run the visual regression tests on each preview which is automatically deployed for each pull request. You will do that in the next section.
3055
3056## Deploy on Vercel
3057
3058Next, you will deploy the name nationality app to Vercel. Please note that Vercel is not a requirement to use Meticulous, it is used for this example. You can use Meticulous without Vercel too.
3059
3060To deploy the app to Vercel, you will need to [register](https://vercel.com/signup) on Vercel if you don’t have an account. It is very easy to sign up for Vercel as seen below:
3061
3062![Meticulous deployment on Vercel](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db9ca984cf1d3addf8_meticulous-deployment-on-vercel.jpeg)
3063
3064Use the “Hobby” option and type in your name. Then click continue, after that, you can continue the registration with GitHub.
3065
3066![Vercel registration](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db34323702a27ec242_vercel-registration.jpeg)
3067
3068Follow the steps and be sure to add the name-nationality-meticulous repository that you had forked in the beginning.
3069
3070After the registration is done, You can go to the “Overview” and click “Add New…” > “Project”.
3071
3072![Vercel create new project page](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dc5b2fa9e353868d07_vercel-create-new-project.jpeg)
3073
3074Then on the next page, select the repository you have forked name-nationality-meticulous and click the “importâ€
3074 button as seen below:
3075
3076![Import repository on Vercel](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbe8cfbe3dcdd7dbb2_import-repository-on-vercel.jpeg)
3077
3078After that, be sure to add the two environment variable before deploying the project:
3079
3080- REACT_APP_METICULOUS_PROJECT_ID - the same value you used in the .env.local file that you copied from the Meticulous UI.
3081- REACT_APP_RUN_VISUAL_TESTS - set this env variable to true as seen below. With this environment variable if you want to test things out on preview without it being recorded on Meticulous you can easily do it if you want to record your activity on preview set it to true. You can set it to false\\\` and your activity on Vercel preview URL will not be recorded.
3082
3083Then you can click the “Deploy” button to deploy your application.
3084
3085![Meticulous-Vercel](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db72ee2a1fb465d2ad_meticulous-vercel.jpeg)
3086
3087Wait for a couple of minutes and your React App to guess the nationality from the given name will be successfully deployed on Vercel.
3088
3089![New project deployed on Vercel](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dbb99d6f30f60ef566_vecel-new-project-deployed.jpeg)
3090
3091At this point, create a new branch from the master branch named readme-change, then make a one-word change to the Readme file and push it to Github. After that open a PR with this readme-change branch. If your recordings are set fine, the test will run for guessing the nationality for chris, and you will see the visual regression tests passing on Meticulous as below:
3092
3093![Visual regression tests passing on Meticulous](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dc21408d4ac4cbc4f7_visual-regression-tests-passing-on-meticulous.jpeg)
3094
3095Hurray! Your first Meticulous test has passed and the good thing is till this point you have not written a single line of code or test code. As seen above there were “zero visual differences” between the saved screenshot of the simulation and the change in your branch.
3096
3097## Add visual difference and check the result
3098
3099As mentioned by [Alister Scott](https://alisterbscott.com/2021/08/11/never-trust-an-automated-test-you-havent-seen-fail/), “Never Trust an Automated Test You Haven’t Seen Fail”. Next, you will make the test fail by making a change. The change is to make the flags smaller. You will make the flags 50px wide on a new git branch from the master branch called smaller-flag. To do this, you will add width: ”50px” on line 58 of src/App.js file after which it will look as follows:
3100
3101\`\`\`
3102<div className="nationalities">
3103    {Array.isArray(nationalities) && nationalities.map(
3104        nationality => {
3105            const flagUrl = \`https://flagcdn.com/w160/\${nationality.country_id.toLowerCase()}.jpg\`;
3106            const altText = \`\${nationality.country_id} flag\`;
3107            return <div key={nationality.country_id}><h3>{nationality.country_id} - {(nationality.probability * 100).toFixed(2)}%</h3> <img src={flagUrl} alt={altText} style={{
3108                border: "1px solid black",
3109                width: "50px"
3110            }} /></div>
3111        }
3112    )}
3113</div>
3114\`\`\`
3115
3116After you save the file, you will need to commit and push the smaller-flag branch to GitHub. Then you can open a pull request to master from the smaller-flag branch. The visual testing on Meticulous will fail at this point:
3117
3118![Visual regression tests failing on Meticulous](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db9ca984cf1d3addf4_visual-regression-tests-failing-on-meticulous.jpeg)
3119
3120Here the main message is Meticulous spotted visual differences thereby the test has failed. Another important detail to notice here is that the unit tests running with Jest on GitHub actions passed and did not catch the visual difference, whereas the Meticulous tests failed. They failed because they are visual difference tests and the flag becoming smaller is surely a visible difference.
3121
3122You check the app locally on port 300 after the 50 px style change. First, make sure the REACT_APP_RUN_VISUAL_TESTS variable in the .env.local file is set to false so that you are not recording the actions. When you run your app after that and try chris in the name text field, it will look as follows:
3123
3124![Visual regression test using the Name Nationalities app](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757db3399677431d95674_visual-regression-test-using-the-name-nationality-app.jpeg)
3125
3126Now it has smaller flags than earlier. So how can this be fixed?
3127
3128### Fixing a broken visual regression test
3129
3130To fix the tests, you will not need to write any more code. The test can be fixed easily by clicking on the view and approve differences detected. link on the comment added by the Meticulous app on your pull request of the smaller-flags branch. That link will show you all the visual differences you have created from the baseline tests as follows:
3131
3132![Fixing a broken visual regression test with Meticulous](/images/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial/656757dc00c40c91afa02659_fixing-a-broken-visual-regression-test-with-meticulous.jpeg)
3133
3134If this visual difference of making the flag smaller is intended, you can click the “Approve all visual differences” green button and that will change the baseline tests. The baseline tests after this action will be the ones with smaller flags.
3135
3136As the visual differences are approved, you can merge the Pull request. After that, the pull requests after this one will use the smaller flags as the baseline.
3137
3138Great! Your failing test is passing and now it can be pushed to your favorite source control management software like GitHub.
3139
3140## Conclusion
3141
3142In this step-by-step guide, you first learn about what visual regression testing is. After that, you looked at adding a meticulous test to an existing React.js application. Then, you changed the code which failed the added test.
3143
3144Consequently, you updated the tests to match the code changes that passed the first test. You can look at the steps followed to do it all in this [branch comparison](https://github.com/geshan/name-nationality/compare/meticulous?expand=1) on GitHub.
3145
3146The main takeaway from this post is that it is a great idea to add visual regression testing on any app React.js included. With Meticulous, changing the test when the code is updated is so easy with the provided commands in the easy-to-use Meticulous CLI.
3147
3148## Meticulous
3149
3150Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
3151
3152This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way 
3152we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
3153
3154This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
3155`,P4=`---
3156title: "JavaScript Syntax Errors | How to Prevent Them, Tutorial"
3157slug: "how-to-prevent-javascript-syntax-errors"
3158date: "2024-01-16"
3159description: "The complete guide on JavaScript Syntax Errors, how they occur, and techniques and tools available to prevent them."
3160author: "Goodness Chris-Ugari"
3161authorImage: "/images/blog/authors/6568fd7b74ebf8bcd6377d05_goodness-chris-ugari.jpeg"
3162heroImage: "/images/blog/how-to-prevent-javascript-syntax-errors/656a5c0e34337f6f7792be56_how-to-prevent-javascript-syntax-errors.png"
3163heroImageAlt: "JavaScript Syntax Errors | How to Prevent Them, Tutorial"
3164categories: ["JavaScript", "Tutorial"]
3165showTableOfContents: true
3166showPromo: true
3167---
3168
3169[Syntax errors](https://en.wikipedia.org/wiki/Syntax_error) are a constant problem for programmers. A missing parenthesis or bracket can stop your code from compiling or executing, leading to lost work time and frustration. Other common errors include missing quotation marks or commas, or even using the wrong syntax entirely.
3170
3171The best solution to this problem is to prevent syntax errors from happening in the first place. If you stay alert for the most common errors, you can correct them before deployment. There are also various automated tools that can do this detection work for you.
3172
3173In this article, you’ll learn more about syntax errors in [JavaScript](https://www.meticulous.ai/blog/javascript-reference-errors). You’ll see some examples of common syntax errors, along with techniques and tools on how to fix them and keep them out of your co
3173de.
3174
3175## Why do You Need to Know about Syntax Errors
3176
3177Syntax errors, also known as parse errors, occur in JavaScript when developers incorrectly use a predefined structure or language pattern. The typos or other mistakes result in invalid code. These errors include open or mismatched brackets, missing parentheses or punctuation, or misspelled keywords.
3178
3179The following are some of the reasons why you need to know about syntax errors:
3180
3181- A program will not successfully compile or run until all syntax errors have been corrected. Avoiding this kind of error can help you achieve more efficient and accurate coding.
3182- It’s easy to reduce the occurrence of this error if you know about it. Browser developer tools display JavaScript syntax errors in the console and also indicate the line in the code where the error occurs, giving you an idea of where to start looking.
3183- Syntax errors will typically prevent build tools from being able to run, meaning you cannot build or deploy your application. If there is no build process and the raw JavaScript is deployed, these errors will happen at runtime.
3184
3185## What are Some Common Syntax Errors
3186
3187The following are some examples of syntax errors that commonly cause trouble for developers.
3188
3189## Missing or Unmatched Quotation Marks
3190
3191Quotation marks are a frequent cause of problems. Errors include adding an opening quotation mark without closing it or trying to close off a double quotation mark with a single quotation mark (or vice versa). Adding more quotation marks than needed will also cause a syntax error because the additional quotes will be unmatched.
3192
3193For example:
3194
3195\`\`\`
3196let arr = ["Joy", "Ruby", "Gail]   // Missing a quotation mark for the last item
3197\`\`\`
3198
3199## Open or Mismatched Brackets
3200
3201Another common mistake is having an open bracket without a matching closing bracket. Trying to close off an open curly bracket with a square bracket or vice versa will also cause this error.
3202
3203For example:
3204
3205\`\`\`
3206function check(x){
3207    if(x === 10){
3208      return true
3209    } else {
3210      return false
3211    // Missing a closing curly bracket
3212  }
3213\`\`\`
3214
3215Also:
3216
3217\`\`\`
3218let arr = [
3219    {
3220        "name": "Lynn"
3221    },
3222    {
3223        "name": "Ruby"
3224    },
3225    {
3226        "name": "Phil"
3227    }
3228    }   // Should be a closing square bracket
3229\`\`\`
3230
3231## Missing Commas
3232
3233You’ll get a syntax error if you forget to add commas between items in arrays or objects:
3234
3235\`\`\`
3236let arr ["apple", "orange" "pear"]   // Missing a comma
3237\`\`\`
3238
3239## Missing Semicolons
3240
3241Forgetting semicolons where they are expected, for instance in a for loop, is another cause of syntax errors in JavaScript:
3242
3243\`\`\`
3244let arr = [0, 1, 2, 3]
3245for(let i =0; i <= arr.length i++){   // Missing a semicolon after the second statement
3246  console.log(arr[i] * 2)
3247}
3248\`\`\`
3249
3250## Missing Parentheses
3251
3252It’s an easy mistake to leave off a closing parenthesis in scenarios where you have nested blocks of code, such as in a nested if statement:
3253
3254\`\`\`
3255function check(x, y){
3256    if((x === 10) && (y ===5){   // Missing closing parenthesis; should be if((x === 10) && (y ===5)){...
3257        return true
3258    } else {
3259      return false
3260     }
3261}
3262\`\`\`
3263
3264## Multilingual Syntactic Confusion
3265
3266As your stack grows and you learn more programming languages, you might mistakenly try to use syntax for another language in JavaScript. You need to remember JavaScript’s rules and be mindful of them when coding.
3267
3268The example below includes a while loop that follows Python syntax, which will cause an error when used in JavaScript:
3269
3270\`\`\`
3271function check(x){
3272    while(x > 5):
3273     return x + 1
3274}
3275\`\`\`
3276
3277## How can You Prevent These Errors
3278
3279There are several tools and best practices you can use to keep syntax errors out of your co
3279de. The following are some examples.
3280
3281## Code Editors or IDEs
3282
3283These tools offer functionalities to help you write and debug code. Features include code autocompletion, which reduces the likelihood of you missing a closing bracket or parenthesis, and syntax highlighting, which uses specific colors to alert you to syntax errors in your code.
3284
3285In the first image below, you’ll see a syntax error highlighted in Visual Studio Code; in the second, you’ll see how the code editor looks when the error is fixed:
3286
3287![Visual Studio Code showing a highlighted syntax error](/images/blog/how-to-prevent-javascript-syntax-errors/6568fd2704f3082ea5d6dcfa_visual-studio-code-showing-a-highlighted-syntax-error.png)
3288
3289![Visual Studio Code when the error is fixed](/images/blog/how-to-prevent-javascript-syntax-errors/6568fd27a399bb647d9f43dc_visual-studio-code-when-the-error-is-fixed.png)
3290
3291Note that while most code editors come with built-in features to help with debugging, some of these features work by default while some might require you to change basic configuration or settings first.
3292
3293## Static Code Analysis Tools
3294
3295Static code analysis tools help detect errors and potential problems in continuous integration (CI) workflows before code is released to production. The tools scan your code and compare it to predefined rules to find errors. [ESLint](https://eslint.org/) and [JSLint](https://www.jslint.com/) are examples of static code analysis tools.
3296
3297ESLint allows you to apply rules that can be used to draw attention to specific patterns, but it may have difficulty with some syntax errors. Depending on what rules you have configured, malformed function declarations like function foo () {}} can make the code unparsable. Since the code cannot be read or interpreted correctly, ESLint will stop highlighting other issues.
3298
3299## TypeScript
3300
3301[TypeScript](https://www.meticulous.ai/blog/safer-exhaustive-switch-statements-in-typescript) can help provide insights into syntax errors and how to solve them. Instead of testing and encountering errors at runtime, you can use the TypeScript compiler to display errors in the terminal during compile time.
3302
3303![TypeScript compiler displaying errors in the terminal](/images/blog/how-to-prevent-javascript-syntax-errors/6568fd27879a6b03caeee108_typescript-compile-displaying-errors-in-the-terminal.png)
3304
3305## Indentation
3306
3307Indenting your code is a standard practice that makes it easier to read the code and spot missing brackets or parentheses. Tools like [Prettier](https://prettier.io/), ESLint, and [Beautify](https://beautifier.io/) can help you format your code properly, making it more legible with consistent formatting, thereby improving code quality.
3308
3309## Challenges of Preventing Errors
3310
3311Sometimes syntax errors will make it difficult to read or interpret code correctly, hindering the effectiveness of certain tools.
3312
3313Additionally, statements like function foo () {}} can cause errors that will stop tools like Prettier from being able to parse and format your code, because these tools do not validate syntax. They would need to be able to parse the code before reprinting with their own rules.
3314
3315## Conclusion
3316
3317Even the smallest errors can cause big problems in your code. As you learned in this article, you need to be able to detect syntax errors in your JavaScript applications so that your linting and other tools which rely on parsing can give you feedback on your code, and so that you can deploy and ship it.
3318
3319If you keep these tips and tools in mind as you write code, you should be able to find and fix these problems. This will improve the quality of your software and [speed](https://www.meticulous.ai/blog/testing-pyramid-for-frontend) up your workflow.
3320
3321## Meticulous
3322
3323Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
3324
3325This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
3326
3327This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
3328`,F4=`---
3329title: "How to Prevent the Error: Cannot Read Property '0' of Undefined"
3330slug: "how-to-prevent-the-error-cannot-read-property-0-of-undefined"
3331date: "2024-01-16"
3332description: 'A guide on how to prevent the error "cannot read property ''0'' of undefined", covering techniques such as try, catch, using const over let, amongst many other techniques to prevent this error.'
3333author: "Aniket Bhattacharyea"
3334authorImage: "/images/blog/authors/656901c3335c1d421430d47a_aniket-bhattacharyea.jpeg"
3335heroImage: "/images/blog/how-to-prevent-the-error-cannot-read-property-0-of-undefined/656a56376f543fdf78fa3e34_how-to-prevent-the-error-cannot-read-property-0-of-undefined.png"
3336heroImageAlt: "How to Prevent the Error: Cannot Read Property '0' of Undefined"
3337categories: ["Tutorial"]
3338showTableOfContents: true
3339showPromo: true
3340---
3341
3342Errors and crashes in software development can be frustrating, especially if they're difficult to debug. In [JavaScript](https://www.meticulous.ai/blog/javascript-typeerrors-and-techniques-to-prevent-them), errors relating to undefined are some of the simplest and most common. One such error is the Cannot read property '0' of undefined error.
3343
3344In this article, you'll learn about the Cannot read property of '0' of undefined error, when it can occur, and how to mitigate it.
3345
3346## What is the Cannot Read Property '0' of Undefined Error
3347
3348undefined is a special value in JavaScript that is used to indicate the absence of a value. When you declare a variable but assign it no value, it's automatically initialized to undefined, and trying to access properties of undefined results in an error. The Cannot read property xxx of undefined error is one such example. In particular, the Cannot read property 'n' of undefined (where n is a number) usually occurs when you try to access elements of an array that are undefined.
3349
3350In JavaScript, an array is a special type of object, and just like an object, you can obtain or set its properties using the . or \\[\\] operators:
3351
3352\`\`\`
3353let arr = [1,2,3]
3354console.log(arr.length) // 3
3355console.log(arr['length']) // 3
3356\`\`\`
3357
3358However, the elements of the array become properties themselves. In the same way that length is a property, the first element becomes the 0 property, the second element becomes the 1 property, and so on:
3359
3360![Logging an array to the console shows its object structure](/images/blog/how-to-prevent-the-error-cannot-read-property-0-of-undefined/6568feb9b553c4a4a34c75b1_logging-an-array-to-the-console-shows-its-object-structure.png)
3361
3362This means accessing the first element with arr\\[0\\] is the same as accessing the 0 property of arr. If you try to access the first element of a variable that is supposed to be an array but is actually undefined, you will get the dreaded Cannot read property '0' of undefined error.
3363
3364The same error can occur for array-like objects as well. Array-like objects are objects that support the subscript (\\[\\]) operator (ie a string).
3365
3366> _Note that, depending on what JavaScript engine you're using, the error message may vary slightly._
3367
3368[Gecko](https://wiki.mozilla.org/Gecko:Overview) (in Firefox) gives this error: TypeError: XXX is undefined. n older versions of the [v8 engine](https://chromium.googlesource.com/v8/v8.git) (in Chrome, Chromium, and Node.js), the error message reads like this: Cannot read property '0' of undefined. It was recently [modified](https://github.com/v8/v8/commit/c0fd89c3c089e888c4f4e8582e56db7066fa779b) to read TypeError:Cannot read properties of undefined (reading '0') in newer versions of v8. This means that in modern Chrome, Chromium-based browsers, and Node.js, you should see TypeError: Cannot read properties of undefined (reading '0').
3369
3370Leaving this error unchecked can be very detrimental to the user experience because it can lead to [bugs](https://www.meticulous.ai/blog/testing-pyramid-for-frontend) or crashes. However, this error is easy to fix once you have pinpointed the exact reason your variable is undefined.
3371
3372## When Does the Cannot Read Property '0' of Undefined Error Occur
3373
3374From the previous discussion, it's clear that the cause of the Cannot read property '0' of undefined error is the array being undefined. There are multiple reasons why an array can be undefined, including the following"
3375
3376## Forgetting to Initialize the Array
3377
3378The most common mistake that leads to a Cannot read property 0 of undefined error is forgetting to initialize the array when declaring it. An uninitialized variable will be undefined by default, and trying to access its elements will throw the Cannot read property '0' of undefined error:
3379
3380\`\`\`
3381let arr // arr is undefined
3382console.log(arr[0]) // throws error
3383\`\`\`
3384
3385## Accessing an Array through a Nonexistent Key
3386
3387If a property of an object is an array, it's possible to accidentally misspell the key or try to access the array through the wrong key. A nonexistent key in an object returns undefined; thus, trying to access its elements throws the Cannot read property '0' of undefined error.
3388
3389For example, consider the following object where marks is an array:
3390
3391\`\`\`
3392let student = {
3393  name: "John",
3394  marks: [80, 90, 70]
3395};
3396\`\`\`
3397
3398You can access the marks array using student.marks or student\\["marks"\\]. But if you misspell "marks", it returns undefined:
3399
3400\`\`\`
3401console.log(student.mark); // undefined
3402\`\`\`
3403
3404If you're not careful and try to access the element of the array, you'll get the Cannot read property '0' of undefined error:
3405
3406\`\`\`
3407console.log(student.mark[0]); // throws error
3408\`\`\`
3409
3410## Accessing a Wrong Index in a Multidimensional Array
3411
3412In many languages, accessing past the bounds of an array throws an error. But in JavaScript, if you try to access an out-of-bound element of an array, you'll get undefined:
3413
3414\`\`\`
3415let arr = [1, 2, 3];
3416console.log(arr[3]); // undefined
3417\`\`\`
3418
3419This feature of JavaScript can cause problems when you're trying to access an element of a multidimensional array (ie an array of arrays):
3420
3421\`\`\`
3422let matrix = [
3423    [1,0,0],
3424    [0,1,0],
3425    [0,0,1],
3426    [0,0,0]
3427]
3428\`\`\`
3429
3430Here, matrix is an array of four elements, each of which is an array of three elements. You can access an individual entry by specifying the index of that entry with respect to both the outer and inner arrays.
3431
3432For example, matrix\\[0\\]\\[0\\] is the first entry of the first array:
3433
3434\`\`\`
3435console.log(matrix[0][0]); // 1
3436\`\`\`
3437
3438Essentially, matrix\\[0\\] returns the first array whose first entry is then accessed using \\[0\\]. If the first index is out of bounds, it returns undefined. Trying to further access its element throws an error:
3439
3440\`\`\`
3441console.log(matrix[5][0]); // throws error
3442\`\`\`
3443
3444In the earlier snippet, matrix\\[5\\] is undefined, so trying to access its element throws the Cannot read property '0' of undefined error.
3445
3446## Populating an Array from a Function
3447
3448It's a common practice to populate an array by calling a function. However, you must make sure that the function is actually called. Not doing so may result in the array being undefined.
3449
3450In the following example, the populate function is responsible for populating the array, but it's not being called. Thus, arr is undefined, and when arr\\[0\\] is accessed, it causes the Cannot read property '0' of undefined error:
3451
3452\`\`\`
3453let arr;
3454
3455function populate() {
3456    arr = [1,2,3]
3457}
3458
3459console.log(arr[0]) // throws error
3460\`\`\`
3461
3462## Having Function Arguments That are Undefined
3463
3464JavaScript is fairly lenient about function arguments. If a function accepts n number of arguments and you pass more than n arguments, the extra arguments are ignored. However, if you pass less than n arguments, the extra arguments are undefined, and trying to access them results in an error.
3465
3466In the following code, the function foo accepts one argument, but none are passed, resulting in data being undefined and the Cannot read property '0' of undefined error:
3467
3468\`\`\`
3469function foo(data) {
3470    console.log(data[0])
3471}
3472
3473foo()
3474\`\`\`
3475
3476## Conditionally Assigning a Variable
3477
3478A common pattern in programming is to assign a variable with a value that depends on some condition. In this case, care must be taken to ensure all the possible cases have been taken care of; otherwise, the variable may be undefined and cause errors.
3479
3480Consider the following function that populates the marks array based on the name parameter:
3481
3482\`\`\`
3483function populate(name) {
3484    let marks
3485
3486    if(name == "Bob") {
3487        marks = [88, 99, 100]
3488    } else if(name == "Alice") {
3489        marks = [78, 86, 98]
3490    }
3491
3492    console.log(marks[0])
3493}
3494\`\`\`
3495
3496The function will work fine if the name is either Bob or Alice. However, any other name (or even misspelling Bob or Alice) will throw the Cannot read property '0' of undefined error:
3497
3498\`\`\`
3499populate("Alice") // Ok
3500populate("alice") // throws error
3501\`\`\`
3502
3503## Creating a Race Condition
3504
3505A very common case, especially when dealing with asynchronous processes, is falling victim to the so-called race condition. A race condition occurs when there's no definite order of completion of multiple processes, and depending on the order, undesirable things can happen.
3506
3507For example, suppose you're populating an array in an async function. If you try to access the array before the array is populated, you'll get the Cannot read property '0' of undefined error:
3508
3509\`\`\`
3510let arr
3511
3512async function populate() {
3513    setTimeout(() => arr = [1,2,3], 2000) // A timeout is used to simulate a long-running job
3514}
3515
3516populate()
3517console.log(arr[0]) // throws error
3518\`\`\`
3519
3520In the previous code snippet, the populate function is an asynchronous function that populates the array. In the code, setTimeOut is used to simulate a delay of two seconds for the purpose of demonstration. In practice, this could be a delay caused by having an API call, reading from a file or a database, or waiting for user input. This means that the console.log statement is executed before the array is populated. At that point, the array is still undefined, and it throws the Cannot read property '0' of undefined error.
3521
3522## Changing API Responses
3523
3524No matter what application you're developing, it's likely that you'll be depending on some external services. This could be an API, a database, a file, or a user input. Apart from the possibility of creating a race condition, these external services can also create problems if their structure changes.
3525
3526If you're populating an array from the response of an API call, it can be undefined due to various reasons, including the following:
3527
3528- The API can return undefined if no data is available.
3529- A poorly designed API can return undefined because of an error.
3530- The API designers can change the structure of the response and remove or rename the fields, resulting in the original fields being undefined.
3531
3532For example, consider the following API call where an array is populated from the items field of some API:
3533
3534\`\`\`
3535fetch(some_url)
3536.then(res => res.json())
3537.then(data => {
3538    let arr = data.items;
3539    console.log(arr[0]);
3540})
3541\`\`\`
3542
3543If the items field is undefined, the previous code will throw the Cannot read property '0' of undefined error. If a new version of the API removes the items field, accessing data.items will return undefined and accessing elements will throw the Cannot read property '0' of undefined error.
3544
3545## How to Mitigate the Cannot Read Property '0' of Undefined Error
3546
3547Unfortunately, there's no one-size-fits-all solution to mitigating the Cannot read property '0' of undefined error. However, the following are some of the ways you can try and prevent it:
3548
3549## Check your Code for Common Mistakes
3550
3551Have you forgotten to initialize the array? Have you made a typo somewhere? Have you forgotten to call the function that populates the array? These are some of the common mistakes that can cause the Cannot read property '0' of undefined error.
3552
3553It's a good idea to go through your code and try to look for these common mistakes. Most often, you'll find an issue in your code, and you can quickly fix it. You can also use a linter like [ESLint](https://eslint.org/) to catch some very common errors, like misspelled variables or function names.
3554
3555## Check for Undefined Before Accessing the Array
3556
3557To prevent the Cannot read property '0' of undefined error, you can check whether the array is undefined before accessing it, and there are multiple ways to do so.
3558
3559A very common solution (that is highly recommended) is to check whether the type of a variable is undefined or not:
3560
3561\`\`\`
3562let arr
3563if (typeof arr !== "undefined") {
3564    console.log(arr[0]) // Will not run because arr is undefined
3565}
3566
3567let arr2 = [1,2,3]
3568if (typeof arr2 !== "undefined") {
3569    console.log(arr2[0]) // Will run because arr2 is defined
3570}
3571\`\`\`
3572
3573However, the problem with this solution is that if a variable is nonexistent, typeof still returns undefined. While this might seem like a useful feature since it prevents you from accessing nonexistent variables, relying on this behavior is not a good practice. If you're trying to access a nonexistent variable, it indicates a bigger problem with your code, and you want to catch this error instead of suppressing it.
3574
3575To solve this, you can check for undefined using the comparison operators:
3576
3577\`\`\`
3578let arr
3579if (arr !== undefined) {
3580    console.log(arr[0]) // Will not run because arr is undefined
3581}
3582\`\`\`
3583
3584If the variable is not defined, it will throw a ReferenceError:
3585
3586\`\`\`
3587if (scores !== undefined) {
3588    console.log(arr[0]) // throws ReferenceError
3589}
3590
3591// Output:
3592// ReferenceError: scores is not defined
3593\`\`\`
3594
3595However, just because the variable is not undefined doesn't mean that you're safe. It could still be null. In addition, there's no guarantee that it's an array or that it has the index you're trying to search for. In this instance, the recommended practice is to use the Array.isArray method to check whether the variable is an array or not and check its length before accessing elements. As an added bonus, Array.isArray() also checks if the variable is undefined or null or not, and it throws a ReferenceError if it's not defined:
3596
3597\`\`\`
3598let arr
3599if (Array.isArray(arr) && arr.length > 0) {
3600    console.log(arr[0]) // Will not run because arr is undefined
3601}
3602
3603let arr2 = [1,2,3]
3604if (Array.isArray(arr2) && arr2.length > 0) {
3605    console.log(arr2[0]) // Will run because arr2 is defined
3606}
3607
3608let arr3 = []
3609if (Array.isArray(arr3) && arr3.length > 0) {
3610    console.log(arr3[0]) // Will not run because arr3 is empty
3611}
3612\`\`\`
3613
3614This method, however, does not work for array-like objects because they are not an Array. While [there are methods](https://stackoverflow.com/questions/24048547/checking-if-an-object-is-array-like/24048615#24048615) that work for all arrays and array-like objects, they can be complicated. It's often easier to simply check for the particular array-like types that you're actually using.
3615
3616This is the easiest solution to mitigate the Cannot read property '0' of undefined error. You can also use it to check if an argument of a function is undefined or not. However, this solution only prevents the code from crashing if an array is undefined, and it doesn't prevent the array from being undefined in the first place. If you have a lot of arrays, checking them every time you want to access them can be tedious and error-prone.
3617
3618If you don't want to deal with type-checks at every location where an array is accessed, try [TypeScript](https://www.meticulous.ai/blog/safer-exhaustive-switch-statements-in-typescript). [TypeScript](https://www.meticulous.ai/blog/safer-exhaustive-switch-statements-in-typescript) will type-check your variables for you and will simply refuse to compile if an array is undefined (unless you explicitly allow it to):
3619
3620\`\`\`
3621let arr: number[] = undefined
3622console.log(arr[0])
3623\`\`\`
3624
3625This code will refuse to compile with the error Type 'undefined' is not assignable to type 'number\\[\\]'.
3626
3627## Provide an Alternative Value When the Array is Undefined
3628
3629Another strategy to make sure the array isn't undefined is to provide an alternative value when the array is undefined. You can use the logical OR (||) operator to retain the original value if the array isn't undefined or provide a default value if the array is undefined:
3630
3631\`\`\`
3632let arr
3633arr = arr || [1,2,3] // Assigns [1,2,3] because arr is undefined
3634console.log(arr[0])
3635\`\`\`
3636
3637This code can be shortened by using the ||= operator:
3638
3639\`\`\`
3640let arr
3641arr ||= [1,2,3] // Assigns [1,2,3] because arr is undefined
3642console.log(arr[0])
3643
3644let arr2 = ["Hello", "World"]
3645arr2 ||= [1,2,3] // Retains the original value
3646console.log(arr2[0])
3647\`\`\`
3648
3649This method is useful if you have predefined default values to assign. For example, in an application, you may set some default configuration if the user doesn't provide any custom configuration.
3650
3651## Use Optional Chaining
3652
3653The optional chaining operator (?.) is a safe way to access the properties of an object without throwing an error if the property is null or undefined. An expression involving the optional chaining operator returns undefined if any one reference is missing (null or undefined). This makes it easier to write code because you don't have to check for undefined or null at every level:
3654
3655\`\`\`
3656let obj = {
3657    name: "John",
3658    age: 30,
3659    marks: {
3660        english: [90, 99, 100]
3661    }
3662}
3663
3664// without optional chaining
3665if(obj && obj.marks && obj.marks.english) {
3666    console.log(obj.marks.english[0])
3667}
3668
3669// With optional chaining
3670console.log(obj?.marks?.english?.[0])
3671
3672console.log(obj?.scores?.english?.[0]) // Will return undefined because scores is undefined but won't throw error
3673\`\`\`
3674
3675This method works best in cases where the structure of the object isn't guaranteed; for example, in an API response.
3676
3677## Use Const Whenever Possible
3678
3679Using const instead of let or var is a good practice. It's also a good idea to use const whenever you're dealing with a variable that you don't intend to change. A const variable also forces you to assign an initial value to it so that it's never unintentionally undefined:
3680
3681\`\`\`
3682const arr; // Not allowed
3683const arr2 = []; // Allowed
3684\`\`\`
3685
3686It will also make sure an array doesn't accidentally (or intentionally) get assigned undefined during the lifecycle of the code:
3687
3688\`\`\`
3689const obj = { name: "John", marks: [1,2,3] }
3690const arr = [1,2,3]
3691arr = obj.scores // Not allowed
3692\`\`\`
3693
3694Using const, however, means that you lose some of the flexibility of let and var. For instance, it's not possible to populate an array by assigning a value to it. The following code will not work with const:
3695
3696\`\`\`
3697const arr
3698arr = [10, 20, 30] // Not allowed
3699\`\`\`
3700
3701You can work around it by initially declaring the variable as an empty array and later adding elements to it:
3702
3703\`\`\`
3704const arr = []
3705arr.push([1,2,3])
3706console.log(arr) // [1,2,3]
3707\`\`\`
3708
3709This works because while const doesn't let you reassign the array, it lets you modify the elements of the array.
3710
3711If you need to reassign the array in multiple places, it's better to use let, as it becomes cumbersome with const. However, if you're sure you won't modify the array, choose const.
3712
3713## Use Async/Await and Promises Correctly
3714
3715To prevent race conditions between background processes, pay extra attention to the order in which the processes are executed. To make sure an array isn't accessed before it's initialized, use async/await and promises:
3716
3717\`\`\`
3718let arr
3719
3720async function populate() {
3721    // Some long-running job
3722    // ...
3723    arr = [1,2,3]
3724}
3725
3726async function access() {
3727    console.log(arr[0])
3728}
3729\`\`\`
3730
3731In the previous code, you must wait for the populate() function to finish executing before you can use the access() function to access the array:
3732
3733\`\`\`
3734// Using await
3735await populate()
3736access()
3737
3738// Using promise
3739populate().then(access)
3740\`\`\`
3741
3742## Use a Try…Catch Block
3743
3744The [try...catch](https://meticulous.ai/blog/javascript-try-catch-guide/) block 
3744is used to catch run-time errors or exceptions. If the code inside the try block throws an error, the error is passed to the catch block for handling. If no error is thrown, the catch block will not be called:
3745
3746\`\`\`
3747try {
3748  let roles
3749  console.log(roles[0])
3750} catch (error) {
3751  console.log(error); // TypeError: Cannot read property '0' of undefined
3752}
3753\`\`\`
3754
3755Although using a try…catch prevents the error from crashing the application, it's merely hiding the symptom, not the cause. If your code is trying to access an array that is undefined, it indicates an issue somewhere else, and it's better to explicitly check for undefined using an if statement, as explained previously.
3756
3757However, in cases where you want to halt the execution of the rest of the program upon encountering an error or you have multiple places in the code that can throw an error, try…catch is a good choice.
3758
3759## Conclusion
3760
3761The Cannot read property '0' of undefined error can be avoided during software development by adopting some best practices. In this article, you learned why this error happens and how to mitigate it by always ensuring that the array you are trying to access is not undefined.
3762
3763[Meticulous](https://meticulous.ai/) can help you catch these bugs before they get to your users in production. Remember, you need to use the right approach for the right situation. This will help you write better code and reduce the risk of bugs.
3764
3765## Meticulous
3766
3767Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
3768
3769This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
3770
3771This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
3772`,J4=`---
3773title: "How to Prevent the TypeError: Cannot Read Property Map of Undefined"
3774slug: "how-to-prevent-typeerror-cannot-read-property-map-of-undefined"
3775date: "2024-01-16"
3776description: "A guide on the root cause of 'cannot read map of undefined' as well as techniques and tools to prevent this error."
3777author: "Marie Starck"
3778authorImage: "/images/blog/authors/6569047bd92fdcf8eed94848_marie-starck.webp"
3779heroImage: "/images/blog/how-to-prevent-typeerror-cannot-read-property-map-of-undefined/656e2797cad4a9da00e4ae5e_how-to-prevent-typeerror-cannot-read-property-map-of-undefined.png"
3780heroImageAlt: "How to Prevent the TypeError: Cannot Read Property Map of Undefined"
3781categories: ["Tutorial"]
3782showTableOfContents: true
3783showPromo: true
3784---
3785
3786Most user interfaces have some kind of list. Whether it’s a display of data returned from an API or simply a drop-down list in a form, lists have become a cornerstone element in web applications. It is common to map over a set of data to render these lists, and bugs will inevitably occur.
3787
3788As a result, the TypeError Cannot read property 'map' of undefined is very common and one of the first errors that developers will be confronted with. It occurs when the variable being executed is of a different type than expected. Recognizing the error and the root cause will save you valuable time in the long run.
3789
3790In this article, you’ll learn about the [TypeError](https://www.meticulous.ai/blog/javascript-typeerrors-and-techniques-to-prevent-them) Cannot read property 'map' of undefined, how it happens, how it can be fixed, and what you can do to mitigate this error in the future.
3791
3792## What is the TypeError Cannot Read Property Map of Undefined
3793
3794Frontend developers are accustomed to running into errors that prevent their applications from compiling or rendering properly. TypeErrors, in particular, are very common. These represent an error occurring because the value is of a different type than the one expected. It’s one of the most generic and common [JavaScript](https://www.meticulous.ai/blog/javascript-reference-errors) errors that developers experience.
3795
3796Understanding why they happen will reduce the time needed to debug and fix them. These errors will stop the execution of a program and, therefore, will be detrimental to the user experience if they are not dealt with - errors can cause an application or UI code to crash, resulting in an error pages, blank spaces or blank pages in your application.
3797
3798## How to Understand and Prevent the Error
3799
3800In this section, you’ll discover what causes the TypeError Cannot read property 'map' of undefined and how to prevent it.
3801
3802## What Causes the Error
3803
3804In JavaScript specific methods live under specific objects. For instance, String.prototype.split, or [split](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split) for short, is a function that takes a string and divi
3804des it into substrings. It lives under the standard built-in object string and accepts nothing else. If you were to give anything other than a string to the split method, you would get a TypeError. Giving it null, for example, throws the TypeError Cannot read property 'map' of undefined.
3805
3806This is what makes TypeErrors so common. They can happen any time a value or variable is used, assuming that value is one type when it is actually another.
3807
3808In the case of map, this method lives on the [Array prototype](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array). So calling map on anything other than on an Array will throw a TypeError.
3809
3810## When Does the Error Occur
3811
3812The TypeError Cannot read property 'map' of undefined occurs in the following situations:
3813
3814## Querying an API
3815
3816In a perfect world, APIs would be consistent. They would always return the requested data in the desired format. In this scenario, they would be easy to parse and never change.
3817
3818Unfortunately, in the real world, APIs can be inconsistent. The response might be in a different format than you expected, and if you don’t add some checks, your code could run into some issues.
3819
3820Here is an example using [Nationalize.io](https://nationalize.io/), an API predicting the nationality of a name passed as a parameter:
3821
3822\`\`\`
3823    // Working fine
3824    const name = 'marie'
3825    fetch(\`https://api.nationalize.io/?name=\${name}\`)
3826      .then(res => res.json())
3827      .then(data => {
3828       // Data returned : { country: [{country_id: 'RE', probability: 0.0755}, ...], name: "marie"}
3829        data.country.map((country_details) => console.log(country_details))
3830    });
3831
3832    // Throwing an error
3833    const emptyName = ''
3834    fetch(\`https://api.nationalize.io/?name=\${emptyName}\`)
3835      .then(res => res.json())
3836      .then(data => {
3837       // Data returned: { error: "Missing 'name' parameter"}
3838       const { country } = data
3839       country.map((country_details) => console.log(country_details))
3840      // Throws TypeError cannot read property ‘map’ of undefined
3841    });
3842\`\`\`
3843
3844In the second fetch, there is no country key in the data object, making it undefined\\\`\\\`. Calling the map\\\` function on it throws a TypeError.
3845
3846## Typing Errors
3847
3848Developers are humans and, therefore, make typos. Similar to the previous example, if you access a property that doesn’t exist on an object, the value will be undefined. Calling the map method will throw the TypeError Cannot read property 'map' of undefined:
3849
3850\`\`\`
3851    const library = {
3852      name: "Public library",
3853      books: [“JavaScript complete reference guide”]
3854    }
3855    // ‘bookss’ is not a property of library, so this will throw an error
3856    library.bookss.map(book => console.log(book))
3857\`\`\`
3858
3859## Trying to Use a Variable Before it’s Set
3860
3861It’s easy to make a call and forget to take into consideration whether it’s an asynchronous one. When a value is populated asynchronously, accessing it too early will result in an error, as the value might still be undefined:
3862
3863The result of this code is the console logging on line 12 will execute before the fetch call is done and, therefore, before the one on line 5. At this point, countriesData is undefined, and calling map on it would throw an error:
3864
3865![Error found as a result of using variable before it's set](/images/blog/how-to-prevent-typeerror-cannot-read-property-map-of-undefined/656903dd224dde6d4e30040d_error-found-as-a-result-of-using-variable-before-it-E2-80-99s-set.png)
3866
3867The asynchronous aspect is something that [React](https://reactjs.org/) developers have to be particularly wary of. Children components will inherit data through props from their parents, but if the parent isn’t done fetching or computing the necessary data before the child starts rendering, this will also throw an error:
3868
3869## How to Mitigate the Error
3870
3871The first thing you can do to mitigate this error is to use [TypeScript](https://www.typescriptlang.org/). This strongly typed programming language will warn you ahead of time if you use an unacceptable type:
3872
3873![Using TypeScript to reduce coding errors](/images/blog/how-to-prevent-typeerror-cannot-read-property-map-of-undefined/656903ddd887f1c1fd2b49ee_using-TypeScript-to-reduce-coding-errors.png)
3874
3875The second way you can mitigate the error is through conditional checks to ensure the value is available before trying to use it. It’s particularly helpful in React, wherein developers regularly use conditional rendering to avoid undefined variables.
3876
3877Using the previous library and books example, here is a way to use conditional check and rendering but only when set:
3878
3879\`\`\`
3880    const BookList = ({books}) => {
3881      return(
3882
3883          {
3884            Array.isArray(books) && books.map(book => {book.title}) //Check if books is not null to map over it
3885          }
3886
3887      )
3888    }
3889
3890    function App() {
3891      const books = getBooks(...) // asynchronous hook to grab the list of books
3892      return (
3893
3894      )
3895    }
3896
3897    export default App;
3898\`\`\`
3899
3900The third solution is [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining). This simple operator will short-circuit and return undefined if you call a function on a property that doesn’t exist:
3901
3902\`\`\`
3903    const library = {
3904      name: "Public library"
3905    }
3906    // books is not a property of library, but with the ? operator, this only returns undefined
3907    library.books?.map(book => console.log(book))
3908\`\`\`
3909
3910Finally, you can wrap your call in a try-catch block. You can read more about try-catch [here](https://meticulous.ai/blog/javascript-try-catch-guide/). In the case of API calls that may fail and return error messages, like the first example, you can also use a catch() after your then () function to handle errors.
3911
3912For this situation, libraries such as [Axios](https://axios-http.com/) are preferred over fetch, as the latter will only throw an error on network issues and not on API errors (such as a 500). Axios, on the other hand, comes with error handling:
3913
3914\`\`\`
3915    const param = ''
3916    axios.get(\`https://api.nationalize.io/?name=\${param}\`)
3917      .then(data => {
3918        // This will not get executed
3919        data.country.map((country_details: any) => console.log(country_details))
3920      })
3921       .catch(error => console.log(error));
3922\`\`\`
3923
3924You can read more about Axios vs. Fetch [here](https://meticulous.ai/blog/fetch-vs-axios/).
3925
3926## Conclusion
3927
3928JavaScript developers have to deal with many different kinds of errors. As you learned in this article, TypeErrors are one of the most common. You learned more about what they are, what a few possible causes are, and how to mitigate them.
3929
3930Over the years, each developer builds a little toolkit of tips and tricks to help them accomplish their job quicker. Keeping in mind all the possible solutions listed in this article will [speed](https://www.meticulous.ai/blog/testing-pyramid-for-frontend) up your development and reduce the time spent hunting bugs.
3931
3932## Meticulous
3933
3934Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
3935
3936This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
3937
3938This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
3939`,G4=`---
3940title: "How to Set up Visual Regression Tests"
3941slug: "how-to-set-up-visual-regression-testing"
3942date: "2024-01-16"
3943description: "A how-to guide on how to set up visual regression testing in a web application using Jest and Cypress."
3944author: "Charly Poly"
3945authorImage: "/images/blog/authors/656906c69c90ec66afecebf9_charly-poly.jpeg"
3946heroImage: "/images/blog/how-to-set-up-visual-regression-testing/656f15b435a33e98903936fc_how-to-set-up-visual-regression-testing.png"
3947heroImageAlt: "How to Set up Visual Regression Tests"
3948categories: ["Testing"]
3949showTableOfContents: true
3950showPromo: true
3951---
3952
3953As covered in [Visual Regressions and How to Prevent Them](https://meticulous.ai/blog/visual-regressions-and-how-to-prevent-them/), visual regression tests are a faster way to preserve your application’s health.
3954
3955While SaaS solutions provide advanced visual regression and project management features, this article will focus on how to implement simple yet effective [visual regression tests](https://www.meticulous.ai/blog/the-beginner-guide-to-visual-regression-testing) using [Jest](https://www.meticulous.ai/blog/how-to-use-jest-spyon) and [Cypress](https://www.meticulous.ai/blog/cypress-testing).
3956
3957We will set up visual regression tests on an example [repository](https://github.com/charlypoly/visual-regression-testing-examples), with the following component:
3958
3959\`\`\`
3960const Avatar = ({ src, className }) => {
3961  const [fallback, setFallback] = React.useState(false);
3962
3963  const onFallback = React.useCallback((e) => setFallback(true), []);
3964
3965  return (
3966    <div className={className}>
3967      {fallback ? <AvatarFallback /> : <img src={src} onError={onFallback} />}
3968    </div>
3969  );
3970};
3971
3972export default styled(Avatar)\`
3973  margin: 10px;
3974
3975  & img {
3976    border-radius: 50%;
3977  }
3978\`\`\`
3979
3980Our <Avatar /> [component](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/src/Avatar.jsx) displays the given image with some styling, and falls back to a custom component if the image fails to load.
3981
3982We will set up both snapshot and screen testing for this repository with integration at the Pull Request level.
3983
3984## Set up Snapshot Tests
3985
3986Most snapshot tests setups rely on [Jest](https://jestjs.io/) (a [JavaScript](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices) Testing Framework), which provides a set of utilities to perform snapshot comparison.
3987
3988Given a test scenario mounting a component, Jest will do an initial snapshot of the rendered DOM tree. Future runs of the same tests will trigger snapshot comparisons that will fail if a difference is found.
3989
3990![Setting up snapshot tests](/images/blog/how-to-set-up-visual-regression-testing/6569056a522396bb11419376_setting-up-snapshot-tests.png)
3991
3992## Installation and Configuration
3993
3994We need first to install the required dependencies:
3995
3996\`\`\`
3997yarn add jest @testing-library/jest-dom @testing-library/react @testing-library/user-event react-test-renderer
3998\`\`\`
3999
4000Then, we need to add some entries in our [package.json](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/package.json):
4001
4002\`\`\`
4003/* … */
4004"scripts": {
4005    "test-snapshot": "react-scripts test",
4006    /* … */
4007  },
4008  "jest": {
4009    "testMatch": [
4010      "**/?(*.)+(snapshot).(spec|test).[jt]s?(x)"
4011    ]
4012  },
4013/* … */
4014\`\`\`
4015
4016The jest entry contains the [Jest configuration](https://jestjs.io/docs/configuration).
4017
4018Since the example project is built with [Create React App](https://create-react-app.dev/), we extend the existing Jest configuration by specifying the test files matching pattern with testMatch.
4019
4020In our setup, we can run tests by calling react-scripts test. Without CRA, simply running the jest command as follows will run the tests:
4021
4022\`\`\`
4023/* … */
4024"scripts": {
4025    "test-snapshot": "jest",
4026    /* … */
4027  },
4028/* … */
4029\`\`\`
4030
4031## Writing and Running our Tests
4032
4033We can now write our <Avatar /> snapshot test in a new src/\\_\\_tests\\_\\_/snapshot/Avatar.jsx [file](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/src/__tests__/snapshot/Avatar.jsx):
4034
4035\`\`\`
4036import React from "react";
4037import renderer from "react-test-renderer";
4038import Avatar from "../src/Avatar";
4039
4040describe("<Avatar />", () => {
4041  describe("with a valid image", () => {
4042    it("renders correctly", () => {
4043      const tree = renderer
4044        .create(
4045          <Avatar
4046            src={
4047              "https://i.picsum.photos/id/623/200/200.jpg?hmac=xquTjHIYmAPV3XEGlIUaV_KWyEofkbortxrK79jJhWA"
4048            }
4049          />
4050        )
4051        .toJSON();
4052      expect(tree).toMatchSnapshot();
4053    });
4054  });
4055  describe("with an invalid image", () => {
4056    it("renders correctly", async () => {
4057      const tree = renderer.create(<Avatar src={"<not_and_url>"} />).toJSON();
4058      expect(tree).toMatchSnapshot();
4059    });
4060  });
4061});
4062\`\`\`
4063
4064The renderer() function from react-test-renderer helps render the given component and serialize the generated DOM tree as JSON.
4065
4066Then, the DOM result is passed to Jest expectation matcher toMatchSnapshot(), creating a snapshot file and raising a failure if a diff is detected with an existing snapshot.
4067
4068We can now run our test with the yarn test-snapshot command:
4069
4070![Writing and running snapshot tests](/images/blog/how-to-set-up-visual-regression-testing/656905e39c90ec66afec51fe_writing-and-running-snapshot-tests.png)
4071
4072A src/\\_\\_snapshots\\_\\_/Avatar.snapshot.spec.jsx.snap file is created.
4073
4074This snapshot file should be committed since it will be used as a reference snapshot for further tests (locally and on CI).
4075
4076## Setup the CI Workflow
4077
4078Our test setup is working locally. However, to ensure that no visual regression is introduced in our component, we must enforce those tests when new Pull Requests are created on Github.
4079
4080Let’s create a new [Github Action](https://docs.github.com/en/actions/learn-github-actions) to enforce such a policy by creating a Workflow file. Github Action Workflow files are a [broad subject to cover](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions); however, let’s have an overview of the main parts of the .github/workflows/snapshot-test.yml [file](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/.github/workflows/snapshot-tests.yml):
4081
4082- We define a [strategy.matrix](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/.github/workflows/snapshot-tests.yml#L12-L14) to run our tests on multiple versions of Node.js (12.x and 17.x)
4083- We configure [caches](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/.github/workflows/snapshot-tests.yml#L25-L40) for yarn dependencies and Jest
4084
4085Finally, we run the [tests](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/.github/workflows/snapshot-tests.yml#L42).
4086
4087## Testing the Workflow
4088
4089The following [Pull Request](https://github.com/charlypoly/visual-regression-testing-examples/pull/1) showcases how snapshots tests are integrated into the CI workflow.
4090
4091This Pull Request introduces a breaking change by inverting the condition on the <Avatar> fallback mechanism.
4092
4093Immediately, the [snapshots tests catch the visual regression](https://github.com/charlypoly/visual-regression-testing-examples/actions/runs/1897429449/jobs/2682953715) by reporting the DOM differences initiated by this new change:
4094
4095![Testing CI workflow](/images/blog/how-to-set-up-visual-regression-testing/656905e37bf3ee7f123c70de_testing-CI-workflow.png)
4096
4097_Note: If the Snapshot changes were intentional, simply running the test locally with the --updateSnapshot argument will update the snapshots with the new changes: yarn test-snapshot --updateSnapshot._
4098
4099_Once the updated snapshots are committed, the CI tests will pass._
4100
4101## Snapshot Test Limitations
4102
4103Looking closely at our snapshot tests reveals two main limitations.
4104
4105First, we can see in our [generated snapshot file (on master)](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/src/__tests__/snapshot/__snapshots__/Avatar.jsx.snap) that the “ with an invalid image renders correctly" test case is not generating the proper snapshot:
4106
4107\`\`\`
4108exports[\`<Avatar /> with an invalid image renders correctly 1\`] = \`
4109<div
4110  className="sc-bdvvtL oSqWo"
4111>
4112  <img
4113    onError={[Function]}
4114    src="<not_and_url>"
4115  />
4116</div>
4117\`;
4118\`\`\`
4119
4120This first limitation is generally linked to the difficulty to test image loading scenarios in Jest tests.
4121
4122To properly test a “failing to load image” scenario with Jest, [we will need to mock many parts of the code to trigger such a scenario](https://stackoverflow.com/questions/44462665/how-do-you-use-jest-to-test-img-onerror). On the other hand, screen tests by running headless browser instances could help in testing such features.
4123
4124Finally, any breaking change at the styling level raises cryptic snapshots changes, as shown in this [Pull Request](https://github.com/charlypoly/visual-regression-testing-examples/pull/2):
4125
4126![Example of a failing snapshot test](/images/blog/how-to-set-up-visual-regression-testing/65690607f0b1d59da905d6e6_example-of-failing-snapshot-test.png)
4127
4128The resulting snapshot test error is not very helpful in understanding which change introduced the issues, or what the final visual regression for end-users is.
4129
4130While some Jest plugins such as [jest-styled-components](https://github.com/styled-components/jest-styled-components) help deal with this issue, screen testing is more appropriate for assessing visual regression on UI-Kit components.
4131
4132## Setup Screen Tests
4133
4134As seen earlier, we need to confirm that our <Avatar> UI-Kit component has no visual regression of any kind, from styling to advanced DOM interactions (image failing load).
4135
4136Based on actual screenshots of components' rendering, screen tests are the best way to avoid visual regressions of our component.
4137
4138Screen tests share the same core logic as Snapshot tests, but by comparing screenshots.
4139
4140![Setting up screen tests](/images/blog/how-to-set-up-visual-regression-testing/6569056a522396bb11419376_setting-up-snapshot-tests.png)
4141
4142Screen tests require [Cypress](https://www.cypress.io/) (a JavaScript End to End Testing Framework) to start a headless browser instance to perform screenshots of the rendered components.
4143
4144## Installation and Configuration
4145
4146The following libraries are required to perform screenshots tests on components:
4147
4148- [Cypress Component testing](https://docs.cypress.io/guides/component-testing/overview): a feature of Cypress that allows running test of components without having to set up a whole application (usually required for E2E testing)
4149- [cypress-image-snapshot](https://github.com/jaredpalmer/cypress-image-snapshot): A Cypress plugin that brings the screenshot capabilities
4150
4151Let’s install all the required dependencies:
4152
4153\`\`\`
4154yarn add @cypress/react @cypress/webpack-dev-server cypress cypress-image-snapshot
4155\`\`\`
4156
4157Then, [creating a cypress.json file](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/cypress.json) to specify test files pattern and location:
4158
4159\`\`\`
4160{
4161  "component": {
4162    "componentFolder": "src",
4163    "testFiles": "**/*.visual.spec.{js,jsx,ts,tsx}"
4164  },
4165  "video": false // Cypress will record videos by default
4166}
4167\`\`\`
4168
4169Again, we need to update our package.json to add a new script entry:
4170
4171\`\`\`
4172// …
4173  “scripts”: {
4174     "test-visual": "cypress run-ct --reporter cypress-image-snapshot/reporter",
4175  }
4176// …
4177\`\`\`
4178
4179Finally, we need to properly configure the [cypress-image-snapsh
4179ot](https://github.com/jaredpalmer/cypress-image-snapshot) plugin by creating two files:
4180
4181- [cypress/plugins/index.js](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/cypress/plugins/index.js): configures Cypress with our CRA webpack config and enables the [cypress-image-snapshot](https://github.com/jaredpalmer/cypress-image-snapshot) plugin
4182- [cypress/support/index.js](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/cypress/support/index.js): configures the [cypress-image-snapshot](https://github.com/jaredpalmer/cypress-image-snapshot) plugin options
4183
4184## Writing and Running our Test
4185
4186We can now define our [<Avatar /> screen tests](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/src/__tests__/screen/Avatar.jsx) as follows:
4187
4188\`\`\`
4189/* eslint-disable no-undef */
4190import React from "react";
4191import { mount } from "@cypress/react";
4192import Avatar from "./Avatar";
4193
4194describe("<Avatar />", () => {
4195  describe("with a valid image", () => {
4196    it("renders the proper UI", () => {
4197      mount(
4198        <Avatar
4199          src={
4200            "https://i.picsum.photos/id/623/200/200.jpg?hmac=xquTjHIYmAPV3XEGlIUaV_KWyEofkbortxrK79jJhWA"
4201          }
4202        />
4203      );
4204      cy.matchImageSnapshot();
4205    });
4206  });
4207  describe("with an invalid image", () => {
4208    it("renders the proper UI", () => {
4209      mount(<Avatar src={"<not_and_url>"} />);
4210      cy.matchImageSnapshot();
4211    });
4212  });
4213});
4214\`\`\`
4215
4216The above tests are similar to previously seen Snapshot tests.
4217
4218The only difference is that we mount the component and call an action on cy (cypress runner): cy.matchImageSnapshot().
4219
4220Similar to Jest’s toMatchSnapshot() method, cy.matchImageSnapshot() will take a screenshot of the rendered component and raise an error if any difference is found with the existing one.
4221
4222We can now run our screen test with the yarn test-visual command:
4223
4224![Example of screen test with the yarn test visual command](/images/blog/how-to-set-up-visual-regression-testing/6569067962fb5eadd033c67f_example-of-screen-test-with-the-yarn-test-visual-command.png)
4225
4226Our tests generate one screenshot file per test case (describe()) that must be committed.
4227
4228## Setup the CI Workflow
4229
4230Again, we need to integrate those tests in the development workflow by creating the corresponding Github Actions.
4231
4232The new [.github/workflows/screen-tests.yml](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/.github/workflows/screen-tests.yml) file contains the following parts:
4233
4234- The [container parameter](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/.github/workflows/screen-tests.yml#L11) initialize a Docker image provided by Cypress that includes all required operating system dependencies and some pre-installed browsers to run the tests
4235- The [“Store screenshots”](https://github.com/charlypoly/visual-regression-testing-examples/blob/master/.github/workflows/screen-tests.yml#L19-L23) action ensures that screenshots will be downloadable
4236
4237## Testing the Workflow
4238
4239By creating a PR that, again, introduces a visual regression by inverting the fallback logic, we can see the following failing screen test results:
4240
4241![Testing a workflow](/images/blog/how-to-set-up-visual-regression-testing/65690679f0b1d59da906291f_testing-a-workflow.png)
4242
4243Then, by [following the test suite link](https://github.com/charlypoly/visual-regression-testing-examples/actions/runs/1898019720), we can download the generated screenshots in the Artifacts section:
4244
4245![Example of failing screen test](/images/blog/how-to-set-up-visual-regression-testing/6569067971ee05239185da13_example-of-failing-screen-test.png)
4246
4247The downloaded .zip file contains all original screenshots along with the diff outputs:
4248
4249![Example of visual regression testing](/images/blog/how-to-set-up-visual-regression-testing/656906794908530b2b34919a_visual-regression-testing-examples.png)
4250
4251We can see that our “ with a valid image renders the proper UI” scenario is no longer behaving as expected.
4252
4253_Note: Like Snapshot tests, running the yarn test-visual --env updateSnapshots=true locally will update the screenshots with the new changes. Once the updated screenshots are committed, the CI tests will pass._
4254
4255## Conclusion
4256
4257Snapshot tests are the fastest way to catch any regression related to:
4258
4259- Presence of a given element in a page (example: sorting button of an actionable list)
4260- Displayed text in a given context (i18n regression testing)
4261- Rendering of components after simple interactions (ex: filter list)
4262
4263However, Snapshot tests are not the best match for catching visual regressions related to pure CSS issues such as overflows or advanced interactions like loading images.
4264
4265Although slower to run, screen tests, by performing diff of screenshots, help catch advanced visual regressions that cannot be spotted at the DOM level.
4266
4267Finally, keep in mind that it might be worth looking at SaaS solutions if your project relies on a growing number of visual regressions tests (more than the critical path) or that non-technical people get involved in the project.
4268
4269## Meticulous
4270
4271Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
4272
4273This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
4274
4275This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
4276`,Y4=`---
4277title: "How to use Jest spyOn with React.js and Fetch"
4278slug: "how-to-use-jest-spyon"
4279date: "2024-01-16"
4280description: "Follow this step-by-step guide on Jest's SpyOn method, with a practical example using React and Fetch. This post also includes a comparison to mocks."
4281author: "Geshan Manandhar"
4282authorImage: "/images/blog/authors/6568d05a61cacbddd7afcac0_geshan-manandhar.jpeg"
4283heroImage: "/images/blog/how-to-use-jest-spyon/656e25d938e7a058350b87d0_how-to-use-jest-spyon.webp"
4284heroImageAlt: "How to use Jest spyOn with React.js and Fetch"
4285categories: ["React", "Jest"]
4286showTableOfContents: true
4287showPromo: true
4288---
4289
4290[Jest](https://www.meticulous.ai/blog/mocking-a-javascript-class-with-jest-two-ways-to-make-it-easier) is one of the most popular JavaScript testing frameworks these days. Some of the reasons for [Jest’s](https://jestjs.io/) popularity include out of the box code coverage, [snapshot testing](https://jestjs.io/docs/snapshot-testing), zero-config, easy-to-use API, works for both frontend and backend frameworks, and of course, great mocking capabilities.
4291
4292In this post, you will learn about how to use Jest’s [spyOn](https://jestjs.io/docs/jest-object#jestspyonobject-methodname) method to peek into calls of some methods and optionally replace the method with a custom implementation. You will also learn how to return values from a spy and evaluate the parameters passed into it with a practical [React](https://www.meticulous.ai/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial) code example.
4293
4294## What is Jest
4295
4296Jest is a batteries included JavaScirpt testing framework which ensures the correctness of applications that run on both the browser and the server with Node.js. In comparison to other [JavaScript](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices) testing frameworks like Mocha and Jasmine, Jest really does have batteries included. There is no need to piece together multiple NPM packages like in other frameworks. For instance, mocking, code coverage, and snapshots are already available with Jest. As per Jest website:
4297
4298> _Jest is a delightful JavaScript Testing Framework with a focus on simplicity._
4299
4300This holds true most of the time :). It also comes bundled with many popular packages like [React](https://reactjs.org/) with the Create React App (CRA) and [Nest JS](https://nestjs.com/). All these factors help Jest to be one of the most used testing frameworks in JavaScript, which is contested pretty frequently by the likes of [Vitest](https://vitest.dev/) and other frameworks.
4301
4302In terms of usage and popularity, As per the state of JS [survey](https://2021.stateofjs.com/en-US/libraries/testing/) of 2021, Jest is the most used testing framework among survey respondents for the third consecutive year with 73% using it. Another notable number is that 95% of the survey respondents are aware of Jest, which is another testament to its popularity.
4303
4304![Popularity of Jest in 2021](/images/blog/how-to-use-jest-spyon/656907b90c4f529ecded0cfa_popularity-of-Jest-in-2021.jpeg)
4305
4306As seen above Jest overtook Jasmine in 2018 with 41% usage and beat Mocha in 2019 with 64% usage to take the number one spot and has held it for 3 years now.
4307
4308## What is Jest spyOn
4309
4310Good testing involves mocking out dependencies. Jest provides multiple ways to mock out dependencies while writing [unit tests](https://www.meticulous.ai/blog/frontend-unit-testing-best-practices). The most common way to replace dependencies is with mocks. Another way to supplant dependencies is with use of Spies. Spies record some information depending on how they are called. Jest’s spyOn method is used to spy on a method call on an object. It is also very beneficial in cases where the Jest mock module or mock function might not be the best tool for the job on hand.
4311
4312While writing unit tests you only test one particular unit of code, generally a function. So, the goal of mocking is to replace something that is beyond your control with something that is within your control. Replacing a dependency on the fly for the scope of the test is also enabled by [Dependency Injection](https://blog.a
4312ppsignal.com/2022/02/16/dependency-injection-in-javascript-write-testable-code-easily.html), which is another topic on its own.
4313
4314Discussing [Jest SpyOn](https://jestjs.io/docs/jest-object#jestspyonobject-methodname) specifically, it can spy or mock a function on an object. It is useful when you want to watch (spy) on the function call and can execute the original implementation as per need. A mock will just replace the original implementation with the mocked one.
4315
4316The example used in the next section will show how to use Jest spyOn to spy on the native fetch and console object’s log method. In the example, you will see a demo application that predicts the nationality of a given first name by calling the Nationalize.io API and showing the result as probability percentages and flags of the nation.
4317
4318## Prerequisites
4319
4320Before getting your hands dirty with the code, let's cover the prerequisites:
4321
43221.  Knowledge about JavaScript basics like variables, loops, etc would be expected
43232.  Understanding async JavaScript with promise and async/await would be helpful
43243.  Prior knowledge of React.js will be beneficial
43254.  Any experience using Jest in the past will be valuable to understand the code examples
4326
4327Given the prerequisites mentioned, the code example will help you understand how to use Jest spyOn for writing useful unit tests.
4328
4329## Example React.js App to Guess Nationalities of a Given Name
4330
4331As an example, a simple yet useful application to guess the nationalities of a given first name will help you learn how to leverage Jest and spyOn. The working application will look like the below with a test for the name “Chris”:
4332
4333![ReactJS app used to guess nationalities of a given name](/images/blog/how-to-use-jest-spyon/656907b9b19545b9d1dc41b8_React.js-app-used-to-guess-nationalities-of-a-given-name.gif)
4334
4335The app hosted on [Netlify](https://name-nationality.netlify.app/) and the code and tests are available on [GitHub](https://github.com/geshan/name-nationality). The code is pretty straightforward, it is built on top of a [Create React App](https://reactjs.org/docs/create-a-new-react-app.html) boilerplate without much CSS styling. The main App.js file looks like:
4336
4337\`\`\`
4338import { useState } from 'react';
4339import './App.css';
4340
4341function App() {
4342  const [nationalities, setNationalities] = useState([]);
4343  const [message, setMessage] = useState('');
4344  const [personName, setPersonName] = useState('');
4345
4346  async function fetchNationalities() {
4347    try {
4348
4349      const data = await (await fetch(\`https://api.nationalize.io/?name=\${personName}\`)).json();
4350      const hasCountryData = data.country && data.country.length
4351      const nationalities =  hasCountryData ? data.country : [];
4352      setNationalities(nationalities);
4353
4354      const message = hasCountryData ? \`\${data.country.length} guess(es) found\` : 'No nationality match found';
4355      setMessage(message);
4356    } catch (err) {
4357      console.log(\`err: \${err.message}\`);
4358      setNationalities([]);
4359      setMessage('Could not fetch nationalities, try again later.');
4360    }
4361  }
4362
4363  async function handleSubmit(e){
4364    e.preventDefault();
4365    await fetchNationalities();
4366  }
4367
4368  return (
4369    <div className="App">
4370      <header className="App-header">
4371        <div className="title-form">
4372          <h2>Check Name's Nationalities percent</h2>
4373          <div style={{ marginBottom: '20px' }}>
4374            <form name="nationalities-form" onSubmit={handleSubmit}>
4375              <input
4376                name="personName"
4377                type="text"
4378                onChange={(e) => setPersonName(e.target.value)}
4379                value={personName}
4380                placeholder="Enter a person's name"
4381              />
4382              <button onClick={handleSubmit}>Get Nationalities</button>
4383            </form>
4384          </div>
4385        </div>
4386        <div className="results">
4387          <div className="message">{message}</div>
4388          <div className="nationalities">
4389            {Array.isArray(nationalities) && nationalities.map(
4390              nationality => {
4391                const flagUrl = \`https://flagcdn.com/w160/\${nationality.country_id.toLowerCase()}.jpg\`;
4392                const altText = \`\${nationality.country_id} flag\`;
4393                return <div key={nationality.country_id}><h3>{nationality.country_id} - {(nationality.probability * 100).toFixed(2)}%</h3> <img src={flagUrl} alt={altText} style={{
4394                  border: "1px solid black"
4395                }} /></div>
4396              }
4397            )}
4398          </div>
4399        </div>
4400      </header>
4401    </div>
4402  );
4403}
4404
4405export default App;
4406\`\`\`
4407
4408First, useState is imported from React, then the [modified CSS](https://github.com/geshan/name-nationality/blob/master/src/App.css) file is imported. After that, the main App function is defined which contains the whole app as a function component. The App has 3 state variables initialized with the useState hook, those are nationalities, message, and personName.
4409
4410Consequently, define the fetchNationalities async function. This is the main function that calls the Nationalize.io API to get the nationalities of a given name. This function calls the API and checks if the country with the percent data is returned properly. Ultimately setting it in the nationalities variable and relevant message in the message variable.
4411
4412If there is an error calling the API like a 429 rate limit exceeded it will land in the catch part. On a successful response, a further check is done to see that the country data is present. If the country data is found nationalities array and message string are set properly so that the flags can be displayed in the later section of the code.
4413
4414Subsequently, write the handleSubmit async function. As the name suggests, it handles the form submission triggred either by clicking the button or hitting enter on the text field.
4415
4416It is intentional that there is no check to see if the name field is empty for the sake of simplicity. This function prevents the default form submission and calls the above fetchNationalities function to get the nationalities which will paint the flags on the screen with their guess percentages.
4417
4418Then, write down the return part. This segment returns the JSX that will render the HTML to show the empty form and flags with the returned response when the form is submitted.
4419
4420It will also show the relevant message as per the Nationalize.io API’s response. The main part here is the Array.map loop which only w
4420orks if there are elements in the nationalities array set as per the response from the API.
4421
4422The crux of the matter is inside that same loop. The Flag CDN API is used to get the flag image from the ISO code of the country. The alt text for the flag is constructed with the same logic.
4423
4424Another point to note here is, that the percent calculator is also done on the display level with the returned probability and for ease, styles are applied inline like the 1 px border on the flag image.
4425
4426That comprehensive description of the code should form a good idea of what this basic but practical app does. You can see the working app deployed on [Netlify](https://name-nationality.netlify.app/). Feel free to peel the [layers](https://github.com/geshan/name-nationality/commits/master) on how it progressed to the current state. In the subsequent section, you will learn how to write tests for the above app.
4427
4428## Tests for the Nationality Guessing App
4429
4430The simple name to nationality guessing app is working with some edge cases deliberately not handled for the sake of brevity. It is time to add the first and most basic test for the nationality guessing app in the App.test.js, start by setting it up correctly as follows:
4431
4432\`\`\`
4433import { fireEvent, render, screen } from '@testing-library/react';
4434import mockFetch from './mocks/mockFetch';
4435import userEvent from '@testing-library/user-event';
4436import App from './App';
4437
4438let windowFetchSpy;
4439
4440beforeEach(() => {
4441  windowFetchSpy = jest.spyOn(window, 'fetch').mockImplementation(mockFetch);
4442})
4443
4444afterEach(() => {
4445  jest.restoreAllMocks();
4446});
4447\`\`\`
4448
4449To start with, this is not a unit test but it is closer to an integration test with the dependencies mocked out. The fireEvent, render and screen are imported from the @testing-library/react package. [React testing library](https://testing-library.com/) comes bundled in the Create React App template. For this test, only use the [screen](https://testing-library.com/docs/queries/about/#screen) object is used.
4450
4451After that, import the ./mocks/mockFetch.js, this will also be used later. The contents of this file will be discussed in a bit. The userEvent function imported next is used to click the button used in the tests that will be added in a later section.
4452
4453Consequently, the [Jest beforeEach](https://geshan.com.np/blog/2022/06/jest-beforeeach/) and afterEach hooks are used to set up the spy on fetch function of the window object as part of [setup and teardown](https://jestjs.io/docs/setup-teardown). As the name implies, these methods will be called before and after each test run. If there are 5 tests in the file, both before each and after each will run 5 times before and after every test.
4454
4455## Fetch Mock File
4456
4457The important ingredient of the whole test is the file where fetch is mocked. The HTTP call and a stubbed response can be seen in the ./mocks/mockFetch.js file with the following contents:
4458
4459\`\`\`
4460const nameNationalizeResponse = {
4461  name: 'john',
4462  country:[
4463    {
4464      country_id: 'US',
4465      probability: 0.048398225615958565
4466    },
4467    {
4468      country_id: 'IM',
4469      probability:0.04438246053773764
4470    },
4471    {
4472      country_id: 'IE',
4473      probability: 0.042102085396037124
4474    }
4475  ]
4476}
4477
4478export default async function mockFetch(url) {
4479  if(url.startsWith('https://api.nationalize.io') && url.includes('john')) {
4480    return {
4481      ok: true,
4482      status: 200,
4483      json: async () => nameNationalizeResponse,
4484    };
4485  }
4486
4487  throw new Error(\`Unhandled request: \${url}\`);
4488}
4489\`\`\`
4490
4491The mock implementation named mockFetch gives back a stubbed response only if the URL starts with https://api.nationalize.io and for the name john which is used in the test shown in the next section. The mock responds following the [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) API having attributes like status and ok. For any other input for example if the name chris or any other URL, the mock function will throw an Error indicating Unhandled request with the passed-in URL. Next the first basic test to validate the form renders correctly will be elaborated.
4492
4493## Testing the Form Render
4494
4495After all the setup, the first basic test to check if the screen loads with the text and form initially is as follows:
4496
4497![Testing the form render](/images/blog/how-to-use-jest-spyon/65690814a090d
4497a6e57b5558d_testing-the-form-render.jpeg)
4498
4499The first test is to make sure the screen looks as desired, the code for the test is as follows:
4500
4501\`\`\`
4502test('renders initial heading and form with elements correctly', () => {
4503  render(<App />);
4504  const titleElement = screen.getByText(/Check Name/i);
4505  expect(titleElement).toBeInTheDocument();
4506  expect(screen.getByRole('form')).toBeInTheDocument();
4507  expect(screen.getByRole('textbox')).toBeInTheDocument();
4508  expect(screen.getByRole('button', {name: 'Get Nationalities'})).toBeInTheDocument();
4509});
4510\`\`\`
4511
4512The test is appropriately named renders initial heading and form with elements correctly. First, the App component is rendered. Then the title element by searching [by text](https://testing-library.com/docs/queries/bytext/) provided in the testing library is grabbed. After that, make sure the element is visible in the document with [toBeInTheDocument method](https://testing-library.com/docs/guide-disappearance/#waiting-for-appearance).
4513
4514Consequently, it is time to check if the form has been rendered correctly. For this, the [getByRole method](https://testing-library.com/docs/queries/byrole/) is used to find the form, textbox, and button. Check all three elements to be in the document. For the button element, it is fetched by passing the name which is the text in the button.
4515
4516Till now, it has been a basic test, in the consequent section, we will test the happy path where the form has a name and it is submitted.
4517
4518## How Jest SpyOn Helps to Mock the Fetch Network Call
4519
4520In this part, a test where the form has a name and is submitted by clicking the button will be added. The test to evaluate this interaction looks as follows:
4521
4522\`\`\`
4523test('should get nationalities for a name', async() => {
4524  render(<App />);
4525
4526  //simulate filling up the textbox
4527  const personNameInput = screen.getByRole('textbox');
4528  fireEvent.change(personNameInput, {target: {value: 'john'}})
4529  expect(personNameInput.value).toBe('john');
4530
4531  //click the button
4532  const getNationalitiesBtn = screen.getByRole('button', { name: 'Get Nationalities' });
4533  expect(getNationalitiesBtn).not.toBeDisabled();
4534  userEvent.click(getNationalitiesBtn);
4535
4536  //verify percent and flag images are displayed
4537  expect(await screen.findByText('3 guess(es) found')).toBeVisible();
4538
4539  expect(windowFetchSpy).toHaveBeenCalled();
4540  expect(windowFetchSpy).toHaveBeenCalledWith('https://api.nationalize.io/?name=john');
4541  expect(screen.getByText('US - 4.84%')).toBeVisible();
4542  expect(screen.getByText('IM - 4.44%')).toBeVisible();
4543  expect(screen.getByText('IE - 4.21%')).toBeVisible();
4544
4545  const flagImages = screen.getAllByRole('img');
4546  expect(flagImages).toHaveLength(3);
4547  expect(flagImages[0]).toHaveAccessibleName('US flag');
4548  expect(flagImages[1]).toHaveAccessibleName('IM flag');
4549  expect(flagImages[2]).toHaveAccessibleName('IE flag');
4550});
4551\`\`\`
4552
4553This test similar to the last one starts by rendering the App component. Then we fill up the textbox the word john using the fireEvent object’s [change](https://testing-library.com/docs/dom-testing-library/api-events/#fireeventeventname) method. This method was imported in the previous section. After that the button is clicked by calling the [click](https://testing-library.com/docs/ecosystem-user-event/#clickelement-eventinit-options) method on the userEvent object simulating the user clicking the button.
4554
4555This is where the important part happens, as we have added the following line in beforeEach hook:
4556
4557\`\`\`
4558windowFetchSpy = jest.spyOn(window, 'fetch').mockImplementation(mockFetch);
4559\`\`\`
4560
4561The request to nationalize via fetch will never reach the real API but it will be intercepted as the fetch method on the window object has been spied. With the above spy, it is instructing to not use the original implementation and use the mock implementation. Given the name is exactly john and it is calling the API endpoint starting with https://api.nationalize.io it will get back the stubbed response object from the mock. It is being verified by:
4562
4563\`\`\`
4564 expect(windowFetchSpy).toHaveBeenCalled();
4565   expect(windowFetchSpy).toHaveBeenCalledWith('https://api.nationalize.io/?name=john');
4566\`\`\`
4567
4568This means the spy has been called once and it has been called with the above URL. It had all been set up aptly in the above set up section.
4569
4570For the remainder of the test, it checks if the element with 3 guess(es) found is visible. This also verifies the country ISO code and percent are as expected, for example US - 4.84% for the US. Similarly, it inspects that there are flag images with expected alt text.
4571
4572Congratulations! Now we have successfully mocked the fetch call with Jest SpyOn and also verified the happy path result. Still, in distributed systems all requests don’t succeed, thereby another test to check how the app will behave when an error occurs is added in the next part.
4573
4574## Using Jest SpyOn with console.log to Spy Only
4575
4576Next, the test for the case when the API responds with an error like 429 Too many requests or 500 internal server error will be appended. This is the part testing for an edge c
4576ase. Below is the test code where we simulate an error from the API:
4577
4578\`\`\`
4579test('should handle error while getting nationalities for a name', async() => {
4580  //const consoleLogSpy = jest.spyOn(console, 'log').mockReturnValue(undefined);
4581  const consoleLogSpy = jest.spyOn(console, 'log');
4582
4583  const { container } = render(<App />);
4584
4585  const personNameInput = screen.getByRole('textbox');
4586  fireEvent.change(personNameInput, {target: {value: 'error'}})
4587  expect(personNameInput.value).toBe('error');
4588
4589  const getNationalitiesBtn = screen.getByRole('button', { name: 'Get Nationalities' });
4590  expect(getNationalitiesBtn).not.toBeDisabled();
4591  userEvent.click(getNationalitiesBtn);
4592
4593  expect(await screen.findByText('Could not fetch nationalities, try again later.')).toBeVisible();
4594  expect(consoleLogSpy).toHaveBeenCalled();
4595  expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('err: Unhandled request'));
4596
4597  const nationalities = container.getElementsByClassName('nationalities')[0];
4598  expect(nationalities).toBeEmptyDOMElement();
4599});
4600\`\`\`
4601
4602In this abovetest, the console.log method is spied on without any mock implementation or canned return value. The commented line before it mocks the return value but it is not used.
4603
4604This is the main difference between SpyOn and Mock module/function. A spy may or may not mock the implementation or return value and just observe the method call and its parameters. On the other hand, a mock will always mock the implementation or return value in addition to listening to the calls and parameters passed for the mocked function.
4605
4606Next, render the App component and do a [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) to a variable called container. Similar to the above test, the textbox is filled with the name error and submitted by clicking the button. After that, expect the text Could not fetch nationalities, try again later to be on the screen. The test also expects the element with nationalities class that would display the flags to be empty.
4607
4608The main part here is, that spy calls are expected as follows:
4609
4610\`\`\`
4611expect(consoleLogSpy).toHaveBeenCalled();
4612  expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('err: Unhandled request'));
4613\`\`\`
4614
4615Given it is a spy, the main implementation is also called. Verify this by running the tests with npm test and it will show the console log output as seen below:
4616
4617![Using Jest spyOn with console.log to spy only](/images/blog/how-to-use-jest-spyon/656908eb0c4f529ecdeddd79_using-jest-spyOn-with-console.log-to-spy-only.jpeg)
4618
4619Great! You have not covered one edge case when the API responds with an error. The full test code file is available on [Github](https://github.com/geshan/name-nationality/blob/master/src/App.test.js) for your reference. At this point, it will be advantageous to know when to use SpyOn compared to mock, that is what will be unraveled next.
4620
4621## When is Jest SpyOn a Better Option Compares to Mock
4622
4623In the above example, for mocking fetch a jest.fn could have been easily used. It can be done with the following line of code replacing the spyOn line in the beforeEach hook:
4624
4625\`\`\`
4626global.fetch = jest.fn().mockImplementation(mockFetch);
4627\`\`\`
4628
4629Notice here the implementation is still the same mockFetch file used with Jest spyOn. On the contrary, now it is a bit more difficult to verify that the mock is called in the test. This is where using spyOn on an object method is easier. Jest spyOn can target only the function relevant for the test rather than the whole object or module.
4630
4631Along the same line, in the previous test console.log was spied on and the original implementation was left intact with:
4632
4633\`\`\`
4634const consoleLogSpy = jest.spyOn(console, 'log');
4635\`\`\`
4636
4637Using the above method to spy on a function of an object, Jest will only listen to the calls and the parameters but the original implementation will be executed as we saw from the text execution screenshot. This is the compelling reason to use spyOn over mock where the real implementation still needs to be called in the tests but the calls and parameters have to be validated.
4638
4639## Conclusion
4640
4641You have learned what Jest is, its popularity, and Jest SpyOn. A small but functional app with React that can guess the nationality of a given name by calling an API was created. The app was showing the probability percentages with the country's flags. The flags for the countries were also shown calling another API.
4642
4643Then you ventured into writing tests for the Name’s nationality guessing app with a stark focu
4643s on Jest SpyOn. First, tested that the form was loaded and then carried on to the happy path. After that, wrote a test for an edge case if the API fails. You also learned when to use Jest spyOn as well as how it differs from Jest Mock.
4644
4645If there is one point to take away from this post, it is Jest spyOn can spy on the method calls and parameters like Jest Mock/fn, on top of that it can also call the underlying real implementation. Mock can only respond with mocks and cannot call the underlying real code. Understand this difference and leverage Jest spyOn to write more effective tests.
4646
4647## Meticulous
4648
4649Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
4650
4651This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
4652
4653This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
4654`,V4=`---
4655title: "How to Use React Testing Library to Wait for Async Elements | Step-by-Step Guide"
4656slug: "how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide"
4657date: "2024-01-16"
4658description: "A guide on how to use React Testing Library's to wait for and test asynchronously loaded elements. Contains code examples and a step-by-step walkthrough."
4659author: "Geshan Manandhar"
4660authorImage: "/images/blog/authors/6568d05a61cacbddd7afcac0_geshan-manandhar.jpeg"
4661heroImage: "/images/blog/how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide/656a25293744023d726dd7c0_how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.png"
4662heroImageAlt: "How to Use React Testing Library to Wait for Async Elements | Step-by-Step Guide"
4663categories: ["React", "Tutorial"]
4664showTableOfContents: true
4665showPromo: true
4666---
4667
4668In this post, you will learn about how JavaScirpt runs in an asynchronous mode by default. You will also get to know about a simple [React](https://www.meticulous.ai/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial).js app that fetches the latest Hacker News front page stories. You will write tests for the asynchronous code using React Testing Library watiFor function and its other helper functions in a step-by-step approach. Let’s get started!
4669
4670## JavaScript is Asynchronous by Default
4671
4672JavaScript is a complicated language, like other popular languages it has its own share of [quirks](https://dev.to/mkrl/javascript-quirks-in-one-image-from-the-internet-52m7) and [good parts](https://www.youtube.com/watch?v=hQVTIJBZook). JavaScript is a [single-threaded and asynchronous](https://thecodest.co/blog/asynchronous-and-single-threaded-javascript-meet-the-event-loop/) language which is a commendable but not so easy-to-understand feature. You might be wondering what asynchronous means. If tasks are executed one after the other where each task waits for the previous task to complete, then it is synchronous. If the execution can switch between different tasks without waiting for the previous one to complete it is asynchronous.
4673
4674Suppose you have a function with 5 lines of code. If it is executed sequentially, line by line from 1 to 5 that is synchronous. If line 2 is put in the background and then line 3 is executed, then when line 4 is executing the result of line 2 is available this is asynchronous. Javascript can run on the asynchronous mode by default. Pushing the task in the background and resuming when the result is ready is made possible by using [events](https://developer.mozilla.org/en-US/docs/Web/Events) and [callbacks](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function). The event can be all data received which triggers a callback to process the received data. A better way to understand async code is with an example like below:
4675
4676\`\`\`
4677console.log('First log message');
4678
4679// 1 second wait
4680setTimeout(function(){
4681  console.log('Third log message - after 1 second');
4682}, 1000);
4683
4684console.log('Second log message');
4685\`\`\`
4686
4687If the above code would execute sequentially (sync) it would log the first log message, then the third one, and finally the second one. But the output will be as follows:
4688
4689\`\`\`
4690First log message'
4691Second log message'
4692Third log message - after 1 second
4693\`\`\`
4694
4695This is where the power of async programming is evident. Line 1 is executed first, then line 3 was executed but pushed in the background with [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) with an instruction to execute the code within setTimeout after 1 second. The code execution moved forward and the last console.log in the script printed Second log message. After one second passed, the callback is triggered and it prints the Third log message console log. This is managed by the event loop, you can learn more about the JavaScript event loop in this amazing [talk](https://www.youtube.com/watch?v=6MXRNXXgP_0). You can also step through the above code in this useful [visualizer](https://www.jsv9000.app/?code=Y29uc29sZS5sb2coJ0ZpcnN0IGxvZyBtZXNzYWdlJyk7CgovLyAxIHNlY29uZCB3YWl0CnNldFRpbWVvdXQoZnVuY3Rpb24oKXsKICBjb25zb2xlLmxvZygnVGhpcmQgbG9nIG1lc3NhZ2UgLSBhZnRlciAxIHNlY29uZCcpOwp9LCAxMDAwKTsKCmNvbnNvbGUubG9nKCdTZWNvbmQgbG9nIG1lc3NhZ2UnKTsK) to better understand the execution flow.
4696
4697You can learn more about this example where the code waits for [1 second](https://geshan.com.np/blog/2022/08/javascript-wait-1-second/) with Promises too. All external API calls can also be dealt with in an async way using Promises and the newer async/await syntax.
4698
4699In terms of testing, the async execution model is important because the way any asynchronous code is tested is different from the way you test synchronous sequential code. For any async code, there will be an element of waiting for the code to execute and the result to be available. You will learn about this in the example app used later in this post. In the next section, you will learn more about React Testing library.
4700
4701## React Testing Library
4702
4703React testing library (RTL) is a testing library built on top of [DOM Testing library](https://testing-library.com/docs/dom-testing-library/intro/). It is built to test the actual DOM tree rendered by React on the browser.
4704
4705> _The goal of the library is to help you write tests in a way similar to how the user would use the application._
4706
4707This approach provides you with more confidence that the application works as expected when a real user uses it. This is based on their guiding principle:
4708
4709> _The more your tests resemble the way your software is used, the more confidence your tests will give you._
4710
4711The end user doesn’t care about the state management library, react hooks, class, or functional components being used. They want your app to work in a way to get their work done. Within that context, with React Testing Library the end-user is kept in mind while testing the application. This user-centric approach rather than digging into the internals of React makes React Testing Library different from [Enzyme](https://enzymejs.github.io/enzyme/). Enzyme was open-sourced by [Airbnb](https://airbnb.io/projects/enzyme/) at the end of [2015](https://github.com/enzymejs/enzyme/tags?after=v1.2.0). It was popular till mid-2020 but later React Testing library became more popular than Enzyme. React Testing Library’s rise in popularity can be attributed to its ability to do user-focused testing by verifying the actual DOM rather than dabbling with React.js internals.
4712
4713React Testing Library is written by [Kent C. Dodds](https://kentcdodds.com/blog/introducing-the-react-testing-library). Kent is a well-known personality in the React and testing space. React testing library became more popular than Enzyme in mid-Sep 2020 as per [NPM trends](https://npmtrends.com/@testing-library/react-vs-enzyme). Currently, RTL has almost 7 million downloads a week on [NPM](https://www.npmjs.com/package/@testing-library/react). In the next section, you will see how the example app to write tests using React Testing Library for async code works.
4714
4715## Example App to get HackerNews Stories
4716
4717For this guide to use React Testing Library waitFor, you will use a React.js app that will get the latest stories from the HackerNews front page. To fetch the latest stories from HN you will use the [unofficial HackerNews API](https://hn.algolia.com/api) provided by Aloglia. This example app is created using [Create React App](https://github.com/facebook/create-react-app) (CRA) and the HackerNews component has the following code:
4718
4719\`\`\`
4720import { useState, useEffect } from 'react';
4721
4722const HackerNewsStories = () => {
4723  const [stories, setStories] = useState(null);
4724  const [loading, setLoading] = useState(true);
4725  const [error, setError] = useState(null);
4726
4727  useEffect(() => {
4728    const fetchStories = async () => {
4729      try {
4730        const data = await (await fetch('https://hn.algolia.com/api/v1/search_by_date?tags=front_page&hitsPerPage=20')).json();
4731        setStories(
4732          data.hits.sort((story, nextStory) => (story.points < nextStory.points ? 1 : -1))
4733        );
4734        setError(null);
4735      } catch (err) {
4736        setError(err.message);
4737        setStories(null);
4738      } finally {
4739        setLoading(false);
4740      }
4741    };
4742    fetchStories();
4743  }, []);
4744
4745  return (
4746    <div className="wrapper">
4747      <h2>Latest HN Stories</h2>
4748      {loading && <div>HackerNews frontpage stories loading...</div>}
4749      {error && <div>{\`Problem fetching the HackeNews Stories - \${error}\`}</div>}
4750      <div className="stories-wrapper">
4751        {stories &&
4752          stories.map(({ objectID, url, title, author, points }) => (
4753            title && url &&
4754            <div className='stories-list' key={objectID}>
4755              <h3><a href={url} target="_blank" rel="noreferrer">{title}</a> - By <b>{author}</b> ({points} points)</h3>
4756            </div>
4757          ))}
4758      </div>
4759    </div>
4760  );
4761};
4762
4763export default HackerNewsStories;
4764\`\`\`
4765
4766You are adding a basic react component that pulls in the latest front-page stories from HackerNews using the unofficial API provided by Algolia. Like most modern React components using hooks this one also starts by importing setState and [useEffect hook](https://reactjs.org/docs/hooks-effect.html).
4767
4768Next, you define a function called HackerNewsStories that houses the whole Hacker News stories component. Three variables, stories, loading, and error are set with initial empty state using [setState function](https://www.geeksforgeeks.org/reactjs-setstate/). After that, the useState hook is defined. This function pulls in the latest Hacker News front page stories using the API. Then, it sorts the stories with the most points at the top and sets these values to the stories variable with the setStories function call. If there are no errors the error variable is set to null.
4769
4770In case of any error, the code goes to the catch block where the error is set to the message of the caught error, then the stories variable is set to null. In both error or no error cases the finally part is executed setting the loading variable to false which will remove the div showing the stories are being loaded message.
4771
4772After this, it returns the function with the [JSX](https://reactjs.org/docs/introducing-jsx.html), which will be rendered as HTML by the browser. The output is also simple, if the stories are still being loaded it will show the loading div with the text HackerNews frontpage stories loading... else it will hide the loading message. The same logic applies to showing or hiding the error message too.
4773
4774The main part here is the div with the stories-wrapper class. In this div, If stories exist, each story title will be rendered in an h3 tag with a link to the story. The author and the points of the story are printed too. It is a straightforward component used in the [App component](https://github.com/geshan/hackernews-react/blob/master/src/App.js#L8) with <HackerNewsStories />. The whole code is available as a [GitHub repository](https://github.com/geshan/hackernews-react) if you want to further dissect the code.
4775
4776The output looks like the below or you can see a working version on [Netlify](https://hackernews-react-js.netlify.app/) if you like:
4777
4778![React testing library waitfor example](/images/blog/how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide/65690a4aceeb3b440ac03ceb_react-testing-library-waitfor-example-app.gif)
4779
4780In the next segment, you will add a test for the above app and mock the API call with a stubbed response of 2 stories.
4781
4782## Testing the App with React Testing Library
4783
4784If you have used Create React App to set up the React.js application you will not need to install the React testing library. If you have set up React.js without the React Testing library you can run the following commands to get the needed NPM packages for testing with React Testing Library:
4785
4786\`\`\`
4787npm install --save-dev @testing-library/react @testing-library/jest-dom
4788\`\`\`
4789
4790The [Jest DOM](https://github.com/testing-library/jest-dom) npm package is needed to use custom matchers like .toBeInTheDocument() and .toHaveAcce
4790ssibleName(), etc. Next, you will write the test to see the component is rendering as expected.
4791
4792## Simple Test Mocking out window.fetch Response
4793
4794Given you have all the necessary packages installed, it is time to write a simple test using React Testing Library:
4795
4796\`\`\`
4797import { render, screen } from '@testing-library/react';
4798import HackerNewsStories from './HackerNewsStories';
4799
4800test('should render latest HN stories H2', async () => {
4801  render(<HackerNewsStories />);
4802  screen.debug();
4803});
4804\`\`\`
4805
4806This will print the current output when the test runs. You can understand more about [debugging React Testing library tests](https://meticulous.ai/blog/a-beginners-guide-to-debugging-react-testing-library-tests/) and also find out about screen.debug and prettyDOM functions. These functions are very useful when trying to debug a React testing library test.
4807
4808When it runs, it will show a CLI output like the below:
4809
4810\`\`\`
4811console.log
4812    <body>
4813      <div>
4814        <div
4815          class="wrapper"
4816        >
4817          <h2>
4818            Latest HN Stories
4819          </h2>
4820          <div>
4821            HackerNews frontpage stories loading...
4822          </div>
4823          <div
4824            class="stories-wrapper"
4825          >
4826          </div>
4827        </div>
4828      </div>
4829    </body>
4830
4831    /<path-to-project>/src/HackerNewsStories.basic.test.js:6:10
4832      4 | test('should render latest HN stories H2', async () => {
4833      5 |   render(<HackerNewsStories />);
4834    > 6 |   screen.debug();
4835\`\`\`
4836
4837As the real API is being called for this test, it is ok for quick and dirty debugging. It is not ideal to run it many times or run it as part of a CI/CD pipeline. Making a test dependent on an external resource like an API can make the test flaky and cause unnecessary requests to the API too. For these reasons, your unit tests should never use any external resource like the network or even the file system. To solve this issue, in the next step, you will mock the API call by using [Jest SpyOn](https://meticulous.ai/blog/how-to-use-jest-spyon/). The new test code will look like the following code which mocks the API call:
4838
4839\`\`\`
4840import { render, screen } from '@testing-library/react';
4841import HackerNewsStories from './HackerNewsStories';
4842
4843let windowFetchSpy;
4844
4845function wait(milliseconds){
4846  return new Promise(resolve => {
4847      setTimeout(resolve, milliseconds);
4848  });
4849}
4850
4851const mockHnResponse = {
4852  'hits': [
4853    {
4854      'created_at': '2022-10-01T20:26:19.000Z',
4855      'title': 'TikTok tracks you across the web, even if you don’t use the app',
4856      'url': 'https://www.consumerreports.org/electronics-computers/privacy/tiktok-tracks-you-across-the-web-even-if-you-dont-use-app-a4383537813/',
4857      'author': 'bubblehack3r',
4858      'points': 123,
4859      'objectID': '33049774',
4860    },
4861    {
4862      'created_at': '2022-10-01T15:16:02.000Z',
4863      'title': 'The self-taught UI/UX designer roadmap (2021)',
4864      'url': 'https://bootcamp.uxdesign.cc/the-self-taught-ui-ux-designer-roadmap-in-2021-aa0f5b62cecb',
4865      'author': 'homarp',
4866      'points': 253,
4867      'objectID': '33047199',
4868    },
4869  ],
4870  'page': 0,
4871  'query': '',
4872};
4873
4874let mockFetch = async (url) => {
4875  if (url.startsWith('https://hn.algolia.com/') && url.includes('front_page')) {
4876    await wait(70);
4877    return {
4878      ok: true,
4879      status: 200,
4880      json: async () => mockHnResponse,
4881    };
4882  }
4883}
4884
4885
4886beforeEach(() => {
4887  windowFetchSpy = jest.spyOn(window, 'fetch').mockImplementation(mockFetch);
4888})
4889
4890afterEach(() => {
4891  jest.restoreAllMocks();
4892});
4893
4894test('should render latest HN stories H2', async () => {
4895  render(<HackerNewsStories />);
4896  const latestStoriesH2 = await screen.getByText('Latest HN Stories');
4897  expect(latestStoriesH2).toBeInTheDocument();
4898});
4899\`\`\`
4900
4901You have added a [Jest spyOn](https://meticulous.ai/blog/how-to-use-jest-spyon/) to the window.fetch function call with a mock implementation. This mock implementation checks if the URL passed in the fetch function call starts with https://hn.algolia.com/ and has the word front_end. If both checks pass, it will send back a stubbed response with 2 stories defined in the mockHnResponse constant. To test the loading div appears you have added the [wait](https://geshan.com.np/blog/2022/08/javascript-wait-1-second/#sleep-with-promise) with a promise. To mock the response time of the API a wait time of 70 milliseconds has been added. Another way to make this API call can be with Axios, bare in mind [Fetch and Axios have their differences](https://meticulous.ai/blog/fetch-vs-axios/) though.
4902
4903React Testing Library (RTL) is the defacto testing framework for React.js. It also comes bundled with the popular Create React app toolchain. React Testing library is also very useful to [test React components](https://meticulous.ai/blog/testing-react-components-with-storybook/) that have asynchronous code with waitFor and related functions.
4904
4905The test uses [Jest beforeEach](https://geshan.com.np/blog/2022/06/jest-beforeeach/) hook to spy on the window.fetch before each test. It also uses the afterEach hook to restore the mock after every test.
4906
4907Take note that only the happy case of the API returning the latest front-page stories is included in the [stub](https://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs), it will be enough for the scope of this tutorial. There won’t be test coverage for the error case and that is deliberate. Line 17-18 of the HackerNewsStories component will not be covered by any tests which is the catch part in the code.
4908
4909If you rerun the tests, it will show the same output but the test will not call the real API instead it will send back the stubbed response of 2 stories.
4910
4911The test checks if the H2 with the text Latest HN Stories exists in the document and the test passes with the following output:
4912
4913\`\`\`
4914 PASS  src/HackerNewsStories.test.js
4915  ✓ should render latest HN stories H2 (12 ms)
4916
4917Test Suites: 1 passed, 1 total
4918Tests:       1 passed, 1 total
4919Snapshots:   0 total
4920Time:        0.376 s, estimated 1 s
4921Ran all test suites related to changed files.
4922
4923Watch Usage: Press w to show more.
4924\`\`\`
4925
4926Great! You have your first test running with the API call mocked out with a stub. In the next section, you will test for the stories to appear with the use of React Testing library waitFor.
4927
4928## Testing for the Stories to Appear
4929
4930As seen in the code and above image, the Hacker News React.js app first shows a “loading” message until the stories are fetched from the API. After that, it shows the stories sorted by the highest points at the top. This triggers a network request to pull in the stories loaded via an asynchronous fetch. For the test to resemble real life you will need to wait for the posts to display. This is where the React testing library waitFor method comes in handy.
4931
4932The waitFor method returns a promise and so using the async/await syntax here makes sense. Alternatively, the .then() syntax can also be used depending on your preference. For this tutorial’s tests, it will follow the async/await syntax. The test to check if the stories are rendered properly looks like the below:
4933
4934\`\`\`
4935test('should show stories after they are fetched', async () => {
4936  render(<HackerNewsStories />);
4937  expect(windowFetchSpy).toHaveBeenCalled();
4938  expect(windowFetchSpy).toHaveBeenCalledWith('https://hn.algolia.com/api/v1/search_by_date?tags=front_page&hitsPerPage=20');
4939
4940  //expect(screen.getByText('The self-taught UI/UX designer roadmap (2021)')).toBeInTheDocument(); //this will fail
4941  await waitFor(() => {
4942    expect(screen.getByText('The self-taught UI/UX designer roadmap (2021)')).toBeInTheDocument();
4943    expect(screen.getByText('homarp')).toBeInTheDocument();
4944  });
4945});
4946\`\`\`
4947
4948Please take note that the API calls have already been mocked out in the previous section resulting in this test using the stubbed responses instead of the real API response. It is a straightforward test where the HackerNewsStories component is rendered first. Then, the fetch spy is expected to be called and it is called with the desired API URL.
4949
4950The first commented expect will fail if it is uncommented because initially when this component loads it does not show any stories. It will be showing the loading message. That is 
4950why you are using React Testing Library waitFor method. It will wait for the text The self-taught UI/UX designer roadmap (2021) to appear on the screen then expect it to be there. The test will do the same process for the username of \\\`homarp\\\`\\\`.
4951
4952The default interval for waitFor is 50 milliseconds (ms) and it has a default timeout of 1000 ms (1 second) as per its [documentation](https://testing-library.com/docs/dom-testing-library/api-async/#waitfor). In the above test, this means if the text is not found on the screen within 1 second it will fail with an error. That will not happen as the stubbed response will be received by the call in [70 milliseconds](https://github.com/geshan/hackernews-react/blob/master/src/HackerNewsStories.test.js#L37) or a bit more as you have set it in the wait in the fetch spy in the previous section.
4953
4954Another way to test for appearance can be done with [findBy queries](https://testing-library.com/docs/dom-testing-library/api-async/#findby-queries), for example, findByText which is a combination of getBy and waitFor. It is discussed in a bit more detail later. For example the following expect would have worked even without a waitFor:
4955
4956\`\`\`
4957expect(await screen.findByText('The self-taught UI/UX designer roadmap (2021)')).toBeInTheDocument();
4958\`\`\`
4959
4960When writing tests do follow the [frontend unit testing best practices](https://meticulous.ai/blog/frontend-unit-testing-best-practices/), it will help you write better and maintainable tests. In the subsequent section, you will learn how to test for the loading message to disappear as the stories are loaded from the API.
4961
4962## Testing for the Loading Message to Disappear
4963
4964Similar to testing an element that has appeared on the screen after the execution of a dependent asynchronous call, you can also test the disappearance of an element or text from the component. In the context of this small React.js application, it will happen for the div with the loading message. As seen above in the image, the div with the loading message will show up for a split second (or less depending on the network [speed](https://www.meticulous.ai/blog/testing-pyramid-for-frontend) and how fast the API responds) and disappear if the API response is received without any problem.
4965
4966This scenario can be tested with the code below:
4967
4968\`\`\`
4969test('should show and then hide the loading message when stories load', async() => {
4970  render(<HackerNewsStories />);
4971  const loadingText = screen.getByText('HackerNews frontpage stories loading...');
4972  expect(loadingText).toBeInTheDocument();
4973  expect(windowFetchSpy).toHaveBeenCalled();
4974
4975  // await waitFor(() => {
4976  //   const loadingText = screen.queryByText('HackerNews frontpage stories loading...');
4977  //   expect(loadingText).not.toBeInTheDocument();
4978  // });
4979
4980  await waitForElementToBeRemoved(() => screen.getByText('HackerNews frontpage stories loading...'), {timeout: 75});
4981});
4982\`\`\`
4983
4984As seen above, you have rendered the HackerNewsStories component first. Then, an expect assertion for the loading message to be on the screen. After that, an expect assertion for the fetch spy to have been called. Testing for an element to have disappeared can be done in two ways. The first way is to put the code in a waitFor function. With this method, you will need to grab the element by a selector like the text and then expect the element not to be in the document. This first method is commented out in the above test where the element is queried by text.
4985
4986Another way to do it is with waitForElementToBeRemoved which is a convenience over the waitFor method discussed above. With this shortcut method, it can be done in a single line as seen above. The element is grabbed with getByText and as waitForElementToBeRemoved returns a promise, an await is added to make that the given element is no longer on screen.
4987
4988An important detail to notice here is you have passed a timeout of 75 milliseconds which is more than the set 70 milliseconds on the stub. This is important as the stub will respond in 70 milliseconds, if you set the timeout to be less than 70 this test will fail.
4989
4990Congrats! You have written tests with both waitFor to test an element that appears on screen and waitForElementToBeRemoved to verify the disappearance of an element from the component. In the next section, you will learn more about the useful findBy method to test async code with React Testing Library.
4991
4992## More Usage of the findBy Method
4993
4994The findBy method was briefly mentioned in the above section about the stories appearing after the async API call. It can be used to deal with asynchronous code easily. As mentioned it is a combination of getBy and waitFor which makes it much simpler to [test c
4994omponents](https://meticulous.ai/blog/testing-react-components-with-storybook/) that don’t appear on the screen up front. These components depend on an async operation like an API call.
4995
4996To see more usage of the findBy method you will test that the sorting of the Hacker News stories by points where the maximum points appear on top works as expected. For this you will write a test as follows:
4997
4998\`\`\`
4999test('should show stories sorted by maximum points first', async() => {
5000  render(<HackerNewsStories />);
5001  expect(windowFetchSpy).toHaveBeenCalled();
5002
5003  const stories = await screen.findAllByRole('heading', {level : 3});
5004  expect(stories).toHaveLength(2);
5005  expect(stories[0]).toHaveAccessibleName('The self-taught UI/UX designer roadmap (2021) - By homarp (253 points)');
5006  expect(stories[1]).toHaveAccessibleName('TikTok tracks you across the web, even if you don’t use the app - By bubblehack3r (123 points)');
5007});
5008\`\`\`
5009
5010In the above test, first, the HackerNewsStories component is rendered. Then the fetch spy is expected to be called. After that, in the stories const the H3 elements are fetched. It is expected that there will be 2 stories because the stubbed response provides only 2. In the stubbed response, the story with [123 points](https://github.com/geshan/hackernews-react/blob/master/src/HackerNewsStories.test.js#L19) appears above the story with [253 points](https://github.com/geshan/hackernews-react/blob/master/src/HackerNewsStories.test.js#L27). As per the [sorting logic](https://github.com/geshan/hackernews-react/blob/master/src/HackerNewsStories.js#L13) in the component, the story with 253 points should come first then the story with 123 points. That is the expected output as the first story story \\[0\\] is the one with 253 points.
5011
5012The important part here is waitFor is not used explicitly. In place of that, you used findByRole which is the combination of getBy and waitFor. So the H3 elements were pulled in as they became visible on screen after the API responded with a stub’s delay of 70 milliseconds. There was no use of any explicit timeout but the test still passed verifying the expected behavior.
5013
5014## Conclusion
5015
5016In this post, you learned about the asynchronous execution pattern of JavaScript which is the default one. Then you were introduced to the HackerNews React.js application that fetches the latest front page stores of HackerNews using the API provided by Algolia. After that, you learned about various methods to test asynchronous code using React Testing Library like waitFor and findBy.
5017
5018You can write a test for asynchronous code even without using waitFor by utilizing the other helper functions like findBy and waitForElementToBeRemoved. These helper functions use waitFor in the background.
5019
5020In the process, you also mocked the API call with a stub injected with Jest’s spyOn helper and a fake wait of 70 milliseconds. This guide has helped you understand how to test any React component with async code. As a reminder, all the code is available in this [GtiHub repository](https://github.com/geshan/hackernews-react). Carry on writing those tests, better tests add more confidence while shipping code!
5021
5022## Meticulous
5023
5024Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
5025
5026This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
5027
5028This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
5029`,W4=`---
5030title: "Is Selenium Worth it in 2024? | In-Depth Analysis"
5031slug: "is-selenium-worth-it"
5032date: "2024-01-16"
5033description: "An overview of Selenium and a discussion on whether it's worth it relative to the other tools available."
5034author: "Elly Obare"
5035authorImage: "/images/blog/authors/65690c31a516e88cfaf546ff_Elly-Obare.jpeg"
5036heroImage: "/images/blog/is-selenium-worth-it/656a184be362241a18e5fa84_is-selenium-worth-it.png"
5037heroImageAlt: "Is Selenium Worth it in 2024? | In-Depth Analysis"
5038categories: ["Resources"]
5039showTableOfContents: true
5040showPromo: true
5041---
5042
5043End-to-end testing has become increasingly popular, and can be achieved by many different tools and approaches, with different degrees of effectiveness and varying levels of effort required for implementation. Tools like Selenium represent one of the most popular methods of _end-to-end testing_, but Selenium and similar tools can be challenging to implement and maintain.
5044
5045In this article, you’ll learn the pros and cons of end-to-end testing using Selenium, and tools like it, to help you decide if Selenium is worth the effort, or if you need to consider a less burdensome alternative.
5046
5047## What is End-to-End Testing
5048
5049End-to-end testing is a practice in the software testing life cycle that keeps the usability, functionality, integration, performance, and security requirements of an application in check, from development to production. This testing verifies that the software and its subsystems all work as expected. End-to-end testing simulates user-like interactions from the start of a workflow to the end.
5050
5051A good starting point for end-to-end testing is to perform it manually, this can later be supplemented by automated scripts and test suites. These manual tests could simply involve manually exploring an application from the perspective of a consumer. This often leads to the discovery of unexpected defects and bugs, including those that may have been missed by automated tests.
5052
5053## What is Selenium
5054
5055Selenium is among the leading tools for end-to-end testing. It is an open source automation framework that’s used to test many common browsers and cloud platforms. Selenium supports writing test scripts in several programming languages such as Java, Ruby, Python, and JavaScript. Selenium doesn’t work as a single tool, but as an integration of tools, including Selenium WebDriver (collection of APIs for testing web apps across browsers), Selenium Grid ( server that facilitates parallel running of scripts in remote machines), and Selenium IDE (a browser plugin for recording and replaying tests).
5056
5057Selenium enables automation engineers the ability to write browser-level automated regression test scripts that are easy to scale across multiple environments and browsers. Through Selenium's WebDriver API, automation engineers can write scripts that mimic a user's interaction with web browsers, including those accessed from mobile devices.
5058
5059End-to-end tests using Selenium manipulate the [Document Object Model (DOM)](https://www.w3schools.com/js/js_htmldom.asp) to verify what elements are displayed to users whilst simulating user input and interaction, i.e. simulating a user journey in an application. For instance, an end-to-e
5059nd test scenario for an e-commerce platform might include a script that verifies sign-up, sign-in, a product search, add to cart functionality, product review views, and checkout logic.
5060
5061A simple login automation script can be as seen below: The script will load the page and sign up as a normal user would do.
5062
5063\`\`\`
5064import org.openqa.selenium.By;
5065import org.openqa.selenium.WebDriver;
5066import org.openqa.selenium.firefox.FirefoxDriver;
5067import org.testng.annotations.Test;
5068import java.time.Duration;
5069
5070//Simple login automation
5071    public class HelloSeleniumTest {
5072        @Test
5073        public void firstTest() {
5074
5075        //Set and Initialize the web driver and browser specification
5076            System.setProperty("webdriver.gecko.driver","/replace/with/path/ of your driver”
5077            WebDriver driver=new FirefoxDriver();
5078
5079            //Define the page address to be automated and access it through the firefox driver
5080            String url = "https://www.amazon.com/";
5081            driver.get(url);
5082
5083            //Set wait time for page
5084            driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(20));
5085
5086            //Locate the user name field and send keys to it
5087            driver.findElement(By.id("user-name")).sendKeys("standard_user");
5088
5089            //Locate the password field and send keys to it
5090            driver.findElement(By.id("password")).sendKeys("secret_sauce");
5091
5092            //Locate the submit button and act on it.
5093            driver.findElement(By.id("login-button")).click();
5094        }
5095    }
5096\`\`\`
5097
5098However, end-to-end testing can be expensive, given how long it takes to write and maintain the tests. To offset this expense, such tests are typically created in smaller, more focused quantities, as described by the [testing pyramid](https://www.meticulous.ai/blog/testing-pyramid-for-frontend).
5099
5100## The Testing Pyramid
5101
5102The software testing pyramid is a model of grouping software tests into categories that follow a pyramid model. The higher up the pyramid you go, the more expensive testing is, and thus, fewer tests of that type should be created.
5103
5104![Software testing pyramid](/images/blog/is-selenium-worth-it/65690bfe0c4f529ecdeff936_software-testing-pyramid.jpeg)
5105
5106Building test suites following this model typically helps teams find a good balance between the different types of tests that they need for their applications, with lots of cheap, isolated tests, and fewer expensive, deeply integrated tests.
5107
5108Let's break down the pyramid:
5109
5110- Unit testing: This forms the bottom of the pyramid. [Unit tests](https://www.meticulous.ai/blog/frontend-unit-testing-best-practices) validate small pieces of code to ensure that individual units of code work correctly in isolation.
5111- Integration testing: Next, to test how your code units interact with other code units to build a functional component, integration tests are needed. Integration tests validate the interaction of a code unit with other external components and dependencies. For example, when a connection to an API or web service to retrieve certain data is made, integration testing can be done to check if the endpoints communicate correctly and if the exact data is returned as desired.
5112- End-to-end testing: This is located at the top layer of the pyramid; it involves running tests that invoke the entire system from start to finish, with minimal mocked components. These tests are the most realistic in terms of accuracy when compared to actual user interactions with the application. Due to the ongoing maintenance and time required to set them up and run them, they’re generally expensive and thus placed higher up on the pyramid.
5113
5114End-to-end tests are typically built to resemble actual workflows that happen as part of the user’s journey through the application. They should simulate the kind of interactions that the users will put the application through in real life, and verify that there are no unexpected issues or breakages in these workflows.
5115
5116In practice, creating end-to-end tests often involves using a tool like Selenium to control a web browser to programatically interact with the application under test in the same way a user would. When writing the test, you would use selectors to identify elements and DOM nodes that you want to interact with and subsequently execute commands to manipulate these elements and finally use assertions to verify that things happened as expected. You can see the login automation script above as an example of this. Learn more about the pyramid [here](https://meticulous.ai/blog/testing-pyramid-for-frontend/).
5117
5118## Alternatives to Selenium
5119
5120Many other tools can be used in place of Selenium to achieve end-to-e
5120nd testing. Here are some alternative tools for testing modern web applications.
5121
5122## Cypress
5123
5124[Cypress](https://www.cypress.io/) is a fast, all-in-one testing framework for creating end-to-end tests that will run on any browser-based application. Cypress also presents an assertion library, with [spies and stub capabilities](https://docs.cypress.io/guides/guides/stubs-spies-and-clocks#Capabilities).
5125
5126Test scripts written in Cypress are simple to understand and execute. Automation engineers who use Cypress get a rich set of APIs, as well as other open source tooling around Cypress, which can make testing relatively straightforward.
5127
5128Cypress is easy to set up and relatively inexpensive to integrate into any QA culture. It also offers some important features like automatic waiting and time travel debugging, along with screenshot recording of the test process and results.
5129
5130## Playwright
5131
5132[Playwright](https://playwright.dev/) is another popular tool for performing end-to-end tests for modern web apps. This tool allows QAs to execute end-to-end tests in parallel across major browsers.
5133
5134Through its auto-wait and introspection feature, robust end-to-end tests can be created, since app elements and features are waited for implicitly before test actions are performed on them. This makes test code much cleaner and easier to work with.
5135
5136Playwright features powerful and built-in tools such as Test Generator (creating tests from recorded user actions), Playwright Inspector (scanning web pages for element locators and click points) and Trace Viewer (tracing point for failed tests with live DOM snapshots, action explorer and more), all of which help testers generate powerful end-to-end tests.
5137
5138## Challenges in End-to-End Testing
5139
5140Though end-to-end testing is a reliable way to ensure that your application works exactly as it should from development to production, there are some significant challenges in implementing this method.
5141
5142## Hard-to-Maintain Tests
5143
5144A major drawback of using a tool like Selenium for end-to-end testing is that it can be difficult to maintain test scripts and avoid flaky tests whenever applications are re-engineered. A Selenium test script written in a tightly coupled manner is likely to break whenever there are changes in the implementation of the application.
5145
5146A change in the web app would require the test scripts to be rewritten, especially scripts pointing to elements' identifiers or paths. These back-and-forth changes in scripting result in slow, expensive, and hard to maintain end-to-end test suites, as testers try to keep failures on track, fix the breakages, and rerun them in a cycle.
5147
5148## No Built-in Reporting Features
5149
5150Working with Selenium WebDriver requires the integration of tools like JUnit or TestNG for the generation of test reports, as Selenium lacks a built-in structured reporting feature. Selenium also has a steep learning curve when building end-to-end test suites for large-scale applications.
5151
5152These inherent difficulties mean that sometimes it can be easier for an organization to use an alternative approach for end-to-end testing that can provide many of the benefits without the challenges (_e.g._ [replay testing](https://www.meticulous.ai/blog/let-users-write-tests-for-you)).
5153
5154## Replay Testing with Meticulous
5155
5156[Replay testing](https://meticulous.ai/blog/let-users-write-tests-for-you/) is a technique for performing tests on web applications using little or no code (codeless testing). This technique involves the use of a well-established platform with features that record performed actions in the browser and then replaying those actions on the new version of the web application.
5157
5158Meticulous is a tool to easily create end to end tests without writing code and without requiring a staging environment. Use the CLI to open an instrumented browser which records your actions as you execute a workflow on your web app. This sequence of actions can then be replayed on any URL, like a new version of your web app on localhost. Meticulous captures a screenshot at the end of each replay. Screenshots can then be diffed in order to create a simple test. Meticulous makes it easy to integrate these tests into your CI (we'll even do it for you if you add us as a collaborator).
5159
5160## Combining Replay Testing and End-to-End Testing
5161
5162While replay testing (with Meticulous) and tools like Selenium have different approaches to end-to-e
5162nd testing, the two can be used together to benefit an organization needing quality tests.
5163
5164Meticulous can step in to fill the gaps of Selenium tests, especially when an organization is transitioning from manual testing to automation testing. Also, at the initial stages of end-to-end testing, Meticulous can be used for any lightweight automation, and as user workflows become complex, Selenium can be incorporated to help create more robust test suites.
5165
5166Moreover, the two tools provide the ability to execute tests in parallel, thus accelerating test coverage for your applications while comparing results achieved in each case.
5167
5168## Benefits of Replay Testing Compared to End-to-End Testing
5169
5170Some of the benefits available to QAs Meticulous replay testing include:
5171
5172- No maintenance burden: Writing and maintaining large codebases of tests without proper project structure and quality standards can be time-consuming and require skills. Meticulous makes it easy to reset the new 'reference' baseline image or replay with a single click.
5173- Easy to record: Initiating replay testing simply starts with a click of a button to start the recording. Once the recording is stopped, a test engineer can replay those flows on new frontend code and make alterations and additions to their scripts if desired. This means that your tests require less effort to create.
5174
5175## Drawbacks of Replay Testing Compared to End-to-End Testing
5176
5177Some drawbacks of replay testing include:
5178
5179- Does not use the real backend: Many record-and-playback platforms are largely procedural (following exact steps previously recorded), which means that any slight change in the application being tested will require all previous tests to be re-recorded. The procedural pattern of testing is therefore only suitable for frontend regression scenarios, making this type of testing ineffective for complex applications that are data-driven (restricting test scope). Though Meticulous doesn’t use the real backend by default, you should note that you can configure it to replay against a backend if needed.
5180- Cost factor: Feature-rich replay platforms aren’t open-source, making them relatively expensive. Most require subscriptions and frequent billing management. There are some free options, but they ordinarily have extremely constrained features.
5181
5182## Conclusion
5183
5184Different organizations will use varied approaches and tools when performing end-to-end testing. This poses a concern in having a stable framework in place to guide the QA team in establishing simple to understand, fast, and scalable test scripts for any application being tested.
5185
5186Having seen all that’s required for end-to-end testing using Selenium or record and replay, it’s clear that Selenium automation requires some level of technical know-how, as well as maintenance of test scripts and the test environment.
5187
5188It would therefore be ideal for an organization to consider a less burdensome alternative platform like Meticulous for recording user sessions and replaying them in CI environments to catch regressions in any of its applications.
5189
5190In this article, you learnt about end-to-end testing with Selenium as the tool of focus. You also saw some of the pros and cons of end-to-end testing using Selenium and other tools like it.
5191
5192## Meticulous
5193
5194Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
5195
5196This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
5197
5198This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
5199`,Q4=`---
5200title: "JavaScript Reference Errors | How to Prevent Them, Tutorial"
5201slug: "javascript-reference-errors"
5202date: "2024-01-16"
5203description: "The complete guide on JavaScript ReferenceErrors, how they can occur, and techniques and tools available to prevent them."
5204author: "David Herbert"
5205authorImage: "/images/blog/authors/65690e0f652302dc9edba136_david-herbert.jpeg"
5206heroImage: "/images/blog/javascript-reference-errors/65690cb409fa6cc6639ce639_javascript-reference-errors.webp"
5207heroImageAlt: "JavaScript Reference Errors | How to Prevent Them, Tutorial"
5208categories: ["JavaScript"]
5209showTableOfContents: true
5210showPromo: true
5211hidden: true
5212---
5213
5214When programming in JavaScript, you’ll commonly encounter reference errors. There are a number of reasons why you might get one, but fortunately, resolving such an error isn’t too difficult. It’s helpful to know what this error is and why it’s being thrown by the compiler because you’ll save yourself precious debugging time.
5215
5216In this guide to JavaScript reference errors, you’ll learn how to identify and s
5216olve them as well as how to avoid this type of error in the future.
5217
5218## What is a Reference Error
5219
5220In JavaScript, when you create or declare a variable, what you are doing is simply creating a reference to an object with an associated value. Here’s an example:
5221
5222\`\`\`
5223     const a = "Apple"
5224\`\`\`
5225
5226The previous code tells the JavaScript compiler that whenever it sees the variable a, it should interpret it as its value—meaning, it should see a as the string with the value of Apple. When JavaScript encounters a variable that doesn’t have a declaration, though, it doesn’t know what to do, so it throws a reference error.
5227
5228In essence, a reference error occurs when JavaScript tries to access a variable that doesn’t exist, hasn’t been defined, or doesn’t exist in the current scope from which you are trying to access it.
5229
5230## Why You Need to Understand Reference Errors
5231
5232It’s important that you understand why JavaScript might throw such an error, especially for the following reasons:
5233
5234- If you understand what’s going on and why, the error is easier and quicker to fix.
5235- It is possible to catch this type of error at build time if your code is properly structured.
5236- If you spend less time trying to understand why the error occurred, you can spend more time on programming, which improves your workflow.
5237
5238## Types of Reference Errors
5239
5240There are a number of different types of reference errors, which can be triggered in different ways. The [MDN JavaScript documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors) specifies six different types of reference errors that can be triggered within your code, but this guide will focus on the four most important types.
5241
5242## Undeclared or Undefined Variable
5243
5244When a variable reference cannot be found or is not declared, it causes a reference error. New developers are most likely to commit reference errors by forgetting to define variables before referencing them. Here’s an example:
5245
5246\`\`\`
5247    console.log(a)
5248    // ReferenceError a is not defined.
5249\`\`\`
5250
5251Similarly, declared variables that have been commented out may also cause reference errors when used, like this example:
5252
5253\`\`\`
5254  // const a = "apple"
5255     console.log(a)
5256  // ReferenceError a is not defined.
5257\`\`\`
5258
5259The solution to this reference error is to ensure that your variables are always declared before using them, as in the following example:
5260
5261\`\`\`
5262    let a = 'apple'
5263    console.log(a)
5264    // apple
5265\`\`\`
5266
5267## Wrong Scope
5268
5269In order for a variable to be available during execution, it must be present in the scope of that execution context—meaning, the variable has to be in the same scope or in a parent scope to be accessible by JavaScript. For example, variables defined inside a function are only accessible inside the function and cannot be accessed outside of it, as shown subsequently:
5270
5271\`\`\`
5272    function inScope() {
5273      let a = "apple";
5274    }
5275    console.log(a);
5276    // ReferenceError a is not defined.
5277\`\`\`
5278
5279To avoid this type of reference error, make sure that the scope from which you’re trying to access a variable is the scope in which the variable exists:
5280
5281\`\`\`
5282    function inScope() {
5283      let a = "apple";
5284      console.log(a);
5285    }
5286    inScope()
5287    // apple
5288\`\`\`
5289
5290Additionally, note that the let and const keywords are both block level scoped, while the var keyword is instead globally scoped when used outside a function and function scoped when used inside a function. This can cause certain reference errors when these keywords are used incorrectly.
5291
5292## Strict Mode (Silent Errors)
5293
5294When you assign a value to a variable, JavaScript requires that the variable be declared using var, let, or const. If you use JavaScript in its default mode (without strict mode), declaring a variable without these keywords will result in a silent error—meaning, JavaScript will ignore it even though it is an error. Here’s an example:
5295
5296\`\`\`
5297    a = 'apple'
5298    console.log(a)
5299    // apple,
5300\`\`\`
5301
5302In strict mode, though, JavaScript will catch this error and throw a reference error:
5303
5304\`\`\`
5305    "use strict";
5306    a = "apple";
5307    console.log(a);
5308    // ReferenceError: a is not defined
5309\`\`\`
5310
5311To avoid silent errors, always use JavaScript in strict mode so as not to miss similar errors that are typically ignored in JavaScript’s default (or sloppy) mode.
5312
5313## Accessing of a Variable Before its Declaration
5314
5315Another way of triggering a reference error is by trying to access a variable declared with let or const before its actual declaration. This will throw a reference error because the variable hasn’t yet been initialized or read by JavaScript, as shown in the following code:
5316
5317\`\`\`
5318    console.log(a);
5319    let a = "apple";
5320    // ReferenceError: Cannot access 'a' before initialization
5321\`\`\`
5322
5323As you may have guessed, the solution is to ensure that a variable is always declared first before using so that JavaScript has access to it:
5324
5325\`\`\`
5326    let a = "apple";
5327    console.log(a);
5328    // apple
5329\`\`\`
5330
5331## How to Fix Reference Errors
5332
5333You have several options for addressing reference errors, including the following:
5334
5335- Code editors and IDEs with syntax highlighting might be able to display such issues.
5336- Certain tools, such as [ESLint](https://eslint.org/), can detect specific patterns and enforce them.
5337- [TypeScript](https://www.meticulous.ai/blog/safer-exhaustive-switch-statements-in-typescript) can identify reference errors when the code compiles and provide recommendations on how to fix them.
5338- [Static analysis](https://en.wikipedia.org/wiki/Static_program_analysis) can catch this type of error in your continuous improvement (CI) pipeline before your code is released to production.
5339
5340## How to Prevent Reference Errors
5341
5342There are several best practices to follow to keep reference errors out of your projects:
5343
5344- Use extensions that aid with [IntelliSense or code completion](https://en.wikipedia.org/wiki/Intelligent_code_completion), such as auto-suggestions, code hinting, and query hints.
5345- Use strict mode in JavaScript so that you won’t miss silent errors in sloppy mode.
5346- Avoid using the var keyword when declaring variables and, instead, use let or const.
5347- Make sure that variables are always declared before calling them.
5348
5349## How Meticulous can Help
5350
5351Errors and [bugs](https://www.meticulous.ai/blog/testing-pyramid-for-frontend) can still sneak through even the most diligently written code. To prevent disruption to your application, you need tools and techniques to identify and prevent these errors before they hit production.
5352
5353One tool is [Meticulous](https://www.meticulous.ai/), a tool for software engineers to catch visual regressions in web applications without writing or maintaining [UI tests](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices).
5354
5355Inject the Meticulous snippet onto production or staging and dev environments. This snippet records user sessions by collecting clickstream and network data. When you post a pull request, Meticulous selects a subset of recorded sessions which are relevant and simulates these against the frontend of your application. Meticulous takes screenshots at key points and detects any visual differences. It posts those diffs in a comment for you to inspect in a few seconds. Meticulous automatically updates the baseline images after you merge your PR. This eliminates the setup and maintenance burden of UI testing.
5356
5357Meticulous isolates the frontend code by mocking out all network calls, using the previously recorded network responses. This means Meticulous never causes side effects and you don’t need a staging environment.
5358
5359Learn more [here](https://meticulous.ai/).
5360
5361## Conclusion
5362
5363Reference errors can slow down your JavaScript workflow, especially if you’re not aware of what those errors are or how to solve them. Fortunately, there are simple solutions to these errors, but the best strategy is to be aware of what triggers them so that you can avoid making these errors in your code.
5364
5365Since mistakes still happen sometimes, you can take additional precautionary measures by choosing a code editor or IDE with intelligent highlighting and support, implementing ESLint to enforce specific patterns, using TypeScript for type checking, or using an automated error handling tool, like [Meticulous](https://meticulous.ai/). The more proactive you are about handling errors, the more you can optimize your team’s productivity.
5366`,$4=`---
5367title: "JavaScript Regressions and How to Prevent Them"
5368slug: "javascript-regressions-and-how-to-prevent-them"
5369date: "2024-01-16"
5370description: "An exploration of why JavaScript regressions are important and an overview of automated testing techniques to prevent their occurrence."
5371author: "James Walker"
5372authorImage: "/images/blog/authors/65690ef1db4eb7f27e6ffc40_james-walker.jpeg"
5373heroImage: "/images/blog/javascript-regressions-and-how-to-prevent-them/656f161ba3e74485ffc5236f_javascript-regressions-and-how-to-prevent-them.png"
5374heroImageAlt: "JavaScript Regressions and How to Prevent Them"
5375categories: ["JavaScript"]
5376showTableOfContents: true
5377showPromo: true
5378---
5379
5380What’s worse than a software bug? How about a bug that comes back to haunt you in the future after you’ve declared it fixed? That’s often what regressions are. They’re a painful reality that most development teams encounter.
5381
5382Regressions are a common source of frustration for users, developers, and managers alike. Regular regressions are often symptomatic of flaky systems that are unreliable. They’re also an indicator of a broken development process that’s incapable of verifying the effects of supposed fixes.
5383
5384In this article, you’ll explore the topic of regressions in [JavaScript](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices) applications. You’ll learn why you should prioritize regressions to guarantee user satisfaction. Then you’ll look at automated testing techniques that help to prevent their occurrence.
5385
5386## Why Regressions Matter
5387
5388Software regressions are bugs wherein previously functioning components stop working correctly. The system has regressed to a less stable state, so it’s no longer fulfilling behavioral expectations.
5389
5390Regressions can be introduced to a codebase in several ways:
5391
5392- Adding a new feature may have unforeseen impacts on existing components, causing them to stop working.Fixing one problem which unintentionally creates a new one or the fix itself contains a bug
5393- Changing fundamental data structures which causes components to receive data they don’t know how to handle
5394- Modifying tightly coupled components may have side effects in completely separate subsystems.
5395
5396Regressions are a particularly thorny class of bugs. Because they only impact existing features, they can slip unnoticed past developers and QA teams. Users might be the first to spot the problem, putting you at an immediate disadvantage before you’ve started an investigation.
5397
5398Having users find your bugs is a surefire way to damage their confidence in your product. Software that unexpectedly breaks creates a highly negative user experience that makes it difficult to establish trust. It’s particularly impactful when the regression concerns a previous bug; for example, if you’ve told a customer that a problem’s been fixed, they won’t be impressed when it later reappears!
5399
5400## Regressions in JavaScript
5401
5402Regressions in JavaScript applications stem from the same roots as those listed earlier. However, JavaScript code can be particularly susceptible to regressions. The language’s dynamic typing means errors can easily be masked during development. For example, you won’t get an immediate error if you pass a variable that’s assigned an integer to a function expecting a string.
5403
5404Consequently, if the function expects passed-in values to have certain types, changes to code inside functions can have knock-on impacts across the application. Failure to supply that type each time the function’s called can lead to crashes that don’t show up until runtime.
5405
5406In addition, JavaScript applications often tend to be visual in nature. This creates a whole new kind of regression, where the code still works correctly but the application looks different. Visual regressions can be especially hard to spot when they occur under specific conditions, such as a button that’s sized incorrectly on tablet-sized screens but only when its neighboring controls are disabled.
5407
5408## Avoiding Future Regressions
5409
5410Protecting your codebase with a comprehensive test suite is the only reliable way to defend against regressions. [Automated tests](https://www.meticulous.ai/blog/testing-pyramid-for-frontend) give you confidence your software is performing consistently, and running your tests as part of your deployment pipeline ensures changes can’t merge into production if they’d break existing code.
5411
5412Most regressions occur in larger codebases with low test coverage scores. If you have poor coverage, it’s difficult to assess the impact of bug fixes and new features. Even if you don’t have many tests yet, it’s important to add one each time you fix an issue. This will help prevent the problem from recurring in the future.
5413
5414However, even automated testing isn’t infallible; some regressions can still occur, regardless of the size of your test suite. The quality of your tests matters as much as their quantity. It’s important to write tests that probe every possible behavior of your application, including any rare edge cases.
5415
5416Take a look at four test types you can use in your JavaScript applications to catch regressions quickly:
5417
5418## End-to-End Tests
5419
5420[End-to-end (E2E) tests](https://circleci.com/blog/what-is-end-to-end-testing/) target the entirety of the system from beginning to end. These tests target the project’s code, it's integrations with external services, and the user experience that’s created.
5421
5422E2E tests validate that complete user flows are achievable inside your application. Here are two examples you could set up:
5423
5424- Validating a user can view the sign-up form, enter their credentials, and create a new account that’s saved in your database.
5425- Checking a user can press Checkout, receive a charge to their payment card, and be sent an order confirmation email.
5426
5427However, E2E tests are often time-consuming to write, run, and maintain. Setting up an E2E environment can be complicated, and test suites often take a long time to run. Sometimes E2E tests might only be used immediately before deployment. This improves iteration speed while developing a change but allows issues to go unnoticed until the last minute.
5428
5429[Cypress](https://www.cypress.io/) and [Selenium](https://www.selenium.dev/) are two popular E2E testing options for JavaScript. Learn more about Selenium [here](https://meticulous.ai/blog/is-selenium-worth-it/). They let you automate web browser windows to validate that your application behaves as expected.
5430
5431## Unit Tests
5432
5433[Unit tests](https://www.meticulous.ai/blog/frontend-unit-testing-best-practices) are the easiest, fastest, and most convenient form of automated testing. They target individual code components on a function-by-function basis.
5434
5435A unit test should cover every possible pathway through its subject piece of code. It will look at the section in isolation without any awareness of your system’s other components or its outer environment. Unit tests work best when your functions are pure, meaning they always produce the same output for a particular set of parameters—but you can mock external dependencies and global states, too.
5436
5437[Jest](https://jestjs.io/), [Mocha](https://mochajs.org/), and [Jasmine](https://jasmine.github.io/) are the three most popular JavaScript unit test frameworks. They each offer a battery-included experience that makes it easy to get started. Because unit tests should be lightweight, they can be executed by individual developers while the software is being written as well as within your CI pipelines.
5438
5439## Integration Tests
5440
5441[Integration tests](https://www.codecademy.com/resources/blog/what-is-integration-testing) sit between unit tests and E2E tests. They assess whether units are calling each other correctly to create higher-level functionality.
5442
5443You normally create integration tests within the same framework you’re using for unit tests. The difference lies in what’s being tested and the extent to which the outer environment is mocked.
5444
5445Integration tests consider the communication paths between individual components in your system. Whereas unit tests routinely mock external dependencies, integration tests should use real ones that match the requirements of production. This lets you uncover issues involving cross-component data flows, config stores, caches, and third-party APIs.
5446
5447Integration testing gives you confidence that your code units work together properly. They can be cumbersome to maintain, though; you need to remember to update all affected integration tests whenever you change one of your components.
5448
5449## Replay Tests with Meticulous
5450
5451Replay tests focus on detecting regressions in your application’s frontend. These tests have traditionally been tricky to orchestrate, so they may have been handled as part of your E2E testing. As mentioned previously, E2E testing often comes late in the development lifecycle, making it less likely that regressions will be detected with time to resolve them.
5452
5453The [Meticulous](https://meticulous.ai/) platform performs frontend testing without making you write code or configure complex environments. Replay tests work by comparing a pre-recorded workflow against the current version provided by your code. Meticulous then diffs the two sessions to identify regressions, including visual discrepancies.
5454
5455You record tests by using the Meticulous CLI to open a new browser window. Interact with the web page as usual, and Meticulous will capture your activity and save it to a new session in your account. You can replay the session against your local development environment after you’ve made changes to your code. Meticulous will spot any discrepancies and alert you of its findings.
5456
5457[Replay testing](https://www.meticulous.ai/blog/let-users-write-tests-for-you) is a great way to reliably uncover visual issues and subtle state changes that would otherwise go unnoticed. Recording workflows in a real in-app session is quicker and easier than manually writing code to automate the browser.
5458
5459One drawback of this approach is that replays don’t automatically target your real backend. Meticulous mocks the network calls made by your sessions. This is often beneficial; for example, replays won’t have any side effects, but it prevents you from performing E2E-like tests where backend interactions need to be included, too. However, you can accommodate these workflows by manually configuring Meticulous to make real API requests against your staging environment.
5460
5461## Conclusion
5462
5463Regressions are a type of software bug where existing functionality ends up broken as a result of a new change. While they can affect any project, they’re most common in codebase
5463s that have poor test coverage. This allows changes in one part of the system to have negative impacts on other areas without the developer being informed.
5464
5465Addressing regressions should be your top priority when they occur. Broken features that used to work erode user confidence and create a lasting impression of a poor-quality product. Each time you resolve a regression, add a test to make sure the code doesn’t silently break again in the future. On a long-term basis, writing a mix of E2E, unit, and integration tests is the best way to ensure stability in your system.
5466
5467JavaScript applications often house unique kinds of regression due to the visual nature of web applications. You can uncover discrepancies in frontend work by using [Meticulous](https://meticulous.ai/) to add replay tests alongside your existing test suite. Replays let you record expected workflows and compare them against new sessions. Meticulous automatically spots the differences so you can resolve them before they reach production.
5468
5469## Meticulous
5470
5471Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
5472
5473This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
5474
5475This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
5476`,X4=`---
5477title: "JavaScript Try Catch | Complete Guide & Tutorial"
5478slug: "javascript-try-catch-guide"
5479date: "2024-01-16"
5480description: "A guide on the try-catch functionality available in JavaScript, it's quirks and tips on using this most effectively."
5481author: "Rhuan Souza"
5482authorImage: "/images/blog/authors/65691077aff83d28a9084929_rhuan-souza.jpeg"
5483heroImage: "/images/blog/javascript-try-catch-guide/65690f3f9c90ec66aff3064a_javascript-try-catch-guide.webp"
5484heroImageAlt: "JavaScript Try Catch | Complete Guide & Tutorial"
5485categories: ["JavaScript", "Tutorial"]
5486showTableOfContents: true
5487showPromo: true
5488---
5489
5490In any software development project, handling errors and exceptions is vital to the success of your application. Whether you’re a novice or an expert, your code can fail for various reasons, like a simple typo, upstream errors, or unexpected behavior from external services.
5491
5492Because there is always the possibility of your code failing, you need to prepare to handle that situation by making your code more robust. You can do this in multiple ways, but one common solution is by leveraging try-catch statements. This statement allows you to wrap a block of code to try and execute. If any errors occur during this execution, the statement will _catch_ them, and you can fix them quickly, avoiding an application crash.
5493
5494This guide will serve as a practical introduction to try-catch statements and will show you how you can use them to handle errors in [Javascript](https://www.javascript.com/).
5495
5496## Why You Need Try-Catch
5497
5498Before learning about try-catch statements, you need to understand error handling as a whole in [JavaScript](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices).
5499
5500When dealing with errors in JavaScript, there are several types that can occur. In this article, you’ll focus on two types: [syntax errors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError) and [runtime errors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error). Learn more about [syntax errors](https://www.meticulous.ai/blog/how-to-prevent-javascript-syntax-errors) in our other blog post.
5501
5502Syntax errors occur when you don’t follow the rules of a specific programming language. They can be detected by configuring a [linter](https://en.wikipedia.org/wiki/Linter), which is a tool used to analyze the code and flag stylistic errors or programming errors. For instance, you can use [ESLint](https://eslint.org/) to find and fix problems in your code.
5503
5504Following is an example of a syntax error:
5505
5506\`\`\`
5507console.log(;)
5508\`\`\`
5509
5510In this case, the error occurs because the syntax of the code is incorrect. It should be as follows:
5511
5512\`\`\`
5513console.log('Some Message');
5514\`\`\`
5515
5516A runtime error happens when a problem occurs while your application is running. For example, your co
5516de may be trying to call a method that doesn’t exist. To catch these errors and apply some handling, you can use the try-catch statement.
5517
5518## What is an Exception
5519
5520Exceptions are objects that signal a problem has occurred at the execution time of a program. These problems can occur when accessing an invalid array index, when trying to access some member of a null reference, when trying to invoke a function that does not exist, etc.
5521
5522For example, consider a scenario where your application relies on an upstream third-party API. Your code might handle responses from this API, expecting them to contain certain properties. If this API returns an unexpected response for whatever reason, it can possibly trigger a runtime error. In this case, you can wrap the affected logic in a try-catch statement and provide the user with an error message or even invoke some fallback logic rather than allow the error to crash your application.
5523
5524## How Does Try-Catch Work
5525
5526Simply put, a try-catch statement consists of two blocks of code—one prefixed with the try keyword and the other with the catch keyword—as well as a variable name to store the error object within. If the code inside the try block throws an error, the error will be passed to the catch block for handling. If it doesn’t throw an error, the catch block will never be invoked.
5527
5528Consider the following example:
5529
5530\`\`\`
5531try {
5532  nonExistentFunction()
5533} catch (error) {
5534  console.log(error); // [ReferenceError: nonExistentFunction is not defined]
5535}
5536\`\`\`
5537
5538In this example, when the function is called, the runtime will find that there is no such function and will throw an error. Thanks to the try-catch statement surrounding it, the error is not fatal and can instead be handled however you like. In this case, it is passed to console.log, which tells you what the error was.
5539
5540There is also another optional statement called the finally statement, which, when present, is always executed after both try and catch, regardless of whether catch was executed or not. It’s often used to include commands that release resources that may have been allocated during the try statement and may not have had a chance to gracefully clean up in the event of an error. Consider the following example:
5541
5542\`\`\`
5543openFile();
5544try {
5545   writeData();
5546} catch (error) {
5547   console.log(error);
5548} finally {
5549   closeFile();
5550}
5551\`\`\`
5552
5553In this contrived example, imagine a file handle is opened prior to the try-catch-finally statement. If something were to go wrong during the try block, it’s important that the file handle still be closed so as to avoid memory leaks or deadlocks. In this case, the finally statement ensures that regardless of how the try-catch plays out, the file will still be closed before moving on.
5554
5555Of course, wrapping your potentially error-prone code in try-catch statements is only one piece of the fault-tolerance puzzle. The other piece is knowing what to do with the errors when they are thrown.
5556
5557Sometimes it might make sense to display them to the user (typically in a more human-readable format), and sometimes you might want to simply log them for future reference. Either way, it helps to be familiar with the Error object itself so that you know what data you have to work with.
5558
5559## Error Object
5560
5561Whenever an exception is thrown inside the try statement, JavaScript creates an [Error object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) and sends it as an argument to the catch statement. Typically, this object has two main [instance properties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#instance_properties):
5562
5563- name: a string describing the type of error that occurred
5564- message: a more detailed description of what went wrong
5565
5566Some browsers also include other properties, like description or fileName, but these are not standard and should typically be avoided because they cannot be reliably used in all browsers. To see what sort of values these properties contain, consider the following example:
5567
5568\`\`\`
5569try {
5570   nonExistentFunction();
5571} catch (error) {
5572    const {name, message} = error;
5573    console.log({ name, message }) // { name: 'ReferenceError', message: 'nonExistentFunction is not defined' }
5574}
5575\`\`\`
5576
5577As mentioned earlier, the name property’s value refers to the [type of error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error#error_types) that occurred. The following is a non-exhaustive list of some of the more common types of errors:
5578
5579- ReferenceError is thrown when a reference to a non-existent or invalid variable or function is detected.
5580- TypeError is [thrown when a value](https://www.meticulous.ai/blog/javascript-typeerrors-and-techniques-to-prevent-them) is used in a way that is not compatible with its type, such as trying to call a string function on a number ((1).split(',');).
5581- SyntaxError is thrown when there is a syntax error in interpreting the code; for example, when parsing a JSON using trailing commas (JSON.parse('\\[1, 2, 3, 4,\\]');).
5582- URIError is thrown when an error occurs in URI handling;
5582 for example, sending invalid parameters in decodeURI() or encodeURI().
5583- [RangeError](https://www.meticulous.ai/blog/range-errors-and-how-to-prevent-them) is [thrown when a value](https://www.meticulous.ai/blog/javascript-typeerrors-and-techniques-to-prevent-them) is not in the set or range of allowed values; for example, a string value in a number array.
5584
5585All native JavaScript errors are extensions of the generic Error object. Based on this principle, you can also create your own error types.
5586
5587## Custom Errors
5588
5589Another keyword in JavaScript that is closely related to errors and error handling is throw. When you use this keyword, you're able to “throw” a user-defined exception. When you do this, the current function will cease execution, and whatever value you used with the throw keyword will be passed to the first catch statement in the call stack. If there are no catch statements to handle it, the behavior will be similar to a typical unhandled error, and the program will terminate.
5590
5591Throwing custom errors can be useful in more complex applications, as it affords you another avenue of flow control over the code. Consider a scenario where you need to validate some user input. If the input is deemed invalid according to your business rules, you do not want to continue processing the request. This is the perfect use case for a throw statement. Consider the following example:
5592
5593\`\`\`
5594// you can define your own error types by extending the generic class
5595class ValidationError extends Error {
5596  constructor(message) {
5597       super(message);
5598      this.name = 'ValidationError';
5599    }
5600}
5601// for demonstrative purposes
5602const userInputIsValid = false;
5603try {
5604    if (!userInputIsValid) {
5605        // manually trigger your custom error
5606        throw new ValidationError('User input is not valid');
5607   }
5608} catch (error) {
5609    const { name, message } = error;
5610    console.log({ name, message }); // { name: 'ValidationError', message: 'User input is not valid' }
5611}
5612\`\`\`
5613
5614Here, a custom error class is defined, extending the generic error class. This technique can be used to throw errors that specifically relate to your business logic, rather than just the default ones used by the JavaScript engine.
5615
5616## Should You Wrap Everything in a Try-Catch Statement
5617
5618Because errors will go up the call stack until they either find a try-catch statement or the application terminates, it might be tempting to simply wrap your entire application in one massive try-catch, or lots of smaller ones, so that you can enjoy the fact that your application will technically never crash again. This is generally not a good idea.
5619
5620Errors are a fact of life when it comes to programming, and they play an important role in your application’s lifecycle that shouldn’t be ignored. They tell you when something has gone wrong. As such, it’s important to use try-catch statements respectfully and mindfully if you want to deliver a good experience to your users.
5621
5622You should generally use try-catch statements anywhere you reasonably expect that an error is likely to occur. Once you’ve caught the error, however, you typically don’t want to just squash it. As mentioned previously, when an error is thrown, it means that something has gone wrong. You should take this opportunity to handle the error appropriately, whether that is displaying a nicer error message to the user in the UI or sending the error to your application monitoring tool (if you have one) for aggregation and later analysis.
5623
5624Typically, when wrapping code in a try-catch statement, you should only wrap code that is conceptually related to the error that you are expecting. If you have functions that are fairly small in size, this might mean that the entire body of the function is wrapped. Alternatively, you may have a larger function where all of the code in the body is wrapped, but spread across multiple try-catch statements. This can actually serve as an indication that the function in question is overly complex and/or handling too many responsibilities. Such code can be a candidate for decomposition into smaller, more focused functions.
5625
5626For areas in your code where you don’t reasonably expect errors, it’s usually better to forego excessive try-catch statements and simply allow errors to occur. This might sound counterintuitive, but by allowing your co
5626de to [fail fast](https://en.wikipedia.org/wiki/Fail-fast), you're actually putting yourself in a better position. Squashing errors might give the appearance of stability, but there will still be underlying issues.
5627
5628Allowing errors to occur when not explicitly handled means that when paired with an application monitoring tool, like [BugSnag](https://www.bugsnag.com/) or [Sentry](https://sentry.io/welcome/), you can globally intercept and record errors for later analysis. These tools let you see where the errors in your application actually are so that you can fix them, rather than just blindly ignoring them.
5629
5630## Asynchronous Functions
5631
5632To understand what asynchronous functions in JavaScript are, you have to understand what [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) are.
5633
5634Promises are essentially objects that represent the eventual completion of asynchronous operations. They define an action that will be performed in the future and will ultimately be resolved (successfully) or rejected (with an error).
5635
5636For example, the following code shows a simple Promise that promptly resolves a value. The value is then passed to the then callback:
5637
5638\`\`\`
5639// This promise will successfully resolve and has no \`catch\` equivalent
5640new Promise((resolve, reject) => {
5641   const a = 10;
5642   const b = 9;
5643   resolve(a + b);
5644})
5645.then(result => {
5646    console.log(result); // 19
5647});
5648\`\`\`
5649
5650The next example shows how errors that are thrown inside a Promise (either by you or by the JavaScript engine) will be handled by the Promises catch callback:
5651
5652\`\`\`
5653new Promise((resolve, reject) => {
5654    const a = 10;
5655   const b = 9;
5656   throw new Error('manually thrown error')
5657    resolve(a + b); // this line is never reached
5658})
5659.then(result => {
5660   console.log(result); // this never happens
5661})
5662.catch(error => {
5663    console.log('something went wrong', error) // Something went wrong [Error: manually thrown error]
5664})
5665\`\`\`
5666
5667Rather than manually throwing errors in your Promises, you can instead use the reject function. This function is provided as the second argument to the Promise’s callback:
5668
5669\`\`\`
5670new Promise((resolve, reject) => {
5671    const a = 10;
5672   const b = 9;
5673   reject('- manual rejection');
5674   console.log(a + b); // 19
5675    resolve(a + b); // never called, as you already called \`reject\`, and a promise cannot resolve AND reject
5676})
5677.then(result => {
5678   console.log(result); // this never happens
5679})
5680.catch(error => {
5681    console.log('something went wrong', error) // something went wrong - manual rejection
5682})
5683\`\`\`
5684
5685You may have noticed something odd in the comments on that last example. There is a difference between throwing an error and rejecting a Promise. Throwing an error will stop the execution of your code and pass the error to the nearest catch statement or terminate the program. Rejecting a Promise will invoke the catch callback with whatever value was rejected, but it will not stop the execution of the Promise if there is more code to run unless the call to the reject function was prefixed with the return keyword. This is why the console.log(a + b); statement still fires even after the rejection. To avoid this behavior, simply end the execution early by using return reject(...).
5686
5687## Conclusion
5688
5689The try-catch statement is a useful tool that you will certainly use in your career as a programmer. However, any method used indiscriminately may not be the best solution. Remember, you need to use the right tools and concepts to solve specific problems. For example, you typically wouldn’t use a try-catch statement when you don’t expect any exceptions to occur. If errors do happen in these cases, you can identify and fix them as they arise.
5690
5691## Meticulous
5692
5693Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
5694
5695This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
5696
5697This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
5698`,K4=`---
5699title: "JavaScript TypeErrors and Techniques to Prevent Them"
5700slug: "javascript-typeerrors-and-techniques-to-prevent-them"
5701date: "2024-01-16"
5702description: "A short guide on best practices, techniques and existing tooling available to prevent TypeErrors in JavaScript or TypeScript."
5703author: "Daniel Li"
5704authorImage: "/images/blog/authors/65691319711713f7df7a83e8_daniel-li.jpeg"
5705heroImage: "/images/blog/javascript-typeerrors-and-techniques-to-prevent-them/656e241c74f1629eb3f38484_javascript-typeerrors-and-techniques-to-prevent-them.png"
5706heroImageAlt: "JavaScript TypeErrors and Techniques to Prevent Them"
5707categories: ["JavaScript"]
5708showTableOfContents: true
5709showPromo: true
5710---
5711
5712When a [JavaScript](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices) engine encounters a problem it can’t resolve, it will create an object that extends the Error interface and throws it. If the error happens in the browser, you’ll see a red error printed in the Developer Tools Console tab, and the page may stop responding; if the error happens in a [Node.js](https://nodejs.org/en/) program, the program will crash.
5713
5714When writing resilient applications, it’s important to learn what the different types of errors are and how to handle them appropriately.
5715
5716There are various errors that a JavaScript engine may encounter, including a SyntaxError, a ReferenceError, and a TypeError. This article will specifically look at the TypeError, which is thrown when you try to access properties that are invalid for that data type.
5717
5718In this article, you’ll learn some of the reasons a TypeError occurs, techniques to prevent them, and how to handle a TypeError when it does occur.
5719
5720## What Causes TypeErrors
5721
5722A TypeError occurs when an operation cannot be performed because the value is not of the expected type. Here are a few examples:
5723
5724## Calling a Non-function
5725
5726A TypeError can occur when you are invoking a value as a function but that value is not actually a function. In the following code snippet, the code is calling obj as a function, and so the JavaScript engine throws a TypeError with the error message obj is not a function:
5727
5728\`\`\`
5729const obj = {}
5730obj()
5731\`\`\`
5732
5733Similarly, the following snippet would print a foo.forEach is not a function error message because forEach is undefined and the code is calling it as if it were a function:
5734
5735\`\`\`
5736const foo = 'hello'
5737foo.forEach(console.log)
5738\`\`\`
5739
5740## Accessing Properties of Undefined or Null
5741
5742A TypeError can also occur when you’re trying to access a property of a value but that value is [nullish](https://developer.mozilla.org/en-US/docs/Glossary/Nullish) (ie undefined or null).
5743
5744In the following code snippet, Object is a built-in class; however, unknownProp is not a property of Object, which means Object.unknownProp is undefined. The code then tries to access the method property of an undefined value, so it will throw a TypeError with the error message Cannot read properties of undefined (reading 'method'):
5745
5746\`\`\`
5747Object.unknownProp.method()
5748\`\`\`
5749
5750## Having Manually Thrown TypeErrors
5751
5752The TypeError is not only thrown by the JavaScript engine. Libraries and program code can also manually throw a TypeError when they encounter a type-related problem.
5753
5754For example, the app.use() function in the [Express.js](https://expressjs.com/) library expects to be passed a middleware function or an array of middleware functions. There is a [check](https://github.com/expressjs/express/blob/158a17031a2668269aedb31ea07b58d6b700272b/lib/application.js#L217) in the definition of app.use that throws a TypeError when it is not passed a middleware function (or an array of them):
5755
5756\`\`\`
5757if (fns.length === 0) {
5758  throw new TypeError('app.use() requires a middleware function')
5759}
5760\`\`\`
5761
5762## Other Causes
5763
5764X is not a function and Cannot read properties of undefined errors tend to be the most commonly encountered TypeError. Other less common TypeError thrown by JavaScript engines include the following:
5765
5766## Iterating Over Non-iterables
5767
5768A TypeError can be thrown when you use language-native constructs (eg for...of) to iterate a value but that value is not iterable. The following code will output the error message TypeError: 42 is not iterable:
5769
5770\`\`\`
5771for (const prop of 42) {}
5772\`\`\`
5773
5774## Using the New Operator on a Non-object Type
5775
5776The new [operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new) allows a developer to create a new instance of a class or function constructor. However, if you use the new operator on a value that cannot be called as a constructor function, a TypeError will be produced.
5777
5778The following code will output the error message Foo is not a constructor:
5779
5780\`\`\`
5781const Foo = 'string'
5782const bar = new Foo()
5783\`\`\`
5784
5785## JSON-Stringifying an Object with a Circular Reference
5786
5787Built-in objects and methods can also throw a TypeError. For example, the JSON.stringify method will throw a TypeError when a circular reference is found in the object being stringified or when the object contains a [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) value:
5788
5789\`\`\`
5790const foo = {};
5791foo.bar = foo;
5792JSON.stringify(foo);
5793\`\`\`
5794
5795The previous code snippet will output the following error message:
5796
5797\`\`\`
5798 TypeError: Converting circular structure to JSON
5799  --> starting at object with constructor 'Object'
5800  --- property 'bar' closes the circle
5801\`\`\`
5802
5803## How to Prevent TypeErrors
5804
5805Now that you know some common causes for a TypeError to occur, we can explore some ways to prevent it from being thrown in the first place.
5806
5807## Adopt Static Type Checking
5808
5809Many TypeError instances can be prevented by using a static type checker, like [Flow](https://flow.org/), or by using [TypeScript](https://www.typescriptlang.org/), a superset of JavaScript that supports static types.
5810
5811With both Flow and [TypeScript](https://www.meticulous.ai/blog/safer-exhaustive-switch-statements-in-typescript), you first need to annotate your code with type information about each value. For example, in the following code snippet, : number \\[\\] tells the type checker that this function expects a single parameter of the numeric array type; the : number after the parameter list tells the type checker that this function is expected to return a single value of type number:
5812
5813\`\`\`
5814function max(numbers: number[]): number {
5815  return numbers.reduce((h, c) => Math.max(h, c), Number.MIN_SAFE_INTEGER)
5816}
5817\`\`\`
5818
5819Not every value needs to be explicitly annotated. For example, in the following snippet, the TypeScript compiler is smart enough to know that foo is of type string, even though you haven’t explicitly annotated it with foo: string:
5820
5821\`\`\`
5822const foo = 'hello'
5823\`\`\`
5824
5825Once your code has been annotated, you can set up your editor or integrated development environment (IDE) to continuously check the code for TypeError instances. If one is found, the error will be highlighted in the editor/IDE, typically by underlining the offending code with a red squiggly line. You can hover over the line to reveal a pop-up that explains what the problem is.
5826
5827For example, if you try to pass in the numbers four times as multiple arguments instead of as a single array of numbers to the max function defined earlier, the type checker will notice that this contradicts the type annotation and will output the error Expected 1 arguments, but got 4, which will be displayed on the IDE:
5828
5829![IDE showing a TypeError](/images/blog/javascript-typeerrors-and-techniques-to-prevent-them/656911fc1db4feff57b8dbcd_IDE-showing-a-TypeError.png)
5830
5831Apart from running the type checker locally with your editor/IDE, you should also run it as part of the CI/CD build process. This will ensure that the checks are passing based on the committed version of the code and not the local version, which may have uncommitted changes that make the checks pass locally.
5832
5833## Static Type Checking Best Practices
5834
5835Static type checking can drastically reduce the number of TypeError instances if you annotate your code correctly. If, however, the provided annotation is incorrect or too broad (eg using the any keyword), then the static type checker is prevented from working correctly and a TypeError may creep in.
5836
5837For example, in TypeScript, you can use [type assertion](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions) to tell the static type checker that a value conforms to a certain interface.
5838
5839In the following snippet, you are asserting that db.get will return an object that conforms to the User interface:
5840
5841\`\`\`
5842const user = db.get(userId) as User
5843res.send(user.preferences.theme)
5844\`\`\`
5845
5846However, if this assumption is incorrect and the user.preferences is nullish, the code will produce a TypeError, even though you’re using a static type checker.
5847
5848Another common pitfall to avoid when writing in TypeScript is the overuse of the any type, which is a type that allows the value to be anything (eg strings, booleans, and numbers). This type is so broad that it essentially opts the value out of being type-checked.
5849
5850The following TypeScript code snippet prevents the TypeScript compiler from checking the user variable. This means you’d have no idea whether user.preferences.theme will be an error until you run it:
5851
5852\`\`\`
5853const user = db.get(userId) as any
5854res.send(user.preferences.theme)
5855\`\`\`
5856
5857Generally speaking, you should avoid using the any type unless your function is actually designed to handle objects of any shape. For example, if you’re writing a utility function that adds a method to an arbitrary value, then any may be the appropriate type to use.
5858
5859In summary, when using static type checking, make sure that any type annotations and assertions are correct and make sure that type definitions are not too broad; otherwise, the static type checker won’t be able to do its job properly.
5860
5861## Add Runtime Checks
5862
5863Using static type checkers alone will not eliminate all TypeError instances. This is because static type checking only works for values with a type that is known at compile time. In many scenarios, the precise type and shape of a variable are only known at runtime. For example, the shape of a parsed HTTP request body depends on user input and, thus, is only known at runtime.
5864
5865Take the following Express.js snippet:
5866
5867\`\`\`
5868app.patch('/theme', (req, res) => {
5869  db.update({ color: req.body.company.appearance.color });
5870})
5871\`\`\`
5872
5873The code assumes that the request body can be parsed as an object with the nested property company.appearance.color, but if the actual request body is a simple string, then the previous code will produce a TypeError with the message [Cannot read properties of undefined](https://www.meticulous.ai/blog/how-to-prevent-the-error-cannot-read-property-0-of-undefined).
5874
5875To prevent the TypeError, you can explicitly write some code to check these runtime values using conditional blocks. For example, in the following snippet, we are responding with a 400 Bad Request status code if the request does not provide a request body with the right shape:
5876
5877\`\`\`
5878app.patch('/theme', (req, res) => {
5879  if (!(req.body && req.body.company && req.body.company.appearance)) {
5880    return res.status(400).end()
5881  }
5882  db.update({ color: req.body.company.appearance.color });
5883})
5884\`\`\`
5885
5886Apart from preventing TypeError instances, it’s good practice to validate user input and data obtained from third-party APIs.
5887
5888But writing a bunch of if statements for every value you want to use may make the code verbose and less maintainable. Instead, you can use tools like [JSON Schema](https://json-schema.org/) and [OpenAPI](https://www.openapis.org/) to define the expected shape of your values and rely on tooling to validate the actual values against the expected values.
5889
5890## Use the Optional Chaining Operator
5891
5892You can also use the optional chaining operator (?.), which will return undefined if any part of the chain is nullish. In the following code snippet, if any of req.body, req.body.company, or req.body.company.appearance are null or undefined, then the expression req.body?.company?.appearance?.color would be undefined:
5893
5894\`\`\`
5895app.patch('/theme', (req, res) => {
5896  db.update({ color: req.body?.company?.appearance?.color });
5897})
5898\`\`\`
5899
5900## How to Handle Unforeseen TypeErrors
5901
5902Adopting type checking and following best practices can help prevent TypeError instances, but some may still sneak through. To make your application resilient to errors, you also need to handle unforeseen errors.
5903
5904## Try-Catch
5905
5906JavaScript provides the try...catch construct; any errors (not just TypeError) thrown within the try block are caught and handled in the catch block.
5907
5908Earlier, you saw that calling Object.unknownProp.method() would throw a Cannot read properties of undefined (reading 'method') error. But when you wrap that call in a try block, the TypeError that is thrown will be passed to the catch block and handled without interrupting the program’s execution:
5909
5910\`\`\`
5911try {
5912  Object.unknownProp.method();
5913} catch (e) {
5914  console.log(e.name)
5915  console.log(e.message)
5916}
5917// ...continue execution
5918\`\`\`
5919
5920Every error thrown is an extension of the [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) interface, which always has a name and a message property. You can use the name and/or message to decide how to handle an error.
5921
5922Note that try...catch blocks can be nested. Errors will be handled by the nearest block. If you want the error to bubble up, you can rethrow the error:
5923
5924\`\`\`
5925try {
5926  produceError();
5927} catch (e) {
5928  if (e.name === 'TypeError') {
5929    console.log(e.name)
5930    console.log(e.message)
5931  } else {
5932    throw e; // re-throw the error
5933  }
5934}
5935\`\`\`
5936
5937You could wrap the entirety of your application’s code in a try...catch construct, which would catch any errors that were not caught and handled. However, this is a bad practice and you should not do this, as it is hard to write a good error-handling function that can precisely cater to a wide range of errors. Instead, you should catch errors as close to the source of the error as possible. You can read more about how to appropriately use try-catch in this [article](https://meticulous.ai/blog/javascript-try-catch-guide/).
5938
5939## Catch-All
5940
5941In your browser, there’s a global window.onError method, which is called when any thrown errors are not handled. You can assign a handler to window.onError that acts as a global, catch-all error handler for all errors not caught by a try...catch block.
5942
5943In Node.js, there’s no window object and, thus, no window.onError handler. Instead, you can listen for the uncaughtException event and handle the error:
5944
5945\`\`\`
5946process.on('uncaughtException', error => {
5947  console.error('Uncaught exception:', error);
5948  process.exit(1);
5949});
5950\`\`\`
5951
5952Note that the browser’s window.onError method and Node.js’s uncaughtException event handler should only be used as a last resort. Whenever possible, errors should be caught and handled close to where the error occurs. This provides the programmer with the most information about where the error occurred, and this prevents the programmer from writing a complex error handler function.
5953
5954The Node.js documentation [recommends that the uncaughtException event handler should still exit the process](https://nodejs.org/api/process.html#process_warning_using_uncaughtexception_correctly) after cleaning up any allocated resources. The program should assume that the unhandled exceptions mean the program is in an undefined state and that it is not safe to resume execution once an error reaches the uncaughtException event handler.
5955
5956## How to Fix TypeErrors
5957
5958A TypeError can be fixed by comparing the actual value with the type definition and identifying any disparities. Once identified, either the type definition needs to be revised or the value needs to be processed to conform to the type.
5959
5960Also, note that the thrown error may not be the root cause of the problem but only a symptom. For example, the line const username = res.body.user.name may throw a Cannot read properties of undefined error because res.body.user is undefined. While you should definitely be handling the case where res.body.user is undefined, the root of the issue may be that you are not catering to cases when the web server is unreachable and the response object is undefined:
5961
5962\`\`\`
5963if (!(res && res.body)) {
5964  // implement retry logic
5965}
5966const username = res.body.username
5967\`\`\`
5968
5969## How Meticulous can Help
5970
5971One of the best ways to identify a TypeError is by writing and running tests so that the errors surface during testing. However, writing good and comprehensive tests is hard and time-consuming.
5972
5973[Meticulous](https://meticulous.ai/) is a testing tool that allows you to record workflows and simulate these on new frontend code. The record feature can help save you many hours of test-writing, and the replay feature can help surface any new uncaught errors (like the TypeError).
5974
5975If you want to prevent the TypeError from popping up on your frontend code, try [Meticulous](https://app.meticulous.ai/) or check out the [docs](
5975https://app.meticulous.ai/docs).
5976
5977## Conclusion
5978
5979A TypeError occurs when a value is not of the expected type. You can prevent a TypeError from occurring by using a static type checker, like Flow, or by writing your code in TypeScript. Make sure the type annotations you write are accurate and not too broad.
5980
5981You should assume that a TypeError will inevitably sneak through, so use try...catch blocks to catch and handle them. Lastly, implement a test pipeline that identifies the TypeError before it goes to production.
5982
5983## Meticulous
5984
5985Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
5986
5987This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
5988
5989This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
5990`,Z4=`---
5991title: "JavaScript UI Testing | Best Practices, Tutorial"
5992slug: "javascript-ui-testing-best-practices"
5993date: "2024-01-16"
5994description: "A guide on the best practices in JavaScript UI testing, from behavior driven development, to naming conventions, to what browsers to execute tests within."
5995author: "Faraz Kelhini"
5996authorImage: "/images/blog/authors/6569147b8286039be11784df_faraz-kelhini.jpeg"
5997heroImage: "/images/blog/javascript-ui-testing-best-practices/656a54568f587f08f9a9b29f_javascript-ui-testing-best-practices.png"
5998heroImageAlt: "JavaScript UI Testing | Best Practices, Tutorial"
5999categories: ["JavaScript", "Tutorial"]
6000showTableOfContents: true
6001showPromo: true
6002hidden: true
6003---
6004
6005The quality of the UI can make or break a web application. The first impression the user gets is crucial and you may 
6005never get a second chance. UI testing has become an increasingly important part of application development. UI testing is the process of verifying aspects of a program that a user will come into contact with. But what methods or techniques should you use to maximize the efficiency of your testing? In this article, you’ll learn about UI testing best practices that you can start using today. Let’s begin.
6006
6007## Automate your UI Tests
6008
6009By using a UI testing tool, you can:
6010
6011- Make sure that UI elements aren’t clashing or out of place
6012- Check for incorrect spelling, capitalization, or punctuation
6013- Ensure clarity and legibility of typography, including color and font size
6014- Verify the alignment of images
6015- Make sure that the browser displays error messages properly
6016- Check the position of UI elements in different screen resolutions
6017
6018But if these tests aren’t automatic, the process can be burdensome. Manual testing is powerful and might work if the software has a limited number of UI elements or is in the early stages of development. However, as the software picks up more features and more UI elements are added, manual verification becomes unmanageable.
6019
6020With automatic UI testing, you can put the program through different test scenarios with different variables. Because tests are automated, you can run tests more frequently and detect errors as quickly as possible. Another benefit of automatic UI testing is lowering the cost of development. With [automated tests](https://www.meticulous.ai/blog/testing-pyramid-for-frontend), the need for manual testers is eliminated, which can save time and money.
6021
6022## Take Advantage of a BDD Framework
6023
6024Behavior-Driven Development (BDD) is a methodology that helps programmers, testers, and managers fully understand each other in a software project. This methodology facilitates collaboration among team members so that everyone is on the same page on how the program should behave.
6025
6026BDD is particularly beneficial when performing UI tests, but you can also use it for other types of testing, including unit and integration tests. Let’s say you want to write a short script to verify the functionality of a gaming app. Consider the following hypothetical example:
6027
6028\`\`\`
6029Feature: user buys a skin
6030
6031  Scenario: Account has adequate coins
6032   Given the account has compatible character
6033 	And the account balance is at least 50 coins
6034 	And the account level is at least 5
6035	When the user confirms purchase
6036	Then the game should add the skin to the account
6037 	And the account balance should be reduced by 50 coins
6038 	And the skin should be automatically applied to the character
6039\`\`\`
6040
6041This example is written in BDD style using [Gherkin syntax](https://cucumber.io/docs/gherkin/reference/). If you’re a new employee, would you rather read dozens of lines of code or read this BDD scenario?
6042
6043When you write your tests with BDD, you save other team members time. And should you come back to work on the tests yourself months later, you will find it easier to refresh your memory and so save your own time as well. BDD has the further advantage in that it lets non-developers, such as the project manager, understand tests and requirements.
6044
6045## Don’t use sleep() to Pause the Test
6046
6047The performance of your application hinges on internal and external factors such as server load, network bandwidth, and the processing power of the user’s device. Since you cannot always control these factors, you cannot predict how much time your app takes to load on the user’s device.
6048
6049To address this problem, you may want to simulate possible delays by implementing a timeout function to halt the testing script for a specific time. But if you’re not careful, you may introduce instability to your UI tests.
6050
6051Suppose you want to write a test script that opens your application’s home page and checks the visibility of the navigation menu. You’ll need to create two functions: one to open the app and the second to check the navigation elements.
6052
6053You also want to pause the execution of the test a little bit so that the app is fully loaded. If you’re using the Java language in Selenium, a browser-based automation suite, there’s a function called sleep() that allows you to do just that:
6054
6055\`\`\`
6056Thread.sleep();
6057\`\`\`
6058
6059The sleep() method takes a number in milliseconds and halts the thread's execution for the specified number of milliseconds (most other languages have a similar function). Say your app takes, on average, 4 seconds to start. And you want to halt the test for 8 seconds to be safe. So, you add Thread.sleep(8000); to the code.
6060
6061Using the sleep() method in this way would be a great way to kill the efficiency of your test. Why? Because you’re wasting about 4 seconds of execution time for each test. While this isn’t much for one test, those seconds quickly add up if you have a large project with hundreds (if not thousands) of UI tests on multiple browsers.
6062
6063Moreover, what if, because of some external factors, your app load time takes more than 8 seconds? Your tests will fail. A better approach is to use the implicitlyWait(
6063) function.
6064
6065By implicitly waiting, you order the browser to wait for a specific duration when trying to find an element. So, as soon as the element is found, the script is executed, and no time is wasted. And if the element isn’t available in the specified time, the test is reported as failed. Here’s an example:
6066
6067\`\`\`
6068WebDriver driver = new FirefoxDriver();
6069driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(8));
6070driver.get("http://exampledomain/index");
6071WebElement myDynamicElement = driver.findElement(By.id("nav"));
6072\`\`\`
6073
6074## Set a Naming Convention for the Test Cases
6075
6076Before creating tests, it’s essential to have a naming convention in place. Every test name should provide a clear and self-descriptive idea of what the test verifies. By creating and following a naming convention, your team members can:
6077
6078- Quickly search for a specific test
6079- Immediately recognize what each test does even months after they wrote them
6080- Identify which operation is causing a test to fail by just looking at the test name
6081
6082A good naming convention should reduce the effort required to understand source code so you can focus on more critical issues. Here’s an example of a poorly named test:
6083
6084\`\`\`
6085@Test
6086public void homepageTest () {.....}
6087\`\`\`
6088
6089This name doesn’t tell you anything about the test other than it’s testing the homepage. Compare:
6090
6091\`\`\`
6092@Test
6093public void userShouldSeeHomepageTitleInBoldFont() {.....}
6094\`\`\`
6095
6096The name of this test clearly describes its purpose. Now, if the test fails, you immediately know which part of the app you need to fix.
6097
6098## Don’t Rely on One Type of Testing
6099
6100You may come across developers that insist UI testing isn’t worth the trouble, and unit testing is all you need to verify the quality of the software. And then there are some developers who only do UI testing and disregard other tests. The reality is unit testing and UI testing are two approaches that complement each other. How?
6101
6102In unit testing, you check each section of the source code (known as the "unit") separately to ensure it meets the needs of the application. This characteristic of unit tests allows them to be straightforward and fast.
6103
6104But unit tests aren’t an accurate representation of the program in the real world. They only check parts of the source code in isolation and do not consider external dependencies. There are all sorts of flaws that unit tests can’t detect.
6105
6106And then there’s integration testing, which enhances unit testing by combining units of the code and testing them as a group. This way, any defect that may arise when the units are used together is exposed. But again, integration testing doesn’t consider the user’s interaction with the UI.
6107
6108That’s where UI testing comes in. UI testing lets you verify the application's visual elements, such as images and links, as well as user actions like mouse clicks and keyboard presses.
6109
6110That doesn’t mean you should only stick with UI testing, though. Unit and integration tests are essential in the Quality Assurance (QA) approach, and you should use them along with UI tests.
6111
6112## Take Advantage of Headless Browser Testing
6113
6114In the previous section, we discussed the importance of unit and integration testing and how they complement UI testing. But there’s another type of testing you may want to use in case you run into performance issues: headless browser testing.
6115
6116Regular browsers have to render HTML, process CSS, and load media such as images and videos, which affect the speed of your UI testing script. Moreover, they consume a lot of memory when you execute tests in parallel.
6117
6118The advantage of headless browser testing is that the browser won’t be loading the application's graphical user interface. As a result, tests are performed much faster and require less processing power.
6119
6120Several browsers are designed especially for this purpose, such as [PhantomJS](https://phantomjs.org/) and [HtmlUnit](https://htmlunit.sourceforge.io/). But some regular browsers like Google Chrome and Mozilla Firefox [can also run in a headless environment](https://developer.chrome.com/blog/headless-chrome/).
6121
6122Headless browser testing is particularly beneficial when testing interactions such as form submission and mouse-clicking. You may also use it to monitor the network traffic or handle [JavaScript](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices) execution.
6123
6124## Don’t Run all Tests in all Target Browsers
6125
6126Sometimes running your tests in multiple browsers does not improve the reliability of your application. The goal of testing your app in different browsers is to achieve better browser compatibility. But not all tests need to run in multiple browsers.
6127
6128For example, say you want to check the functionality of a form on the three most used browsers (Chrome, Safari, Firefox). You want to verify two things: 1) the form is displayed correctly in those browsers, and 2) the form responds appropriately when the user hits the submit button.
6129
6130The first goal involves verifying that the inputs, checkboxes, and buttons are correctly displayed and work as they're supposed to. Should you run these tests on all three browsers? The answer is yes. But should you run each test 100 times to confirm that? No. Ru
6130nning these tests a few times would be more than enough to verify their functionality.
6131
6132The second goal is different, however. Verifying the form response is not connected with the browser compatibility testing. Therefore, you can do it in just one browser. Running all tests in all target browsers would be misguided and may squander the cost savings you gain by incorporating test automation.
6133
6134## Use the Right UI Automation Testing Tools
6135
6136So, you’re finally ready to begin using automated UI testing. But what testing tool best suits your application? There are various automated UI testing tools on the market, including [Selenium](https://www.meticulous.ai/blog/is-selenium-worth-it), [Meticulous](https://meticulous.ai/), [Applitools](https://applitools.com/), [Percy](https://percy.io/), [Storybook](https://storybook.js.org/), [Chromatic](https://www.chromatic.com/), [Cypress](https://www.cypress.io/), [Puppeteer](https://github.com/puppeteer/puppeteer), and [Playwright](https://playwright.dev/).
6137
6138The first step in selecting an automated testing tool is whether it supports your platforms and technology. Are you going to test a mobile application or a web application? Does it support your programming language, and on what operating systems?
6139
6140Does the testing tool provide a self-healing algorithm? Self-healing allows the tool to detect changes in the user interface and adapt the testing to ensure the testing can go on without human involvement.
6141
6142Does the testing tool support a BDD framework? Without BDD, some team members may not understand what’s happening in the testing project.
6143
6144A good testing tool should be able to meet all your testing requirements.
6145
6146## Conclusion
6147
6148When it comes to quality assurance, software testing should be at the top of your to-do list. In this article, we learned how automating your testing process will significantly increase the quality and frequency of tests.
6149
6150We also looked at different types of testing. A robust testing strategy doesn’t involve only one type of testing, such as unit or UI testing. Instead, it incorporates different tests based on the application requirements.
6151
6152Following a naming convention for test cases is often an overlooked yet essential part of testing. The name of each test should provide a clear idea of what it verifies.
6153
6154Most often, it’s not necessary to run all possible tests in all target browsers. It would be a waste of time and resources. Instead, examine which tests you should test for cross-browser compatibility.
6155
6156Don’t use sleep() to halt your test. Take advantage of the implicit wait so you can complete the test in less time.
6157
6158Increase the efficiency of your tests by leveraging headless browser testing, especially when testing interactions such as form submission and mouse-clicking. Remember, a headless test skips loading the UI, so it’s multiple times faster.
6159
6160Thanks for reading! Stay tuned to the Meticulous blog to learn more about app development best practices.
6161
6162## Meticulous
6163
6164Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
6165
6166This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
6167
6168This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
6169`,e3=`---
6170title: "JavaScript Unshift | In-Depth Guide & Tutorial"
6171slug: "javascript-unshift-complete-guide"
6172date: "2024-01-16"
6173description: "An in-depth guide on the API, quirks and performance of JavaScript's unshift method, as well as best practices when using it."
6174author: "Gabriel Spencer-Harper"
6175authorImage: "/images/blog/authors/655c3f789adcc1019a5a2caf_gabriel.png"
6176heroImage: "/images/blog/javascript-unshift-complete-guide/656915dfd8fc9f2b38aefa44_javascript-unshift-complete-guide.webp"
6177heroImageAlt: "JavaScript Unshift | In-Depth Guide & Tutorial"
6178categories: ["JavaScript"]
6179showTableOfContents: true
6180showPromo: true
6181---
6182
6183## Introduction
6184
6185In this blog post we will examine unshift's function signature, it's use case and two potential pitfalls of using unshift due to it's mutation property and performance cost. We examine the underlying implementation of unshift and the algorithm prescribed by ECMAScript to inform this analysis, and wrap up with a few alternative options. If you are already fam
6185iliar with the basics of unshift, skip the 'what is unshift section'.
6186
6187This is going to be fun! 🤓
6188
6189## What is Unshift
6190
6191Unshift is a method for adding elements to the beginning of an array or an object resembling an array. It's one of the most commonly used [JavaScript](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices) methods when dealing with arrays and has the signature of unshift(element1, element2... elementN). It takes these elements and inserts them at the beginning of the array. This works for any number of elements less than 2^53-1 (more on this later).
6192
6193Let's examine how it works below.
6194
6195\`\`\`
6196const arr = [2,3]
6197
6198arr.unshift(1)
6199
6200console.log(arr)
6201\`\`\`
6202
6203The output in this example is \\[1,2,3\\].
6204
6205Let's try with multiple elements:
6206
6207\`\`\`
6208const arr = [3,4,5]
6209
6210const res = arr.unshift(1,2)
6211
6212console.log(arr)
6213
6214console.log(res)
6215\`\`\`
6216
6217The first log statement here will output \\[1,2,3,4,5\\] , which is the value of arr. The second log statement will output 5, because the length of the new array is 5. If you're interested, you can read more about a simple logging solution in React [here](https://meticulous.ai/blog/getting-started-with-react-logging/).
6218
6219So that's the signature of unshift. This may seem straightforward, but there's a lot of interesting detail here. Let's start with unshift's mutation.
6220
6221## Unshift Mutates
6222
6223You will notice here that arr is _mutated_. We assigned the returned value to a new variable res, which contains the length of the new array. The original array arr was mutated and contains a different value before and after the .unshift call. Mutation is something to be careful about because it is a common cause of unexpected issues which can be tricky to debug. To drive this point home, the paradigm of functional programming has an explicit goal of avoiding mutation and having pure functions (functions without mutation or side effects) for this very reason.
6224
6225We won't cover mutation, but if you're intersted, here are a few interesting articles: Clojure's philosophy on mutation [here](https://clojure.org/about/state) , MIT's course [here](https://web.mit.edu/6.005/www/fa15/classes/09-immutability/) or this blog post [here](https://blog.sapegin.me/all/avoid-mutation/#:~:text=Mutation%20may%20lead%20to%20unexpected,careful%20when%20reading%20the%20code..).
6226
6227Fortunately unshift does _not_ return the new array. Fucntion signatures which both mutate the original object and return a new newly mutated object are particularly pernicious, since it is often not clear that the original object has been mutated.
6228
6229Suppose instead we had an immutable function call that returns a new variable. If a new variable is created, it can be given a new name, thereby reducing the chance of confusion for the developer. Naming things is one of the most powerful tools we have as developers!
6230
6231OK. Enough about mutation. Onto performance!
6232
6233## Unshift's Performance Cost
6234
6235Before we can examine unshift's performance cost, we need to understand it's underlying implementation. Different browsers' have different JavaScript engines and so may have different implementations. Fortunately for us, ECMAScript specifies an exact algorithm for how JavaScript engines should implement unshift. This is sufficient for our purposes, so let's take a look at ECMA20's specification for the unshift method:
6236
6237\`\`\`
62381) Let O be ? ToObject(this value).
62392) Let len be ? LengthOfArrayLike(O).
62403) Let argCount be the number of elements in items.
62414) If argCount > 0, then
6242 a. If len + argCount > 253 - 1, throw a TypeError exception.
6243 b. Let k be len.
6244 c. Repeat, while k > 0
6245    i. Let from be ! ToString(𝔽(k - 1)).
6246   ii. Let to be ! ToString(𝔽(k + argCount - 1)).
6247  iii. Let fromPresent be ? HasProperty(O, from).
6248   iv. If fromPresent is true, then
6249     1. Let fromValue be ? Get(O, from).
6250     2. Perform ? Set(O, to, fromValue, true).
6251    v. Else,
6252      1. Assert: fromPresent is false.
6253      2. Perform ? DeletePropertyOrThrow(O, to).
6254    vi. Set k to k - 1.
6255  d. Let j be +0𝔽.
6256  e. For each element E of items, do
6257    i. Perform ? Set(O, ! ToString(j), E, true).
6258   ii. Set j to j + 1𝔽.
62595) Perform ? Set(O, "length", 𝔽(len + argCount), true).
62606) Return 𝔽(len + argCount).
6261\`\`\`
6262
6263Wow! There's a lot going on here and the notation is a little arcane. You can read the full implementation, along with references [here](https://262.ecma-international.org/12.0/#sec-array.prototype.unshift). We're not going to dissect this code line-by-line in this post, but there is one key takeaway here:
6264
6265Unshift has a computational complexity of O(N+X), where N is the number of arguments passed to unshift and X is the number of elements in the array or array-like object. This means that the time taken to execute the operation scales linearly with the number of elements in the array. This can quickly get expensive, particularly if we're repeatedly calling unshift within some sort of loop construct.
6266
6267Another interesting note is that unshift will fail for an array and number of arguments where their combined lengths exceed 2^53-1. This is because the implementation above specifies a number type for storing the number of elements of an array, which has a max value specified by MAX_SAFE_INTEGER which is 2^53-1. However, you will encounter an out of memory error before encountering this error. Suppose each element had just one byte, then the array would reach 9000TB before hitting the MAX_SAFE_INTEGER limit. This would result in an out of memory error.
6268
6269In terms of optimizing the performance cost, there's not too much we can do directly and this really is more dependent upon the context in which unshift is being called. If you are regularly prepending to an array, you could consider an alternative data structure with a lower time complexity cost, such as a linked list or circular buffer.
6270
6271One strategy for avoiding the mutation is to make use of JavaScript's spread operator, which 'unpacks' the values within an array. The performance cost of the spread operator is still O(N), where N is the number of elements in the array being unpacked. The spread operator can be used to make new arrays, like the example below:
6272
6273\`\`\`
6274const arr = [2,3]
6275
6276const newArr = [1, ...arr]
6277
6278console.log(newArr);
6279\`\`\`
6280
6281The output here is \\[1,2,3\\].
6282
6283Beware of unshift and hope you enjoyed this article!
6284
6285## Meticulous
6286
6287Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
6288
6289This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way 
6289we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
6290
6291This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
6292`,t3=`---
6293title: "How to Test a TypeScript App with Jest | A Step-by-Step Tutorial"
6294slug: "jest-typescript"
6295date: "2024-01-16"
6296description: "Automated testing is an important aspect of software engineering. In the JavaScript ecosystem, TypeScript provides an extra layer of assurance with types in JavaScript. For testing, Jest has been the …"
6297author: "Geshan Manandhar"
6298authorImage: "/images/blog/authors/6568d05a61cacbddd7afcac0_geshan-manandhar.jpeg"
6299heroImage: "/images/blog/jest-typescript/656a1916ec3f99329fd2ef53_jest-typescript.png"
6300heroImageAlt: "How to Test a TypeScript App with Jest | A Step-by-Step Tutorial"
6301categories: ["Testing"]
6302showTableOfContents: true
6303showPromo: true
6304---
6305
6306Automated testing is an important aspect of software engineering. In the JavaScript ecosystem, TypeScript provides an extra layer of assurance with types in JavaScript. For testing, Jest has been the de facto testing framework for JavaScript until now. Both TypeScript and Jest have risen in popularity in the past 12 months. According to the 2022 State of JS survey, Jest is the [most frequently used testing framework](https://2022.stateofjs.com/en-US/libraries/testing/), with 68% of survey respondents using it and 93% aware of it in 2022.
6307
6308This guide will show you how to use Jest to write effective unit tests for a TypeScript app.
6309
6310## What is TypeScript?
6311
6312TypeScript, according to the [official website](https://www.typescriptlang.org/), is “JavaScript with syntax for types.” Developed by Microsoft, it’s a superset of JavaScript that is strongly typed, is object-oriented, and has better IDE support.
6313
6314You don’t run TypeScript as such; the code is written in TypeScript (a .ts file), and the TypeScript compiler compiles it to JavaScript (a .js file). The compiled code is executed. Because of this compilation process, you can compile TypeScript to older versions of JavaScript if needed. In recent years, the popularity of TypeScript has gone up significantly. According to the 2022 State of JS survey, [28% of respondents](https://2022.stateofjs.com/en-US/usage/) write TypeScript 100% of the time, compared with only 11% writing JavaScript all the time. Another advantage of TypeScript is that it can be used in both frontend and backend (Node.js) applications.
6315
6316Having types on top of a dynamic language like JavaScript makes the software safer in execution. With TypeScript, the support for types in an IDE gives software engineers insights into what’s available. For example, if you’re using a [typed SDK for GitHub](https://
6316octokit.github.io/types.ts/)’s API, you’ll know exactly what parameters to send in the request and the fields to expect in the response. You’ll also know about the required and optional parameters, as they will be defined in the types. This comes in very handy and is much better than combing through the documentation to find the obvious answers. In the next section, you’ll learn about the importance of automated testing.
6317
6318## Why is Automated Testing Important?
6319
6320Generally, any code that is written is tested; the point is when and how the output of the code is tested. The most basic way of testing any web application that can be accessed via a web browser is by hitting a URL and then verifying the output visually. Usually, software engineers write the code and reload their browser tab to see if the result is as expected. This is a manual form of testing.
6321
6322Depending on the size of the project and the resources allocated to it, there might be a quality assurance (QA) engineer or department to do this type of testing before the software is released. Employing feature flags can help minimize the blast radius if anything goes wrong (feature flags will be a topic of discussion for another post).
6323
6324The issue with manual testing is the long feedback loop. The engineer has to change the code, switch the context to a browser, hit refresh, and see if the code change worked. This is where automated unit testing comes in very handy. If the setup is done properly and the software engineer practices test-driven development (TDD), the engineer would write the test first and then the code. This is also called the Red Green Refactor cycle of TDD.
6325
6326First, you as a software engineer write a test that will fail (red) because the code needed to do the work is not written; then you write the minimum amount of code to make the test pass (green). After that, you refactor the code and/or write the actual implementation without breaking your unit test.
6327
6328Another thing to note here is that unit testing tests only one unit of code. This unit of code is usually a single function, so the tests are laser-focused, fast, and independent of external factors like network and file system. If the tests are written in watch mode, which will rerun the tests when the code changes, the feedback loop is measured in milliseconds. If the tests are run in an IDE, there is almost no context switching involved. This helps with both developer productivity and software quality.
6329
6330The most important reason to write tests is to reduce the number of bugs reaching end users. There are multiple forms and layers of tests that help you to achieve this primary goal. Practices like TDD and unit testing also help create a great developer experience. Automated unit testing adds confidence that the code does what it is expected to do. It’s also useful when you’re changing existing code or adding new features, because running the whole test suite will catch any regression introduced.
6331
6332To recap, automated unit testing is better due to the fast feedback loop, and it can be done repeatedly with consistent results. If you want higher-quality software, having only manual testing is not scalable. Use automated tests to cover most cases and manual testing to verify that the happy path works.
6333
6334For JavaScript and TypeScript, among other testing frameworks, Jest is the [most popular](https://2022.stateofjs.com/en-US/libraries/testing/) in terms of usage. In the next section, we’ll list some prerequisites for the sample app and testing the TypeScript application with Jest.
6335
6336## Prerequisites
6337
6338Before we dive in, below are some prerequisites for better understanding the code:
6339
63401.  Prior experience with Node.js and Express.js in particular will be useful.
63412.  Knowledge of using the NPM command line will be helpful to install packages
63423.  It will be good if you have used TypeScript and know about its basics
63434.  Any prior experience with GitHub will be beneficial.
6344
6345Now it is time to get your hands dirty with some code. Let’s get coding!
6346
6347## Example app
6348
6349For this guide on how to test a TypeScript application with Jest, you will write tests for a simple quotes API built with Express.js. It will serve up 10 quotes per page, and the final output will look like the example below:
6350
6351![The output of the Quotes API built using Express.js with TypeScript](/images/blog/jest-typescript/6568e468e6e36b1c79c4b9c0_the-output-of-the-quotes-api-built-using-expressjs-with-typescript.jpeg)
6352
6353It’s a simple API that sends back mock static data from an array. It does not connect to a database, since the main focus of this tutorial is to write tests. The application is structured as follows without the tests:
6354
6355![Application structure for the Quotes API built using Express.js with TypeScript](/images/blog/jest-typescript/6568e469b382f40c957669a1_application-structure-for-the-quotes-api-built-using-expressjs-with-typescript.jpeg)
6356
6357The TypeScript setup resembles the setup done in this [guide](https://blog.logrocket.com/how-to-set-up-node-typescript-express/). The bulk of the code is in the src folder, which has QuotesController and QuotesService in the respective controllers and services folders. There is a custom type called [Quote](https://github.com/geshan/typescript-jest/blob/master/src/types/Quote.ts) with properties like id, quote, and author.
6358
6359The entry point of the application is index.js. The app.js has an App class that instantiates Express and glues together with the controller and routes.
6360
6361All the code is available on this [GitHub repository](https://github.com/geshan/typescript-jest) for your reference. You can also view the working application deployed on [Render](https://typescript-express-g4fe.onrender.com/api/quotes?page=1).
6362
6363The app is using TypeDI for dependency injection (which will be discussed later in this post for people who are new to it). It’s also using the [NPM concurrently](https://geshan.com.np/blog/2022/09/npm-concurrently/) package to run the TypeScript compiler in watch mode and the server on watch mode with Nodemon, as seen in this [package.json command](https://github.com/geshan/typescript-jest/blob/master/package.json#L9). You can clone the repository with the no-tests-or-jest branch by running:
6364
6365\`\`\`
6366git clone -b no-tests-or-jest [email protected]:geshan/typescript-jest.git
6367\`\`\`
6368
6369You can go into the directory with cd typescript-jest and install the needed NPM modules with npm install. To run the project in the development node, you can run:
6370
6371\`\`\`
6372npm run dev
6373\`\`\`
6374
6375Then you can check the output by placing http://localhost:3000/api/quotes in your favorite browser’s address bar — this will result in:
6376
6377![API output listing quotes](/images/blog/jest-typescript/6568e468e6e36b1c79c4b9c0_the-output-of-the-quotes-api-built-using-expressjs-with-typescript.jpeg)
6378
6379In the next section, you’ll learn about dependency injection and how it’s related to unit testing.
6380
6381## The Relationship Between Unit Testing and Dependency Injection
6382
6383Unit testing is focused on the unit being tested, which is the function or the class. Anything that is outside of the function should be mocked for a unit test. This includes not only network calls and access to the file system, but also other code dependencies like another class or methods of a different class. This is where unit testing and dependency injection intersect.
6384
6385Dependency injection is a pattern where any dependency used in a class is not instantiated within the class; it is injected from an external source into the class. This concept comes out of the [inversion of control](https://en.wikipedia.org/wiki/Dependency_inversion_principle) paradigm used to create loosely coupled software. Being able to inject any dependency into a class using a [dependency injection container](http://fabien.potencier.org/do-you-need-a-dependency-injection-container.html) makes it very easy to send in the mock classes for unit testing.
6386
6387In the example below, you will see how a mock Quotes Service class is injected and used while testing the Quotes controller.
6388
6389This is why writing testable code is the backbone of writing useful tests.
6390
6391Dependency injection is the bedrock for writing testable object-oriented code. For this example of Jest and TypeScript, a [TypeDI](https://github.com/typestack/typedi) library is used for dependency injection. You can learn more about TypeDI in this helpful [tutorial](https://blog.logrocket.com/dependency-injection-node-js-typedi/). In the next section, you will install, configure, and use Jest to prepare for writing some unit tests.
6392
6393## Install Jest
6394
6395From a code point of view, you have cloned the no-tests-or-jest branch from the GitHub repository. There are no tests in the no-tests-or-jest branch, and Jest has not been installed. You are going to install Jest and configure it for testing with TypeScript.
6396
6397To install Jest with TypeScript support, you can execute:
6398
6399\`\`\`
6400npm install -D jest @types/jest ts-jest
6401\`\`\`
6402
6403The above command installs Jest and types for Jest. Jest is the main library for testing and you are also adding related types for Jest. It also installs ts-jest that helps you test Type
6403Script projects with Jest by adding a transformer with source map support. If you’re interested, read more about ts-jest in their [documentation](https://kulshekhar.github.io/ts-jest/docs/).
6404
6405You can configure the Jest options in the package.json file with a new key called jest, which looks like this:
6406
6407\`\`\`
6408"jest": {
6409    "moduleFileExtensions": [
6410      "js",
6411      "json",
6412      "ts"
6413    ],
6414    "rootDir": "./",
6415    "testMatch": [
6416      "/test/**/*.(spec|test).ts?(x)"
6417    ],
6418    "transform": {
6419      "^.+\\\\.(t|j)s$": "ts-jest"
6420    },
6421    "coverageReporters": [
6422      "text",
6423      "html"
6424    ],
6425    "collectCoverageFrom": [
6426      "**/src/**/**/*.{ts,tsx,js,jsx}",
6427      "!app.ts",
6428      "!index.ts",
6429      "!dist/**"
6430    ],
6431    "coverageDirectory": "/test/.coverage",
6432    "testEnvironment": "node",
6433    "setupFiles": ["/test/setup.ts"]
6434  }
6435\`\`\`
6436
6437Jest has a lot of configuration [options](https://jestjs.io/docs/configuration). The above configuration starts with moduleFileExtensions, which is an array of file extensions used in your app modules. For this demo application, the provided .ts, .js, and .json should suffice. Then the root dir is set to the root of the project, /, where the Jest config file and package.json are placed. After that, tests are expected to be in the /tests directory in the root folder with a .spec.ts or .test.ts suffix; the .tsx file extension is also supported.
6438
6439Jest runs the code of your project as JavaScript, thereby the transforming process is needed to compile the TypeScript code to JavaScript using ts-jest. Subsequently, test coverage reports will be available in text and HTML. The coverage will be applied on the /src folder’s ts, tsx, js and jsx files excluding the app.ts and index.ts files and the dest folder. The code coverage will be placed in the /test/.coverage folder. Jest is expected to run on Node, and it will execute the /test/setup.ts once per test file.
6440
6441You will also add the following command in the package.json’s scripts section:
6442
6443\`\`\`
6444"test": "jest",
6445"test:watch": "jest --watch",
6446"test:cov": "jest --coverage"
6447\`\`\`
6448
6449There are three commands; the first one is the test command, which you run by executing npm t or npm test. This will run all the tests in your test suite with Jest. The next one is the watch command you can run with npm run test:watch. This command will run tests when the file changes. It will run tests only for the file that has been saved. You can press the A key to run all tests while in watch mode. To come out of watch mode, press Ctrl-C.
6450
6451The last npm script in the list is test:cov for test coverage. It will run all the tests and generate the code coverage as per the above config values. Jest uses [Istanbul JS](https://istanbul.js.org/) in the background to report the code coverage. You’ll learn more about code coverage later in this tutorial. In the following section, you’ll see the code for the Quotes controller and write unit tests for that code.
6452
6453## The Quotes Controller and its Tests
6454
6455As a best practice for unit testing, you should always write tests for the code you have written. Therefore, you will write tests for the controller and service. Writing tests for instantiating Express and writing it up with the controller is not a blocker. Below is the code for the QuotesController:
6456
6457\`\`\`
6458import { Service } from 'typedi';
6459import { QuotesService } from '../services/QuotesService';
6460import { Quote } from '../types/Quote';
6461@Service()
6462export class QuotesController {
6463  private quotesService: QuotesService;
6464  public constructor(quotesService: QuotesService) {
6465    this.quotesService = quotesService;
6466  }
6467
6468  public getQuotes(page: number = 1): Quote[] {
6469    return this.quotesService.getQuotes(page);
6470  }
6471}
6472\`\`\`
6473
6474You start this class by importing Service from type di. This is used as a decorator to allow the QuotesController class to be injectable using the container. Next, you import the QuotesService and the Quote type. The quotes service will get the quotes from the data source, and each quote will be of the type Quote, which looks like:
6475
6476\`\`\`
6477export type Quote = {
6478  id: number,
6479  quote: string,
6480  author: string,
6481};
6482\`\`\`
6483
6484It’s a simple type with an id that’s a number. It has two other attributes: quote and author, both of type string.
6485
6486Going back to the controller, next, the Quotes controller class is defined with a Service decorator. For this decorator to work, you’ll need to set the experimentalDecorators and emitDecoratorMetadata to true in the tsconfig.json file.
6487
6488After that, the constructor of the Quotes controller class is defined, which takes in the QuotesService as a dependency. Next, you define a method called getQuotes; this takes the page parameter, which is a number set to 1 by default. It has an array of type Quote as the return type of the method. In this method, you call the getQuotes method on the quotes service. You also pass in the page number for pagination. The Quotes service is responsible for getting the quotes data from the appropriate data source, which is a static array for this demo.
6489
6490You can write the test for the QuotesController even without the QuotesService, as shown below:
6491
6492\`\`\`
6493import { QuotesController } from "../../../src/controllers/QuotesController";
6494import { QuotesService } from '../../../src/services/QuotesService';
6495import { Quote } from "../../../src/types/Quote";
6496describe('QuotesController', () => {
6497  let controller: QuotesController;
6498  const mockQuotesService = {
6499    getQuotes: jest.fn()
6500  } as QuotesService;
6501  beforeEach(() => {
6502    controller = new QuotesController(mockQuotesService);
6503  });
6504  it('should define quotes controller', () => {
6505    expect(controller).toBeInstanceOf(QuotesController);
6506  });
6507  describe('getQuotes', () => {
6508    it('should get quotes', () => {
6509      mockQuotesService.getQuotes = jest.fn().mockReturnValueOnce([
6510        {
6511          id: 1,
6512          quote: 'There are only two kinds of languages: the ones peo
6512ple complain about and the ones nobody uses.',
6513          author: 'Bjarne Stroustrup'
6514        },
6515        {
6516          id: 2,
6517          quote: 'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.',
6518          author: 'Martin Fowler'
6519        },
6520      ] as Quote[]);
6521      const quotes:Quote[] = controller.getQuotes();
6522      expect(quotes).toHaveLength(2);
6523      expect(quotes[0].author).toBe('Bjarne Stroustrup')
6524      expect(quotes[1].quote).toEqual(expect.stringContaining('Any fool can write code that'));
6525      expect(quotes[1]).toEqual(expect.objectContaining({
6526        id: 2
6527      }));
6528      expect(quotes[1]).toEqual({
6529        id: 2,
6530        quote: 'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.',
6531        author: 'Martin Fowler'
6532      });
6533      expect(mockQuotesService.getQuotes).toHaveBeenCalledTimes(1);
6534      expect(mockQuotesService.getQuotes).toHaveBeenCalledWith(1);
6535    });
6536  });
6537});
6538\`\`\`
6539
6540The Jest unit test starts with importing the QuotesController, which is the System Under Test (SUT). You also import the QuotesService class, which is a dependency for the controller and the Quote type. Then the main describe is named the same as the class to be tested, which is QuotesController. Just below the describe, the controller variable of type QuotesController and the mockQuoteService are defined. The mock quote service has a getQuotes function which is assigned a Jest function.
6541
6542In the beforeEach hook, the controller is assigned the QuotesController calls with the mock Quotes services passed into the constructor. As the controller variable will be reused in multiple tests and functions, it is defined in the beforeEach function on the outer scope. Then you encounter the first test: it simply tests that the controller variable is an instance of the QuotesController class.
6543
6544After that, another describe block begins to test the getQuotes method. It has an it function that tests whether the controller method can return some quotes. Here, you set the Quote service’s getQuotes method to return a couple of quotes as an array of the “Quote” type. Then you call the getQuotes on the controller and expect it to have a length of 2. Next, you assert that the author of the first quote is Bjarne Stroustrup and that the quote attribute of the second quote is a string containing Any fool can write code that.
6545
6546There are multiple variations of assertions included in this test, to give you an idea of another way to utilize the expectfunction in Jest. You can be lenient and expect that an object has an id and not care about the other attributes. On the other hand, you can be stringent and expect the whole object to be exactly equal to the passed value.
6547
6548Toward the end of the test, you expect the mock service’s getQuotes to have been called exactly once and to have been called with the value 1.
6549
6550If you run the test with npm t or npm test, it will pass and show the following output:
6551
6552![Output of running tests for the Quotes Controller using Jest](/images/blog/jest-typescript/6568e56835c91e047ebf1c2f_output-of-running-tests-for-the-quotes-controller-using-jest.jpeg)
6553
6554In the next section, you’ll witness the code for the Quotes service and tests for it.
6555
6556## Quotes Service and Tests for it
6557
6558The Quotes service is the layer responsible for getting the data by querying a data source. It also abstracts out the data source from the caller. For this guide, to keep the scope small, a static array with 17 quotes is used as the data. The Quotes service could have communicated with a relational database or a NoSQL database using an Object Relational Mapper (ORM), but that is out of the scope of a tutorial focused on testing. Below is the code for the Quotes service:
6559
6560\`\`\`
6561import { Service } from 'typedi';
6562import { Quote } from '../types/Quote';
6563@Service()
6564export class QuotesService {
6565  public getQuotes(page: number): Quote[] {
6566    if (page < 1) {
6567      throw new Error('Page number should be 1 or more');
6568    }
6569    const quotes:Quote[] =  [
6570      {
6571        id: 1,
6572        quote: 'There are only two kinds of languages: the ones peo
6572ple complain about and the ones nobody uses.',
6573        author: 'Bjarne Stroustrup',
6574      },
6575      {
6576        id: 2,
6577        quote:
6578          'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.',
6579        author: 'Martin Fowler',
6580      },
6581      {
6582        id: 3,
6583        quote: 'First, solve the problem. Then, write the code.',
6584        author: 'John Johnson',
6585      },
6586      {
6587        id: 4,
6588        quote: 'Java is to JavaScript what car is to Carpet.',
6589        author: 'Chris Heilmann',
6590      },
6591      {
6592        id: 5,
6593        quote:
6594          'Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.',
6595        author: 'John Woods',
6596      },
6597      {
6598        id: 6,
6599        quote: "I'm not a great programmer; I'm just a good programmer with great habits.",
6600        author: 'Kent Beck',
6601      },
6602      {
6603        id: 7,
6604        quote: 'Truth can only be found in one place: the code.',
6605        author: 'Robert C. Martin',
6606      },
6607      {
6608        id: 8,
6609        quote:
6610          'If you have to spend effort looking at a fragment of code and figuring out what it\\'s doing, then you should extract it into a function and name the function after the "what".',
6611        author: 'Martin Fowler',
6612      },
6613      {
6614        id: 9,
6615        quote:
6616          'The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.',
6617        author: 'Donald Knuth',
6618      },
6619      {
6620        id: 10,
6621        quote:
6622          'SQL, Lisp, and Haskell are the only programming languages that I’ve seen where one spends more time thinking than typing.',
6623        author: 'Philip Greenspun',
6624      },
6625      {
6626        id: 11,
6627        quote: 'Deleted code is debugged code.',
6628        author: 'Jeff Sickel',
6629      },
6630      {
6631        id: 12,
6632        quote:
6633          'There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies.',
6634        author: 'C.A.R. Hoare',
6635      },
6636      {
6637        id: 13,
6638        quote: 'Simplicity is prerequisite for reliability.',
6639        author: 'Edsger W. Dijkstra',
6640      },
6641      {
6642        id: 14,
6643        quote: 'There are only two hard things in Computer Science: cache invalidation and naming things.',
6644        author: 'Phil Karlton',
6645      },
6646      {
6647        id: 15,
6648        quote:
6649          'Measuring programming progress by lines of code is like measuring aircraft building progress by weight.',
6650        author: 'Bill Gates',
6651      },
6652      {
6653        id: 16,
6654        quote: 'Controlling complexity is the essence of computer programming.',
6655        author: 'Brian Kernighan',
6656      },
6657      {
6658        id: 17,
6659        quote: 'The only way to learn a new programming language is by writing programs in it.',
6660        author: 'Dennis Ritchie',
6661      },
6662    ];
6663    const itemsPerPage:number = 10;
6664    const end:number = page * itemsPerPage;
6665    let start:number = 0;
6666    if(page > 1) {
6667      start = (page - 1) * itemsPerPage;
6668    }
6669    return quotes.slice(start , end);
6670  }
6671}
6672\`\`\`
6673
6674The Quotes service starts by importing Service from typedi and the Quote type. It has no constructor, so it has no constructor dependencies. If a real data source were used, it could take in the Quotes repository as a parameter to get data from a data source like a relational database.
6675
6676Next, you define the getQuotes function. It takes in the page parameter of the type number. Typing here is possible, as you are writing TypeScript. This page parameter is used to do basic pagination. First, the method checks whether the page parameter is a number less than 1, if that is the case, an error is thrown mentioning that page can be 1 or more. This method also has 17 quotes related to programming in the quotes array.
6677
6678Toward the end of the method, there is the logic to do the pagination of 10 quotes per page. The logic is self-explanatory, and it uses the array slice function to get the pagination working. This means page 1 will give quotes from index 0-9, and page 2 will send back quotes with array index 10-16.
6679
6680Let’s look at how you can write tests for the above QuotesService with full code coverage, the file is placed at \\\`/test/unit/services/QuotesService.spec.ts’ with the following contents:
6681
6682\`\`\`
6683import { QuotesService } from "../../../src/services/QuotesService";
6684import { Quote } from "../../../src/types/Quote";
6685describe("QuotesService", () => {
6686  let service: QuotesService;
6687  beforeEach(() => {
6688    service = new QuotesService();
6689  });
6690  it("should define quotes service", () => {
6691    expect(service).toBeInstanceOf(QuotesService);
6692  });
6693  describe("getQuotes", () => {
6694    it("should get mock fixed quotes", () => {
6695      const quotes: Quote[] = service.getQuotes(1);
6696      expect(quotes).toHaveLength(10);
6697      expect(quotes[0]).toEqual({
6698        author: "Bjarne Stroustrup",
6699        id: 1,
6700        quote:
6701          "There are only two kinds of languages: the ones people complain about and the ones nobody uses.",
6702      });
6703    });
6704    it("should get mock fixed quotes for page > 1", () => {
6705      const quotes: Quote[] = service.getQuotes(2);
6706      expect(quotes).toHaveLength(7);
6707    });
6708    it("should throw error for page number less than 0", () => {
6709      expect(() => {
6710        service.getQuotes(-1);
6711      }).toThrow("Page number should be 1 or more");
6712    });
6713  });
6714});
6715\`\`\`
6716
6717This test file has four tests and three of them are for the getQuotes method of the QuotesService class. Similar to the above test for the controller, the Quote type is imported and the System Under Test (SUT) in this case is the Quotes service.
6718
6719The first test checks that the quotesService variable is an instance of QuotesService. Then in the “describe” section for getQuotes, the first test verifies that if 1 is passed in as the page number, it returns 10 quotes and also matches the first quote to be an object with expected values.
6720
6721The next test does the same with the page variable being passed as 2. It expects to get back 7 quotes that are relevant. The final test in this file handles the error-throwing scenario in which the page is passed in as less than 1. It passes the page number as -1 and expects it to throw an error with the correct message. With all these tests, the file has full code coverage; you can see it by running npm test, which is in the package.json file and gives the following output:
6722
6723![Output of running tests for both Quotes Controller and Quotes Service](/images/blog/jest-typescript/6568e5b8fa163a58f103f0e7_output-of-running-tests-for-both-quotes-controller-and-quotes-service.jpeg)
6724
6725In the following section, you will learn about checking code coverage for the tests written.
6726
6727## Checking Code Coverage
6728
6729Code coverage is a metric that can trigger a healthy debate. For starters, having 100% code coverage does not mean bug
6729-free code; rather, it means that the software engineer has made an effort to write tests that cover all the code. There can be logical errors or errors related to data that unveil edge cases that weren’t thought about when writing the code.
6730
6731With Jest, to check the code coverage, you can execute jest --coverage; it has been included in the package.json scripts section as test:cov. This will translate to you running npm run test:cov to see the code coverage that looks like this:
6732
6733![Showing test code coverage running tests with Jest for the TypeScript app](/images/blog/jest-typescript/6568e5b85ed4aae6c64adc42_showing-test-code-coverage-running-tests-with-jest-for-the-typescript.jpeg)
6734
6735As seen previously, you have written enough tests to cover all relevant files and all the code inside them. As the HTML reporter is also enabled in the config, you will see HTML files that have code coverage information at /test/.coverage/index.html. If you open the file, you will see something like what follows:
6736
6737![Showing code coverage from the HTML file](/images/blog/jest-typescript/6568e5b8c8469619c24d3e86_showing-code-coverage-from-the-html-file.jpeg)
6738
6739For instance, if you remove the test in the QuotesService.spec.ts file with the description should throw error for page number less than 0 seen in lines 31-34. Then run the coverage you will see:
6740
6741![Showing that the code coverage decreases when a test is removed](/images/blog/jest-typescript/6568e5b814b9409e63c45be7_showing-that-the-code-coverage-decreases-when-a-test-is-removed.jpeg)
6742
6743This means line number 10 in the QuotesService is not covered by any tests, which is the result of removing the above-mentioned test. This is how code coverage works.
6744
6745As a software engineering team, it’s wise to go after a high code coverage as agreed upon by the team. Obsessing over getting 100% code coverage doesn’t make for a healthy tech culture. As with most things, the amount of time and effort you spend to get the target code coverage should be logical, optimal, and justified.
6746
6747## Conclusion
6748
6749In this step-by-step tutorial, you learned how to add Jest to an existing TypeScript project. Then you wrote unit tests for two important files: the Quotes controller and the Quotes service.
6750
6751In addition to learning the concepts of testing, you also saw practical examples like how code coverage works and got introduced to testing terminology within the context of unit tests.
6752
6753## Meticulous
6754
6755Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
6756
6757This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
6758
6759This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
6760`,n3=`---
6761title: "Testing Frontend — Lessons from over a million lines of TypeScript at Palantir"
6762slug: "lessons-from-a-decade"
6763date: "2025-01-06"
6764description: "The most critical three learnings for testing frontend, from ten years helping lead frontend engineering at Palantir."
6765author: "Quentin Spencer-Harper"
6766authorImage: "/images/blog/authors/6590373d3fcec21648cdc202_profile.jpeg"
6767heroImage: "/images/blog/lessons-from-a-decade/659033b24b3e0306b46556bb_Frame-10.png"
6768heroImageAlt: "Testing Frontend — Lessons from over a million lines of TypeScript at Palantir"
6769categories: ["Testing", "TypeScript", "JavaScript", "Featured"]
6770showTableOfContents: true
6771showPromo: true
6772---
6773
6774## Lessons from a decade: the most critical three
6775
6776Prior to Meticulous.ai I spent ten years at Palantir helping lead engineering for Palantir’s main frontend. Through this we learnt how to test frontends effectively, the hard 
6776way.
6777
6778With dozens of apps, 100s of developers, and millions of lines of TypeScript we were able to try different approaches, study the root causes behind each bug that made it to production, iterate and learn. We combined our own experiences with talking with other tech leaders - and reading the best materials we could find out there. Our learnings over those years are something I’ve never been able to find somewhere else. This post distills the most critical three.
6779
6780There’s a lot to FE stability beyond just testing (monitoring & alerting, incremental metric-dependent rollout, fast or auto rollback etc.). Here we concentrate just on testing. Here’s why:
6781
6782## Getting testing right is the only investment that has the potential to double your engineering velocity
6783
6784The main determinant of the impact of an engineering org is building the right product<sup>†</sup>: prioritising the investments & experiments that give you maximal _information_ on product/market fit, and iterating on a daily or weekly cycle with your users.
6785
6786But if you look at pure engineering changes — developer education, developer tooling, developer practices etc. — most only make incremental improvements to your total velocity. Few have the potential to double your impact. Testing is the single exception.
6787
6788If you look carefully at how your team spends their hours, only a fraction of it is likely hammering out code — in most teams more time is sunk into:
6789
6790- Searching through the codebase to understand all code that could be affected, before making your change, and so minimise risk of any unexpected impacts
6791- Manually testing your changes as you write them and before pushing up your PR. And then testing again after addressing PR comments, and sometimes testing again at release stage (each time often testing not just the direct targets of your change but also all related paths that _could_ be affected, each sometimes across multiple permutations -- configurations; user types, data responses, feature flags, screen sizes etc.)
6792- Triaging, repro’ing, root causing and fixing issues that occur in the field
6793- … and all the context switching in-between
6794
6795In contrast if you can make any change with confidence then you can not only iterate dramatically faster (shipping code at the speed you can write it), you can also refactor continuously with complete ease, and ensure you can _maintain_ that initial velocity over time. When developers don’t have the confidence to make sweeping changes they tend to just avoid large refactors to protect their individual velocity. The result can be a self-reinforcing downward spiral: as the codebase quality degrades, refactors become even more risky, and we become even more cautious to do them.
6796
6797Despite this most automated frontend tests we’ve seen teams add (whether at the unit level or the end-to-end level) actually make the team slower rather than faster. The time taken to author them and, much more significantly, to _maintain_ them, outweighs the value they provide.
6798
6799However… if you think carefully about a testing strategy for your projects you can make new tests trivial to write, and often almost entirely eliminate any maintenance. If you can crack this then you can move extremely fast. But before we move onto our three most important learnings in how to make that happen, it’s worth understanding the most critical determinant of success in a testing strategy: maintenance cost.
6800
6801<span style="font-size: 80%; margin-left: 10px;">**†** _The second largest determinant of impact for mature engineering organisations may well be careful strategy (and extreme caution!) around migrations and rebuilds where you have to match existing functionality or compatibility. Tip: break it down into incrementally deliverable steps that allow you to iterate as you learn the full costs and tradeoffs involved. Deliver incremental value and ensure you can successfully abandon at any point.</span>_
6802
6803## Maintenance cost _really_ matters
6804
6805Roughly speaking, given a fixed budget, the number of tests you can afford in the long run is inversely proportional to the maintenance cost of each test:
6806
6807![](/images/blog/lessons-from-a-decade/67910626bcafbac4661ac0e7_65904cad11f274f3b2155cde_maintenance-cost.png)
6808
6809The more tests you can afford the more coverage you have, and the more value your tests provide. This means the maintenance cost **_really matters._** A fixed decrease in maintenance cost per test can give an exponential increase in the collective value of your tests.
6810
6811There are two strategies to achieve this: the first is to assume the tests break all the time, but make it lightning fast to understand whether the change is expected or not, and to update the tests or snapshots. In this case every second matters: shaving off the time to review and update from 5 seconds to 4 seconds can be transformative.
6812
6813The second strategy is to avoid having to update tests at all — and the most important principle here is to test at the minimal cut:
6814
6815## #1: Unit tests: test the right sized unit, at the [minimal cut](https://en.wikipedia.org/wiki/Minimum_cut)
6816
6817When you write a test, whether a unit test or an integration test, you have to decide the scope of your test — what application code do you run as part of your test, and what do you mock out.
6818
6819Often it’s tempting to fall into the trap of having a unit test test a single class, function or component — but more often than not this doesn’t make sense.
6820
6821Imagine you have an app that calculates an optimal route between two map points and renders it to a canvas:
6822
6823<div class="white-bordered-image"></div>
6824
6825![](/images/blog/lessons-from-a-decade/67910626bcafbac4661ac10f_659042a47ab093299d225aa8_Untitled-201.png)
6826
6827Your test can test the route calculation and the route rendering separately, or test it together. It may be easier to cover all the route-calculation edge c
6827ases (different types of route tradeoffs) and all the rendering edge cases (different types of curves) by testing each component separately. If the way you want to render routes changes frequently then you won’t need to touch the tests for the route-calculation edge cases; and when something breaks it’ll be easier to narrow it down to the part of code broken. Each of these modules has enough complexity to be worth testing separately.
6828
6829However now imagine that your optimal route calculation module uses a sub-function to calculate the time taken for each potential sub-part of the route, and then utilises that to calculate the optimal overall route. In this case it’s possible that it makes more sense to test the whole route calculation module as a single unit: you only care that it continues to return the optimal route over time, not how the internals are wired together. You may want to change the internal implementation (say, improving performance) over time without breaking the tests.
6830
6831In general you can imagine your app as a network of collaborating modules (functions/classes/components etc.):
6832
6833<div class="white-background-image"></div>
6834
6835![](/images/blog/lessons-from-a-decade/67910626bcafbac4661ac0ff_659042d088fbae0751ed9c2f_graph.png)
6836
6837When you write a unit test, you decide what application code is inside the scope of the unit test:
6838
6839<div class="white-background-image"></div>
6840
6841![](/images/blog/lessons-from-a-decade/67910626bcafbac4661ac0fc_6590494cdbb6f549d68735fe_graph-good-no-caption.png)
6842
6843<center style="font-size: 80%; margin-left: 40px; margin-right: 40px;"><p>Any API that has to be mocked out adds fragility to the test. Try to minimise the API surface that your test boundary bisects, and bisect APIs that are as stable as possible: APIs that naturally reflect your domain, rather than APIs that are internal implementation details that may change over time.</p></center>
6844
6845Any communication lines crossing this boundary have to be mocked out. And if any of those communication lines change - say due to refactoring how the modules communicate, or by changing the APIs of those communication lines — then your test breaks and you have to update it. You therefore want to choose a minimum cut, that gives maximal coverage: choose the boundary to intersect a minimum API surface, that is as stable as possible, while containing inside of the boundary code of a sufficient complexity to be worth testing as a single unit.
6846
6847If you choose those boundaries poorly then your tests will frequently break as the codebase evolves, and you may find their cost exceeds their value:
6848
6849<div class="white-background-image"></div>
6850
6851![](/images/blog/lessons-from-a-decade/67910626bcafbac4661ac102_659042ec582e5f01dc760e56_graph-bad.png)
6852
6853‍
6854
6855**The art of writing productive tests requires working out how to carve up your application such that each test exposes itself to a minimal set of APIs that are as stable as possible.**
6856
6857Getting the scope of the tests matters more than pretty much anything else. When deciding the scope of your tests, consider:
6858
6859- What behaviour do you care about preserving over time? If your test implicitly tests implementation details you don’t care about then you may be testing at the wrong level.
6860- Choose natural boundaries for your units to test, that run across stable API lines in your codebase that are easy to mock.
6861- Have the scope of the test sufficiently large such that the unit is complex enough to be worth testing, and the test does not assert too many implementation details. But not so large that it becomes hard to test the many combinations of edge cases, and hard to keep track of what you have vs haven’t tested.
6862
6863## #2: (Probably) don’t write Enzyme or component tests
6864
6865[Component tests](https://enzymejs.github.io/enzyme/) are unit tests that render a React component in a virtual environment and test interacting with it. We’ve found these tests are rarely worthwhile writing, except for maybe a minority of the more complex collections of components in your application. If you do write them, it often makes sense to test a collection of collaborating utilities and components that together form a single much more complex component.
6866
6867Even in the most extreme case where you’d expect such tests to be a clear win — Palantir’s open source component framework [BlueprintJS](https://blueprintjs.com/), which has complex components, used by 10s of 1000s of projects, where the slightest bug can have huge consequences — it wasn’t always clear that the Enzyme tests actually paid off.
6868
6869The reason is:
6870
6871(1) these tests are slow to write and fragile to maintain - you’re often tweaking your UX and components, and as you do so the tests need updating
6872
6873(2) most of the complexity can be more reliably tested by pulling the complex logic out of your component, into utility functions, separate from the UI itself, and testing those. These tests are less fragile since they break only when you change your abstractions or logic: not when you change the structure of your DOM. They interact with a much smaller API surface.
6874
6875**If you have over 50% test coverage from manual unit tests in a frontend codebase then you’ve over-invested in testing**: you’re almost certainly slowing yourself down rather than speeding yourself up.
6876
6877## #3: Scalable integration tests: look for stable APIs in your app where the variation in the values passed through that API is the root of a lot of the complexity in your app
6878
6879_\\- and build a structure that makes each next integration test trivial to add, and costs zero to maintain_
6880
6881Think carefully about your application — where does the majority of the complexity you want to test come from? What APIs (internal and external) change frequently vs rarely? And can you setup structures that make each individual test trivial to add, and align the testing boundary with the APIs that change rarely, such that these tests rarely need updating?
6882
6883This won’t work with all applications, but it does for many. MapboxGL, an open source library for rendering maps, is a great example. All the information to render a map is defined by a [JSON document](https://docs.mapbox.com/style-spec/guides/) called a style spec. Their main test suite maps style specs to screenshots of the resultant rendered map: adding a test is just a case of dropping a [style spec JSON file](https://github.com/mapbox/mapbox-gl-js/blob/main/test/integration/render-tests/real-world/bangkok/style.json) into a folder. If the code changes it’ll show you the difference between the before and after screenshot, and you can merge the updated snapshot. The style spec is public API for MapboxGL, so it rarely if ever breaks — and since the style spec never breaks the tests never break.
6884
6885Similar techniques can be used with any application that has a stable API format, where variation in the values provided to that API account for the majority of the complexity that needs to be tested. This includes any app where users author things — like Figma (designs), Google Docs (docs), and Slack (messages). But it can also cover anywhere there’s a stable API between services, or inside an app.
6886
6887For example, one of Palantir’s backing services is responsible for returning sets of objects from the database systems given a certain query. The tests consist of a library of triples of files: the first file defines the objects to populate into the test database, the second a query, and the third a generated snapshot of the expected results. Adding a test is just a case of dropping in a couple of extra files. Since the query format should never break the tests are write-once-change-never: your test coverage grows over time but you never need to update an existing test.
6888
6889## What tests are worth writing?
6890
6891Ok, so:
6892
6893- Don’t fall into the trap of every unit test testing a single function/class.
6894- Choose the size and the scope of the code covered by each test judiciously: not too big to be complex to cover all edge cases; but large enough to cover sufficiently complex functionality to make the test pay off, with a minimal surface to mock out — and pick a boundary that runs along stable API lines in your codebase that are easy to mock.
6895- If you’re writing tests manually, don’t aim for 100% coverage for FE apps (or anywhere near) — instead think whether the test will provide more value than it costs
6896- Skip the component tests
6897- Look for opportunities specific to your application, and build a structure that makes each next integration test trivial to add, and costs zero to maintain.
6898
6899But what tests _are_ worth writing? If you’re still writing tests manually, rather than automatically generating tests, then we’d suggest:
6900
6901![](/images/blog/lessons-from-a-decade/67910626bcafbac4661ac0ea_659050e7720c2702e4af0800_pyramid.png)
6902
6903- Write unit tests for sufficiently complex logic (a single test may test may a small network of functions/classes collaborating together).
6904- Write a smaller number of integration tests for key parts of the system, that test multiple of those modules collaborating together.
6905- And a couple of end to end tests to smoke test key flows that everything works together as a whole.
6906
6907You don’t want too many end-to-end tests, or to use these to cover all edge cases, since they’ll be extremely expensive to maintain as your UX evolves. This is true whether they’re written in code or recorded through a point-and-click tool.
6908
6909These techniques will accelerate your team. But they won’t let you double your engineering velocity, not even close. To get there you’ll need to move beyond manually writing and maintaining tests, and:
6910
6911## Take the denominator to zero
6912
6913A large web-app has 10s of 1000s of edge cases (logical branches, feature flags, configurations, screen sizes, locales, variations in user data & content and flows initiated from different starting states and in different orders). As long as you’re manually writing and maintaining tests it’ll be infeasible (and unadvisable) to get anywhere close to 100% test coverage: the maintenance cost will kill you.
6914
6915A different approach is needed. For that we first need to step back:
6916
6917At most engineering organisations every feature is used at least once before it’s deployed to production: developers click around on localhost while they iterate on their branch, previewing their changes. The issue is that every existing user flow can not feasibly be retested on every future change to the codebase (or even on follow up commits on the original pull request). You have an N<sup>2</sup> problem.
6918
6919However those daily interactions on localhost, staging stacks, and preview URLs by your development team are the key. This data stream describes the complex flows that unlock the path to test every feature of your app -- and can be used to automatically test for regressions.
6920
6921[Meticulous AI](https://meticulous.ai/) is the category leader here - and can even be used to sample production flows too. Every interaction is recorded, and by understanding every line of code executed by each flow it maintains a visual snapshot test suite that covers close to 100% of your code. This suite tests your apps UI, user flows and logic at all layers, across all edge cases, all branches, all feature flags. As your app evolves, your test suite evolves. The tests are executed in an environment that preserves determinism from the browsers scheduling layer up — so no flakes.
6922
6923You’ve taken the denominator to zero, and the maximum maintainable test count becomes unbounded.
6924
6925If you’ve ever had the rare chance of working on a project with testing at this level you’ll know that programming feels completely different. Every dev can refactor with complete confidence and speed. And so they do, keeping the code fresh & nimble. You’ll be able to move faster than you’ve likely ever experienced before in your career.
6926
6927Never write, debug or fix a test again. Book a call [here](https://www.meticulous.ai/book-a-demo) to learn more.
6928`,a3=`---
6929title: "Let Your Users Write Your Tests for You | Part I"
6930slug: "let-users-write-tests-for-you"
6931date: "2024-01-16"
6932description: "An explanation of what replay testing is, how to leverage user session data to create automated tests, and an overview of existing companies using replay testing."
6933author: "Gabriel Spencer-Harper"
6934authorImage: "/images/blog/authors/655c3f789adcc1019a5a2caf_gabriel.png"
6935heroImage: "/images/blog/let-users-write-tests-for-you/656916ea2967e28b96369f2d_let-users-write-tests-for-you.webp"
6936heroImageAlt: "Let Your Users Write Your Tests for You | Part I"
6937categories: ["Resources"]
6938showTableOfContents: true
6939showPromo: true
6940---
6941
6942## Replay Testing
6943
6944This blog post introduces the concept of 'replay testing' and why it can be a valuable tool in a software engineer’s toolbox.
6945
6946During my time as a software engineer, previously at Opendoor and Dropbox, I discovered that however much you unit or integration test your code, you will tend to find that actual users of your application find a way to break it.
6947
6948![Collapsing building](/images/blog/let-users-write-tests-for-you/65691711938f0c44daf4f0a2_collapsing-building.gif)
6949
6950So we write tests. Lots and lots of them. Writing tests require you to simulate user behavior and write assertions around how an application should behave. This poses two problems. The first is that we can only write test cases for scenarios that we can think of. This makes it tricky to cover all edge cases, and is particularly difficult when an application is complex. The second problem, particularly pernicious with [end-to-e
6950nd tests](https://www.meticulous.ai/blog/testing-pyramid-for-frontend), is that every test carries a maintenance burden.
6951
6952This is where replay testing comes in. Replay testing simulates what users’ actually do on your web application. When testing a given change, a user session is replayed on two versions of your web application: base commit and head commit. Before and after.
6953
6954Suppose you want to detect new uncaught exceptions. During replay, the application is monitored and we can listen for uncaught exceptions. These can then be diffed in order to detect regressions. This moves from an assertion/expectation testing model towards surfacing differences for developers to approve/reject. Another example might be capturing screenshots and diffing these to detect visual regressions.
6955
6956Replay testing can avoid these pitfalls, but it’s complex to build.
6957
6958## Why has this not been Done Before and Why is it Tricky
6959
6960It has. However, only large companies have the resources to sponsor a sufficiently large engineering lift. Twilio invested heavily into implementing replay testing in the early 2010s. Google has a system still-in-use today which takes visual snapshots and diffs these. However, most companies are left high and dry.
6961
6962Implementing replay testing requires solving many intricate problems. Let’s explore two example problems. The first problem is that replaying a session on a production environment will cause side effects.
6963
6964In order to overcome this, companies will often spin up a staging environment to replay against. However, building a staging environment that is representative of production is a tricky problem in and of itself. In addition, companies must implement a mechanism to reset state after each replayed session(imagine replaying a user signing up to your web application).
6965
6966There are many more problems than this, but just solving the ones above require significant investment!
6967
6968At [Meticulous](https://meticulous.ai/) we capture all network traffic at record time and then automatically mock out all network calls at replay-time. This isolates the frontend, avoids causing any side effects and allows any company to implement replay testing for their frontend code. This approach is not as powerful as full-stack replay against a representative staging environment - it only catches frontend regressions. However, it works out of the box and is a valuable additional layer of defense that you get, more or less, for free.
6969
6970I will add more detail on how exactly this works and what other issues can crop up when implementing replay testing in part 2. Thanks for reading
6971
6972Replay testing is what we’re building at Meticulous, as part of our mission to work towards making the world’s code safe, performant and reliable.
6973
6974Are you interested in working on this problem or trying out Meticulous?
6975
6976Email me at gabe@ this domain and mention ‘blog’ in the subject line. We’re backed by some of the best technical investors in the world, like the CEO & founder of Vercel, CTO of GitHub, CPO of Adobe, YC partner and Scribd CTO Jared Friedman and many other incredible folks.
6977
6978If you’ve implemented replay testing at your company, please reach out - I’d love to learn more!
6979
6980## Meticulous
6981
6982Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
6983
6984This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
6985
6986This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
6987
6988Authored by Gabriel Harper, founder of Meticulous
6989`,i3=`---
6990title: "JavaScript LocalStorage | In-Depth Guide & Tutorial"
6991slug: "localstorage-complete-guide"
6992date: "2024-01-16"
6993description: "The complete guide on LocalStorage, it's API, a code walkthrough, and various trade-offs and limitations compared to other storage options."
6994author: "Subha Chanda"
6995authorImage: "/images/blog/authors/656918cf162348c627e0eb1d_subha-chanda.jpeg"
6996heroImage: "/images/blog/localstorage-complete-guide/65691790341bc3fa24842d44_localstorage-complete-guide.webp"
6997heroImageAlt: "JavaScript LocalStorage | In-Depth Guide & Tutorial"
6998categories: ["JavaScript"]
6999showTableOfContents: true
7000showPromo: true
7001hidden: true
7002---
7003
7004For most web browsers, the [web storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API) provides a mechanism to store key-value pairs in the browser. It’s generally divi
7004ded into localStorage and sessionStorage, and the main difference between the two is how long the browser stores the data. With sessionStorage, the data is removed once the session ends or the browser closes. Data in localStorage, though, persists until it is cleared.
7005
7006A common real-world example of localStorage is the game [Wordle](https://www.nytimes.com/games/wordle/index.html). Once you type in a word and press Enter, the values are stored in localStorage. If you close and reopen the Wordle page on the same device, you’ll get the same words you already tried. The storage is reset at the end of the day.
7007
7008![Wordle as an example of localStorage](/images/blog/localstorage-complete-guide/656917a72367ea23ec8cfd3f_wordle-as-an-example-of-localStorage.jpeg)
7009
7010The daily answer is also stored in localStorage, so that you can play the game offline after opening the page.
7011
7012The localStorage feature can prove helpful in a number of use cases. This article will explain more about localStorage and how it works, so that you can use it with your applications.
7013
7014## Why Do You Need localStorage
7015
7016There are a number of useful features in localStorage, including the ability to store user information and to allow you to work offline as needed.
7017
7018As part of the web storage API in web browsers, localStorage works similarly to cookies. However, it can store a larger amount of data. The storage [available in Google Chrome is 5 MB](https://web.dev/storage-for-the-web/) maximum per domain, while Opera’s localStorage holds 3 MB but can be increased.
7019
7020Because the internet may not be consistently accessible everywhere, localStorage enables you to take your work [offline](https://medium.com/@onejohi/offline-web-apps-using-local-storage-and-service-workers-5d40467117b9), just like with Wordle.
7021
7022You can also store the state of your web page, even though HTTP is stateless. Say you only wanted to use the _Solarized Dark_ theme on the [Alligator.io](https://www.digitalocean.com/community) site. Using localStorage, you wouldn’t have to change the theme every time you reopen the browser and visit the site.
7023
7024![Frontend web development](/images/blog/localstorage-complete-guide/656917a790dbf9088cd6dc6e_frontend-web-development.jpeg)
7025
7026## Benefits of localStorage
7027
7028Using localStorage offers a number of benefits, including the following:
7029
7030- More storage: As noted earlier, localStorage can store up to 5 MB of data in most browsers, much more than what cookies can hold.
7031- Developer-friendly API: The API makes it easy to access and add data in localStorage. On any browser, the localStorage functions are accessible from the Window object. You can add data just by writing window.localStorage.setItem ("Test Data", "Hello from localStorage").
7032- Persistence: One of the most common reasons for using localStorage is to keep the data persistent. While sessionStorage can also store data in key-value pairs, it’s cleared when the session ends. With localStorage, however, the data is continuous until it is explicitly removed.
7033
7034## LocalStorage Anti-Patterns
7035
7036## Anti-Pattern: Saving Access Tokens
7037
7038A widespread use of localStorage is to store access tokens, such as JWT tokens, on the user’s side so the user stays logged in for a specified amount of time.
7039
7040This is insecure and should never be done, since it’s accessible using [JavaScript](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices) on the same domain. This means any JavaScript code running on the page could access the storage, leaving your application vulnerable to cross-site scripting (XSS) attacks.
7041
7042Applications also frequently use third-party scripts to get analytics or ads, and if even a single script is compromised, you risk being hacked.
7043
7044## LocalStorage Use Cases
7045
7046Following are some common use cases for localStorage.
7047
7048## Saving Partially Submitted Form Data
7049
7050If a user is filling out a long form, localStorage can be helpful in storing partial data. Even if the internet is disconnected between beginning the form and submitting it, the user won’t lose their inputs and can continue from where they left off.
7051
7052## Caching
7053
7054According to a 2022 [analysis by Portent](https://www.portent.com/blog/analytics/research-site-speed-hurting-everyones-revenue.htm), customer conversion rate can increase by 2.5 times when your page loads in one vs. five seconds. Building a slow website is not an option anymore. But when an end user asks for specific data and the request has to travel across the wire with associated latency, caching c
7054an be a life-saver. The localStorage can be used to cache a website or to store static data to show the client information even when the page is offline, then fetch necessary data when the internet reconnects.
7055
7056## Syncing Data Between Tabs
7057
7058Using localStorage, a user can open a timer website on a browser tab, start the timer, then open another tab of the same website and sync the timer between both tabs. You can also sync music or your video player between tabs.
7059
7060## Pre-Populating Data
7061
7062You can use localStorage to store a pre-populated version of your application. When a user visits your application, they immediately see something on the screen, then your application can make calls to the backend to fetch new information. E-commerce sites often use localStorage to store items the user has viewed and other user-specific data.
7063
7064## Performing Operations on localStorage
7065
7066You have a few methods to choose from when performing operations on localStorage. They are:
7067
7068- setItem()
7069- getItem()
7070- removeItem()
7071- clear()
7072- key()
7073
7074Following are more details on each one.
7075
7076## Storing Values with setItem()
7077
7078The setItem method is used to store values to localStorage. This method takes in two parameters: a key and a value. The key is used to fetch the data later.
7079
7080Here’s a simple example of using the setItem method to store data:
7081
7082\`\`\`
7083window.localStorage.setItem("Data", "Hello from localStorage")
7084\`\`\`
7085
7086If you run this code on a browser console, it will store a key with the name Data and a value of Hello from localStorage on localStorage.
7087
7088![DevTools storing values with setItem](/images/blog/localstorage-complete-guide/656917caa237a90a968d44f4_devtools-storing-values-with-setItem--.gif)
7089
7090After running the code, if you open the Applications tab and click localStorage, you’ll be able to see the Data key.
7091
7092Note that you can only store strings in localStorage. (On Firefox and Chrome, the localStorage data is stored inside a Sqlite database.) If you want to keep arrays or objects, a straightforward way is to convert the data to JSON using the JSON.stringify method.
7093
7094## Accessing Items with getItem()
7095
7096The localStorage API uses the getItem method to retrieve data. The method takes a single argument that is the key to the data. If the data is not found, the method returns null.
7097
7098The syntax of the code is shown below:
7099
7100\`\`\`
7101window.localStorage.getItem("Data"); // Hello from localStorage
7102window.localStorage.getItem("data"); // null
7103\`\`\`
7104
7105![DevTools accessing items with getItem](/images/blog/localstorage-complete-guide/656918026c73ea3ca982ccba_devtools-accessing-items-with-getItem--.gif)
7106
7107## Removing Specific Item with removeItem()
7108
7109As the name suggests, the removeItem() method removes a specific key-value pair from localStorage. The syntax of the data is similar to getItem. It also takes a single parameter, the item’s key, and returns undefined if the item is not available.
7110
7111\`\`\`
7112window.localStorage.removeItem("Data");
7113\`\`\`
7114
7115You can check your Applications tab of the browser dev tools to confirm that the key is removed.
7116
7117## Deleting All Items with clear()
7118
7119If you want to clear localStorage of a particular domain, use the clear method. It doesn’t take any parameters and removes all localStorage items for the domain.
7120
7121\`\`\`
7122window.localStorage.clear();
7123\`\`\`
7124
7125## Retrieving Key Names with key()
7126
7127The key method comes in handy when you want to loop through the keys of localStorage. It takes a parameter of a number. The number can be an index position of the localStorage item.
7128
7129Here’s an example:
7130
7131\`\`\`
7132window.localStorage.setItem('Test', 'test'); // Adds New Item
7133window.localStorage.key(0); // Test
7134\`\`\`
7135
7136The returned value can be different if your localStorage for the domain already holds some value.
7137
7138## Listening to Storage Events
7139
7140To listen to events related to localStorage with DOM, use the storage event. Putting it inside a window.addEventListener can help you perform DOM operations when invoking a storage event.
7141
7142Below is a simple example of listening to storage events:
7143
7144\`\`\`
7145window.addEventListener("storage", myFunction);
7146function myFunction(event) {
7147  document.getElementById("app").innerHTML = "Change Made";
7148}
7149function changeValue() {
7150  const newWindow = window.open("", "myWindow");
7151  newWindow.localStorage.setItem("mytime", Date.now());
7152  newWindow.close();
7153}
7154\`\`\`
7155
7156Whenever a change is made to localStorage, the code sets an inner HTML to the div with the ID app to Change Made. In this case, a new item is added to localStorage for the new window, and after writing the value to localStorage, the window is closed.
7157
7158Here’s the HTML for the above code:
7159
7160\`\`\`
7161  <h1 id="app"></h1>
7162  <button onclick="changeValue()">Change a Storage Item</button>
7163\`\`\`
7164
7165You can also check out the [CodePen link](https://codepen.io/nemo011/pen/vYpqBzv) for the above example.
7166
7167## LocalStorage vs. SessionStorage
7168
7169LocalStorage and sessionStorage do have some similarities. For example:
7170
7171- Both storage types are offered by the web storage API
7172- Both can only store string-type values
7173- The data limit is about 5 MB in most cases
7174- Both storage types can only store values in key-value pairs
7175
7176However, there are several differences between the two. One is persistence: data stored in localStorage persists over sessions. Opening a new tab, visiting a new domain, or closing the browser will not clear localStorage. On the other hand, sessionStorage is cleared whenever a session ends. Opening a new tab or visiting a new domain will clear the session for a specific domain.
7177
7178Another difference is that, in the case of a few browsers, localStorage doesn’t work in incognito mode but sessionStorage does. In these cases, it’s a good idea to write a wrapper for your localStorage code in order to use sessionStorage.
7179
7180## LocalStorage vs. IndexedDB
7181
7182[IndexedDB](https://www.meticulous.ai/blog/getting-started-with-indexeddb) is an API for storing a large amount of structured data, including files, on the client side. The data stored using IndexedDB is also persistent and is stored until it’s explicitly cleared. IndexedDB also offers a built-in mechanism for schema versioning.
7183
7184IndexedDB offers several benefits over localStorage. For instance, IndexedDB [doesn’t block the DOM](https://nolanlawson.com/2015/09/29/indexeddb-websql-localstorage-what-blocks-the-dom/) when used with a worker, unlike localStorage. However, localStorage is slightly faster than IndexedDB. The API of localStorage is also much easier to get started with, making it the more popular choice.
7185
7186The primary reason for introducing IndexedDB was to make a [better version of localStorage](https://hacks.mozilla.org/2010/06/beyond-html5-database-apis-and-the-road-to-indexeddb/). So, why not use IndexedDB in all cases? If you want to store structured data on the client side, IndexedDB is the better choice, especially since localStorage isn’t built to store sensitive information. But if you’re storing a simple, small amount of key-value pair data, use localStorage.
7187
7188## Conclusion
7189
7190The localStorage feature can offer benefits to your applications, including open-ended storage to persist data for as long as you need it and the ability to make your applications work offline. This form of data storage won’t be the best choice in every use case, and you might need to consider IndexedDB instead in some instances.
7191
7192## Meticulous
7193
7194Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
7195
7196This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way 
7196we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
7197
7198This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
7199`,s3=`---
7200title: "Meticulous Announces $4 million Seed Round to Help Developers Make the World's Code Safe, Performant and Reliable"
7201slug: "meticulous-announces-4m-seed-round"
7202date: "2024-01-16"
7203description: "Meticulous, a Y Combinator startup, has closed a $4 million Seed Round to build the first automated front-end testing software for developers."
7204author: "Gabriel Spencer-Harper"
7205authorImage: "/images/blog/authors/655c3f789adcc1019a5a2caf_gabriel.png"
7206heroImage: "/images/blog/meticulous-announces-4m-seed-round/656a35f52aa5e9aae859ff06_meticulous-announces-4m-seed-round.png"
7207heroImageAlt: "Meticulous Announces $4 million Seed Round to Help Developers Make the World's Code Safe, Performant and Reliable"
7208categories: ["News"]
7209showTableOfContents: true
7210showPromo: true
7211---
7212
7213## By Gabriel Spencer-Harper, co-founder
7214
7215I am thrilled to share that Meticulous, a Y Combinator startup, has closed a $4 million Seed Round to build the first automated front-end testing software for developers. This capital will accelerate Meticulous’s mission to radically accelerate the pace of software development for every company in the world.
7216
7217Our investors include Coatue, Soma Capital, Base Case Capital and Y Combinator. We also continue to be supported by our incredible angel investors, including Jason Warner (CTO GitHub), Scott Belsky (CPO Adobe), Guillermo Rauch (founder Vercel), Calvin French-Owen (founder Segment), Jared Friedman (YC Partner), JD Ross (co-founder Opendoor) and Lachy Groom (Stripe).
7218
7219## Our Founding Story
7220
7221During my time as a software engineer at Dropbox and Opendoor, I discovered that however much you unit or integration test your code, you tend to find that actual users of your application find a way to break it.
7222
7223At Dropbox, our engineering team would sit in a meeting room for hours to manually test the same workflows, week after week. This testing was effective, but time-consuming. Alternatively, we invested in automated end-to-end tests, but these involved huge amounts of mocks that are painful to write and tedious to maintain.
7224
7225This prompted me to think about building a testing product for software engineers with zero setup and zero maintenance burden. Especially as I witnessed our engineering team at Dropbox throw away the recordings of our bug bashing sessions each week, I thought “what if there was a way to turn these into [automated tests](https://www.meticulous.ai/blog/testing-pyramid-for-frontend)?”
7226
7227This is how the idea for Meticulous was born – a first-of-its-kind software to automatically detect regressions in web applications with zero-effort from developers.
7228
7229## Meet Meticulous
7230
7231Meticulous’s goal is to record thousands of sessions and simulate those on new code to automatically catch bugs before they hit production.
7232
7233In the future, when a developer submits a code change, Meticulous will show them all the runtime information about that code: exceptions it introduces, visual differences in the UI, the performance impact, how many users it impacts and other runtime information. Imagine hovering over a line of code in your IDE and seeing its performance characteristics before it has ever been released into production.
7234
7235Building this requires solving many intricate technical problems, so we’re developing Meticulous in stages. Our first product is a tool that makes it easy for engineers to write end-to-end tests. Developers can now use our CLI to open an instrumented browser that records their actions and translates these into a test.
7236
7237Watch the demo [here](https://meticulous.ai/).
7238
7239## Why Now
7240
7241Only huge companies have the resources and enough engineers to build out automatic regression detection. Twilio invested heavily into implementing a system like this in the early 2010s. Google has a system still in use today, which diffs visual snapshots. For hundreds of thousands of smaller companies, however, developer teams are left on their own to create robust low-maintenance testing suites.
7242
7243One of the biggest problems for smaller developer teams simulating end-to-e
7243nd user flows on a production environment is that it will cause side effects. In order to overcome this, companies will often spin up a staging environment. However, building a staging environment that is representative of production is a tricky problem in and of itself. Furthermore, companies’ must implement a mechanism to reset state after each simulated session (imagine simulating a user signing up to your web application).
7244
7245Meticulous captures all network traffic at record-time and automatically mocks out all network calls at simulate-time. This isolates the frontend, avoids causing any side effects and allows any company to implement testing for their frontend code without any backend environment. This feature can be toggled off for stateless flows or for companies with a staging environment who want to test throughout the whole stack.
7246
7247Try Meticulous [here](https://app.meticulous.ai/signup).
7248
7249## What are our Investors Saying
7250
7251“The demand for software has outpaced the tooling to support developers. Meticulous is a solution to help front-end developers build more robust software that is safe and reliable as they build the future of our web-based economy. Coatue sees great potential from the early team Gabriel is leading and we look forward to supporting Meticulous’s evolution.” - Andy Chen, General Partner, Coatue
7252
7253“I spend the majority of my time with engineers — listening to their pain points, understanding their motivations, and helping connect them with relevant startups. Over and over, I hear the frustrations that come with creating and maintaining UI tests. For high velocity teams who want to build and ship valuable products to their users, UI tests are quite frankly a pain in the butt, yet shipping those products with bugs can cost you. Meticulous has assembled one of the strongest software engineering teams that we've seen, and I’m excited to see them build a product that automatically detects regressions in web applications, so that engineers can go back to focusing on what they do best: building.” - Alana Goyal, Base Case Capital
7254
7255"Regression testing as a whole, especially in an automated manner, remains a pressing challenge for engineering teams; particularly for those within companies that haven’t reached the scale to build in-house solutions. With Meticulous, teams are able to streamline the deployment of code and product launch, sans the bottleneck of timely UI tests The Soma team stands excited by the robust [UI testing](https://www.meticulous.ai/blog/frontend-unit-testing-best-practices) tool in development and looks forward to continuing to support Gabriel and the Meticulous team in their execution." - Aneel Ranadive, General Partner at Soma Capital
7256`,o3=`---
7257title: "Mocking a JavaScript Class with Jest, Two Ways to Make it Easier"
7258slug: "mocking-a-javascript-class-with-jest-two-ways-to-make-it-easier"
7259date: "2024-01-16"
7260description: "A guide on how to mock a JavaScript class using Jest, comparing dependency injection and mocking, along with guides on how to implement both."
7261author: "Geshan Manandhar"
7262authorImage: "/images/blog/authors/6568d05a61cacbddd7afcac0_geshan-manandhar.jpeg"
7263heroImage: "/images/blog/mocking-a-javascript-class-with-jest-two-ways-to-make-it-easier/656a51de168e1dff8f954229_mocking-a-javascript-class-with-jest.png"
7264heroImageAlt: "Mocking a JavaScript Class with Jest, Two Ways to Make it Easier"
7265categories: ["JavaScript", "Jest"]
7266showTableOfContents: true
7267showPromo: true
7268hidden: true
7269---
7270
7271[Jest](https://www.meticulous.ai/blog/how-to-use-jest-spyon) is the most popular automated testing framework for [JavaScript](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices). It can be used both on the front end and back end with Node.js. Jest is a feature-rich, batteries included testing framework. Amongst other valuable features, the ability to mock dependencies out of the box is a useful one.
7272
7273In this post, you will learn about the need for mocking in [unit testing](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices) and the difference between dependency injection and mocking. You will also get acquainted with mocking in Jest. Furthermore, you will see an example script with two classes and mock the dependent class to test the other class with a full code example using module factory and Jest SpyOn. Let’s get rolling!
7274
7275## Need of Mocking in Unit Testing
7276
7277There are multiple types of tests used in software engineering. Some of the popular ones are unit testing, integration testing, end-to-e
7277nd testing, [smoke testing](https://www.meticulous.ai/blog/smoke-testing), etc. Among these types, unit testing is one of the fastest and repeatably reliable forms of automated testing. [Unit tests](https://www.meticulous.ai/blog/frontend-unit-testing-best-practices) are super fast and predictably reliable because they test one particular unit of code in isolation. This unit of code is generally a function or method.
7278
7279To test a piece of code swiftly and have consistent reliable results all the other dependencies have to be replaced with mocks controlled by the software engineer writing the tests. For example, if you are testing a function that relies on a file in the file system or makes an HTTP call over the network these external resources must be replaced with mocks for the tests to be a unit test. Relying on any external resource can make the test results flaky and hence unreliable.
7280
7281Any external resource or even code outside the system under test should be mocked out. Replacing any external resource like network calls or file system access not only makes the unit test blazing fast but also removes any chance of the test failing due to external factors out of your control. For instance, if a test is doing an HTTP call to another server and that server is down for 1 hour, your unit tests don’t fail as the unit test verifies that piece of code in isolation sans any dependencies.
7282
7283> _To make the unit tests repeatedly reliable and blazing fast any external code and external resources must be replaced with dummy artifacts that are controlled by the software engineer. Jest also supports mocking in various forms, for the scope of this post you will learn how to mock an ES6 Class with Jest. In the next part, you will find out the difference between dependency injection and mocking._
7284
7285## Dependency Injection vs Mocking
7286
7287Dependency injection is a pattern where any dependency used in a class is not instantiated within the class, it is injected from an external source into the class. This source knows how to instantiate all required dependencies for a class to function properly. Separating the instantiation of objects from their use stems from the [inversion of control](https://en.wikipedia.org/wiki/Dependency_inversion_principle) concept. Conversely, if the dependencies for a class are initialized within the class, you lose the ability to easily swap them out when doing unit tests.
7288
7289For example, suppose you want to get the latest tweets of a person from their GitHub username. The tweets retriever class will need two other classes. The first is the GitHub client to pull in the profile of the person and pluck out the Twitter user name. The second is the Twitter client which will talk to the Twitter API to fetch the latest tweets. Both the Github and Twitter client classes can be passed into the tweets retriever class and then be used in the class.
7290
7291> _This eliminates the need for the tweets retriever class on how to create both dependencies and knowing things like the API keys for both services. It can be visualized as follows:_
7292
7293![Dependency injection visualized](/images/blog/mocking-a-javascript-class-with-jest-two-ways-to-make-it-easier/6569196e79dcea2163254cfa_dependency-injection-visualized-brph2.png)
7294
7295With the use of Dependency Injection, the Tweets Retriever receives the GitHub Client and the Twitter Client classes in its container. As these objects have been instantiated the Tweets Retriever class does not need any details or configuration to instantiate both of them. The Dependency Injection concept is geared more toward the object-oriented programming paradigm.
7296
7297Mocking is a technique used for unit testing where any code or non-code dependencies are swapped out with dummy implementations. The software engineer writing the tests has full control over these dummy implementations. These types of dummy objects have other forms too. Some of them are [spies, fakes, and stubs](https://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs). For instance, stubs respond with canned answers to calls when running tests. Spies are special stubs that also record some information based on how they were called.
7298
7299The main idea here in relation to the Tweets Retriever class is while running the unit tests both the dependencies of GitHub Client and Twitter Client can be swapped on the fly. These swapped mocks will respond with canned responses and never hit the real APIs. That will result in the tests being reliable and fast too.
7300
7301This is the point where mocking and dependency injection intertwine. If the Twitter client was instantiated in the constructor of the Tweets Retriever class with the new keyword, testing the Tweets Retriever class would be difficult. Because the Twitter Client class is injected into the constructor from outside, for the Unit test context a mock or spy can be easily injected in place of the real object. This creates a clear separation of concerns between constructing the object and using it. Dependency injection makes the code testable with writing unit tests becomes easier because any dependency can be injected at test time with a dummy implementation.
7302
7303> _For statically typed languages like Go or Rust, as long as the type is maintained for the parameters the tests will work without any problem. In the case of dynamically typed languages like JavaScript, types are not even considered. One difference between dependency injection and mocks is that dependency injection is related to object-oriented programming. Whereas mocks can be applied to other programming paradigms as well. For instance, if you use_ [_CommonJs modules_](https://nodejs.org/api/modules.html#modules-commonjs-modules) _exporting functions without using class constructs, these can be easily mocked with a testing framework like Jest or an NPM package like_ [_Rewire_](https://github.com/jhnns/rewire)_._
7304
7305> _For intercepting a require or import with a Mock in the context of a unit test, you might not need to understand dependency injection concepts. But for writing easily testable object-oriented code using dependency injection is a must. The above explanation should make the relationship between Dependency Injection and mocking for unit testing clear. In the next section, you can learn about mocking specifically for the Jest testing framework._
7306
7307## Mocking in Jest, an Introduction
7308
7309In Jest mocking is supported out of the box - this is unlike other JavaScript testing frameworks like Mocha. It has built-in [mock functions](https://jestjs.io/docs/mock-functions) that allow you to replace the actual implementation of a function, capture calls to a function, and verify parameters passed.
7310
7311Jest mock functions can also mock constructors and inject return values for mocked functions for the scope of the test. If you want to watch a method call but keep the original implementation or mock the implementation and later restore to the original implementation [Jest SpyOn](https://meticulous.ai/blog/how-to-use-jest-spyon/) should be used.
7312
7313In addition to mocking functions, Jest can also [mock modules](https://jestjs.io/docs/mock-functions#mocking-modules) or just mock a subset of a module. Jest can also mock ES6 Class and its methods. Calling Classes syntactic sugar on top of [prototypal inheritance](https://www.educative.io/blog/understanding-and-using-prototypal-inheritance-in-javascript#what-is) and “[special functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classe
7313s)” is an opinion that can be debated as another post. For the scope of this article, you will learn how to mock Class in unit tests written with Jest. The official documentation shows [4 ways](https://jestjs.io/docs/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock) to create an ES6 class mock with Jest. You will learn 2 ways that provide a balance between both flexibility and maintainability in the next section.
7314
7315## Prerequisites
7316
7317Before you get your hands dirty with the code, below are some good to have things:
7318
73191.  Any prior knowledge about Unit testing and Jest will be helpful
73202.  Knowing the [difference between CommonJs and ES modules](https://blog.logrocket.com/commonjs-vs-es-modules-node-js/) in Node.js will be nice to have, ES6 modules will be used in this example as it is a JavaScript standard.
73213.  Previous experience with mocking will be beneficial, as a pre-read going through [Jest SpyOn](https://meticulous.ai/blog/how-to-use-jest-spyon/) post will be worthwhile.
7322
7323All the code and tests have been run with the latest LTS version of Node.js which is 18, at the time of writing. With the requisites mentioned, you will be introduced to the example Classes in the next section.
7324
7325## Example Class - ExchangeRateApi Client
7326
7327As an example to mock ES6 Class with Jest, you will build a simple Exchange Rate API client. This basic client calls the [Open access endpoint](https://www.exchangerate-api.com/docs/free) from ExhangeRate-API. The open API is a bit limited but you can access it without any key or authentication. You can send only one currency and the data is refreshed once in 24 hours. The calls are also rate-limited at 1.5K requests per month.
7328
7329This simple client ES6 class calls the ExchangeRate API for the latest exchange rate of the from currency and picks the rate for the to currency. If the exchange rate is found in the response, it returns that back else returns 0. For example, if the latest rate for USD to AUD is requested, it will get the latest rates for USD and find the AUD value in the response from the API and then send it back. If AUD is not found in the response it will return 0. The code to do this looks like the below:
7330
7331\`\`\`
7332export default class ExchangeRateClient {
7333  constructor(axios) {
7334    axios.defaults.baseURL = 'https://open.er-api.com/v6';
7335    this.axios = axios;
7336  }
7337  async getLatestExchangeRate(fromCurrency = 'USD', toCurrency = 'AUD') {
7338    try {
7339      const response = await this.axios.get(\`/latest/\${fromCurrency}\`);
7340      return response.data.rates[toCurrency] || 0;
7341    } catch(e) {
7342      console.log(\`Error while getting exchange rate \${e.message}\`, e);
7343      return 0;
7344    }
7345  }
7346}
7347\`\`\`
7348
7349The class starts with an [export default](https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export#using_the_default_export) to export the ES6 class as it is the only thing to be exported in this module. It has a constructor that takes the popular [Axios](https://github.com/axios/axios) library object. Axios is used to make the HTTP call to the API. In the constructor, the base URL is set and the Axios object is set on the class level.
7350
7351The getLatestExchangeRate is the method that does the main task. It calls the API to fetch the latest exchange rates of the from currency and plucks out the rate of the to currency else returns 0. In case of any error, the catch block logs the error and returns back 0. Using the default parameters if the from and to currency pair is not passed USD and AUD will be used for them respectively. Below is a quick usage of the above Client class in the Service class. This Service class is the system under test (SUT) to understand how to mock an ES6 class with Jest:
7352
7353\`\`\`
7354import ExchangeRateClient from './exchangeRateClient.js';
7355import axios from 'axios';
7356export default class ExchangeRateService {
7357  constructor() {
7358    this.client = new ExchangeRateClient(axios);
7359  }
7360  async getLatestExchangeRate(fromCurrency = 'USD', toCurrency = 'AUD') {
7361    return this.client.getLatestExchangeRate(fromCurrency, toCurrency);
7362  }
7363}
7364\`\`\`
7365
7366This ExchangeRateService class is on the same level as the client file. Firstly, it imports the client and Axios. Then, in the constructor it instantiates the client passing in the needed Axios parameter.
7367
7368In the getLatestExchangeRate method you call the client's getLatestExchangeRate method passing the from and the to currency. Similar to the Client, here also default parameters have been used for the from and to currency pair. The code inclusive of the tests is available on [GitHub](https://github.com/geshan/jest-mock-es6-class) for your reference. You can clone the repository with:
7369
7370\`\`\`
7371git clone [email protected]:geshan/jest-mock-es6-class.git
7372\`\`\`
7373
7374Then go into the local folder with cd jest-mock-es6-class. To install the NPM packages you can run npm install and to quickly test out the script you can execute:
7375
7376\`\`\`
7377node index.js
7378\`\`\`
7379
7380The above command will render and output like the below:
7381
7382\`\`\`
7383Exchange rate from USD to AED for today is 3.6725
7384\`\`\`
7385
7386The USD to AED conversion pair is chosen because AED is [pegged](https://www.oanda.com/currency-converter/en/currencies/majors/aed/) to the US dollar so the rates don’t change. In the next section, you will grasp how to set up tests using Jest.
7387
7388## Setup Tests with Jest using AAA
7389
7390Regardless of the language or the framework, unit tests follow a pattern of [Arrange Act Assert 
7390(AAA)](https://automationpanda.com/2020/07/07/arrange-act-assert-a-pattern-for-writing-good-tests/). First, the inputs, mocks, parameters, and targets are arranged. Then the system under test which is generally a function is called or acted on to verify the target behavior. Finally, expected outcomes are asserted after the Act. Assertions can be simple as checking value or also checking that the mock functions are called with expected parameters.
7391
7392> _For any type of test including unit tests, assertions will eventually decide if a given unit test passes or fails. Following the AAA pattern, you will mock ES6 class to test the behavior of the ExchangeRateService class which imports the ExchangeRateClient. As each unit test should only test the system under test, for the context of this post it will be the ExchangeRateService. To be a unit test the ExchangeRateClient class must be mocked out. The official Jest docs list 4 ways to mock an ES6 class, they are automatic mock, manual mock, jest.mock with module factory parameters, and replacing using mock implementation. Saying the docs are convoluted would not be an overstatement. To simplify things, use the AAA pattern and follow these_ [_unit testing best practices_](https://meticulous.ai/blog/frontend-unit-testing-best-practices/)_. In the next section, you will use the module factory parameter and later mock specific methods with Jest SpyOn._
7393
7394## Mocking a JavaScript Class in Jest
7395
7396There are multiple ways to mock an ES6 class in Jest. To keep things simple and consistent you will use the module factory parameters method and jest SpyOn to mock specific method(s) of a class. These two methods are not only flexible but also maintainable down the line. Both methods give you the flexibility to change these mocks for each test. You will replace the whole object with the module factory pattern. It is used if you want to mock most or all of the methods in the class. If there are one or two methods to mock for the unit test, the spy method will be better suited.
7397
7398You can get started with the module factory parameter mocking in the subsequent section.
7399
7400## Mocking with Module Factory Parameter
7401
7402A module factory is a function that returns the mock. In Jest the jest.mock(path, moduleFactory) does the job by taking the module factory as the second parameter. A module factory must be a function that returns a function. It will be much easier to understand this with a code example:
7403
7404\`\`\`
7405import ExchangeRateClient from '../src/exchangeRateClient.js';
7406import ExchangeRateService from '../src/exchangeRateService.js';
7407
7408const mockGetLatestExchangeRate = jest.fn().mockResolvedValueOnce(3.6725).mockResolvedValueOnce(0);
7409jest.mock('../src/exchangeRateClient.js', () => {
7410  return jest.fn().mockImplementation(() => {
7411    return { getLatestExchangeRate: mockGetLatestExchangeRate };
7412  });
7413});
7414
7415describe('ExchangeRateService', () => {
7416  let service = {};
7417
7418  beforeEach(() => {
7419    service = new ExchangeRateService();
7420  });
7421
7422  it('should be defined', () => {
7423    expect(service).toBeDefined();
7424  });
7425
7426  describe('getLatestExchangeRate', () => {
7427    it('should get the latest exchange rate', async ()=> {
7428      const latestExchangeRate = await service.getLatestExchangeRate('USD', 'AED');
7429      expect(latestExchangeRate).toBe(3.6725);
7430      expect(ExchangeRateClient).toHaveBeenCalled();
7431
7432      expect(mockGetLatestExchangeRate).toHaveBeenCalled();
7433      expect(mockGetLatestExchangeRate).toHaveBeenCalledWith('USD', 'AED');
7434    });
7435
7436    it('should return 0 as latest exchange rate in case of error on client', async ()=> {
7437      const latestExchangeRate = await service.getLatestExchangeRate('USD', 'CAD');
7438      expect(latestExchangeRate).toBe(0);
7439      expect(mockGetLatestExchangeRate).toHaveBeenCalled();
7440    });
7441  });
7442});
7443\`\`\`
7444
7445It begins by importing the ExchangeRateClient and ExchangeRateService from the src folder. The system under test for this unit test is the service class. Next, you define a constant named mockGetLatestExchangeRate which is assigned as jest.fn(). This mock will return two promise resolved values of 3.6725 and 0 respectively for the two consecutive calls.
7446
7447One important thing to take note of here is the const’s name is prefixed with mock. This prefix is a requirement in Jest, since calls to jest.mock() are [hoisted on the top of the file](https://jestjs.io/docs/es6-class-mocks#calling-jestmock-with-the-module-factory-parameter). If you name this mock variable beginning with fake or spy it will not work throwing a ReferenceError. You can learn more about [JavaScript reference errors](https://meticulous.ai/blog/javascript-reference-errors/), how they happen and how to prevent them too. When writing tests be careful about [JavaScript syntax](https://meticulous.ai/blog/how-to-prevent-javascript-syntax-errors/) errors as well.
7448
7449Next, the module factory is used to mock the client class, and the factory returns a jest mock implementation function. This function returns a dummy object having the getLatestExchangeRate method which is assigned the mock function. The mock function was created in the previous line. The main thing to remember here is, the whole client class has been mocked with a Module factory function. This factory function returns a dummy object with a method that has the mock function assigned to it.
7450
7451Then the describe part is written where a service is instantiated in the beforeEach hook. This service is your system under test. After that, a very simple test is written to check the service variable is defined.
7452
7453Consequently, you write a new describe named the same as the method n
7453ame of getLatestExchangeRate. The first test here is to verify the getLatestExchangeRate method of the service returns an exchange rate for the given pair of from and to currency. This is the happy path. The arrangement part has already been done ahead of all tests in the beforeEach hook and above it. The act part of calling the service’s getLatestExchangeRate methods is done here with USD and AED as two parameters for the from and to currency respectively. The test is to get the latest rate of 1 USD to AED and it is expected to be 3.6725. The value 3.6725 is the same as the first resolve value of the mock.
7454
7455Next, the constructor mock for the client is expected to have been called. Similarly, the mock function is also expected to have been called and to have been called with the currency pair of USD to AED.
7456
7457After that, there is another test verifying the error scenario. In case of any errors, a 0 is returned. The main difference is the value returned is asserted to be 0. The expected 0 is the second resolved value of the mock function. In the end, the mock function is again expected to have been called. The main section of the above test is where the Exchange rate client has been replaced by the mock object using the module factory. Another noticeable part is the use of a mock function. This mock function comes in very handy later to check that it has been called. As the system under test is the service, the calls to the client class have been mocked. The constructor of the client class has also been mocked with a function that returns the dummy object.
7458
7459In the next section, you will witness a different way to mock the ES6 Class for the client with Jest.spyOn to focus on the client class’ getLatestExchangeRate method. The test’s assertions will look almost the same.
7460
7461## Mocking Specific Method with SpyOn
7462
7463Another way to mock specific methods of an ES6 class with Jest is by using the Jest.spyOn method. It helps to selectively mock the needed method(s) without the need to mock the whole class.
7464
7465> _Using jest spyOn to mock a specific method of an ES6 class is like performing a surgical operation. Using it you can target one or more specific methods called in the test’s context. Below is an example code of the ES6 class mock using Jest spyOn to mock the ExchangeRateClient class' \\\`\\\`getLatestExchangeRate\\\` method only._
7466
7467\`\`\`
7468import ExchangeRateClient from '../src/exchangeRateClient.js';
7469import ExchangeRateService from '../src/exchangeRateService.js';
7470
7471const getLatestExchangeRateSpy = jest.spyOn(ExchangeRateClient.prototype, 'getLatestExchangeRate')
7472  .mockResolvedValueOnce(3.6725)
7473  .mockResolvedValueOnce(0);
7474
7475describe('ExchangeRateService', () => {
7476  let service = {};
7477
7478  beforeEach(() => {
7479    service = new ExchangeRateService();
7480  });
7481
7482  it('should be defined', () => {
7483    expect(service).toBeDefined();
7484  });
7485
7486  describe('getLatestExchangeRate', () => {
7487    it('should get the latest exchange rate', async ()=> {
7488      const latestExchangeRate = await service.getLatestExchangeRate('USD', 'AED');
7489      expect(latestExchangeRate).toBe(3.6725);
7490
7491      expect(getLatestExchangeRateSpy).toHaveBeenCalled();
7492      expect(getLatestExchangeRateSpy).toHaveBeenCalledWith('USD', 'AED');
7493    });
7494
7495    it('should return 0 as latest exchange rate in case of error on client', async ()=> {
7496      const latestExchangeRate = await service.getLatestExchangeRate('USD', 'CAD');
7497      expect(latestExchangeRate).toBe(0);
7498      expect(getLatestExchangeRateSpy).toHaveBeenCalled();
7499    });
7500  });
7501});
7502\`\`\`
7503
7504This test looks similar to the above test. The main difference here is how the Client ES6 Class has been mocked. Rather than mocking the whole class, a spy has been attached to the getLatestExchangeRate method of the class’ [prototype](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#inspecting_prototypes_a_deeper_dive). This is done because JavaScript works with [Prototypal inheritance](https://javascript.info/prototype-inheritance). Regular object-oriented programming inheritance is different than Prototypal inheritance. It can be a different tangent for another post.
7505
7506This makes it possible to intercept the call to the real class and doctor in the values you want the method to respond with for the test’s context. This manipulation is happening just below the imports in the above test code with jest.spyOn(ExchangeRateClient.prototype, 'getLatestExchangeRate').
7507
7508The rest of the test is similar to the above test code. The other difference in this test c
7508ode is there is no expectation for the constructor to be called. As the whole object with the constructor has not been mocked it is not relevant for this way of testing.
7509
7510Another way to partially mock an ES6 class is with [jest.requireActual](https://jestjs.io/docs/jest-object#jestrequireactualmodulename). If a class has 5 methods, it can be used to mock 2 and leave the remaining 3 as actual implementation. There you have it, two ways to mock the whole ES6 class or just a single (or more) method of a class. These methods are flexible and easily maintainable.
7511
7512## Conclusion
7513
7514In this post, you learned about some deep concepts related to unit testing with a focus on mocking and its interwoven relation with dependency injection. Then you witnessed a simple yet useful script to get the latest currency conversion rates which involved 2 ES6 classes. Then you tested the service class that had a dependency on the client class. The client class was mocked while testing the service class. A couple of main takeaways from this post are, always exploit dependency injection to make your unit testing easier.
7515
7516> _There are multiple ways to mock an ES6 class in Jest, use a consistent way that works for you, and is flexible and maintainable. You can test your ES6 classes smoothly without running into unnecessary roadblocks following a consistent way. Keep mocking those ES6 classes effortlessly as shown in the above_ [_examples_](https://www.meticulous.ai/blog/frontend-unit-testing-best-practices)_._
7517
7518## Meticulous
7519
7520Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
7521
7522This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
7523
7524This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
7525`,r3=`---
7526title: "Power <> Meticulous case study"
7527slug: "power-meticulous-case-study"
7528date: "2024-03-26"
7529description: "An overview of Power's feedback regarding their experience with Meticulous."
7530author: "Gabriel Spencer-Harper"
7531authorImage: "/images/blog/authors/655c3f789adcc1019a5a2caf_gabriel.png"
7532heroImage: "/images/blog/power-meticulous-case-study/65fc793430d60c2932345f46_power-meticulous-case-study.png"
7533heroImageAlt: "Power <> Meticulous case study"
7534categories: ["Resources"]
7535showTableOfContents: true
7536showPromo: true
7537---
7538
7539## What does your company do?
7540
7541[Power](https://www.withpower.com/) connects patients with clinical trials. We're making it easier for patients who have a specific condition to find leading medical researchers. At the same time we help researchers who are conducting these clinical trials find patients to bring their treatments to market.
7542
7543## How did you hear about Meticulous?
7544
7545One of our co-founders heard about it through an investment group. At the time I think you had just got your seed round.
7546
7547We were looking into different frontend testing systems and then he said, “Hey take a look at Meticulous!”. We looked at the site and it seemed like it was too good to be true, and so we reached out.
7548
7549## What were Power looking for in a front-end testing solution?
7550
7551I’ve worked on frontend tests before, both unit testing and puppeteer-style tests. Those are always a lot of work to write - it would often take longer to write the test than the change itself. They are also brittle, where sometimes it’s hard to know if a failure is intentional or not.
7552
7553We wanted to do better than that. We wanted to have a system where our team didn't have to spend a lot of time writing or maintaining tests.
7554
7555## How did Meticulous match up to your expectations?
7556
7557It far exceeded anything I expected.
7558
7559Meticulous does a great job of covering our front end testing. We trust it fully, like 100%. We definitely get coverage on the specific surface areas that we want, such as guaranteeing our signup flow works.
7560
7561However, what blows us away is it catches bugs that we wouldn’t have even thought about. We wouldn’t have even written tests for these classes of bugs. But because of the way it works, by recording user flows, it catches a class of errors we didn’t even think were possible to catch with a framework.
7562
7563## Do you have any metrics or KPIs or anything that show how meticulous has been doing?
7564
7565We essentially don’t have UI regressions anymore.
7566
7567I know you had a short outage in December and then we had a bunch of regressions suddenly appear, and we were all like “this is weird, what’s going on?” and it turned out it was an outage.
7568
7569I know you’ve fixed those issues and implemented better alerting to catch these. The support was great. But it actually kind of showed us how much Meticulous was catching for us.
7570
7571The other metric would be in terms of time saved.
7572
7573One way to think about this is for every 1 hour of frontend dev time I'd expect to spend 15 mins writing traditional frontend tests, and maybe 10% of the time would introduce a regression that would take 30 mins to fix.
7574
7575So across the team of 6, if we spent 40 hours per week building frontend, that's 10 hours saved on not building frontend testing, and 2 hours saved not fixing regressions. I'd say the team probably spends 1 hour per week cumulative using the meticulous system.
7576
7577So maybe Meticulous saves 10 hours for every 40 hours of frontend engineering time or 25%.
7578
7579## What have you been most impressed with?
7580
7581How responsive the team has been.
7582
7583The team has been great to work with, and flexible. For example, we have a lot of sensitive data we’re dealing with and need to treat carefully. The team worked with us to make sure the setup was secure.
7584
7585The other aspect is that with traditional testing, there’s all sorts of upkeep. Like this test is flaky, or this test is broken, or we need to update our chrome version.
7586
7587What’s amazing about Meticulous is there’s no upkeep.
7588
7589It’s kind of funny, you ask me how meticulous is going and the answer honestly is I didn’t even think about it this week. It just works nicely in the background and catches bugs when there are issues.
7590
7591## What plans do you have to use Meticulous in future?
7592
7593This is kind of out there. We send a lot of emails. We found a way to render emails in webpages so that Meticulous could test these as well. Not sure if that’s on your roadmap, but better support for emails.
7594
7595We trust this system and so we want to get as much of our UI surface into it.
7596
7597## You signed a 2-year contract, what made Power take that decision?
7598
7599We think of it as a core part of our testing infrastructure now and we’re happy to invest in the pieces which we know are foundational to our engineering org.
7600
7601## When you open a meticulous diff page, how long does it take you?
7602
760390% of the time I spend less than 20 seconds looking at it. Usually it’s pretty obvious to me what’s going on.
7604
7605## Anything else we should know?
7606
7607One other angle that has been exciting for us, and I think you’re going to love this: Being a software developer at Power is better because of Meticulous. We have better developer satisfaction!
7608
7609We spend way more time building stuff and not worrying about some classes of UI test implementation.
7610
7611We talk a lot about developer experience at Power. Meticulous is a fantastic example where it solves a lot of problems which developers don’t like, and solves this in a really elegant way. So I think we’re proud of it when we onboard new team members and say, ”Hey, you don’t have to write frontend tests anymore, this is all handled for you!”
7612
7613PS. [Power is hiring](https://trypower.notion.site/Careers-at-Power-f885aa53d8d14a288dd154f13b644e4a)!
7614`,l3=`---
7615title: "5 Rainforest QA Alternatives to Consider in 2024"
7616slug: "rainforest-qa-alternatives"
7617date: "2024-01-16"
7618description: "This article compares Rainforest QA with five popular alternatives, evaluating their visual testing capabilities, standout features, and pricing to help you choose the right tool."
7619author: "Kasper Siig"
7620authorImage: "/images/blog/authors/6556f52006849f153f151c05_kasper-siig.png"
7621heroImage: "/images/blog/rainforest-qa-alternatives/656a1afd825962cfb448524a_rainforest-qa-alternatives.png"
7622heroImageAlt: "5 Rainforest QA Alternatives to Consider in 2024"
7623categories: ["Testing"]
7624showTableOfContents: true
7625showPromo: true
7626hidden: true
7627---
7628
7629The constant evolution of technology and the increasing complexity of online platforms make for a variety of challenges when it comes to visual testing — and the simultaneous demands for cross-platform compatibility and speedy releases present even more challenges.
7630
7631In this article, we'll highlight Rainforest QA as one way to overcome these challenges. Rainforest QA is an option for:
7632
7633- No-code testing
7634- Automating visual testing
7635- Continuous visual testing
7636
7637Then we'll compare Rainforest QA with five popular alternatives that might better suit your needs.
7638
7639## Essential Features of Effective Visual Testing Tools
7640
7641Identifying visual differences — like a button with a changed color or a new text field — is a core feature of visual testing tools. Although such changes are often intentional, unwanted changes do occur as well.
7642
7643And unintended changes can negatively affect user experience, which can ultimately lead to [user churn](https://www.investopedia.com/terms/c/churnrate.asp). This is why organizations often look for tools to aid them in generating test cases, executing tests, and reporting results.
7644
7645When you're [researching testing tools](https://www.meticulous.ai/blog/visual-regression-testing-tools), it's important to be aware of the differences between script-based and traffic-based testing. The former is what most organizations have used for decades: with this method, engineers use scripts to replicate user behavior. However, replicating user behavior exactly is often difficult — especially in visual testing. This is why [traffic-based testing](https://speedscale.com/blog/reduce-risk-with-production-traffic-replication/) has seen a rise in popularity. With this method, user interactions are recorded and replayed to ensure that all the details of a user's behavior are captured.
7646
7647## Rainforest QA's Visual Testing Capabilities
7648
7649Rainforest QA is a no-code solution that focuses on real-world testing scenarios. It enables users to write, run, and maintain tests without needing any coding knowledge. Rainforest QA implements what the company refers to as "[true UI testing](https://www.rainforestqa.com/blog/user-interface-testing)" — in other words, a test's ability to accurately replicate typical user behavior: for example, clicking fields, entering text, and submitting forms.
7650
7651This approach differs from that of many other visual testing platforms — visual testing tools often interact with the underlying code — and provides a genuine representation of the user experience. Ultimately, this makes it easier to identify and mitigate UI discrepancies that other testing methodologies might miss. Rainforest QA offers three pricing plans tailored to different needs:
7652
7653- The free Essentials plan includes up to five hours of automated testing each month.
7654- The Startup plan provides five free hours of automated testing every month, with every hour beyond that costing $5 per hour. On-demand manual testing is available at $25 per hour.
7655- With pricing starting at $30,000 per year, the Unlimited plan is aimed at larger enterprises or organizations with extensive testing needs.
7656
7657When looking for alternatives, you should:
7658
7659- Research the unique advantages and limitations of each platform.
7660- Understand the core testing capabilities of each tool.
7661- Consider cost and integration. Although Rainforest QA is a strong contender, its cost structure will not be suitable for everyone. Comparing Rainforest QA with its competitors is essential.
7662
7663## Exploring Alternatives to Rainforest QA
7664
7665In addition to whether a tool offers script-based or traffic-based testing, there are many factors to consider when you're deciding which testing option is the right one for you.
7666
7667### Meticulous: An Integrated Visual Testing Solution for Developers
7668
7669![Meticulous website](/images/blog/rainforest-qa-alternatives/6568de74753c08d0edd5cc65_meticulous-for-visual-testing.png)
7670
7671[Meticulous](https://www.meticulous.ai/) offers a suite of features that help ensure visual consistency and quality. It has a strong integration capability, and it allows developers to run automatic [visual regression](https://www.meticulous.ai/blog/the-beginner-guide-to-visual-regression-testing) tests on pull requests, so they can make sure that code changes don't introduce visual discrepancies.
7672
7673More importantly, Meticulous eliminates the need to maintain tests, due to its reliance on recorded user behavior. Note that this is different from other "no-code" tools, as those tools require test maintenance, while Meticulous automatically updates and maintains the test suite with no developer intervention.
7674
7675Browsers are inherently non-deterministic, so all tools come with an inherent risk of flakes due to factors like:
7676
7677- Animations
7678- Changing dates and times
7679- Random numbers
7680- Timers
7681- Libraries
7682
7683Given that Meticulous reports visual inconsistencies by analyzing the [pixel depth](https://support.esri.com/en-us/knowledge-base/faq-what-does-the-pixel-depth-mean-000006576) of screenshots, it's crucial that these screenshots match the original recording exactly.
7684
7685The solution to this is two-part: mocking any dependencies (for instance, databases or third-party APIs) and fully replicating the browser environment with augmentations to make it completely deterministic.
7686
7687Moreover, because of the use of recordings and pixel depth, you'll never need to maintain your visual tests again, whether new features are being added or old features are being removed. Simply use the new UI, have [Meticulous's script](https://app.meticulous.ai/docs) record it, and you're all set.
7688
7689Meticulous's [compatibility with CI/CD pipelines](https://app.meticulous.ai/docs/ci) ensures that every build undergoes rigorous [visual regression tests](https://www.meticulous.ai/blog/the-beginner-guide-to-visual-regression-testing). Other integrations, such as [Vercel](https://app.meticulous.ai/docs/cloud-replay), allow you to deploy a live version of each pull request, which ensures that the new code being tested is as close to your production environment as possible.
7690
7691Testimonials from people who have tried Meticulous make it clear that not having to write tests is a major benefit of the tool.
7692
7693![Client feedback on Meticulous](/images/blog/rainforest-qa-alternatives/6568e03db076ff0e9cc17263_meticulous-testimonials.png)
7694
7695Meticulous offers a Free plan that includes 10 pull requests per month, a $399 Starter plan that includes 40 pull requests per month, and custom pricing for larger customers.
7696
7697### Test IO: A Crowdtesting Solution for Visual QA Teams
7698
7699![Test IO website](/images/blog/rainforest-qa-alternatives/6568e03d3c03637732084b36_test-io-website.png)
7700
7701Test IO offers a unique approach called "crowdtesting," which leverages the insights and expertise of a diverse group of individuals to test digital products. In other words, the testers are real people who use the product in real-world scenarios. This can catch bugs that may go unnoticed with automated tools.
7702
7703Test IO's crowdtesting model is both flexible and scalable, making it appropriate for organizations of any size and in any industry. Its platform is designed with users in mind; it has an intuitive dashboard that allows you to easily create, manage, and monitor tests.
7704
7705Their pricing model is flexible, with Startup, Pro, and Enterprise options, each billed as a monthly subscription. However, to get the exact pricing, you'll need to [contact their sales team](https://get.test.io/pricing/).
7706
7707The actual cost of Test IO depends on the number of testers and test cycles required. The Startup and Pro options are well-suited to baseline testing (exploratory testing, coverage, focused testing, and so on). They differ primarily in the number of tests you can run. However, the Pro option does provide some unique additional features, including:
7708
7709- Rapid testing
7710- On-demand bug validation
7711- On-demand bug reproduction
7712
7713The Enterprise package includes dedicated support, unlimited test cycles, and integration assistance, along with potential add-ons, including:
7714
7715- Accessibility testing
7716- Location testing
7717- Real Payments Testing
7718
7719### Sauce Labs: Emulation-Based Device Testing
7720
7721![Sauce Labs website](/images/blog/rainforest-qa-alternatives/6568e03d3a443a9259457eba_sauce-labs-testing.png)
7722
7723[Sauce Labs](https://saucelabs.com/products/visual-testing) provides a blend of emulators, simulators, and real devices, ultimately delivering great test coverage — without compromising on speed. The real-time feedback allows users to monitor tests as they run. Sauce Labs' pricing is based on usage, with options for both manual and automated testing.
7724
7725Teams can streamline their testing processes, track bugs, manage test cases, and share knowledge seamlessly with [Sauce Labs' integrations](https://saucelabs.com/products/integrations-and-plugins) with TestRail, Slack, Jira, GitHub, and Confluence.
7726
7727| Plan                   | Price                            | Details                                                                                                                                                                                                                                                                                                                  |
7728| ---------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
7729| Free                   | $0                               | 2,000 free credits per week. Ru
7729n tests on up to 10 devices in parallel. Includes 2,000 free credits per week to run Automated and Live Tests. Credits expire weekly. Thousands of mobile devices, emulators/simulators, and browser/OS combinations. Unlimited users.                                                    |
7730| Monthly (Starter Plan) | $50 per month (+ taxes and fees) | 25,000 credits/month. Run tests on up to 25 devices in parallel. Includes 25,000 credits per month to run Automated and Live Tests. Credits expire monthly. Thousands of mobile devices, emulators/simulators, and browser/OS combinations. Unlimited users. Auto-refill credits.                                        |
7731| Annual                 | $600 per year (+ taxes and fees) | 330,000 credits/year (30,000 bonus). Run tests on up to 50 devices in parallel. Includes 300,000 credits + 30,000 bonus credits per year to run Automated and Live Tests. Credits expire annually. Thousands of mobile devices, emulators/simulators, and browser/OS combinations. Unlimited users. Auto-refill credits. |
7732| Enterprise             | Custom                           | Reliable & premium support for enterprise. Unlimited automated testing minutes. Private device cloud. Sauce Connect proxy local tunneling. Single sign-on (SSO). Team management. Premium support options. Enterprise-grade security.                                                                                    |
7733
7734### Cypress: A Javascript-based Tool for Developers
7735
7736![Cypress testing](/images/blog/rainforest-qa-alternatives/6568e0d2ebfcbace373f5c31_cypress-testing.png)
7737
7738[Cypress](https://www.cypress.io/) is a robust testing tool that addresses the challenges of non-deterministic tests. Flakiness due to dynamic content is a common issue in visual testing, which is why [Cypress has built-in retry-ability features](https://www.meticulous.ai/blog/cypress-testing). That being said, retries provide no guarantees of not producing flakes, they simply lower the chance of them occurring in the test suite as a whole.
7739
7740The focus on aiding developers is clear from the beginning — especially when you look into features like explicit waits, mocking, and randomization. Cypress's ecosystem is enriched by an abundance of [plugins and integrations](https://docs.cypress.io/plugins/), making it a Swiss Army knife for visual testing: perfectly adequate for most use cases, but rarely the most optimal solution.
7741
7742Its interactive and real-time testing environment provides immediate feedback and allows developers to see exactly what happens during each test step. The Test Runner UI shows passed and failed tests in distinct colors, and is particularly praised for its clarity.
7743
7744![Cypress test runner ui](/images/blog/rainforest-qa-alternatives/6568e0d2abc4df6a9ec45615_cypress-test-runner-ui.png)
7745
7746| Plan       | Cost                                   | Users     | Included Test Results | Additional Test Results   | Features                                                                                            |
7747| ---------- | -------------------------------------- | --------- | --------------------- | ------------------------- | --------------------------------------------------------------------------------------------------- |
7748| Free       | $0/month                               | 3         | 500/month             | Not applicable            | Parallelization, Load Balancing, Debugging, Analytics, Integrations                                 |
7749| Team       | $67/month (Billed annually at $799)    | 10        | 120,000/year          | $6 per 1,000 test results | Everything in the Free plan, Flake Detection, Flaky Test Analytics, Jira Integration, Email Support |
7750| Business   | $267/month (Billed annually at $3,199) | 40        | 120,000/year          | $5 per 1,000 test results | Everything in the Team plan, Smart Orchestration, GitHub & GitLab Enterprise, SSO                   |
7751| Enterprise | Custom pricing based on needs          | Unlimited | Custom                | Custom                    | Everything in the Business plan, Premium Support, Custom Legal Terms                                |
7752
7753### Browserstack: Real-World Device Testing
7754
7755![Browserstack website](/images/blog/rainforest-qa-alternatives/6568e199b995b7283ddb3bb3_browserstack-testing.png)
7756
7757[BrowserStack](https://www.browserstack.com/) is a cloud-based platform that offers reliable, on-demand testing solutions for digital products. Its real device cloud is constantly updated, ensuring that testers get the most accurate results. Its cloud-based nature ensures consistent performance and accessibility from any location.
7758
7759This is quite different from tools like Sauce Labs, which focuses on emulation. Although emulation is often more than enough, there may be cases where you need to verify on a real physical device — for instance, if you rely on a phone's satellite antenna.
7760
7761With its vast device and browser offerings, [BrowserStack](https://www.meticulous.ai/blog/browserstack-vs-saucelabs) provides a comprehensive solution for ensuring application quality across platforms. Its user-friendly interface allows you to quickly select devices, browsers, and operating systems from a comprehensive list and initiate tests with just a few clicks.
7762
7763However, pricing is a consideration, as Browserstack offers a tiered pricing structure with separate plans for live testing, automation, and app testing. For larger organizations or those with specific requirements, Browserstack provides custom enterprise solutions with dedicated support and SLAs. The exact pricing varies a lot based on your specific needs, both in terms of your team size and feature set. To see what your exact cost will be, visit [BrowserStack's pricing page](https://www.browserstack.com/pricing?product=live).
7764
7765## A Comparative Analysis of Rainforest QA and Its Alternatives
7766
7767- Meticulous detects UI bugs without requiring that users write or maintain UI tests, so it offers immediate testing with minimal maintenance. Its Free plan includes 10 pull requests per month, its $399 Starter plan includes 40 pull requests per month, and custom pricing is available for organizations with greater needs.
7768- Test IO provides real-world testers for visual testing. It encompasses exploratory, regression, and functional testing. Pricing isn't public.
7769- Sauce Labs focuses on emulation-based device testing. There's a free option for getting started, with additional packages available depending on your needs.
7770- Cypress is a JavaScript-based tool known for non-deterministic tests that can sometimes result in inconsistencies. It supports testing on real devices and emulators. While there's a free open-source version for smaller entities, paid options are also available.
7771- BrowserStack specializes in real-world device testing, offering testing on actual devices and emulators across various platforms. However, its pricing might be on the steeper side for bigger clients, and requires careful calculations to determine exactly.
7772
7773When you're selecting a visual testing tool, choosing one that aligns with your business's specific needs is essential. Each tool highlighted in this article has distinct advantages and potential drawbacks. Factors like budget, testing requirements, and team size will influence your final decision.
7774
7775| Tool          | Primary Testing Types               | Pricing                                                                     |
7776| ------------- | ----------------------------------- | --------------------------------------------------------------------------- |
7777| Rainforest QA | Functional, Regression, Load        | Custom pricing plans                                                        |
7778| Meticulous    | Functional, Visual Regression       | Free starter plan for 20 pull requests, custom pricing for larger customers |
7779| Test IO       | Exploratory, Regression, Functional | Pay-per-bug pricing model                                                   |
7780| Sauce Labs    | Emulation-based device              | More flexible than Rainforest QA                                            |
7781| Cypress       | Non-deterministic                   | Free and open-source version, enterprise pricing can be expensive           |
7782| Browserstack  | Real-world device                   | Expensive for larger customers, tiered pricing structure                    |
7783
7784## Meticulous
7785
7786Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
7787
7788This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
7789
7790This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here](/).
7791`,c3=`---
7792title: "JavaScript RangeErrors | How to Prevent Them, Tutorial"
7793slug: "range-errors-and-how-to-prevent-them"
7794date: "2024-01-16"
7795description: "The complete guide on JavaScript RangeErrors, how they can occur, and techniques and tools available to prevent them."
7796author: "Jacob Goldfarb"
7797authorImage: "/images/blog/authors/65691bb96a65d8fb22b2f2e2_jacob-goldfarb.jpeg"
7798heroImage: "/images/blog/range-errors-and-how-to-prevent-them/65691ad51147e20f0fe69de0_range-errors-and-how-to-prevent-them.webp"
7799heroImageAlt: "JavaScript RangeErrors | How to Prevent Them, Tutorial"
7800categories: ["JavaScript", "Tutorial"]
7801showTableOfContents: true
7802showPromo: true
7803---
7804
7805If you’ve spent time developing in [JavaScript](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices), you’ve likely come across a RangeError. A RangeError is one of the eight Error subtypes that are a part of the JavaScript standard built-in objects. Other standard errors include the AggregateError, EvalError, ReferenceError, SyntaxError, TypeError, and URIError. The InternalError can also be mentioned here, but it’s currently not standard and not on track to become standard.
7806
7807In this article, you’ll learn all about RangeErrors and what causes them. You’ll also learn how to effectively deal with RangeErrors when they occur and, more importantly, how to mitigate the error in the future.
7808
7809## What is a RangeError
7810
7811A RangeError is used to convey to a user that a value was passed into a function that does not accept an input range that includes that value. For example, if a function converts centimeters to inches, then the input range might be all the non-zero positive numbers. In this instance, the number -100 would not be in the allowed input range and would cause a RangeError to be thrown.
7812
7813Understanding what a RangeError is, why one might be thrown, and when you should generate one yourself will help you write more expressive, correct code. In addition, understanding the motivation behind a RangeError will help you understand how to quickly and easily recover when one is thrown by the JavaScript standard library or external dependencies. As an example, take a look at the following code:
7814
7815\`\`\`
7816const numberGuesser = (numberGuess) => {
7817    // Validate guess is valid before checking if it's correct
7818    if (numberGuess > 10 || numberGuess <= 0) {
7819        throw RangeError(\`Invalid Guess: \${numberGuess}, number must be between 1 and 10, inclusive\`)
7820    }
7821    …
7822}
7823\`\`\`
7824
7825In the previous code, the allowed number set is implicitly defined as the integers between 1 and 10, inclusive. This is an example of a custom code that c
7825ould reasonably throw the RangeError. There are also a few JavaScript functions and constructors that could throw the RangeError, including the following:
7826
7827- Calling the String.prototype.normalize(): this returns the [Unicode Normalization of a string](https://unicode.org/reports/tr15/) and, optionally, accepts a form argument, which is any of the following sets {"NFC", "NFD", "NFKC", "NFKD"}. If the form is not one of these strings, then a RangeError is thrown. You can read more about this function in the [official MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize).
7828- Creating an array with an illegal length: in this instance, an illegal length is one that is negative or too large. An array length must be [between 0 and 2^32 - 1](https://www.oreilly.com/library/view/javascript-the-definitive/9781449393854/ch07.html). If you want to create an array that’s too large, in Google Chrome, open up [DevTools](https://developer.chrome.com/docs/devtools/) and type Array(Math.pow(2,32)). If performed correctly, you should be met with an uncaught RangeError with the message “Invalid array length.”
7829- Passing invalid values to the methods: invalid values include Number.prototype.toExponential(digits), Number.prototype.toFixed(fractionDigits), or Number.prototype.toPrecision(precision). The methods toExponential and toFixed accept a digits and fractionDigits argument, respectively, between 0 and 100, inclusive. toPrecision accepts a precision argument between 1 and 100, inclusive.
7830- Exceeding the maximum call stack size: this occurs when a recursive function calls itself too many times. You can test this in Google Chrome by writing a simple recursive function, like the following:
7831
7832\`\`\`
7833const recurse = (numTimes) => {
7834    if (numTimes == 0) {
7835        return
7836    }
7837    recurse(numTimes - 1)
7838}
7839\`\`\`
7840
7841After writing the function, you can call it with different inputs. In this instance, a RangeError will be thrown since the function was called with a numTimes argument of about 11,400. (Please note: your experiment may be different depending on what processes are running on your machine and how much memory is available.)
7842
7843The earlier bullet points list situations when RangeError can be instantiated and thrown by built-in functions in ES6 JavaScript. Other JavaScript built-in functions can also propagate RangeError from the function calls mentioned earlier. In addition, other dependencies may also throw RangeError, either propagated from the functions mentioned or explicitly instantiated and thrown.
7844
7845## Preventing and Mitigating Range Errors
7846
7847Mitigating and preventing uncaught RangeError entail understanding which functions are throwing them and how you should deal with each of those instances.
7848
7849You can prevent a RangeError from ever instantiating by ensuring that every function that throws a RangeError is given valid input that is included in the expected set. For example, if you’re creating a spreadsheet-style app and would like the user to be able to specify how many digits should be shown for a given cell, you may want to use the Number.prototype.toFixed method.
7850
7851To ensure a RangeError will not be thrown, you need to make sure that the user’s desired number of digits appearing after the decimal point are between 1 and 100:
7852
7853\`\`\`
7854const updateCellFormat = (desiredPrecision, cell) => {
7855    if (desiredPrecision < 1 || desiredPrecision > 100) {
7856        return cell
7857    }
7858    cell.contents = cell.contents.toFixed(desiredPrecision)
7859    return cell
7860}
7861\`\`\`
7862
7863The previous code looks at the user’s desired precision (the desiredPrecision parameter), and if it’s not in the valid range for the toFixed function, it simply returns the unaltered cell. Otherwise, it updates the cell’s contents (assuming the contents are a JavaScript Number type) to reflect the user’s desired decimal precision and returns the updated cell.
7864
7865Ensuring a RangeError can never be thrown is extremely difficult in practice. Input sanitization, especially in JavaScript, can be a daunting task. Users can behave unpredictably, and JavaScript’s dynamic typing doesn’t help. In addition, checking all the inputs in every method can lead to large functions, run-time, and maintenance costs, as well as overly [defensive programming](https://www.wikiwand.com/en/Defensive_programming). Ideally, you should be able to rely solely on the interfaces of libraries you interact with without needing to look at the implementation to see if a RangeError might be thrown.
7866
7867You can also mitigate RangeError damage by catching them before they propagate up the call stack. This can be achieved by ensuring that every function call to a function that can throw a RangeError is wrapped in a try...catch block. For example, you could change your spreadsheet formatter code to look like this:
7868
7869\`\`\`
7870const updateCellFormat = (desiredPrecision, cell) => {
7871try {
7872    cell.contents = cell.contents.toFixed(desiredPrecision)
7873    return cell
7874} catch (error) {
7875    if (error instanceof RangeError) {
7876        return cell
7877    }
7878}
7879}
7880\`\`\`
7881
7882Instead of explicitly checking if the desiredPrecision parameter is in an allowable range, the previous code tries to call the toFixed function in a try...catch block, which will automatically throw a RangeError if the parameter is invali
7882d. You can read more about try-catch in this blog post [here](https://meticulous.ai/blog/javascript-try-catch-guide/).
7883
7884Another option to mitigate errors is by writing unit tests that verify valid function inputs do not result in an unexpected RangeError. This will help give you confidence that an uncaught RangeError will not make an appearance in the production system.
7885
7886## Best Practices using Range Errors
7887
7888There are cases in which a RangeError is a valid response and can even be displayed to the user. You can follow some best practices to ensure you’re using RangeErrors appropriately.
7889
7890For example, in the number guesser game program shown earlier in the “What Is a RangeError” section, you might choose to display the error directly to the user since it provides valuable information that the user can apply to make better decisions in the future. However, you still don’t want to allow the user to witness an uncaught error, but you do want them to understand that an error was thrown:
7891
7892\`\`\`
7893​​ var guess = window. prompt("Enter a guess between 1-10.");
7894try {
7895    numberGuess(guess)
7896} catch (error) {
7897    if (error instanceof RangeError) {
7898        console.error(error.message)
7899    }
7900}
7901\`\`\`
7902
7903In the previous code, if a user typed “11,” they would get a console error saying, “Invalid Guess: number must be between 1 and 10, inclusive,” as that’s the message you initialized your custom RangeError with. This system provides useful feedback to the user because they’re able to understand why their input was incorrect and how to correct it in the future.
7904
7905You can also try and prevent a user from entering invalid input into the text field, but that would require more code and more complex unit tests, and you still might need to figure out a way to inform the user why they’re unable to enter certain numbers in the text field.
7906
7907In some cases, [TypeScript](https://www.typescriptlang.org/) can help catch a RangeError that shouldn’t be a RangeError. For example, check out this [TypeScript](https://www.meticulous.ai/blog/safer-exhaustive-switch-statements-in-typescript) function:
7908
7909\`\`\`
7910const chooseJerseyNumber = (jerseyRequest: number) => {
7911const validJerseyNums= new Set([4, 13])
7912if (!validJerseyNums.has(jerseyRequest)) {
7913throw RangeError(\`Invalid Number: \${jerseyRequest}, jersey request should be one of: \${Array.from(a.validJerseynums())}\`)
7914}
7915…
7916}
7917\`\`\`
7918
7919In this example, it doesn’t make sense to throw a RangeError if the input is a string version 4 or 13 since these aren’t invalid elements (they are mistyped). The user will be very confused if they receive an error message along the lines of “Invalid Number: ‘4’, jersey request should be one of \\[4, 13\\].” In this instance, the error is truly a variable type error. TypeScript has the ability to throw a [TypeError error](https://www.meticulous.ai/blog/javascript-typeerrors-and-techniques-to-prevent-them) the moment the function call executes with the string “4” instead of the number “4”. TypeScript will display something like “Argument of type ‘string’ is not assignable to parameter of type ‘number’.”
7920
7921As a developer, encountering a [TypeError](https://www.meticulous.ai/blog/javascript-typeerrors-and-techniques-to-prevent-them) instead of a RangeError should make it immediately obvious what needs to be fixed in the code module that calls the chooseJerseyNumber function.
7922
7923## Using Third-Party Dependencies
7924
7925Aside from the number guesser example earlier, in most cases, a RangeError should be prevented from ever manifesting. As mentioned earlier, this is an incredibly daunting task, and sometimes, it’s not practical or prudent to spend engineering hours ensuring every possible error is caught. That’s where [Meticulous](https://meticulous.ai/) comes in.
7926
7927While errors can certainly be mitigated through the methods discussed in the previous section, any nontrivial app will usually have some errors or unexpected behavior. Generally, you might rely on manual testing or automated QA sessions to make sure a new feature works correctly and doesn’t break any existing functionality. Integrating Meticulous with your app allows you to replay developer or QA testing sessions to see where uncaught JavaScript errors arise after introducing a new feature or update.
7928
7929Meticulous also plays nicely with external HTTP requests. During session recordings, requests are mocked so that they don’t actually execute again during the recording playback, ensuring no unintended side effects. For example, if you’re testing a sign-up flow, Meticulous intelligently saves the sign-up response from the backend. Then when replaying the session, Meticulous uses the saved response. In that way, replaying a sign-up QA session ten times doesn’t create ten new users.
7930
7931Mocking HTTP requests also ensures all changes are not dependent on external dependencies during playback, which might experience outages or other unexpected behavior; in this way, replaying sessions will always be consistent.
7932
7933## Conclusion
7934
7935Error handling is an incredibly powerful aspect of programming. Solid error handling portrays new information to other developers or users, allowing them to understand what went wrong and how they should best proceed.
7936
7937Lack of errors and exceptions can create a frustrating experience, which is why the RangeError should be used to ensure you understand how functions (or mechanisms) are constrained so they can be used as designed without unexpected behavior.
7938
7939Ensuring valid input to functions that throw a RangeError, catching range errors, incorporating TypeScript, and using [Meticulous](https://meticulous.ai/) are all engineering decisions that can be incorporated to help create an error-free product that works the way you intend.
7940
7941## Meticulous
7942
7943Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
7944
7945This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
7946
7947This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
7948`,d3=`---
7949title: "React Error Boundaries | Complete Guide"
7950slug: "react-error-boundaries-complete-guide"
7951date: "2024-01-16"
7952description: "A complete guide to implementing React Error Boundaries, and how to use a third-party tool for handling more sophisticated scenarios."
7953author: "Charly Poly"
7954authorImage: "/images/blog/authors/656906c69c90ec66afecebf9_charly-poly.jpeg"
7955heroImage: "/images/blog/react-error-boundaries-complete-guide/65691c2fce8ceff434cd6921_react-error-boundaries-complete-guide.webp"
7956heroImageAlt: "React Error Boundaries | Complete Guide"
7957categories: ["React"]
7958showTableOfContents: true
7959showPromo: true
7960---
7961
7962While [catching errors before they hit production](https://app.meticulous.ai/docs) is ideal, some of them, such as network errors, might slip through testing and impact your users.
7963
7964If your [React](https://www.meticulous.ai/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial) components are not properly catching errors thrown by third-party libraries or React hooks, such errors either end-up crashing the React lifecycle or reaching the top-level of the main execution thread, resulting in the “white screen” scenario:
7965
7966> _As of React 16, errors that were not caught \\[...\\] will result in unmounting of the whole React component tree_
7967
7968![React errors bubbling](/images/blog/react-error-boundaries-complete-guide/65691c4a56d4bdc384b8ccfd_react-errors-bubbling.png)
7969
7970It is crucial that your application gracefully handle such errors by providing proper visual feedback and potential actions (ex: retry mechanisms).
7971
7972Fortunately, implementing such UX patterns can be achieved with little work with the React API and, for the most advanced UX, with the help of lightweight React libraries.
7973
7974Using JavaScript’s try-catch around React hooks calls won’t work due to the asynchronous nature of their execution. However, React API offers the [Error boundaries](https://reactjs.org/docs/error-boundaries.html) mechanism to catch all types of errors that might “bubble out” from a component.
7975
7976For example, if the <ComponentA /> is wrapped in a React Error boundary, the error propagation will stop at the Error Boundary level, preventing the React App from crashing:
7977
7978![React errors bubbling with error boundary](/images/blog/react-error-boundaries-complete-guide/65691c4afdb26ce826751528_react-errors-bubbling-with-error-boundary.png)
7979
7980This article will cover how to implement Error Boundaries in your application, from simple error catching to displaying visual feedback and providing retry mechanisms.
7981
7982## Simple Error Boundaries: Catching and Reporting Errors
7983
7984Behind its sophisticated name, an Error Boundary is just a plain class React component implementing the componentDidCatch(error) method:
7985
7986Note: React is not yet offering a hook-based alternative to implement error boundaries.
7987
7988[As showcased in this CodeSandbox](https://codesandbox.io/s/meticulous-ai-blog-error-boundaries-simple-error-boundary-l7s66f?file=/src/App.tsx), the componentDidCatch() class method will be called as soon as an error reaches our MyErrorBoundary component, allowing us to prevent the React app from crashing and forwarding the error to our error reporting tool. (The CodeSandbox might display a development error overlay that only shows in development, you can dismiss it to see the rendering result).
7989
7990Let’s make our <ErrorBoundarySimple> more friendly by adding simple visual feedback when errors are raised. For this, we add some state to ErrorBoundarySimple and use the getDerivedStateFromError() method, as follows:
7991
7992\`\`\`
7993class ErrorBoundarySimple extends React.Component {
7994  state = { hasError: false };
7995
7996  componentDidCatch(error: unknown) {
7997    // report the error to your favorite Error Tracking tool (ex: Sentry, Bugsnag)
7998    console.error(error);
7999  }
8000
8001  static getDerivedStateFromError(error: unknown) {
8002    // Update state so the next render will show the fallback UI.
8003    return { hasError: true };
8004  }
8005
8006  render() {
8007    if (this.state.hasError) {
8008      return <p>Failed to fetch users.</p>;
8009    }
8010
8011    return {this.props.children};
8012  }
8013}
8014\`\`\`
8015
8016With the above setup, any error in the <Chat>
8016 component (or its descendant) would be caught in the Error Boundary wrapping the <Chat> component (not the “App” Error Boundary), allowing us to give a contextualized visual feedback. However, any error coming from all <App> descendants (excluding <Chat> and <TodoList>) will be caught by the “App” Error Boundary.
8017
8018With a few lines of code, we just greatly improved our user experience by gracefully handling errors in our application.
8019
8020However, such simple Error Boundaries implementations do have limitations. First, [according to the React documentation](https://reactjs.org/docs/error-boundaries.html#introducing-error-boundaries), Error boundaries do not catch errors for:
8021
8022- Event handlers
8023- Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
8024- Server-side rendering
8025- Errors thrown in the error boundary itself (rather than its children)
8026
8027And, the previously showcased Error Boundaries do not provide any action to the user to recover from the error, for example, with a retry mechanism. In the next section, we will see how to leverage the react-error-boundary library to handle all these edge cases.
8028
8029## Advanced Error Boundaries: Catching all Errors and Retry Mechanisms
8030
8031Let’s now provide a superior error handling user experience by catching all kinds of errors and exposing recovery actions to the users. For this, we will use the react-error-boundary library which can be installed as follows:
8032
8033\`\`\`
8034npm install --save react-error-boundary
8035
8036yarn add react-error-boundary
8037\`\`\`
8038
8039## Provide a Retry Mechanism
8040
8041[Our new CodeSandbox](https://codesandbox.io/s/meticulous-ai-blog-error-boundaries-simple-error-boundary-retry-vx8cbl?file=/src/App.tsx:23-721) defines a <Users> component that will fail to load users 50% of the time. (The CodeSandbox might display a development error overlay that only shows in development, you can dismiss it to see the rendering result).
8042
8043Let’s use react-error-boundary to properly catch errors and provide a retry mechanism:
8044
8045\`\`\`
8046import { ErrorBoundary, FallbackProps } from "react-error-boundary";
8047import { Users } from "./Users";
8048
8049function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
8050  return (
8051    <div role="alert">
8052      <p>Failed to load users:</p>
8053      <pre>{error.message}</pre>
8054      <button onClick={resetErrorBoundary}>Try again</button>
8055    </div>
8056  );
8057}
8058
8059export default function App(): JSX.Element {
8060  return (
8061    <div className="App">
8062      <h1>Hello CodeSandbox</h1>
8063      <h2>Start editing to see some magic happen!</h2>
8064      <ErrorBoundary FallbackComponent={ErrorFallback}>
8065        {/* Users will fail to load 50% of the time */}
8066        <Users />
8067      </ErrorBoundary>
8068    </div>
8069  );
8070}
8071\`\`\`
8072
8073<ErrorBoundary> takes one mandatory FallbackComponent= prop that should be the react component or JSX that will be rendered in case of error. In the case of a component, this FallbackComponent= function will receive FallbackProps:
8074
8075- error can be used to display the error.
8076- resetErrorBoundary is a callback to reset the error state and re-render the children's components.
8077
8078An ononError prop can also be provided to forward the error to your favorite error reporting tool (ex: Sentry). The [react-error-boundary documentation](https://github.com/bvaughn/react-error-boundary#error-recovery) showcases how to leverage other props (ex: onReset=) to handle more advanced scenarios.
8079
8080## Catching all Errors
8081
8082As aforementioned, Error boundaries do not catch errors for:
8083
8084- Event handlers (learn more)
8085- Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
8086
8087Because such errors happen outside of the React rendering lifecycle, Error boundaries won’t be invoked. Again, react-error-boundary has us covered by providing a handleError() hook that helps with catching event-related and asynchronous errors.
8088
8089\`\`\`
8090import { useErrorHandler } from 'react-error-boundary'
8091
8092function Greeting() {
8093  const [greeting, setGreeting] = React.useState(null)
8094  const handleError = useErrorHandler()
8095
8096  function handleSubmit(event) {
8097    event.preventDefault()
8098    const name = event.target.elements.name.value
8099    fetchGreeting(name).then(
8100      newGreeting => setGreeting(newGreeting),
8101      error => handleError(error),
8102    )
8103  }
8104
8105  return greeting ? (
8106    <div>{greeting}</div>
8107  ) : (
8108    <form onSubmit={handleSubmit}>
8109      <label>Name</label>
8110      <input id="name" />
8111      <button type="submit">get a greeting</button>
8112    </form>
8113  )
8114}
8115\`\`\`
8116
8117Errors happening inside of handleSubmit() function won’t be caught by React rendering lifecycle. For this reason, we use the handleError function provided by react-error-boundary’s useErrorHandler() to rethrow the error in the React lifecycle so that the nearest ErrorBoundary can catch it.
8118
8119## Conclusion
8120
8121Behind its sophisticated name, a React Error Boundary is a straightforward way to gracefully handle any kind of error in a React application.
8122
8123[Good products should prevent errors from reaching production](https://meticulous.ai/) but also should use error boundaries to provide contextual feedback and recovery actions to their users in case of unexpected errors.
8124
8125## Meticulous
8126
8127Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
8128
8129This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way 
8129we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
8130
8131This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
8132`,u3=`---
8133title: "5 Reflect Alternatives to Consider in 2024"
8134slug: "reflect-alternatives"
8135date: "2024-01-16"
8136description: "Explore in-depth analyses of Reflect and five compelling alternatives, assessing their strengths, limitations, and pricing, providing a comprehensive guide for choosing the right no-code testing tool in 2024."
8137author: "Kasper Siig"
8138authorImage: "/images/blog/authors/6556f52006849f153f151c05_kasper-siig.png"
8139heroImage: "/images/blog/reflect-alternatives/656a1afd825962cfb448524a_rainforest-qa-alternatives.png"
8140heroImageAlt: "5 Reflect Alternatives to Consider in 2024"
8141categories: ["Testing"]
8142showTableOfContents: true
8143showPromo: true
8144hidden: true
8145---
8146
8147[Reflect](https://reflect.run/) is one of many automated end-to-end testing platforms on the market. "End-to-end" means that you can test everything from the backend (how an API is written) to the frontend (how users are interacting with different elements of an app or a website) — and, of course, how everything fits together. In other words, you can test from one end to the other.
8148
8149Reflect uses [generative AI](https://reflect.run/test-better/) to help simplify test creation and maintenance. This means that your developers can spend more time on actually developing your products and less on writing tests. In this blog post, we'll be looking at Reflect, along with five alternatives worth considering. We’ll focus primarily on these tools’ [visual testing](https://www.meticulous.ai/blog/visual-regression-testing-tools) features, as they are one of Reflect’s big selling points.
8150
8151## Essential Features of Effective No-Code Testing Tools
8152
8153When you're looking into no-code testing tools, you’ll find that they provide a wide variety of features. But keep in mind that there are some essential features that you need to prioritize — for instance, [QA automation](https://www.accelq.com/blog/qa-automation).
8154
8155There are two primary reasons you’d want a no-code testing tool. Either you want developers to have a more efficient development cycle, where they don't spend significant time writing additional code to test their existing code, or you want someone without programming experience to be able to set up tests and get them running. This person could be a dedicated QA engineer, for instance, or if your team is small, it may be someone like the head of sales or the head of product.
8156
8157No-code features help democratize the testing process. They allow more team members from different departments to collaborate on testing, which can instill a sense of ownership over the product. For instance, if the head of sales or the head of product can easily create a test suite for an issue that they’ve frequently heard customers mention, they can feel ownership over that process from start to finish.
8158
8159### No-Code vs. Script-Based Tests
8160
8161While script-based tests are powerful and offer a high degree of customization, they often require specialized skills. They’re also prone to breaking when there are changes in your application, either because you need to write very specific selectors or because the code you write has to interpret the screen visually. Often, you'll find that you experience test flakiness or suites that break intermittently. So having a no-code, purely visual-based testing tool can be an excellent way to achieve not only an efficient development process but also reliable and stable tests.
8162
8163## Reflect's Testing Capabilities
8164
8165![](/images/blog/reflect-alternatives/671793fbf25617e2c132a09f_659c29
8165c1f139e9976338ea47_screenshot-of-reflect.png)
8166
8167As we’ve mentioned, Reflect uses generative AI to target different elements on your site. This essentially frees up time and also makes testing more available to people who aren't necessarily developers. You can input plain text instructions, which will be converted into actions, assertions, and whatever else you may need in order to produce your tests.
8168
8169This is intended to prevent flaky tests, which are a huge problem when it comes to visual testing. By having AI build out the test suites, you don't have to get specific about what selectors to choose. If a selector changes in the future, the AI will figure it out.
8170
8171One of the ways Reflect is marketing this is by saying that you can "build tests at the speed of thought." This implies that you don't have to spend a lot of time designing and writing tests; you can just put in your instructions and get the test suite out. Reflect claims that it allows you to build end-to-end tests 10 times faster than you can with other code-based frameworks like Selenium or Playwright.
8172
8173![](/images/blog/reflect-alternatives/671793fbf25617e2c132a089_659c2a58be45672b92e8f1e9_reflect-build-at-the-speed-of-thought.png)
8174
8175In addition, Reflect offers various features to help you build out a comprehensive test suite and achieve comprehensive test coverage. This includes support for visual testing, API testing, and JavaScript, and compatibility with various technology stacks.
8176
8177Last but absolutely not least, Reflect offers compatibility with CI/CD. You can implement CI/CD in Reflect in a variety of ways, [all underpinned by the Reflect API](https://reflect.run/docs/integrations/continuous-integration/), which you can use to trigger all your test runs. This makes it very easy to integrate with popular CI/CD pipeline tools such as Azure Pipelines, Bitbucket Pipelines, GitLab CI/CD, and others.
8178
8179### Pricing 
8180
8181There are various plans to choose from when you want to get started with Reflect:
8182
8183- **Free:** Costs $0 per month and includes one hour of testing per month, approximately 200 test runs, unlimited test creation, and a two-week free trial for AI-assisted testing.
8184- **Team:** Starts at $200 per month and includes 500 monthly runs, unlimited test creation, and 10 users.
8185- **Reflect AI:** Focuses more on AI features and offers unlimited test execution time, users, and AI-assisted testing.
8186
8187For a more detailed overview, you can visit Reflect’s [pricing page](https://reflect.run/pricing/).
8188
8189### Strengths and Limitations of Reflect
8190
8191One of Reflect’s major strengths is the AI. The company places significant emphasis on marketing this aspect. The AI aims to make test creation as dynamic and adaptable as possible and boasts a diverse feature set. It also facilitates easy integration with a variety of technologies.
8192
8193On the flip side, working with AI has its challenges. While it may speed up the testing process, it can take some time to learn how to prompt the AI tool effectively. Simply inputting instructions may not yield the desired results immediately. However, for people without programming skills who still want to create test suites, working with AI is arguably a lot easier than learning programming from scratch. 
8194
8195There are also feature limitations to consider with the more inexpensive plans, particularly with the Free plan.
8196
8197## Exploring 5 Alternatives to Reflect for Testing
8198
8199While Reflect offers a robust set of features for no-code testing, considering other tools that might better align with specific project requirements or budget constraints is essential. This section will explore five notable alternatives to Reflect, focusing on how their features compare with Reflect’s.
8200
8201We’ll take a comprehensive look at the capabilities of Meticulous, LambdaTest, BrowserStack, Sauce Labs, and Cypress. Each of these tools offers something distinct in the realm of automated testing, and understanding these nuances can help you make an informed decision.
8202
8203### Meticulous: An Integrated Visual Testing Solution for Developers
8204
8205![](/images/blog/reflect-alternatives/671793fcf25617e2c132a0d9_659c2abc8f070044b545a032_screenshot-of-meticulous.png)
8206
8207[Meticulous](https://www.meticulous.ai/) stands out for its approach to both visual and functional testing. Unlike many other traditional or no-code testing platforms, it relies heavily on recorded user behavior in order to create test suites. This eliminates the need for QA teams to write or maintain test scripts, which helps create a more streamlined testing process and, as mentioned previously, gives developers more time to work on developing your product. 
8208
8209Meticulous creates your test suite by capturing screenshots at specific points during your simulated user sessions or your real user sessions — depending on where on your website you implement [their JavaScript snippet](https://app.meticulous.ai/docs). Then it uses those screenshots to perform a pixel-depth analysis that checks for any visual differences.
8210
8211This is a unique way of doing visual testing. Most other tools rely on selecting different elements on your website; then they verify that all of those elements appear in the right place (and so on). By capturing actual screenshots of real user interactions and then using those screenshots to identify visual differences, Meticulous ensures that even the tiniest of differences will be captured. So while Reflect may use generative AI to create tests, Meticulous’s AI uses CSS selectors.
8212
8213You may not have to create the tests yourself, but they’ll still be created based on underlying code. This approach to visual testing creates more stability in your tests and reduces flakiness, because you don't have to rely on code being rendered at the right time. That's all taken care of within the tool.
8214
8215### Testing Environment, Automation, and Maintenance-Free Approach
8216
8217Another really helpful feature of Meticulous is its automatic mocking of backend services. When you implement the recorder script on your website, recording screenshots is just one part of what it does. It also — at the same time — records all of the network requests that are taking place. So it's very easy for the tool to automatically mark any outgoing requests, meaning that, all in all, it really becomes a maintenance-free approach to visual testing once you've set up the recording script. 
8218
8219With many other tools, especially no-code tools, you'll be sold on a "maintenance-free" approach that actually ends up being a _low-maintenance_ approach. Meticulous’s approach — taking screenshots and mocking outgoing requests automatically — is the only true way to have a _fully maintenance-free_ approach.
8220
8221Once a user session has been recorded, Meticulous automatically updates and maintains the test suite. This, of course, also means that you open up the possibility of anybody in your organization being able to make test suites, because doing so requires no technical knowledge (aside from the initial recorded script, but that's just one time). 
8222
8223Building on the whole principle of being maintenance-free and being very automatic, Meticulous also offers [integration with your CI/CD provider](https://app.meticulous.ai/docs/make-check-blocking). Their specific integration works by presenting you with the initial snapshot of your app and the screenshot that was taken when the test was run. 
8224
8225It's important to mention that you can also set Meticulous up to automatically spin up a development environment. You can use preview URLs that you've set up yourself with a third-party tool, but you can also set Meticulous to [connect with Vercel or Netlify](https://app.meticulous.ai/docs/cloud-replay), and have it create the environment for you.
8226
8227### LambdaTest: An Integrated Testing Solution for Developers
8228
8229![](/images/blog/reflect-alternatives/671793fcf25617e2c132a0e7_659c2b05e0d35a70fefb16a5_screenshot-of-lambdatest.png)
8230
8231While Meticulous focuses only on visual testing, it's very broad in terms of the stack that you can use and the way that you can use it. [LambdaTest](https://www.lambdatest.com/) has gone for more of a niche approach, in that its focus is on cross-browser testing capabilities with mobile device testing. That's their main selling point. LambdaTest also shares some functionality with Reflect: with it, you can produce race tests using AI. And LambdaTest’s cloud platform means that you don't have to host anything yourself.
8232
8233### Real Device Testing
8234
8235With LambdaTest, it's not only about being able to test different resolution sizes and screen sizes; you can also test [natural gestures and interactions](https://www.lambdatest.com/real-device-cloud). You can test network throttling, just like you’d experience on a mobile device on a flaky 
8235network (or maybe something even lower speed than that). You'll also get detailed device and app crash logs, so you can get a proper view into how any errors may actually appear on a mobile device. This allows you to debug it properly instead of debugging it in a browser and then seeing specific issues only when they come up on the app. There's also GPS-based geo-load testing, geolocation testing, and even biometrics authentication support.
8236
8237So you get a tool that supports testing most of the features you’d expect to be able to test on a mobile device. LambdaTest is for teams that are very certain that their app, or maybe even their website, will be accessed on mobile devices. If you’re building an application that you expect to very frequently used in a browser, then LambdaTest might not be the right choice for you. But if you're building a mobile app, LambdaTest is surely one of the tools you’ll want to check out.
8238
8239### Pricing
8240
8241![](/images/blog/reflect-alternatives/671793fbf25617e2c132a083_659c2b558167aa6d2cfc7681_lambdatest-pricing.png)
8242
8243Pricing really depends on which part of LambdaTest you want to use. They have five different features you can pay for: manual testing, web automation testing, native app automation, Hyper Execute, and Smart UI (learn more about these features on [their website](https://www.lambdatest.com/)). But sticking with the automation testing theme of this post, I can tell you that the web automation testing has a Free plan that includes 100 minutes of testing each month. Then there is a Desktop Automation Testing - Linux plan, which is $29 a month if it's billed annually.
8244
8245With that plan, you get, of course, desktop browser testing, support for all popular automation frameworks like [Selenium](https://www.lambdatest.com/selenium-automation), integration with CI/CD, detailed test execution logs, and a bunch of other cool features.
8246
8247They say their most popular plan is the Web & Mobile Browser on Real Device plan, which gives you access to a whole suite of different real devices.
8248
8249![](/images/blog/reflect-alternatives/671793fbf25617e2c132a086_659c2bba36d0ec5e92c08b8e_lambda-test-web-and-mobile-browser-on-real-device-plan.png)
8250
8251### BrowserStack: A Cloud-Based Testing Environment
8252
8253![](/images/blog/reflect-alternatives/671793fcf25617e2c132a0d6_659c2be3f5321d84e225827d_screenshot-of-browserstack.png)
8254
8255Another option for app and website testing is [BrowserStack](https://www.browserstack.com/), arguably one of the most popular options when it comes to cross-browser and mobile testing. Getting started with BrowserStack can be a bit confusing because they have a lot of options to choose from. If you want to test websites, you can choose between Live, Automate, or Percy. And if you want to test mobile apps, you can use App Live or App Automate.
8256
8257![](/images/blog/reflect-alternatives/671793fbf25617e2c132a08c_659c2c194d12e2b23b43f604_browserstack-features.png)
8258
8259Sticking with the theme of this blog post, let's focus on Percy, BrowserStack’s tool for visual testing and review. Similar to Meticulous, the Percy feature works by capturing a screenshot of your application as it's running in a test environment, comparing that screenshot with a baseline image, and highlighting any visual changes.
8260
8261![](/images/blog/reflect-alternatives/671793fbf25617e2c132a0a2_659c2c5867c71c24548bc599_untitled.png)
8262
8263BrowserStack is, however, different from Meticulous in one key aspect:  you set up BrowserStack by using either the Percy SDK or the BrowserStack SDK to integrate the test suite into your projects. 
8264
8265In the [guide to creating a Percy project](https://www.browserstack.com/docs/percy/get-started/create-project), you’ll see that you can choose from popular frameworks like Cypress and Storybook. Meaning that Percy, while it does provide a lot of features, is a tool that you add to something that already exists. On the other hand, Meticulous is a completely new approach that requires putting only a single JavaScript snippet on your website.
8266
8267BrowserStack is also frequently compared with Sauce Labs — if you’re considering these two tools, [take a more in-depth look at their differences, in this](https://www.meticulous.ai/blog/browserstack-vs-saucelabs) blog post.
8268
8269![](/images/blog/reflect-alternatives/671793fcf25617e2c132a0b4_659c2cbb46d3ad6372c1f36c_untitled.png)
8270
8271### Pricing
8272
8273You can choose between two different plans when it comes to [Percy specifically](https://www.browserstack.com/pricing?product=percy); there are other pricing options if you want to use Live, App Live, or Automate. For Percy, you can start with the Desktop plan, which is $199 a month if it's billed annually, or you can get the Desktop & Mobile plan, which is $269 per month billed annually. The major difference between the two plans is that with the Desktop & Mobile plan, you'll get real mobile devices to test on, as well as mobile browsers.
8274
8275![](/images/blog/reflect-alternatives/671793faf25617e2c132a07e_659c2cfa36f85270571c0cf8_pricing-page-of-percy.png)
8276
8277Like with LambdaTest, if you expect your application to be used mostly on a regular desktop, then you may get more benefits from another tool. But if you know that your application will be used mostly on mobile devices, then Percy may be an option for you. 
8278
8279At the end of the day, remember what it is that you need to test on a mobile device. If it's only the UI, then it’s most often going to be merely a matter of setting the right resolution. If you also need to make sure that various mobile-specific features like GPS location work, then you’ll likely need a mobile-specific testing tool.
8280
8281It's worth mentioning that while BrowserStack has a great reputation, a lot of anecdotal evidence suggests that it can sometimes be a bit lacking in terms of performance. Getting real mobile devices is great, but you also need to take into consideration the time that it takes to actually spin up that real mobile device or, rather, your connection to that mobile device.
8282
8283### Sauce Labs: Emulation-Based Device Testing
8284
8285![](/images/blog/reflect-alternatives/671793fcf25617e2c132a0b7_659c2d323662aaee1a594bbc_screenshot-of-saucelabs.png)
8286
8287The third and last option on this list that relates specifically to mobile testing is [Sauce Labs](https://saucelabs.com/). Sauce Labs is another one of the most popular tools when it comes to cross-browser and mobile device testing, and it is a huge competitor to BrowserStack. The main difference is that BrowserStack focuses on real devices, while Sauce Labs focuses on emulation-based device testing. And it's difficult to say whether one of these is better than the other; it comes down to what your use case is and how much you depend on very specific features of different mobile devices. 
8288
8289It's important to mention here that Sauce Labs does offer a real device cloud, but keep in mind that they focu
8289s on emulation-based testing.
8290
8291(If Sauce Labs seems like it might be the right choice for you, you may want to consider these [five Sauce Labs alternatives](https://www.meticulous.ai/blog/saucelabs-alternatives).)
8292
8293### Pricing
8294
8295![](/images/blog/reflect-alternatives/671793fbf25617e2c132a08f_659c2d5fdda0c588eb95221d_pricing-page-of-saucelabs.png)
8296
8297You can choose from [four plans](https://saucelabs.com/pricing). There's a Live Testing plan, which is $39 per month when billed annually, and which includes one parallel test. You can upgrade that to as many as 24 parallel tests, which would cost $769 per month billed annually. The other plans are structured similarly: pricing will depend on how many tests you want to run in parallel. The Virtual Cloud plan starts at $149 per month, and the Real Device Cloud plan starts at $199 per month. There's also an Enterprise plan, which has features like a private device cloud and SSO.
8298
8299### Cypress: A JavaScript-Based Tool for Developers
8300
8301![](/images/blog/reflect-alternatives/671793fcf25617e2c132a0b1_659c2d901d398de59a297112_screenshot-of-cypress.png)
8302
8303[Cypress](https://www.cypress.io/) is definitely the most developer-centric option on this list. This is the choice for you if you are purely focused on having a testing tool that's great for developers, as Cypress tests are written in JavaScript. So, especially if you're using a front end that's written in JavaScript, which is most applications these days, then you'll find that your developers can easily get set up with Cypress without having to learn a new language or new structure. They will just have to learn the different Cypress commands that you can use.
8304
8305Earlier in this post, it was mentioned that flakiness is an important thing to consider, as you can experience many issues with flaky tests; a false positive saying that a test works fine when something is wrong will impact users. But also on the flip side, if the testing tool reports an error where there isn't an error, then you'll end up spending a lot of time trying to fix something that's not actually an issue. Cypress tries to resolve this by having [built-in retryability](https://docs.cypress.io/guides/core-concepts/retry-ability). But it is important to mention that that is more of a workaround than an actual solution. It may not be the thing that's going to turn you off from using Cypress, but it's definitely something to keep in mind.
8306
8307### Pricing
8308
8309In terms of pricing, there are again four different plans to choose from. You can start with a free tier, where you'll get three users and 500 included test results. Or you can go up to the Team plan, which starts at $67 a month. There you'll get 10 users and 120,000 included test
8310
8311.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #eceff1; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
8312
8313| Plan       | Cost                                   | Users     | Included Test Results | Additional Test Results   | Features                                                                                            |
8314| ---------- | -------------------------------------- | --------- | --------------------- | ------------------------- | --------------------------------------------------------------------------------------------------- |
8315| Free       | $0/month                               | 3         | 500/month             | Not applicable            | Parallelization, Load Balancing, Debugging, Analytics, Integrations                                 |
8316| Team       | $67/month (Billed annually at $799)    | 10        | 120,000/year          | $6 per 1,000 test results | Everything in the Free plan, Flake Detection, Flaky Test Analytics, Jira Integration, Email Support |
8317| Business   | $267/month (Billed annually at $3,199) | 40        | 120,000/year          | $5 per 1,000 test results | Everything in the Team plan, Smart Orchestration, GitHub & GitLab Enterprise, SSO                   |
8318| Enterprise | Custom pricing based on needs          | Unlimited | Custom                | Custom                    | Everything in the Business plan, Premium Support, Custom Legal Terms                                |
8319
8320## Choosing the Right No-Code Testing Tool
8321
8322The landscape of no-code testing tools is diverse, each offering a unique set of features tailored to different testing needs. Reflect.run is a notable contender in this space, providing a no-code, AI-driven solution that simplifies the testing process, particularly for those without coding expertise. Its capabilities in visual testing and ease of use make it a viable option for teams looking to implement regression testing without the complexities of code-based solutions.
8323
8324However, choosing the right tool extends beyond ease of use and should align with your specific testing requirements. For teams that require a more comprehensive approach to both visual and functional testing, Meticulous offers a compelling alternative. Its unique method of leveraging recorded user behavior for test automation provides a high level of detail and accuracy, making it an excellent choice for implementing regression testing. With its maintenance-free approach and compatibility with CI/CD pipelines, Meticulous stands as a robust, yet efficient, no-code testing solution.
8325
8326## Meticulous
8327
8328Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
8329
8330This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way 
8330we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
8331
8332This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
8333`,h3=`---
8334title: "Safer Exhaustive Switch Statements in TypeScript"
8335slug: "safer-exhaustive-switch-statements-in-typescript"
8336date: "2024-01-16"
8337description: "A guide on various methods and techniques for implementing safer switch and if/else statements, including use of the never type and eslint rules."
8338author: "Alex Langdon"
8339authorImage: "/images/blog/authors/6568e8c0efa94e4ab8341350_alex-langdon.jpeg"
8340heroImage: "/images/blog/safer-exhaustive-switch-statements-in-typescript/656a1934d09a3c1cc575f9b0_safer-exhaustive-switch-statements-in-typescript.png"
8341heroImageAlt: "Safer Exhaustive Switch Statements in TypeScript"
8342categories: ["TypeScript"]
8343showTableOfContents: true
8344showPromo: true
8345---
8346
8347In this blog we will discuss how exhaustive type checking can help us write safer switch statements, as well as some simple techniques using TypeScript and ESlint rules that can help catch mistakes in our switch (and even if/else) statements at compile time.
8348
8349## Motivation
8350
8351Let's consider a simple example of a switch statement to return the list of permissions for different user types who may access a document. Owners should have the full set of permissions, Viewers can only have view permissions, while unauthorised users have no permissions.
8352
8353(Note that in this case we are using an enum for our UserType but a union of [string literals](https://www.typescriptlang.org/docs/handbook/literal-types.html#string-literal-types) would work equally well.)
8354
8355\`\`\`
8356enum UserType {
8357    Owner = "Owner",
8358    Viewer = "Viewer",
8359    Unauthorised = "Unauthorised"
8360}
8361
8362enum PermissionType {
8363    Delete = "Delete",
8364    Edit = "Edit",
8365    View = "View",
8366}
8367
8368function getUserPermissions(userType: UserType) {
8369    switch(userType) {
8370        case UserType.Owner:
8371            return [
8372                PermissionType.Delete,
8373                PermissionType.Edit,
8374                PermissionType.View
8375            ];
8376        case UserType.Viewer:
8377            return [
8378                PermissionType.View
8379            ];
8380        case UserType.Unauthorised:
8381            return [];
8382    }
8383}
8384\`\`\`
8385
8386This switch statement currently works as expected, however what would happen if a new value is added to the UserType enum? For example an Editor type that should have view and edit permissions:
8387
8388\`\`\`
8389enum UserType {
8390    Owner = "Owner",
8391    Editor = "Editor", // New enum member
8392    Viewer = "Viewer",
8393    Unauthorised = "Unauthorised"
8394}
8395
8396// Unhandled case - returns undefined
8397getUserPermissions(UserType.Editor);
8398\`\`\`
8399
8400Now our switch statement above would need correcting, by adding a case UserType.Editor which returns \\[PermissionType.Edit, PermissionType.View\\].
8401
8402Broadly speaking, such changes to UserType would mean we must remember to manually review all switch statements across our [codebase](https://www.meticulous.ai/blog/testing-pyramid-for-frontend) that depend on a UserType parameter.
8403
8404We could of course add a default case (see below), but that on its own would not tell us automatically that we have forgotten to handle a new case - case UserType.Editor.
8405
8406\`\`\`
8407function getUserPermissions(userType: UserType) {
8408    switch(userType) {
8409        //
8410        // ... Other cases here ...
8411        //
8412        default:
8413            throw new Error("Error - Unexpected default case!");
8414    }
8415}
8416\`\`\`
8417
8418Ideally, we would like to catch these mistakes early automatically (at compile time) and be sure that our switch statements are exhaustive i.e. include a case clause for every enum member type.
8419
8420## The Exhaustive Guard Function
8421
8422One easy way of achieving this is with an exhaustive guard function:
8423
8424\`\`\`
8425function exhaustiveGuard(_value: never): never {
8426    throw new Error(\`ERROR! Reached forbidden guard function with unexpected value: \${JSON.stringify(_value)}\`);
8427}
8428\`\`\`
8429
8430Notice how we are using the [never](https://www.typescriptlang.org/docs/handbook/basic-types.html#never) TypeScript type here for both the input parameter and return type. This will inform the TypeScript compiler that no argument should ever be passed to this guard function, and that the function should never return a value.
8431
8432We can now include exhaustiveGuard as the default case for our earlier switch statement:
8433
8434\`\`\`
8435function getUserPermissions(userType: UserType) {
8436    switch(userType) {
8437        case UserType.Owner:
8438            return [
8439                PermissionType.Delete,
8440                PermissionType.Edit,
8441                PermissionType.View
8442            ];
8443        case UserType.Viewer:
8444            return [
8445                PermissionType.View
8446            ];
8447        case UserType.Unauthorised:
8448            return [];
8449        default:
8450    		// We should never reach this default case
8451            return exhaustiveGuard(userType);
8452    }
8453}
8454\`\`\`
8455
8456(Note that never is a subtype of Array<PermissionType>, so calling return exhaustiveGuard will not cause compile errors here.)
8457
8458The TypeScript compiler will fail if it can find a logical path through our code that may call exhaustiveGuard. In other words, if we have not included a case for each UserType, we can logically reach the default case and therefore call exhaustiveGuard - leading to a TypeScript error.
8459
8460## Exhaustive Switch Guard in Action
8461
8462After applying the exhaustive guard function to the initial switch statement we get the following error in the IDE:
8463
8464![exhaustive-switch-guard-in-action](/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e861efa94e4ab833c201_image-of-an-error-in-IDE-after-applying-exhaustive-guard-function.jpeg)
8465
8466As explained above, the error indicates that we have not handled the full range of types and may pass a userType of UserType.Editor to exhaustiveGuard. After adding the missing Editor case our code will compile:
8467
8468\`\`\`
8469function getUserPermissions(userType: UserType) {
8470    switch(userType) {
8471        case UserType.Owner:
8472            return [
8473                PermissionType.Delete,
8474                PermissionType.Edit,
8475                PermissionType.View
8476            ];
8477        case UserType.Editor:
8478            return [
8479                PermissionType.Edit,
8480                PermissionType.View
8481            ];
8482        case UserType.Viewer:
8483            return [
8484                PermissionType.View
8485            ];
8486        case UserType.Unauthorised:
8487            return [];
8488        default:
8489            return exhaustiveGuard(userType);
8490    }
8491}
8492\`\`\`
8493
8494Naturally, the same exhaustiveGuard function can be declared once and reused in other situations across your codebase. For example in if/else statements if there are logical branches that should never be reached:
8495
8496![IDE-showing-exhaustive-switch-guard-in-action](/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e88527e4347591f9d51a_image-of-an-IDE-after-applying-exhaustive-guard-function.jpeg)
8497
8498Overall, using an exhaustive guard function like this is a simple, versatile pattern to consider for branching logic consuming enumerated or union types, and is suggested in the official [TS handbook](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#union-exhaustiveness-checking).
8499
8500However, remembering to use such guard functions may not always be practical. So it is worth considering alternative solutions as well.
8501
8502## Alternative 1 - Exhaustiveness ESlint Rule
8503
8504One alternative is to enable the [switch-exhaustiveness-check](https://typescript-eslint.io/rules/switch-exhaustiveness-check/) ESlint rule, which similarly highlights non-exhaustive switch statements and which cases they are missing (as shown below):
8505
8506![image-of-an-IDE-using-ESlint-rule](/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e88557c23c2bf31794dd_image-of-an-IDE-using-ESlint-rule-9c1ab.jpeg)
8507
8508Using a linting rule has a number of benefits:
8509
8510- Automatic - ESlint rules can run automatically as you build/save code, meaning that you (and your team) do not need to adopt any new patterns or guard functions.
8511- Can be a Warning - Unlike the exhaustiveGuard, you can control whether these non-exhaustive switch statements should be flagged as just a warning, or a blocking error.
8512- Quick Fix - You can even add the missing switch cases with the “Quick Fix” option directly in your IDE.
8513
8514However, it does have limitations:
8515
8516- Does not work for if statements - [switch-exhaustiveness-check](https://typescript-eslint.io/rules/switch-exhaustiveness-check/) is specifically for switch statements and will not work for the if/else example mentioned earlier.
8517- Does not throw informative errors at runtime - ESlint rules are intended for c
8517ompile/build time checks. So if your switch statement receives an unexpected value at runtime (e.g. an invalid string like “ADMIN” or undefined instead of a UserType) you may not emit an informative error and may just return undefined from your switch statement.
8518
8519## Alternative 2 - noImplicitReturns Compiler Option
8520
8521Another alternative to the exhaustive guard function is to enable the [noImplicitReturns](https://www.typescriptlang.org/tsconfig#noImplicitReturns) TypeScript compiler option. TypeScript will then [verify](https://www.meticulous.ai/blog/typescript-unit-tests-with-debugging) that all code paths in every function will return a value. This can be useful in general to check that you have not forgotten to write an explicit return statement for each logical branch across your switch or if/else statements, for example:
8522
8523![image-of-an-IDE-when-noImplicitReturns-compiler-is-enabled](/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e8859a207665bc8b873a_image-of-an-IDE-when-noImplicitReturns-compiler-is-enabled-c0ji9.jpeg)
8524
8525But there are a couple of drawbacks to consider:
8526
8527This won’t work if the switch or if/else statement explicitly returns a void type i.e. only calls functions without returning any values.Unlike the other options above, this approach may only give a general compilation error, without identifying the missing case clause (see below):
8528
8529![image-of-an-IDE-with-a-general-compilation-error](/images/blog/safer-exhaustive-switch-statements-in-typescript/6568e885bc03f39375be966b_image-of-an-IDE-with-a-general-compilation-error-mvzpc.jpeg)
8530
8531So as we have shown, each of the above solutions comes with different benefits. So it is worth considering if one (or all three) may be suited for your project or code style.
8532
8533## Conclusion
8534
8535In this blog, we have compared a few ways to use TypeScript type checks and ESlint rules to ensure that our switch statements always include a case for each enumerated o
8535ption at compile-time.
8536
8537We should now be more confident in changing the underlying types used by our switch or (if/else) logic and quicker to make corrections if the branching cases are not exhaustive.
8538
8539If you are interested in learning more about writing robust and well-tested code you may find a range of [relevant blogs](https://meticulous.ai/blog/) from Meticulous, including on [React Error Boundaries](https://meticulous.ai/blog/react-error-boundaries-complete-guide/) and [Frontend Unit Testing Best Practices](https://meticulous.ai/blog/frontend-unit-testing-best-practices/).
8540
8541Thank you for reading!
8542
8543## Meticulous
8544
8545Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
8546
8547This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
8548
8549This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
8550`,f3=`---
8551title: "5 Sauce Labs Alternatives to Consider in 2024"
8552slug: "saucelabs-alternatives"
8553date: "2024-01-16"
8554description: "This article explores five alternatives to traditional Sauce Labs testing and evaluates the benefits and drawbacks of each option. Learn more about these new ways to ensure product quality and reliability."
8555author: "Kasper Siig"
8556authorImage: "/images/blog/authors/6556f52006849f153f151c05_kasper-siig.png"
8557heroImage: "/images/blog/saucelabs-alternatives/656a0097cf5de148a77a9eae_saucelabs-alternatives.png"
8558heroImageAlt: "5 Sauce Labs Alternatives to Consider in 2024"
8559categories: ["Resources"]
8560showTableOfContents: true
8561showPromo: true
8562hidden: true
8563---
8564
8565[Sauce Labs](https://saucelabs.com/) has emerged as a prominent player in the software testing domain, thanks to its comprehensive solutions that include [cross-browser testing](https://www.meticulous.ai/blog/visual-regression-testing-tools), mobile application testing, and live debugging sessions. However, while Sauce Labs offers a robust and flexible platform, there might be situations where an alternative solution more closely aligns with a team's unique needs, requirements, or financial constraints.
8566
8567In this post, we’ll compare five Sauce Labs alternatives that have proven effective in 2024, carefully evaluating each platform’s features and benefits.
8568
8569## Sauce Labs' Testing Capabilities
8570
8571![Screenshot of Sauce Labs’ test results](/images/blog/saucelabs-alternatives/6568b28e4021fb690413c201_saucelabs-testing-capabilities.png)
8572
8573Sauce Labs specializes in automated testing of both desktop and mobile applications. The platform provides a comprehensive range of testing solutions that ensure compatibility and performance. Its key features include:
8574
8575- The Sauce Test Toolchain: A unified platform that streamlines testing from development to post-release, offering insights that allow you to detect and correct errors swiftly.
8576- Cross-Browser and Cross-Platform Testing: Guarantees optimal functionality across various browser and OS combinations.
8577- Mobile App Testing: Ensures a uniform mobile app experience across different devices, browsers, and operating systems.
8578- Sauce Insights: Provides real-time visibility so you can prioritize and address high-impact issues.
8579- Automation Frameworks: Supports frameworks including Appium, Cypress, Espresso, Selenium, Playwright, Puppeteer, TestCafe, and XCUITest.
8580
8581### Standout Features and Unique Selling Points
8582
8583The platform's ability to record screenshots and videos of ongoing tests, along with its [detailed logs](https://docs.saucelabs.com/insights/debug/), enhances the transparency of the testing process. Its cloud-based device testing and [automation capabilities](https://saucelabs.com/solutions/scalable-test-automation) allow users to conduct tests on a wide array of operating systems, browsers, and devices, ensuring compatibility and performance across varied environments.
8584
8585The platform's support for multiple browsers and versions, coupled with Sauce Labs’ commitment to including support for new browsers upon release, ensures that users can asse
8585ss their applications on the latest platforms. Sauce Labs' scalability, [enterprise-level security](https://saucelabs.com/company/security), comprehensive reporting, and [machine learning–driven analytics](https://saucelabs.com/company/news/sauce-labs-launches-new-machine-learning-based-analytics-solution-to-improve-test-quality) make it a robust and versatile testing solution.
8586
8587### Challenges and Areas for Improvement
8588
8589Despite its strengths, Sauce Labs has faced challenges in certain areas. Some users have reported [intermittent outages](https://status.saucelabs.com/) that disrupt the testing process and cause delays. These outages may be attributed to factors such as server issues or maintenance, and addressing them would enhance user satisfaction.
8590
8591Sauce Labs’ inconsistency in [scaling virtual machines](https://docs.saucelabs.com/dev/error-messages/#internal-server-error) horizontally has also raised concerns. For some users, this makes the platform too costly and inefficient. A detailed analysis of Sauce Labs' pricing structure reveals that while it offers a variety of plans to suit different needs, [some users may find the cost](https://www.trustradius.com/compare-products/browserstack-vs-sauce-labs) of specific features or scaling options to be high compared with other testing platforms.
8592
8593### Sauce Labs Features & Pricing
8594
8595![Screenshot of Sauce Labs’ pricing page](/images/blog/saucelabs-alternatives/6568b28da09f13dbb5abaaee_saucelabs-features-and-pricing.png)
8596
8597Sauce Labs offers various [pricing models and plans](https://saucelabs.com/pricing) tailored to different needs:
8598
8599.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #eceff1; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
8600
8601| Feature                                                                        | Free                                      | Monthly (Starter Plan)                    | Annual                                    | Enterprise                          |
8602| ------------------------------------------------------------------------------ | ----------------------------------------- | ----------------------------------------- | ----------------------------------------- | ----------------------------------- |
8603| Thousands of mobile devices, emulators/simulators, and browser/OS combinations | ✓                                         | ✓                                         | ✓                                         | ✓                                   |
8604| Unlimited users                                                                | ✓                                         | ✓                                         | ✓                                         | ✓                                   |
8605| Auto-refill credits                                                            |                                           | ✓                                         | ✓                                         | ✓                                   |
8606| Private device cloud                                                           |                                           |                                           |                                           | ✓                                   |
8607| Sauce Connect proxy local tunneling                                            |                                           |                                           |                                           | ✓                                   |
8608| Single sign-on (SSO)                                                           |                                           |                                           |                                           | ✓                                   |
8609| Team management                                                                |                                           |                                           |                                           | ✓                                   |
8610| Premium support options                                                        |                                           |                                           |                                           | ✓                                   |
8611| Enterprise-grade security                                                      |                                           |                                           |                                           | ✓                                   |
8612| Price                                                                          | $0                                        | $50 per month (+ taxes and fees)          | $600 per year (+ taxes and fees)          | Custom Pricing                      |
8613| Credits                                                                        | 2,000 free credits per week               | 25,000 credits/month                      | 330,000 credits/year (30,000 bonus)       | Unlimited automated testing minutes |
8614| Concurrency                                                                    | Run tests on up to 10 devices in parallel | Run tests on up to 25 devices in parallel | Run tests on up to 50 devices in parallel | Custom                              |
8615
8616## Why Choose Automation for Cross-Browser Testing
8617
8618Different browsers interpret HTML, CSS, and JavaScript in different ways, and cross-browser testing helps you detect and mitigate these inconsistencies, so you can deliver a consistent user experience.
8619
8620While manual testing involves human testers working across multiple browsers, automated cross-browser testing employs tools that execute test scenarios on several browsers simultaneously. Here's why automation is often preferred:
8621
8622- Time-Saving: Automated testing performs repetitive tests rapidly, allowing developers to focus on other tasks.
8623- Better Test Coverage: Automation guarantees extensive coverage across browser-OS combinations, leaving no compatibility issues unresolved.
8624- Improved Accuracy: Automation reduces human error, facilitating the detection and correction of bugs.
8625- User Experience and Compatibility: Automation ensures a uniform user experience and identifies browser-specific errors.
8626
8627With cross-browser testing, your compatibility benchmarks, scenarios, timelines, and budget will help you determine which features to test. Here's an overview of typical features:
8628
8629- Consistency: Validates both intended and unintended visual changes.
8630- Base Functionality: Confirms basic functionality across most browser-OS combinations.
8631- Design: Verifies that the website's appearance aligns with specifications.
8632- Accessibility: Assesses compliance with [Web Content Accessibility Guidelines (WCAG)](https://www.w3.org/WAI/standards-guidelines/wcag/).
8633- Responsiveness: Checks adaptability to different screen sizes and orientations.
8634
8635By adopting automation for cross-browser testing, organizations can provide a uniform user experience across various browsers and devices. Thoughtful feature selection ensures that testing is customized to meet the specific needs of the target audience.
8636
8637## Meticulous: An Integrated Visual Testing Solution for Developers
8638
8639![Example of a visual diff using the Meticulous app](/images/blog/saucelabs-alternatives/65675603ffedcc5b58a56f2d_meticulous-testing-app.png)
8640
8641[Meticulous](https://www.meticulous.ai/) presents a streamlined integration into your [continuous integration (CI)](https://app.meticulous.ai/docs/ci) pipeline with minimal setup, allowing you to record user sessions via a simple JavaScript snippet. The platform provides specific instructions for various frameworks, such as Next.js, and validates new changes via pull requests.
8642
8643### Automating UI Testing with Zero Flakes
8644
8645Meticulous takes great pride in its close-to-zero [flake rate](https://docs.gitlab.com/ee/development/testing_guide/flaky_tests.html). It pinpoints UI bugs by replaying user traffic, thereby removing the need for traditional UI test writing — both scripted and no-code testing. Also, replaying real-world traffic allows you to catch bugs that traditional UI tests may miss. The automatic updates of baseline images and visual regression detection expedite the review process. The isolation of front-end code guarantees no side effects, eliminating the need for a staging environment in most cases.
8646
8647### Maintenance and Reliability
8648
8649![Screenshot of feedback on Meticulous from engineers and CTOs](/images/blog/saucelabs-alternatives/6568b28ef44b50a9172518a3_feedback-on-meticulous.png)
8650
8651Meticulous' zero-maintenance approach has gotten great feedback from engineers and CTOs alike. As Joel Hollingsmith, an engineer at Patchwork, puts it, "Once you start using Meticulous, you won’t want to stop. It provides confidence without the labor of writing and maintaining tests." Seth Carney, CTO of Courier, underscores how Meticulous has "eliminated the need to write and maintain front-end tests across \\[his\\] engineering organization." These endorsements mirror the trust and assurance that Meticulous has instilled in its users, who have been able to detect innumerable UI bugs and regressions without authoring a single test.
8652
8653### Pricing Models and Plans: Flexibility for All Users
8654
8655Meticulous offers a free starter plan, meant for individual developers and small teams, that allows testing of 10 pull requests per month. For larger organizations, custom pricing is available, to suit various needs and budgets. Additionally, Meticulous is free with unlimited pull requests for any open-source project.
8656
8657## LambdaTest: Cross Browser Testing Made Easy
8658
8659![LambdaTest website](/images/blog/saucelabs-alternatives/6568b28e32c9755f582f1724_lambatest-testing-capabilities.png)
8660
8661[LambdaTest's](https://www.lambdatest.com/) exhaustive cross-browser testing capabilities, which encompass both manual and automated methodologies on a cloud-based infrastru
8661cture, distinguish it from numerous competitors. Its support for real mobile device testing and geolocation testing, coupled with its abundance of options for integration with various tools and frameworks, makes it a sensible choice.
8662
8663Whether you’re an individual developer seeking core testing capabilities, a QA team pursuing collaboration and efficiency, or an enterprise in need of advanced features and security, LambdaTest offers a way to satisfy your precise requirements.
8664
8665### Real Mobile Device Testing: LambdaTest's Pinnacle of Precision
8666
8667Real mobile device testing, a distinctive feature prominently showcased on [LambdaTest's homepage](https://www.lambdatest.com/), allows developers and QA testers to gain insights that go beyond visual appearance and probe deeper into the details of functionality, responsiveness, and performance across diverse devices, operating systems, and network conditions.
8668
8669Real mobile device testing in LambdaTest supports:
8670
8671- Natural Gestures and Interactions: Emulating authentic user behavior, including swipes, pinches, and taps.
8672- Network Throttling: Simulating varying network speeds to show how an application behaves under disparate connectivity conditions.
8673- Detailed Device and App/Crash Logs: Supplying comprehensive information so issues can be diagnosed and rectified.
8674- GPS-Based Geolocation Testing: Testing from divergent geographical locations.
8675- Camera Image Injection: Simulating camera input for applications that need access to a device's camera.
8676- Biometrics Authentication Support: Testing security attributes reliant on fingerprint or facial recognition.
8677
8678### Analysis of Plans for Different Personas
8679
8680- Free Plan: Ideal for individual developers or small teams exploring basic testing capabilities without any cost.
8681- Live Plan: Suitable for QA teams and developers who require extensive testing across various browsers and devices. The automation testing feature improves efficiency.
8682- Real Device Plan: Aimed at enterprises with a more substantial budget for QA, this plan offers real mobile device testing, ensuring precise results. It's comprehensive and includes features like network throttling and geolocation testing.
8683- Enterprise Plan: Customized for large organizations, this plan provides advanced features like premium support, IP whitelisting, and enterprise-grade security. It's crafted to meet the intricate testing needs of large-scale projects.
8684
8685For a complete overview of the different features being offered, please refer to the table below:
8686
8687.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #eceff1; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
8688
8689| Features/Plans                              | Free Plan | Live Plan | Real Device Plan | Enterprise Plan |
8690| ------------------------------------------- | --------- | --------- | ---------------- | --------------- |
8691| Price                                       | $0        | $15/month | $25/month        | Custom          |
8692| Parallel Tests                              | 1         | 1-25      | 1-25             | 25+             |
8693| Live Real Time Testing                      | ✓         | ✓         | ✓                | ✓               |
8694| 200+ Desktop Browsers                       | ✓         | ✓         | ✓                | ✓               |
8695| Screenshot Tests per month                  | 10        | Unlimited | Unlimited        | Unlimited       |
8696| Responsive Test per month                   | 10        | Unlimited | Unlimited        | Unlimited       |
8697| Native App Testing on Emulators/Simulators  | 3         | ✓         | ✓                | ✓               |
8698| Real Mobile Devices                         |           |           | ✓                | ✓               |
8699| Geolocation Testing                         |           | ✓         | ✓                | ✓               |
8700| Web and Mobile Debugging Tools              |           | ✓         | ✓                | ✓               |
8701| Integrate with JIRA, Asana, Slack           |           | ✓         | ✓                | ✓               |
8702| App Uploads                                 |           |           | ✓                | ✓               |
8703| Network Throttling                          |           |           | ✓                | ✓               |
8704| GPS Based Geolocation Testing               |           |           | ✓                | ✓               |
8705| Smart UI Inspector                          |           |           | ✓                | ✓               |
8706| Camera Image Injection                      |           |           | ✓                | ✓               |
8707| Biometrics Authentication Support           |           |           | ✓                | ✓               |
8708| Multiple Locale Support                     |           |           | ✓                | ✓               |
8709| Premium Support Options                     |           |           |                  | ✓               |
8710| Advanced Access Controls                    |           |           |                  | ✓               |
8711| Single Sign-On                              |           |           |                  | ✓               |
8712| IP Whitelisting                             |           |           |                  | ✓               |
8713| Advanced Data Retention Rules               |           |           |                  | ✓               |
8714| Dedicated Device Cloud                      |           |           |                  | ✓               |
8715| Advanced Local Testing                      |           |           |                  | ✓               |
8716| Usage Analytics                             |           |           |                  | ✓               |
8717| Early Access to Beta Features               |           |           |                  | ✓               |
8718| Unlimited Users                             |           |           |                  | ✓               |
8719| Enterprise-Grade Security                   |           |           |                  | ✓               |
8720| Apple Pay Testing                           |           |           |                  | ✓               |
8721| AI-Powered Test Intelligence/Test Analytics |           |           |                  | ✓               |
8722
8723## TestComplete: A Comprehensive Testing Platform
8724
8725![TestComplete’s  app](/images/blog/saucelabs-alternatives/6568b28e3bf5da4e516877aa_testcomplete-testing-tool.png)
8726
8727[TestComplete](https://smartbear.com/product/testcomplete/) goes beyond the traditional testing tool paradigm; it’s a comprehensive platform that integrates into your software development lifecycle. From test creation to execution and subsequent analysis, TestComplete’s extensive array of features caters to diverse stages of testing.
8728
8729### Test Creation: A Versatile Approach
8730
8731TestComplete’s automated UI testing capabilities encompass desktop, web, and mobile applications, ensuring exhaustive coverage. With both scriptless “record and replay” capabilities and scripted testing, TestComplete is tailored to testers across the spectrum of skill levels. [Integration](https://smartbear.com/product/testcomplete/integrations/) with tools like Selenium and Appium amplifies the creation of functional tests.
8732
8733[Keyword-driven testing](https://smartbear.com/product/testcomplete/features/keyword-driven-testing/) ensures that every team member, irrespective of programming knowledge, can contribute to test automation. Concurrently, data-driven testing permits users to execute automated UI tests with varied sets of input data, augmenting test coverage and simplifying maintenance.
8734
8735### Test Execution: Robust and Scalable
8736
8737TestComplete's execution features are meticulously designed to be both robust and scalable. [Parallel testing](https://support.smartbear.com/testcomplete/docs/testing-with/running/parallel.html) facilitates execution across multiple physical and virtual machines, while integration with Azure DevOps and Jenkins assures dependable building, testing, and deployment of software.
8738
8739Cross-platform testing guarantees that UI tests can be automated for desktop, web, and mobile applications. The tool's AI-driven engine identifies dynamic UI elements, ensuring that tests remain resilient even when the UI undergoes changes. Integration with BitBar supports testing on diverse platforms and browser configurations, further enhancing the tool's adaptability.
8740
8741### Test Management & Analysis: Streamlined and Efficient
8742
8743Managing and analyzing tests with TestComplete is effortless. Users can obtain real-time information on the progress and status of their tests from a unified interface. Integration with common defect tracking tools like Jira and Bugzilla refines the defect management process, facilitating tracking and resolution of issues.
8744
8745Continuous testing for DevOps is endorsed through seamless integration with the DevOps ecosystem, while automated test reuse across disparate projects and environments broadens test coverage and conserves time. Integration with Git and Atlassian Bitbucket for version control amplifies the reusability of tests, rendering TestComplete an invaluable asset in any testing environment.
8746
8747### TestComplete Pricing: Tailored to Your Needs
8748
8749TestComplete offers pricing plans for diverse needs and budgets:
8750
8751- TestComplete Base: Starting at $3,046, this plan offers unparalleled object recognition, scriptless record and replay, automated reporting, and more.
8752- TestComplete Pro: Priced at $4,739, this sought-after plan encompasses all the features of the Base plan, as well as de
8752sktop, mobile, and web testing, and access to the parallel testing engine, TestExecute.
8753
8754Add-ons like [TestExecute](https://support.smartbear.com/testcomplete/docs/other-tools/testexecute.html) and [Intelligent Quality](https://smartbear.com/product/testcomplete/features/intelligent-quality/) add features such as unlimited testing across parallel devices and AI-powered object recognition.
8755
8756TestComplete distinguishes itself with its comprehensive automated UI testing capabilities across desktop, web, and mobile platforms. Its extensive range of integrations ensures that it integrates seamlessly into most development ecosystems, so it’s a versatile tool for testers of all skill levels. Whether you’re seeking to create, execute, manage, or analyze tests, TestComplete offers a robust and scalable solution meticulously tailored to your needs.
8757
8758## Perfecto: A Cloud-Based Mobile Testing Lab
8759
8760![Screenshot of Perfecto](/images/blog/saucelabs-alternatives/6568b28e688649424b1923fc_perfecto-mobile-testing.png)
8761
8762In the dynamic and rapidly evolving domain of software development, [Perfecto](https://www.perfecto.io/) is a solution for high-velocity web and mobile testing. Leveraging cloud-based solutions, Perfecto ensures that teams can operate with agility and efficiency, and that they can quickly adapt to the fast-changing landscape of contemporary technology.
8763
8764The appeal of Perfecto starts with its mobile device cloud, a streamlined cloud-based solution for testing on authentic mobile devices.
8765
8766### Advanced Testing Capabilities
8767
8768Perfecto's features should meet the needs of contemporary testing teams. Test failure analysis and reporting accelerate issue detection through [automated scanning supported by machine learning](https://www.perfecto.io/blog/ml-in-testing-automation), and Perfecto’s [large knowledge base](https://help.perfecto.io/perfecto-help/content/kb/home-kb.htm) empowers teams to precisely address those issues. The r[eal user simulation](https://www.perfecto.io/platform/feature-page-real-user-simulation) and r[emote access and debugging](https://www.perfecto.io/platform/feature-page-remote-device-access-dev-tunnel) features foster a lifelike environment for more accurate testing, bridging the gap between development and tangible user experience.
8769
8770### Pricing and Customization
8771
8772Perfecto's pricing plans are carefully crafted to suit a broad range of testing requirements:
8773
8774- Live: Ideal for manual testing, starting at $83 per month.
8775- Automate: A blend of automated and manual testing, beginning at $125 per month.
8776- Scriptless: A no-code solution tailored for users who value simplicity.
8777- Enterprise: A comprehensive package for businesses requiring dedicated devices and enhanced features.
8778
8779Each plan reflects Perfecto's commitment to providing value across all levels.
8780
8781### Professional Services & Support
8782
8783Beyond its robust testing platform, Perfecto provides a professional services team. From quick-start onboarding to automation acceleration, these services are designed to hasten time to value, boost productivity, and ensure success from the outset. Whether you’re new to testing or looking to refine your existing processes, Perfecto's consulting services serve as an invaluable ally.
8784
8785## Browserstack: Real-World Device Testing
8786
8787![Screenshot of Browserstack’s integrations with other apps](/images/blog/saucelabs-alternatives/6568b28ea9051e969944026e_browserstack-testing-app.png)
8788
8789[BrowserStack](https://www.browserstack.com/) was designed to provide flawless app and browser testing experiences. The platform’s emphasis on genuine devices and browsers ensures accurate results and eliminates dependence on unreliable emulators and simulators.
8790
8791Getting started with BrowserStack involves exploring manual cross-browser testing through its Live feature, which allows users to examine websites across various browsers, guaranteeing compatibility and functionality. For users more interested in automation, the Automate feature conducts automated tests across a multitude of browsers.
8792
8793### Augmenting Visual Quality, Mobile App Testing, and Integrations & Collaboration
8794
8795With its Percy feature, BrowserStack offers visual testing and review; it meticulously inspects every pixel to confirm that visuals align with the planned design.
8796
8797The complexity of the mobile domain requires a sophisticated approach, and BrowserStack rises to the challenge with App Live and App Automate. The former facilitates manual testing on real devices, while the latter introduces automation to physical devices. These complementary strategies guarantee that mobile applications undergo testing under conditions that mirror true user experiences.
8798
8799BrowserStack's capabilities extend beyond individual features; it serves as a hub that integrates seamlessly with renowned tools and platforms, not only ensuring a smooth blend with existing workflows but also fostering collaboration and efficie
8799ncy across teams.
8800
8801### Selenium Integration
8802
8803BrowserStack's integration with Selenium allows for automated browser testing. BrowsterStack does not generate Selenium tests for you, but it does provide a platform where you can run your Selenium tests on multiple browsers and operating systems. This ensures that your web application works consistently across different environments.
8804
8805### Jenkins Integration
8806
8807If you’re  using Jenkins for continuous integration, BrowserStack offers a plugin that enables you to run your automated tests as part of your build process. This ensures that any code changes are immediately tested, so detecting and fixing issues takes less time.
8808
8809### Slack Integration
8810
8811BrowserStack can send real-time notifications to Slack channels, keeping your team updated about test statuses. This fosters quick communication and immediate action in case of test failures or other issues.
8812
8813### Appium Integration
8814
8815BrowserStack integrates with Appium to offer automated native and hybrid mobile app testing. As with the way BrowserStack and Selenium work together, you write your Appium tests and then run them on BrowserStack's real device cloud.
8816
8817### Additional Tools and Platforms
8818
8819BrowserStack also offers integration with many other tools — such as JIRA for bug tracking and GitHub for code repository — making it a versatile choice for modern DevOps environments.
8820
8821In summary, BrowserStack's integrations do not automatically generate tests for you, but it is a robust platform where your tests can be executed across a diverse range of environments. These integrations are designed to enhance workflows, encourage collaboration, and elevate the quality of the end product.
8822
8823### Pricing Pathways
8824
8825BrowserStack outlines a range of pricing plans, carefully tailored to various needs and stages of the testing journey:
8826
8827- Live: Aimed at manual testing, starting at $29 per month.
8828- Automate Pro: Created for automated browser testing, beginning at $129 per month.
8829- Automate Mobile: Focused on mobile automation, starting at $199 per month.
8830- App Live: Designed for manual mobile testing, starting at $29 per month.
8831- App Automate: For mobile app automation, starting at $199 per month.
8832- Percy: For visual testing, beginning at $29 per month.
8833
8834Enterprise and custom pricing options are also available, reflecting a flexible approach to accommodate diverse budgets and requirements.
8835
8836It should be noted that while many customers are happy with BrowserStack’s product overall, [experiencing lag at times](https://www.trustradius.com/products/browserstack/reviews?qs=pros-and-cons#reviews) when spinning up a new device is common, as is experiencing high latency once the device is up and running.
8837
8838## Choosing the Right Testing Tool
8839
8840Choosing a testing tool should not be a quick decision — your choice signifies a long-term commitment. The tool must align with the long-term vision for a project, enabling growth and scaling as needed. Choosing a tool that fits perfectly today but fails tomorrow is shortsighted. The ability to look beyond immediate needs and align the tool with future growth is a hallmark of strategic planning.
8841
8842The right tool is more than just software; it’s a strategic partner. It aligns with the vision, evolves with the project, and adapts to a constantly changing landscape of business requirements. This decision requires insight, foresight, and a thorough understanding of the project's direction.
8843
8844## Meticulous
8845
8846Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
8847
8848This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
8849
8850This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
8851`,g3=`---
8852title: "Meticulous raises $15m to accelerate the pace of software development"
8853slug: "series-a"
8854date: "2026-07-14"
8855description: "To ship at AI speed you need exhaustive tests: Meticulous makes that possible."
8856author: "Quentin Spencer-Harper"
8857authorImage: "/images/blog/authors/6590373d3fcec21648cdc202_profile.jpeg"
8858heroImage: "/images/blog/series-a/6a573faa128fb0c4eacd01d8_Frame-2--1-.png"
8859heroImageAlt: "Meticulous raises $15m to accelerate the pace of software development"
8860categories: ["Featured"]
8861showTableOfContents: false
8862showPromo: false
8863---
8864
8865If you have truly exhaustive testing you can ship extremely fast with AI. If you don’t — or have testing that gives anything less than full confidence — you can’t.
8866
8867Tests comprehensive enough to unlock such velocity requires:
8868
88691.  **Extreme scale & robust curation:** Truly exhaustive testing requires testing at a scale orders of magnitude beyond traditional testing. Operating functionally at this scale requires a new set of technologies — deterministic browsers for zero noise, fully automated generation and evolution of the test suite, efficient test execution & curation and robust coverage guarantees.
88702.  **Visibility into the full impact of the change:** Determining whether a code change is good to ship requires more than just the absence of failures on pre-defined assertions but rather the ability to quickly understand and visualize the full impact of the change, and from there judge its suitability.
8871
8872Meticulous solves both these issues without noise, at huge scale, and fully autonomously, making such confidence possible for the first time. The system automatically generates and maintains thousands of flows to cover every edge case across the millions of lines of code in our customer’s codebases - including Dropbox, Notion, Wiz, ElevenLabs and many of the world's leading engineering organizations. It will detect even a single pixel difference in the video streams of any of those flows, and thus test the logic behind them, before any production change is shipped.
8873
8874_"We piloted the tool, were immediately impressed and rolled it out across the entire engineering organization. Meticulous is unlike anything else, developers love it and it is now an essential guardrail of our software development process."_ - Erdem Alparslan, head of developer experience at Notion.
8875
8876The summarised data allows engineers, PMs and designers to preview every impact before hitting merge. And it allows agents to iterate on the change prior to pushing it for human review — giving your agents visibility into realistic data over every edge case and the impact of their change upon it.
8877
8878_‘We’re moving into an era where code generation is relatively inexpensive, but that means code review and the feedback loop become the bottleneck. Meticulous helps us simultaneously move fast and raise the quality bar.’_ - Zach Davis, Director of Engineering LaunchDarkly
8879
8880_“Meticulous has built one of the most deeply loved products that I have come across in my eighteen years of investing.”_ - Ethan, Chemistry
8881
8882As we move closer and closer to truly exhaustive testing we’ve observed that this allows our customers to move at unprecedented speed. We’re excited to take the platform to the next level to allow another step change in velocity for our customers.
8883
8884<div style="position: relative; width: 100%; padding-top: 56.25%; overflow: hidden;">
8885  <iframe src="https://player.vimeo.com/video/1209997510?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" title="Meticulous Series A" allow="autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share" referrerpolicy="strict-origin-when-cross-origin" style="position: absolute; inset: 0; width: 100%; height: 100%; border: 0;" allowfullscreen></iframe>
8886</div>
8887`,p3=`---
8888title: "Smoke Testing | A Key Tool for Identifying Software Issues"
8889slug: "smoke-testing"
8890date: "2024-01-16"
8891description: "Smoke testing is a wise first step in any software testing process. Learn the basics of smoke testing and how it can help you identify issues with key features and functionalities, before you move on to more comprehensive testing."
8892author: "Kasper Siig"
8893authorImage: "/images/blog/authors/6556f52006849f153f151c05_kasper-siig.png"
8894heroImage: "/images/blog/smoke-testing/655db87c8fb904aae00d6ad0_smoke-testting-main.png"
8895heroImageAlt: "Smoke Testing | A Key Tool for Identifying Software Issues"
8896categories: ["Tools", "Testing"]
8897showTableOfContents: true
8898showPromo: true
8899hidden: true
8900---
8901
8902The term “[smoke testing](<https://en.wikipedia.org/wiki/Smoke_testing_(electrical)>)” may have originated in the plumbing industry, and then it came to be widely used in the realm of hardware testing: it referred to the act of powering up a device to check for smoke (caused by bad wiring, for instance). These days, what we call the smoke testing process has also become an essential part of modern software development, serving as a valuable tool for software developers, QA engineers, and tech decision-makers. It saves both time and resources by helping them identify critical issues at an early stage.
8903
8904In this post, we’ll give you an overview of smoke testing’s underlying principles, the various types of smoke testing, and each type’s benefits. On top of that, we aim to offer valuable insights into how smoke testing can be incorporated into your software development processes.
8905
8906## What Is Smoke Testing?
8907
8908Assessing the stability of a newly developed or modified software build is often the primary aim of smoke testing. It focuses on key functionalities so you can verify that they operate as intended. If essential features aren’t working — or if significant bugs remain unresolved — there’s likely no reason to continue with more comprehensive testing.
8909
8910Smoke testing is a non-exhaustive testing approach — that is, it does not cover all possible use cases. Typically, a smoke test suite is executed using expected scenarios and valid data, in order to gain a somewhat reliable measure of the softw
8910are's stability and readiness for further testing.
8911
8912And it’s crucial to understand that “a somewhat reliable measure” is perfectly acceptable for smoke tests. The goal is to help you avoid wasting time on more comprehensive tests. For instance, you could smoke test a web app’s login functionality by:
8913
8914- Navigating to the login page.
8915- Entering a valid username and password.
8916- Clicking on the login button.
8917- Verifying that the user is redirected to the dashboard or home page.
8918
8919You can also think of smoke tests as “verification tests.”
8920
8921In most scenarios, smoke testing is going to revolve around [functional testing](https://circleci.com/blog/functional-vs-non-functional-testing/); however, some tools also allow you to perform smoke tests — and other, related forms of testing — in the area of [non-functional testing](https://speedscale.com/blog/regression-test-performance/).
8922
8923Smoke testing does have its limitations. For example, the heavy reliance on expected scenarios and valid data means that it may not catch all possible issues, especially those that arise from edge cases or that might be uncovered by negative testing. Additionally, smoke testing’s non-exhaustive nature means that it’s not a substitute for more thorough testing methods; rather, it’s a precursor to them. 
8924
8925## Why Smoke Testing Is Crucial in Software Development
8926
8927The smoke testing process serves as a quality gate between different stages of the development pipeline, particularly after the generation of a new build. In DevOps, a smoke test suite is often the first set of automated tests that’s executed. Successfully completing smoke testing allows development and QA teams to proceed with confidence to more exhaustive testing and, eventually, production.
8928
8929Consider a cloud storage service that has recently integrated a new file-sharing feature. A well-designed smoke test will swiftly verify that users can still upload, download, and share files without encountering errors, thereby enabling quicker iterations and more reliable deployments.
8930
8931This not only ensures that critical functionalities are intact but also aids in the early identification of significant issues, thereby facilitating more efficient and effective development processes.
8932
8933## Understanding the Different Forms of Smoke Testing
8934
8935When diving into the world of smoke testing, it’s important to understand the different types you might want to implement.
8936
8937### Manual Smoke Testing
8938
8939Manual smoke testing allows testers to adapt to different testing conditions and requirements on-the-fly, which is particularly beneficial with projects where the requirements are not fully defined or where they’re subject to frequent changes.
8940
8941This is on top of the nuanced evaluation capabilities that manual testing provides. Human testers can assess the functionality of an application _as well as_ its usability and user experience. They can identify issues that automated tests might overlook, such as inconsistencies in the user interface or problems with the user flow that could affect the end-user experience.
8942
8943Use-Case: Verifying a New Feature in a Mobile App
8944
8945When a new feature is introduced in a mobile application, manual smoke testing can be used for initial validations. For example, if a mobile app introduces a new chat feature, manual testers can quickly ensure that it works as expected without disrupting other features like notifications or user settings.
8946
8947Moreover, manual testing allows for a nuanced evaluation of the new feature's impact on the overall user experience, as testers can validate the feature's ease of use, its integration into the existing user interface, and even whether certain design elements follow the brand’s design principles exactly but somehow still look “off.” This ensures that the new feature not only works as intended but also aligns with the user experience goals of the application.
8948
8949Use-Case: Checking Login Functionality After a Security Update
8950
8951In the wake of a security update, manual smoke testing is essential for ensuring that core functionalities remain intact. For instance, if a security update aims to enhance password encryption, manual testers can verify that users are still able to log in using their existing credentials.
8952
8953Testers can check for specific error messages, verify that two-factor authentication is functioning, and even simulate various login scenarios to ensure that no new vulnerabilities have been introduced. Of course, this may also be possible with some automated testing tools, but it’s important to consider how much time you spend automating something in relation to how often it’s being done.
8954
8955![Is automation worth it?](/images/blog/smoke-testing/65675604ba11c5bb11af8f8a_automating-a-task.png)
8956
8957### Automated Smoke Testing
8958
8959When you do get to a point where manual testing is limiting you, automated smoke testing can be a highly efficient way of running multiple tests in parallel, thereby reducing the time required for the testing phase. This is particularly advantageous in larger projects or in environments that prioritize speed, such as DevOps and continuous integration (CI) frameworks.
8960
8961Automated tests can be easily scaled up to handle larger test suites, so they are ideal for projects that grow in complexity over time. Moreover, once the initial setup is complete, automated tests can be run with minimal human intervention, thereby reducing the likelihood of human error and ensuring consistent test results.
8962
8963But as mentioned in the previous section, you should make sure that the time saved by implementing automated tests outweighs the effort required to configure them. Because of this, it’s crucial to use a tool that requires [as little configuration as possible](https://www.meticulous.ai/blog/rainforest-qa-alternatives).
8964
8965Use-Case: Continuous Integration in a DevOps Pipeline
8966
8967Automated smoke testing tools can be integrated into a CI/CD pipeline to automatically identify bugs. For example, after every code merge, you can run a series of tests to ensure that the changes [haven't introduced any regressions](https://www.meticulous.ai/blog/the-beginner-guide-to-visual-regression-testing) or broken existing functionalities.
8968
8969The advantages of this automated approach are its speed and efficiency. Because these tests are automated, they can be run in parallel, thereby providing quick feedback to the development team. This is particularly beneficial in a DevOps setting where continuous integration and rapid deployments are the norm.
8970
8971Use-Case: Validating Multiple Builds in Parallel
8972
8973Automated smoke testing shines in scenarios where multiple builds need to be validated simultaneously. For instance, if a software company is working on different versions of an application for various platforms, automated tests can validate all these builds in parallel.
8974
8975Tools like [Meticulous](https://app.meticulous.ai/docs) further enhance this efficiency by focusing on catching UI bugs without requiring that development teams write or maintain UI tests. This drastically reduces time to market and allows development teams to concentrate on feature development rather than late-stage bug fixing.
8976
8977![Screenshot of the Meticulous app showing some visual differences](/images/blog/smoke-testing/65675603ffedcc5b58a56f2d_meticulous-testing-app.png)
8978
8979### Hybrid Smoke Testing
8980
8981Hybrid smoke testing offers the best of both manual testing and automated testing. It allows teams to leverage automated tests for repetitive, time-sensitive tasks, and manual tests for tasks that require human judgment and contextual understanding.
8982
8983The versatility of hybrid testing is a significant advantage. It can be customized to fit a project’s specific needs and constraints. For instance, for a complex project involving multiple microservices, automated tests can validate the interactions between services, while manual tests can focus on end-to-end scenarios and user experience.
8984
8985Use-Case: E-commerce Platforms
8986
8987In the context of e-commerce platforms, a hybrid approach offers a balanced testing strategy. Automated tests can swiftly validate database interactions and server logic, ensuring that the backend is robust. For example, automated tests can quickly verify that a new payment gateway is integrated correctly with the existing system.
8988
8989At the same time, manual tests can focus on the user experience and visual elements, which are critical for customer engagement and satisfaction. Testers can manually navigate through the checkout process to ensure that it is intuitive and that all visual elements, such as buttons and banners, display correctly.
8990
8991Use-Case: Complex Applications Involving Multiple Microservices
8992
8993For applications built on a microservices architecture, hybrid smoke testing is often the most effective approach. Automated tests can efficie
8993ntly validate the interactions between different services. For instance, if a new microservice for user authentication is introduced, automated tests can ensure that it communicates correctly with the existing user database and other related services.
8994
8995However, you may still want to get a human perspective on the user experience — in which case, testers can manually verify that a new user authentication microservice works seamlessly from the user's perspective. 
8996
8997A hybrid smoke test ensures that the login process is smooth and that all security protocols are followed.
8998
8999## Comparing Different Types of Smoke Testing
9000
9001Understanding the nuances between different types of smoke testing — manual, automated, and hybrid — will help you make informed decisions about your software testing strategy. This section serves as a decision-making guide, focusing on two key criteria: speed and accuracy.
9002
9003### Speed as a Decision Factor
9004
9005Imagine you're working against a tight deadline, and every minute counts. In a high-pressure scenario like this, the speed of your testing process is a critical factor. Let's explore how each type of smoke testing process fares when speed is of the essence.
9006
9007- Manual Testing: While manual testing allows for targeted, in-depth testing, it’s often time-consuming, especially for larger applications.
9008- Automated Testing (Meticulous): Automated tests, particularly those run by tools like Meticulous, are exceptionally fast, and multiple automated tests can be run at the same time. However, the initial setup time can be significant.
9009- Hybrid Testing: A hybrid approach offers a balance between speed and thoroughness: you can leverage automated tests for quick validations and manual tests for more complex scenarios. The challenge here is coordinating the two types, which can sometimes lead to delays.
9010
9011### Accuracy as a Decision Factor
9012
9013Now picture a scenario where a minor bug could result in a significant security vulnerability. In a case like this, the accuracy of your testing process is non-negotiable. Here's how each testing method measures up in terms of accuracy.
9014
9015- Manual Testing: Manual testing offers a high level of accuracy for tests that require human judgment, but with repetitive tasks, human error will always be a factor. Manual testing is generally more time-consuming over the course of an application’s lifetime.
9016- Automated Testing: Automated testing is highly accurate for automatable tasks like catching UI bugs. However, it may miss issues that require human intuition or business context understanding.
9017- Hybrid Testing: A hybrid approach combines the accuracy of both automated and manual tests but requires careful management to avoid missing issues.
9018
9019### Best Practices
9020
9021- For Speed: If you're in a race against time, and quick feedback loops are your lifeline, then automated testing is your most reliable ally.
9022- For Accuracy: When the intricacies of human judgment come into play, and the stakes are high for even the smallest error, manual testing is the gold standard.
9023- For a Balance: For projects that require both speed and accuracy, a hybrid approach is often the most effective. It combines the rapidity of automated tests with the meticulous scrutiny of manual tests, offering a well-rounded testing strategy.
9024
9025Choosing the right type of smoke testing is a nuanced decision that should be based on the specific needs and constraints of your project. Whether you prioritize speed or accuracy, understanding the strengths and weaknesses of each type can guide you in optimizing your testing strategy.
9026
9027## When Should You Perform Smoke Testing?
9028
9029The timing of smoke testing can substantially impact its efficacy. This section explores the optimal timing for creating and executing smoke test cases. It’s segmented by the nature of the project: web development, mobile development, and DevOps environments. Although many more types exist, these three cover most scenarios that developers will experience.
9030
9031### Web Development
9032
9033In the fast-paced world of web development, smoke testing is an indispensable asset. It serves as an immediate validation checkpoint, confirming that new code integrations haven't adversely affected existing functionalities. This is especially vital in agile frameworks, where the ability to pivot quickly is a requirement.
9034
9035### Mobile Development
9036
9037For mobile application development, the timing of smoke testing is equally crucial. It's generally advisable to conduct smoke tests immediately after each new build, to quickly validate essential features such as navigation, login, and data retrieval. This proactive approach ensures that the application remains both functional and user-friendly.
9038
9039### DevOps Environments
9040
9041Within DevOps ecosystems, particularly those that emphasize continuous integration, the frequency of smoke testing should be synchronized with the rate of new code commits and integrations. This alignment guarantees that emerging issues are promptly identified and addressed, thereby preserving the integrity and fluidity of the development pipeline.
9042
9043The timing for conducting smoke tests is not a universal standard; it depends on the specific project type and the development methodology being used. But figuring out the ideal timing for smoke testing is essential. Timing plays a significant role in o
9043ptimizing the test's effectiveness and, consequently, contributes to the overall success of your project.
9044
9045## Smoke Testing vs. Other Types of Testing
9046
9047Understanding the nuanced differences between smoke testing and other forms of testing — namely sanity testing and regression testing — is pivotal for crafting an effective testing strategy. This section aims to explain these differences by offering a criteria-based comparison, so you can make informed decisions tailored to your project's unique requirements and challenges.
9048
9049### Objective: More Than Just Semantics
9050
9051Each type of testing serves a unique objective. Smoke testing serves as a gatekeeper, ensuring that a new build is stable and functional enough to merit more detailed testing. [Sanity testing](https://www.geeksforgeeks.org/sanity-testing-software-testing/) is akin to a surgical strike, focusing exclusively on specific functionalities or components that have recently been modified, while still not moving into full regression testing. [Regression testing](https://katalon.com/resources-center/blog/regression-testing) serves as a comprehensive safety net, ensuring that new code changes have not inadvertently affected existing functionalities.
9052
9053.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
9054
9055| Testing | Objective |
9056| ------- | --------- |
9057
9058| Smoke Testing  
9059 | Validates that new builds are stable and functional enough for further testing |
9060| Sanity Testing  
9061 | Focuses on specific functionalities or components that have undergone recent changes |
9062| Regression Testing | Ensures that new code changes haven't disrupted existing functionalities |
9063
9064### Scope: The Breadth of Examination
9065
9066Smoke testing is a broad sweep, designed to test essential functionalities and ensure an application's basic stability. Sanity testing narrows its focus to specific functionalities or components that have been altered. Regression testing is the most exhaustive; it scrutinizes all areas of an application to ensure that even untouched segments remain unaffected by new changes.
9067
9068.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
9069
9070| Testing | Scope |
9071| ------- | ----- |
9072
9073| Smoke Testing  
9074 | Broad, covering essential functionalities |
9075| Sanity Testing  
9076 | Narrow, focusing on recently changed functionalities or components |
9077| Regression Testing | Comprehensive, scrutinizing all areas of the application |
9078
9079### Timing: When to Test?
9080
9081Smoke testing is typically the first line of defense, performed immediately after a new build or major release. Sanity testing is more situational; it comes into play after specific changes or bug fixes. Regression testing is a constant companion throughout the development cycle, especially following any code changes or updates.
9082
9083.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
9084
9085| Testing | Timing |
9086| ------- | ------ |
9087
9088| Smoke Testing  
9089 | Immediately after a new build or major release |
9090| Sanity Testing  
9091 | After specific changes or bug fixes |
9092| Regression Testing | Constantly, especially after any code changes or updates |
9093
9094### Depth: A Matter of Complexity
9095
9096Smoke testing is quick and shallow, designed for early identification of high-level issues. Sanity testing, while also quick, delves deeper into specific functionalities to ensure that they are working as intended. Regression testing is the most thorough; it meticulously examines an application for unintended side effects due to new code changes.
9097
9098.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
9099
9100| Testing | Depth |
9101| ------- | ----- |
9102
9103| Smoke Testing  
9104 | Quick and shallow |
9105| Sanity Testing  
9106 | Quick but deep into specific functionalities |
9107| Regression Testing | Thorough and meticulous |
9108
9109## Automating Smoke Testing: A Smart Move
9110
9111In the ever-evolving landscape of software development, the need for efficient and reliable testing methods is high. Automated smoke testing has emerged as an indispensable tool, especially in environments that prioritize DevOps and continuous integration. But what sets it apart from manual methods?
9112
9113A biased comparison of automated and manual testing can end up producing a table like this:
9114
9115.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
9116
9117| Criteria | Manual Testing | Automated Testing |
9118| -------- | -------------- | ----------------- |
9119
9120| Speed  
9121 | Thorough but time-consuming. | Can run in parallel, significantly reducing the time needed for testing. |
9122| Accuracy  
9123 | Susceptible to human error. | Offers a higher degree of reliability, ensuring that the tests are performed the same way every time. |
9124| Reliability | Limited by human fatigue and subject to inconsistency. | Can be run as frequently as needed without fatigue, ensuring consistent results. Crucial for continuous integration pipelines. |
9125
9126It’s important to note again that manual testing has its place in some organizations. However, with more and more tools becoming available with a focus on easing test automation, it’s becoming tougher and tougher to justify manual testing.
9127
9128Also, automation in smoke testing is not just a technical upgrade; it's a business enabler.
9129
9130.table_component { overflow: auto; width: 100%; } .table_component table { border: 1px solid #dededf; height: 100%; width: 100%; table-layout: fixed; border-collapse: collapse; border-spacing: 1px; text-align: left; } .table_component caption { caption-side: top; text-align: left; } .table_component th { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; } .table_component td { border: 1px solid #dededf; background-color: #ffffff; color: #000000; padding: 5px; }
9131
9132| Technical Advantages | Business Benefits |
9133| -------------------- | ----------------- |
9134
9135| Speed: Tests can run in parallel, reducing the overall testing time.  
9136 | Cost-Efficiency: Automated tests, once set up, can run without additional costs, making them cost-effective in the long run. |
9137| Reusability: Test scripts can be reused, ensuring consistency across different stages.  
9138 | Faster Time-to-Market: With quicker testing cycles, products can be released to the market more rapidly. |
9139| Accuracy: Eliminates human error, increasing the test's reliability. | |
9140
9141### Meticulous as a Benchmark for Automated Smoke Testing
9142
9143![Screenshot of the Meticulous app showing visual differences](/images/blog/smoke-testing/65675605baad647a9398a057_visual-diffs-on-a-meticulous-app.png)
9144
9145Meticulous stands out as a popular tool for automated smoke testing, particularly for UI bug detection. It integrates seamlessly into CI/CD pipelines and offers unique features like replaying user traffic to catch UI bugs. Below is a sample configuration for integrating Meticulous into your CI/CD pipeline:
9146
9147\`\`\`
9148# Sample Configuration for Meticulous in a CI/CD pipeline
9149steps:
9150  - name: Run Meticulous Smoke Tests
9151    uses: meticulous-action@v1
9152    with:
9153      api_key: \${{ secrets.METICULOUS_API_KEY }}
9154\`\`\`
9155
9156The decision to automate smoke testing is not merely a technical one; it's a strategic choice that has far-reaching implications for both the quality and business aspects of a project. By understanding the comparative advantages and integrating tools like Meticulous, organizations can significantly enhance their testing efficiency and overall software quality.
9157
9158## Final Thoughts
9159
9160Smoke testing is a cornerstone of software quality assurance — it serves multiple roles, from gatekeeper to early-warning system. Its versatility is evident in its adaptability to various forms of testing — manual, automated, and hybrid — each with its unique set of advantages and limitations. Choosing a type of smoke test is a nuanced process. Your decision should be tailored to the specific requirements and constraints of your project.
9161
9162The timing of smoke tests is another pivotal factor that varies depen
9162ding on the nature of the project and the development methodology in use. Whether you’re engaged in web development, mobile application creation, or operating within a DevOps framework, the timing of these tests can significantly influence their effectiveness.
9163
9164Moreover, understanding the differences between smoke testing and other types of testing — such as sanity and regression testing — is indispensable for crafting a comprehensive and effective testing strategy. Each serves a unique purpose and has its own scope and depth, and understanding these can guide you in optimizing your overall testing approach.
9165
9166Automated smoke tests, especially in fast-paced DevOps and CI/CD environments, is not only a technical decision but also a strategic one. Tools like Meticulous offer a streamlined approach to automated testing, saving time and reducing human error — thereby acting as significant business enablers. The initial investment in setting up automated tests is quickly offset by the gains in speed, reliability, and overall efficiency.
9167
9168### Final Recommendations
9169
91701.  Assess Your Needs: Before implementing any form of smoke testing, conduct a thorough assessment of your project’s specific needs, constraints, and objectives. This will guide you in choosing the most appropriate form of testing — manual, automated, or hybrid.
91712.  Prioritize Timing: Align the timing of your smoke tests with your development cycle. Immediate testing after new builds or major releases can provide quick feedback, enabling timely corrections.
91723.  Choose the Right Tools: If you opt for automated or hybrid testing, invest in reliable and user-friendly tools that require minimal setup but offer maximum efficiency. This not only speeds up the testing process but also ensures more accurate results.
91734.  Continual Review: Given the dynamic nature of software development, it's crucial to continually review and update your testing strategies to adapt to new challenges and requirements.
91745.  Integrate into CI/CD: If you are operating in a DevOps environment, integrate your automated smoke tests into your CI/CD pipeline for seamless and automated quality checks.
9175
9176By implementing smoke testing in your software development processes, you not only ensure the quality and reliability of your product but also contribute to more efficient and effective development cycles. It is an investment in quality that pays rich dividends in both the short and long term.
9177
9178## Meticulous
9179
9180Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
9181
9182This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
9183
9184This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
9185`,m3=`---
9186title: "How to Speed Up Your Frontend Monorepo Build"
9187slug: "speed-up-your-frontend-monorepo-build"
9188date: "2024-01-16"
9189description: "An overview of how to speed up your frontend monorepo build."
9190author: "Myles Scolnick"
9191authorImage: "/images/blog/authors/655c8ea4571721b00f779bac_myles-scolnick.png"
9192heroImage: "/images/blog/speed-up-your-frontend-monorepo-build/656a186285f6674a84d471c4_speed-up-your-frontend-monorepo-build.png"
9193heroImageAlt: "How to Speed Up Your Frontend Monorepo Build"
9194categories: ["Resources"]
9195showTableOfContents: true
9196showPromo: true
9197---
9198
9199In today's fast-paced development environment, speed and efficiency are crucial for a successful software development process. A monorepo is a popular strategy for organizing and managing codebase
9199s, especially in large-scale projects. It offers several advantages, such as simplified dependency management, code sharing, and collaboration.
9200
9201However, as the size of your frontend monorepo grows, build times can become a bottleneck, slowing down your development process and negatively impacting developer productivity. In this blog post, we'll explore how to speed up both your local and continuous integration (CI) builds using [Turborepo](https://turbo.build/repo) and [Turbocache](http://turbocache.build/). Not only will these techniques lead to faster development cycles but will also reduce your CI costs.
9202
9203## Getting started with Turborepo
9204
9205Turborepo is a high-performance build system specifically designed for monorepos, optimizing and parallelizing tasks while leveraging caching. Let's dive into how to set up Turborepo for your project.
9206
9207If you have already set-up Turborepo, you can skip to the next section.
9208
9209To get started with Turborepo, you need to install it as a global dependency:
9210
9211\`\`\`
9212npm install -g turbo
9213\`\`\`
9214
9215Once Turborepo is installed, add a root turbo.json to your monorepo with your desired tasks. This configuration sets up a pipeline with three tasks: build, test, and lint. You may reference the [configuration options](https://turbo.build/repo/docs/reference/configuration) on the Turbo docs to fit your monorepo.
9216
9217\`\`\`
9218{
9219  "$schema": "https://turbo.build/schema.json",
9220  "pipeline": {
9221    "build": {
9222      "outputs": [".next/**", "!.next/cache/**"],
9223      "dependsOn": ["^build"]
9224    },
9225    "test": {
9226      "dependsOn": ["build"]
9227    },
9228    "lint": {}
9229  }
9230}
9231\`\`\`
9232
9233You can confirm your setup by running:
9234
9235\`\`\`
9236turbo lint build test
9237\`\`\`
9238
9239## Speeding up your CI builds with Turbocache
9240
9241Turbocache is a remote caching service that can significantly speed up your CI builds. It is designed to work with Turborepo and can be used with any CI provider, although this blog post will focus on GitHub Actions. Along with caching, Turbocache also provides a dashboard to monitor your builds as well as analytics to help you optimize your builds for better cacheability.
9242
9243![Turbocache screenshot](/images/blog/speed-up-your-frontend-monorepo-build/6568d8b608eb1ebee8035709_235961689-70570278-397c-474b-8e5d-51eff764805d.png)
9244
9245To get started, you need to create an account at [turbocache.build](https://turbocache.build/). Once you create an account, you will need to create an API token. Store this API token in your repository's secrets as TURBO_TOKEN.
9246
9247Update your Github Action workflow to use Turbocache. Your updated workflow should look similar to this:
9248
9249\`\`\`
9250name: CI
9251
9252env:
9253  # Turborepo
9254  TURBO_API: "https://cache.turbocache.build"
9255  TURBO_TOKEN: \${{ secrets.TURBO_TOKEN }}
9256  TURBO_TEAM: "meticulous_ci" # you may change this to your team name
9257  TURBO_RUN_SUMMARY: true
9258
9259jobs:
9260  # ...
9261  build:
9262    # ...
9263    steps:
9264      - uses: turbocache-build/turbocache-action@v1
9265      - uses: actions/checkout@v3
9266        # ...
9267      - run: npm install
9268      - run: npx turbo lint build test
9269\`\`\`
9270
9271For up-to-date instructions, please refer to the [Turbocache documentation](https://turbocache.build/docs).
9272
9273\\--
9274
9275In this blog post, we've explored how to speed up your frontend monorepo builds using [Turborepo](https://turbo.build/repo) and [Turbocache](http://turbocache.build/). By implementing these tools, you can optimize your local and CI builds, leading to faster development cycles and reduced CI costs. Don't let slow build times hold you back!
9276
9277## Meticulous
9278
9279Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
9280
9281This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
9282
9283This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
9284`,b3=`---
9285title: "Testing Pyramid for Frontend | In-Depth Tutorial"
9286slug: "testing-pyramid-for-frontend"
9287date: "2024-01-16"
9288description: "An overview and discussion of the testing pyramid, the balance between unit tests, integration tests and e2e tests, along with information on how this relates to frontend testing."
9289author: "Tahanima Chowdhury"
9290authorImage: "/images/blog/authors/65691e3f6bbef6ac2df2fd06_tahanima-chowdhury.jpeg"
9291heroImage: "/images/blog/testing-pyramid-for-frontend/65691d32cd8a0c688b0e2e64_testing-pyramid-for-frontend.webp"
9292heroImageAlt: "Testing Pyramid for Frontend | In-Depth Tutorial"
9293categories: ["Testing", "Tutorial"]
9294showTableOfContents: true
9295showPromo: true
9296hidden: true
9297---
9298
9299A testing pyramid is an outline that structures automated tests. It compares the cost, speed, and effort of different types of testing and provides a guideline for what proportion of each test should be present in an automated test suite.
9300
9301As your codebase grows in size, a proper test suite becomes necessary to diminish the frequency of costly bugs. Without a strategy, your test suite may become poorly designed, unreliable, and ineffective.
9302
9303In this article, you’ll learn about the frontend testing pyramid, including what the different kinds of tests are and what ratio of these tests you should maintain in your automated test suite.
9304
9305## What is the Testing Pyramid
9306
9307Any new change you introduce to your codebase has the potential to be error-prone. Software errors have varying [repurcussions](https://blog.qatestlab.com/2019/09/05/bugs-impact-business/) and may bring severe risks.
9308
9309To mitigate these risks, you need to have a quality assessment process in place, and automated tests should be an integral part of this process. Automated tests are the safety nets that provide you with quick feedback on the correctness of your codebase. However, you need different types of automated tests to develop confidence in your software.
9310
9311Not all automated test types provide the same return on investment. For instance, end-to-end tests give good feedback on the overall workflow of the application from the end user’s perspective. However, they tend to be fragile, which can result in high maintenance costs. On the contrary, [unit tests](https://www.meticulous.ai/blog/frontend-unit-testing-best-practices) cannot guarantee the functional correctness of the application; however, they are cheap to write and easy to maintain.
9312
9313For an effective automated test suite, you need to maintain the right balance of tests, and the testing pyramid provides a framework that addresses this balance.
9314
9315The testing pyramid constitutes three distinct levels. Unit tests are at the bottom level, while integration tests and end-to-end tests make up the middle and the top levels, respectively. According to the [testing pyramid](https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html), you should have a lot of unit tests, some integration tests, and very few end-to-end tests in comparison to the other types. As you move up the testing pyramid, each layer of testing represents a decrease in speed of execution and an increase in scope, complexity, and maintenance. Here’s what a testing pyramid looks like:
9316
9317![The testing pyramid](/images/blog/testing-pyramid-for-frontend/65691d48627ed3e15f2d4569_testing-pyramid.jpeg)
9318
9319## Types of Frontend Tests
9320
9321This article focuses on the _frontend_ testing pyramid, and in the following sections, you’ll look at the different types of tests that you can use on the frontend.
9322
9323## Unit Tests
9324
9325Unit tests validate that the units within your codebase are working as expected. A unit is the smallest piece of code that encapsulates a small behavior and can be tested in isolation. A unit can be a function, a class, or even a method in a class.
9326
9327When you refactor your code or introduce any new code, you can rerun all the unit tests to ensure the change doesn’t break existing functionalities. As each unit test acts on a small chunk of code, the execution happens in seconds and provides you with quick feedback. If any of the unit tests break, you can quickly isolate the root cause, which reduces your debugging efforts.
9328
9329A [JavaScript](https://www.meticulous.ai/blog/javascript-reference-errors) framework like [Jest](https://jestjs.io/) facilitates unit testing of your code. The following is an example of a unit test:
9330
9331\`\`\`
9332function sum(a, b) {
9333  return a + b;
9334}
9335
9336test('1 + 2 equals 3', () => {
9337  expect(sum(1, 2)).toBe(3);
9338});
9339\`\`\`
9340
9341[Jest](https://www.meticulous.ai/blog/how-to-use-jest-spyon) is simple, relies on no third-party tools for most of its functionality, and requires zero configuration. After installation, you can start writing your tests immediately.
9342
9343## Component Tests
9344
9345Component tests assess the usability and behavior of individual components in an application. They are executed after unit tests and before integration tests. In this way, they help prevent bugs before they propagate to higher levels of testing.
9346
9347Here’s an example component test using [Jest](https://jestjs.io/docs/tutorial-react#react-testing-library) and the [React Testing Library](https://github.com/testing-library/react-testing-library):
9348
9349\`\`\`
9350import {cleanup, fireEvent, render} from '@testing-library/react';
9351import CheckboxWithLabel from '../CheckboxWithLabel';
9352
9353// Note: running cleanup afterEach is done automatically for you in @testing-library/[email protected] or higher
9354// unmount and cleanup DOM after the test is finished.
9355afterEach(cleanup);
9356
9357it('CheckboxWithLabel changes the text after click', () => {
9358  const {queryByLabelText, getByLabelText} = render(
9359    ,
9360  );
9361
9362  expect(queryByLabelText(/off/i)).toBeTruthy();
9363
9364  fireEvent.click(getByLabelText(/off/i));
9365
9366  expect(queryByLabelText(/on/i)).toBeTruthy();
9367});
9368\`\`\`
9369
9370## Snapshot Tests
9371
9372Snapshot tests ensure that your application’s UI has not changed unintentionally. They achieve this by comparing the current snapshot with the previous snapshot of a rendered component before and after code changes. A failing test can indicate two things: if the test results are unexpected, you need to resolve the issue with your component; if the test results are expected, you need to update your snapshot tests to support the new output.
9373
9374Snapshot tests are useful to track changes in simple, stable components. If anything changes in these components, snapshot tests can immediately detect and minimize the regression efforts.
9375
9376Following is an example snapshot test using [Jest](https://jestjs.io/docs/snapshot-testing#snapshot-testing-with-jest).
9377
9378\`\`\`
9379import renderer from 'react-test-renderer';
9380import Link from '../Link';
9381
9382it('renders correctly', () => {
9383  const tree = renderer
9384    .create(Facebook)
9385    .toJSON();
9386  expect(tree).toMatchSnapshot();
9387});
9388\`\`\`
9389
9390## Integration Tests
9391
9392Integration tests verify whether two or more units work together correctly after combining. These tests focus on the integrating links instead of the correctness of individual units that have already been tested. You only need to perform integration tests when your unit tests give a green light.
9393
9394A passing integration test is a prior indicator of the correctness of the application’s interactions. If an integration test fails, you can take measures to fix it early on. This prevents the bug from slipping out to a higher level of testing and, therefore, reduces operational costs.
9395
9396Here’s an example integration test, taken from the [CSS-Tricks website](https://css-tricks.com/react-integration-testing-greater-coverage-fewer-tests/#aa-option-2-integration-tests):
9397
9398\`\`\`
9399test('successful login', async () => {
9400  jest
9401    .spyOn(window, 'fetch')
9402    .mockResolvedValue({ json: () => ({ token: '123' }) });
9403
9404  render();
9405
9406  const emailField = screen.getByRole('textbox', { name: 'Email' });
9407  const passwordField = screen.getByLabelText('Password');
9408  const button = screen.getByRole('button');
9409
9410  // fill out and submit form
9411  fireEvent.change(emailField, { target: { value: '[email protected]' } });
9412  fireEvent.change(passwordField, { target: { value: 'password' } });
9413  fireEvent.click(button);
9414
9415  // it sets loading state
9416  expect(button).toBeDisabled();
9417  expect(button).toHaveTextContent('Loading...');
9418
9419  await waitFor(() => {
9420    // it hides form elements
9421    expect(button).not.toBeInTheDocument();
9422    expect(emailField).not.toBeInTheDocument();
9423    expect(passwordField).not.toBeInTheDocument();
9424
9425    // it displays success text and email address
9426    const loggedInText = screen.getByText('Logged in as');
9427    expect(loggedInText).toBeInTheDocument();
9428    const emailAddressText = screen.getByText('[email protected]');
9429    expect(emailAddressText).toBeInTheDocument();
9430  });
9431});
9432\`\`\`
9433
9434## End-to-End Tests
9435
9436End-to-end tests simulate end users and their interactions with the application. They cover user journeys from beginning to end and ensure all the integrated pieces of the application work as expected. End-to-end tests boost confidence in the overall functional correctness of the application.
9437
9438They provide an indicator of the application’s production readiness. However, they are very slow to run because they involve building, deploying, opening a browser, navigating to the web page, and performing actions around the application. If an end-to-e
9438nd test fails, it’s difficult to trace back the root cause because of the scope that it covers. They are hard to maintain and can be fragile.
9439
9440End-to-end tests should complement other low-level tests and are not meant to replace them.
9441
9442Following is an example [end-to-end test](https://docs.cypress.io/guides/end-to-end-testing/writing-your-first-end-to-end-test#Adding-more-commands-and-assertions) using [Cypress](https://www.meticulous.ai/blog/cypress-testing).
9443
9444\`\`\`
9445describe('My First Test', () => {
9446  it('Gets, types and asserts', () => {
9447    cy.visit('https://example.cypress.io')
9448
9449    cy.contains('type').click()
9450
9451    // Should be on a new URL which includes '/commands/actions'
9452    cy.url().should('include', '/commands/actions')
9453
9454    // Get an input, type into it and verify that the value has been updated
9455    cy.get('.action-email')
9456      .type('[email protected]')
9457      .should('have.value', '[email protected]')
9458  })
9459})
9460\`\`\`
9461
9462Cypress is simple to set up, requires no dependencies or downloads, and provides debugging capabilities. If you want an alternative to Cypress for end-to-end testing, you can check out [Playwright](https://playwright.dev/) and [Selenium](https://www.meticulous.ai/blog/is-selenium-worth-it). Read more about Selenium in this post [here](https://meticulous.ai/blog/is-selenium-worth-it/).
9463
9464## Visual Regression Tests
9465
9466[Visual regression](https://www.meticulous.ai/blog/the-beginner-guide-to-visual-regression-testing) tests scan for visual bugs (e.g. font, layout, color, and rendering issues) and verify the content of a web page b
9466y comparing screenshots taken before and after code changes. These tests are useful for applications that have graphical elements, such as tables, charts, and dashboards, since visual validation with traditional automated functional testing tools can be difficult.
9467
9468[Percy](https://percy.io/) and [Chromatic](https://www.chromatic.com/) are some visual regression test tools. When you run a visual regression test, Percy takes screenshots of the UI across different browsers and responsive breakpoint widths. It then performs a pixel-by-pixel baseline comparison to detect and highlight any visual change.
9469
9470Chromatic automates workflows for [Storybook](https://storybook.js.org/). Learn more about Storybook in this blog post [here](https://meticulous.ai/blog/testing-react-components-with-storybook/). As you publish a new Storybook, Chromatic takes screenshots and highlights any visual change.
9471
9472Following is an [example visual regression test](https://docs.percy.io/docs/cypress#setup) using Percy and Cypress:
9473
9474\`\`\`
9475describe('Integration test with visual testing', function() {
9476  it('Loads the homepage', function() {
9477    // Load the page or perform any other interactions with the app.
9478    cy.visit();
9479
9480    // Take a snapshot for visual diffing
9481    cy.percySnapshot();
9482  });
9483});
9484\`\`\`
9485
9486## Replay Tests
9487
9488Replay tests, typically executed by a tool like [Meticulous](https://meticulous.ai/), determine whether there are any unexpected changes in the application’s workflow. As you manually interact with the application, the underlying tool captures your actions and converts them into tests. They require minimal effort to create, and you only need to structure your user flows to reap the benefits of these tests and provide a great convenience for validating end-to-end functionalities. You can even use Meticulous to record sessions on prod or dev, providing some testing with zero setup.
9489
9490For more information check out [Meticulous](https://www.meticulous.ai/).
9491
9492## Accessibility Tests
9493
9494Accessibility tests are metrics to determine whether people with disabilities can use the application without any problems. An application with better accessibility widens its reach to a large audience. It’s important to incorporate these tests as you build your application and not as an afterthought.
9495
9496Here’s an example accessibility test using Cypress and [axe-core](https://github.com/dequelabs/axe-core):
9497
9498\`\`\`
9499describe(‘Accessibility tests’, () => {
9500  beforeEach(() => {
9501    cy.visit('/');
9502    cy.injectAxe();
9503  });
9504
9505  it(‘Has no accessibility violations’,() => {
9506    cy.checkA11y();
9507  });
9508});
9509\`\`\`
9510
9511## Creating your Own Testing Pyramid
9512
9513As previously stated, a testing pyramid groups tests into three different layers, where unit, integration, and end-to-end tests occupy the bottom, middle, and top layers of the pyramid. The basic idea is that you should have high coverage of unit tests because they are cheap to build, easy to maintain, fast to run, and stable. Once tests, such as end-to-end tests, involve multiple units and interactions, they tend to become more expensive to build, hard to maintain, slower to run, and more brittle. For this set of tests you should aim for coverage of critical workflows, bearing in mind that you will almost certainly have a very limited number of end-to-end tests.
9514
9515The testing pyramid referred to here should be used as a reference point as you think through structuring your own automated tests. It’s up to you to decide what types of tests are best suited to your application and in what proportion. For example, the testing pyramid suggests having few end-to-end tests in your test suite because of the maintenance burden. However, recent years have seen significant advances in tools that support end-to-end tests.
9516
9517[Replay tests](https://meticulous.ai/blog/let-users-write-tests-for-you/), which are a form of end-to-end tests, ease the testing process to a great extent. With replay tests, you interact with the application manually, and the underlying tool records your actions and saves them as repeatable tests. You can then use these tests for regressions. [Replay testing](https://www.meticulous.ai/blog/let-users-write-tests-for-you) tools empower everyone on the team, from technical to nontechnical members, to contribute. They deliver more value since they precisely convert user actions to test steps.
9518
9519Reiterating, you do not need to abide by the design decision made in the testing pyramid. Instead of a testing pyramid, your design may represent the [testing trophy](https://kentcdodds.com/blog/the-testing-trophy-and-testing-classifications) based on the priority you have set for your tests.
9520
9521## Conclusion
9522
9523In this article, you learned about the frontend testing pyramid, the different types of tests that you can incorporate into your application’s frontend, and the proportion of these tests that you can include in your automated test suite.
9524
9525## Meticulous
9526
9527Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
9528
9529This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way 
9529we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
9530
9531This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
9532`,w3=`---
9533title: "How to Test React Components in Isolation with Storybook"
9534slug: "testing-react-components-with-storybook"
9535date: "2024-01-16"
9536description: "A guide and tutorial on how to test React components in isolation using Storybook. This includes guide uses a 'React counter' for code examples, and discusses various advantages of using Storybook."
9537author: "Alex Merced"
9538authorImage: "/images/blog/authors/6568f9276e20a9ef23a6e08d_alex-merced.webp"
9539heroImage: "/images/blog/testing-react-components-with-storybook/65691ed0908a038c2f08e339_testing-react-components-with-storybook.webp"
9540heroImageAlt: "How to Test React Components in Isolation with Storybook"
9541categories: ["React"]
9542showTableOfContents: true
9543showPromo: true
9544hidden: true
9545---
9546
9547One of the challenges in frontend development is isolating and testing components, especially those made with frameworks like [React](https://www.meticulous.ai/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial), Vue, Svelte and others. It is often required to build out other related parts of the application just to test a single component. This gets harder when trying to simulate unique edge cases which requires orchestrating particular configurations with other components. On top of this, tracking and documenting the various state and props of components’ gets tedious to maintain.
9548
9549This is where Storybook comes in, which works with many frontend frameworks. It’s an open source tool which makes it easy to test and document components. Let’s dive in.
9550
9551## Setting Up Story Book with React
9552
9553The final code for this tutorial is visible on [this repository](https://github.com/AlexMercedCoder/storybook-react-example/) for reference.
9554
9555![Setting up Storybook with React](/images/blog/testing-react-components-with-storybook/65691ee2282dd37a09d1c47f_repository.gif)
9556
9557Prerequisites:
9558
9559- NodeJS Installed
9560- IDE of your choice
9561
9562We are going to use React for this tutorial. Create a new React project with the following command.
9563
9564\`\`\`
9565npx create-react-app sb-testrun
9566\`\`\`
9567
9568Now that we have our React application, change directory in your terminal to your new application and run the storybook installer. Storybook will automatically determine which framework you’re using.
9569
9570\`\`\`
9571npx storybook init
9572\`\`\`
9573
9574You’ll notice a new .storybook directory that contains some configuration files. We don’t need to touch these at the moment.
9575
9576![Storybook directory containing some configuration files](/images/blog/testing-react-components-with-storybook/65691f102c3ab90e368d0ef1_storybook-directory-containing-some-configuration-files.png)
9577
9578Notice that your package.json has a couple of new scripts that weren’t there before.
9579
9580\`\`\`
9581    "storybook": "start-storybook -p 6006 -s public",
9582    "build-storybook": "build-storybook -s public"
9583\`\`\`
9584
9585We can run Storybook and see the storybook interface by running npm run storybook. This will open a tab with your Storybook.
9586
9587![ Storybook](/images/blog/testing-react-components-with-storybook/65691f2e6c73ea3ca9889094_storybook.png)
9588
9589There isn’t much other than some links and example“stories”, but soon enough we’ll build out our Storybook.
9590
9591## Building a Basic Counter Component
9592
9593Let’s build a basic component which we can then test using Storybook.
9594
9595- create a folder in src called components
9596- in that folder create a Counter.js file
9597- in that file add the following
9598
9599\`\`\`
9600import { useState } from "react";
9601function Counter(props) {
9602  const [count, setCount] = useState(0);
9603  const addOne = () => setCount((previous) => previous + 1);
9604  return <button onClick={addOne}>{count}</button>;
9605}
9606export default Counter;
9607\`\`\`
9608
9609This will provide us a basic button that displays the number of times it has been clicked. To see this on our storybook dashboard we will need to create a story.
9610
9611## Stories: The Building Blocks of Storybook
9612
9613A story captures the rendered state of a UI component. Developers write multiple stories per component that describe all the“interesting” states a component can support.
9614
9615The storybook dashboard will show any “stories” we create. These are created with files called <name>
9615.stories.js which storybook will detect then load up on the Storybook dashboard. Let’s create a Counter.stories.js file in the src/stories folder with the following contents:
9616
9617\`\`\`
9618import React from 'react';
9619import Counter from '../components/Counter';
9620// This exported object determines what is shown on the dashboard
9621export default {
9622  title: 'Counter',
9623  component: Counter,
9624};
9625
9626export const Basic = () => <Counter/>;
9627\`\`\`
9628
9629If we open up Storybook with npm run storybook you should see Counter now added to our list of stories and under it the “Basic” story. You can now see and interact with the component in isolation but so far not much else.
9630
9631![Stories, the building blocks of Storybook](/images/blog/testing-react-components-with-storybook/65691f61326d6e8f8b8f2d28_stories--20the-building-blocks-of-storybook.png)
9632
9633Let’s modify the component so we can set the starting value of the counter. Update the 'Counter.js' to look like so:
9634
9635\`\`\`
9636import { useState } from "react";
9637function Counter({start = 0}) {
9638  const [count, setCount] = useState(start);
9639  const addOne = () => setCount((previous) => previous + 1);
9640  return <button onClick={addOne}>{count}</button>;
9641}
9642export default Counter;
9643\`\`\`
9644
9645Now we can add stories showing the counter with different starting values, let’s update Counter.stories.js like so:
9646
9647\`\`\`
9648import React from 'react';
9649import Counter from './components/Counter';
9650// This exported object determines what is shown on the dashboard
9651export default {
9652  title: 'Counter',
9653  component: Counter,
9654};
9655
9656export const Basic = () => <Counter/>;
9657export const Five = () => <Counter start={5}/>;
9658export const Ten = () => <Counter start={10}/>;
9659\`\`\`
9660
9661Now imagine we had a much more complex component with many props and many different possible permutations. Using Storybook we can create stories for many different possibilities without having to edit the actual application each time we want to test a particular scenario.
9662
9663## Adding Controls to Your Story
9664
9665Let’s edit our story so that we can set the props directly from storybook. Luckily, this can be done quite easily by create a template and using it our stories. Update your Counter.stories.js like so:
9666
9667\`\`\`
9668import React from 'react';
9669import Counter from './components/Counter';
9670// This exported object determines what is shown on the dashboard
9671export default {
9672  title: 'Counter',
9673  component: Counter,
9674};
9675// create a template that maps arguments to the component
9676const Template = (args) => <Counter {...args}/>;
9677// Define a story that uses the template
9678export const Basic = Template.bind({});
9679// Define the starting Arguments for that story
9680Basic.args = {
9681    start: 0
9682}
9683export const Five = () => <Counter start={5}/>;
9684export const Ten = () => <Counter start={10}/>;
9685\`\`\`
9686
9687- Template defines how the argument object relates to the Counter component.
9688- Basic is a story that uses Template to define it can take arguments.
9689- Basic.args defines the initial set of arguments, the initial values also help determine the type of inputs that will display in Storybook.
9690
9691Now when we look at the Storybook dashboard we can set the start prop from Storybook, then hit the little refresh button next to “docs” to have it rerender with the new arguments.
9692
9693![Storybook controls](/images/blog/testing-react-components-with-storybook/65691fb7080bcad1469888ae_storybook-controls.png)
9694
9695## Writing Documentation in Storybook
9696
9697We can also use our Storybook dashboard as documentation. We can use MDX to write our documentation. In the src/stories folder create a Counter.mdx file. This will act as documentation for Counter component. The contents of this file are below.
9698
9699\`\`\`
9700## Counter Component
9701This component render a button that counts.
9702### Props
9703- \`start\` a number that determines the starting count
9704\`\`\`
9705
9706We can display this on the dashboard for documentation by updating our Counter.stories.js like so, adding a ‘docs’ parameter:
9707
9708\`\`\`
9709import React from 'react';
9710import Counter from '../components/Counter';
9711import CounterDocs from './Counter.mdx';
9712// This exported object determines what is shown on the dashboard
9713export default {
9714  title: 'Counter',
9715  component: Counter,
9716  // Here is where we set the docs which we imported above
9717  parameters: {
9718    docs: {
9719      page: CounterDocs
9720    }
9721  }
9722};
9723// create a template that maps arguments to the component
9724const Template = (args) => <Counter {...args}/>;
9725// Define a story that uses the template
9726export const Basic = Template.bind({});
9727// Define the starting Arguments for that story
9728Basic.args = {
9729    start: 0
9730}
9731export const Five = () => <Counter start={5}/>;
9732export const Ten = () => <Counter start={10}/>;
9733\`\`\`
9734
9735- We import the .mdx file into a variable CounterDocs
9736- We then pass the variable to parameters.docs.page to set it as the docs page
9737
9738Now if you click on “Docs” on the Storybook dashboard when browsing the Counter story you’ll see our documentation.
9739
9740![Storybook docs](/images/blog/testing-react-components-with-storybook/65691fec81f4a6c477f1389e_storybook-docs.png)
9741
9742## Running Tests on Your Components
9743
9744We can do a spot test where we manually inspect the component in the browser and change the props using the dashboard to see their effect. However, we can easily introduce automated testing with one of Storybook’s many integrations with testing libraries. Let’s use Jest.
9745
9746To setup testing, run the following command to install the necessary libraries:
9747
9748\`\`\`
9749npm install @storybook/test-runner jest@27 --save-dev
9750\`\`\`
9751
9752Add the following to your scripts in package.json:
9753
9754\`\`\`
9755"test-storybook": "test-storybook"
9756\`\`\`
9757
9758Run npm run test-storybrook and it will run all your stories to make sure they all run successfully and print out the results. So now all your stories can double as [unit tests](https://www.meticulous.ai/blog/frontend-unit-testing-best-practices)! The [test-runner](https://storybook.js.org/docs/react/writing-tests/test-runner/) can go a lot deeper into snapshot testing and other functionality.
9759
9760## Other Types of Testing
9761
9762- [Visual Testing](https://storybook.js.org/docs/react/writing-tests/visual-testing) : Using the Chromatic service, you can create UI snapshots between commits to catch visual changes you may 
9762not have intended. Visual testing compares the rendered output which is different than [snapshot testing](https://storybook.js.org/docs/react/writing-tests/snapshot-testing) which compares the html to be rendered, both can be very useful for catching unexpected UI changes.
9763- [Accessibility Testing](https://storybook.js.org/docs/react/writing-tests/accessibility-testing): Accessibility is extremely important to make sure your application has the largest audience possible and is inclusive. To that end, Storybook has a11y add-on so you can run accessibility testing. These tests will check for accessibility concerns such as how friendly your application is to screen readers.
9764- [Interaction Testing](https://storybook.js.org/docs/react/writing-tests/interaction-testing): With Storybooks interaction testing add-on you can run tests that simulate user interactions with your components to test whether they respond correctly to click events or other interaction events.
9765
9766## Conclusion
9767
9768Storybook provides a suite a tools to allow frontend developers to sleep better at night by enabling them to experience their components in isolation, create documentation and run a variety of unit tests, snapshot tests, accessibility tests and more.
9769
9770## Meticulous
9771
9772Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
9773
9774This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
9775
9776This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
9777`,y3=`---
9778title: "Visual Regression Testing | The Beginner Guide in 2024"
9779slug: "the-beginner-guide-to-visual-regression-testing"
9780date: "2024-01-16"
9781description: "A guide covering the benefits of visual regression testing, automated vs. manual testing, as well as a description of Meticulous, a tool for visual regression testing without a maintenance burden."
9782author: "Geshan Manandhar"
9783authorImage: "/images/blog/authors/6568d05a61cacbddd7afcac0_geshan-manandhar.jpeg"
9784heroImage: "/images/blog/the-beginner-guide-to-visual-regression-testing/656a2ae55bc91e9b6399c2af_visual-regression-testing-guide.png"
9785heroImageAlt: "Visual Regression Testing | The Beginner Guide in 2024"
9786categories: ["Testing"]
9787showTableOfContents: true
9788showPromo: true
9789hidden: true
9790---
9791
9792Have you ever faced a situation where a feature was working but after a seemingly-unrelated change it now looks broken? [Visual regression testing](https://www.meticulous.ai/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial) helps you verify the visual aspects of your application’s user interface look the same across code changes, preventing this issue.
9793
9794In this post, you will learn about visual testing, its benefits, and an example showing how it works. You will also find out about the tradeoffs between manual and automated visual regression testing. Let's get started!
9795
9796## What is Visual Regression Testing
9797
9798A regression is a return to a former or less developed state. In software testing, regression testing refers to ensuring new changes applied to the software system do not unintentionally break something that was working earlier. These new changes could be a new feature, a bug fix, any code change, or even an update to a third-party library. You can read more about catching [JavaScript regressions](https://meticulous.ai/blog/javascript-regressions-and-how-to-prevent-them/) and preventing them.
9799
9800Visual regression guarantees that a change does not adversely impact the visual appearance of the user interface of an application. For instance, a software engineer changes the text box in the search bar. This may work fine for the search functionality, but suppose the text box style was also applied to the username in the login interface, and this broke the login functionality. This unintentional event of breaking the login functionality that was previously working c
9800an be avoided with proper visual regression testing.
9801
9802Visual regression tests check what the end user will see by comparing screenshots taken before and after code changes. This testing can be baked into the software’s [CI/CD pipeline](https://www.redhat.com/en/topics/devops/what-cicd-pipeline). In the next section, you will learn about the benefits of visual regression testing.
9803
9804## Visual Regression Testing Benefits
9805
9806The cost of any software bug [increases multifold](https://www.celerity.com/insights/the-true-cost-of-a-software-bug) when it is found later in the software engineering process. Similar to any other form of testing, the main benefit of visual regression testing is to help you catch any visual bug much earlier in the development phase rather than a later phase. A bug found by a software engineer while developing the feature will cost the company exponentially less compared to the same bug found by the customer after releasing the software. This is an area where [feature flags](https://www.atlassian.com/continuous-delivery/principles/feature-flags) and gradual release can help, but this is beyond the scope of this post.
9807
9808Visual regression testing can even find bugs that easily slip through functional testing. Functional testing can check those elements on a webpage render fine and can even be clicked on. But functional tests won’t check that the text on a button is partially overlapped by the input box. Such hard-to-test scenarios can have big impacts, like breaking a sign-up flow. As visual regression tests compare before and after screenshots even minor CSS changes can be verified to work. Next, you will see an example where good visual regression testing could have made the customer experience a lot better.
9809
9810## Example of What Visual Regression Testing can Uncover
9811
9812Visual regression testing becomes indispensable for customer-facing applications. These applications define your company’s brand image so even small issues can affect your brand image adversely. Below you can see an example of the homepage of [American Airlines](https://www.aa.com/homePage.do?locale=en_US):
9813
9814![American Airlines homepage](/images/blog/the-beginner-guide-to-visual-regression-testing/6568e717627d73bb56b40040_american-airlines-homepage.png)
9815
9816The page is relatively responsive up to the dimensions of a tablet. If you decrease the width of the desktop website’s homepage even more you can see the cracks show up. They load a different website for mobile web, so it is not a stark problem. If functional tests were run on the above page they will pass because all the text and elements are present, loaded successfully, and visible on the page but not visible for a smaller resolution. If there was visual regression testing for the above-shown smaller mobile-like dimension of the webpage, American Airlines could have easily found issues with the navigation bar. For example, adding the fourth link on the navigation bar called “Flight status” visually breaks the first link with the text “Find Flights”. Similarly, another benefit they would have got is adjusting the width of the “From and To” input boxes will make the text for Adults (12+) and Children(2-11) clearly visible, and so on.
9817
9818Visual regression testing can verify how the elements on the page are rendered visually. These tests don’t need to rely on selecting each element with unique selectors, making them much faster to write and maintain. In addition to simplifying the process, they also make functional testing practices much more effective and on par with what the end user sees. In the following part, you will learn about how visual regression tests work.
9819
9820## How Visual Regression Testing Works
9821
9822One form of visual regression testing, screenshot testing, works by taking screenshots of the UI before and after a code change is applied. Then the two screenshots are compared and any difference between them is highlighted. In this automated comparison, the difference threshold is configurable. A tiny difference can be allowed but any major unexpected visual difference should be flagged as a test failure. This is where the power of visual comparison comes to play. There is no need to find the right selector and write lots of code around it. Simply compare before and after screenshots for a given browser and screen dimension, then make sure the visibly highlighted differences are intended and caused by the change to be deployed.
9823
9824Below is a quick example of comparing two screenshots before and after some intended CSS changes for the Name and nationality guessing app used for the [Jest spyOn](https://meticulous.ai/blog/how-to-use-jest-spyon/) example:
9825
9826![Visual difference caught by visual regression test](/images/blog/the-beginner-guide-to-visual-regression-testing/6568e7175b6250bf2de936f0_visual-difference-caught-by-visual-regression-test.png)
9827
9828The flags in this app have been deliberately made smaller with the CSS change. This is an intentional change and not a regression. If the flag sizes had decreased without intention they would be caught easily with visual regression testing. Similar to other forms of tests, visual regression testing can be done manually or in an automated way, let's discuss that next.
9829
9830## Manual Visual Regression Testing
9831
9832Visual regression testing can be done manually, you can use tools to carry out the testing process. Software engineers and designers do it all the time. They try to find any visual defects when changing visual elements, especially when changing CSS code. They go to the browser after a code change and hit the refresh button or keyboard shortcut. Then verify that the code change is showing the expected output.
9833
9834Unfortunately, this process is slow, clumsy, painful, and not scalable. In addition, it is prone to human errors. Doing it in the early stages of an application can make sense. When the application is big, it is not humanly possible to check 100s of screens 
9834when 2 lines of code have changed. This is where regression can come up as the designer or software engineer will be focused on the page in their browser, and they might miss the other page(s) or component(s) using the same styling.
9835
9836Another way to do visual regression testing manually is with the use of image diff tools. You should take a screenshot of a page before and after a code change. Then those two screenshots can be passed into an [image diff tool](https://www.diffchecker.com/image-compare/) to see if there is any change detected. This is part manual and part automated. Still, it is not a scalable and automated way of visual regression testing. That surfaces the important need to do automated visual regression testing which is discussed next.
9837
9838## Automated Visual Regression Testing
9839
9840The process of visual regression testing can be automated with proper and purpose-built tools. It takes a few steps to get started with these. Like functional testing, it can be done for happy paths and main screens initially. As per the need, more pages can be covered by these tests.
9841
9842The visual regression testing process should be a step of the CI/CD pipeline too.
9843
9844A follow-up post will discuss the various tools available today. Some of these tools work by calculating a configurable pixel difference. However, all of these tools require you to write a test for each scenario that you want to test. The number of scenarios explodes as you may want to test many different flows and configuration combinations of your web app.
9845
9846These tests are highly effective for testing core flows, like a sign-up or checkout flow, but quickly become infeasible when trying to cover a large surface area. It’s simply not feasible to maintain hundreds of end-to-end [UI tests](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices) to cover hundreds of screens across a codebase, and this problem will only grow over time as the surface area of the codebase expands.
9847
9848Meticulous solves this problem by automatically generating [UI tests](https://www.meticulous.ai/blog/javascript-ui-testing-best-practices) for you, but without a maintenance burden. It does this by recording user sessions (on development or production) and simulating these sessions on new versions of code in order to enumerate various scenarios within your web application. You can learn more and try out Meticulous [here](https://meticulous.ai/).
9849
9850## Conclusion
9851
9852In this post, you learned about visual regression testing and the benefits of doing it with a real-life example. You were briefed about how visual testing works, which is by taking screenshots before and after a code change and comparing them to find any differences. After that, you learned about doing visual regression testing manually and in an automated way.
9853
9854Surely automating tests has the benefits of scalability and running the tests over and over again swiftly. Hopefully, you will exploit visual regression testing to catch any visual bugs and add more value to the software you deliver.
9855
9856## Meticulous
9857
9858Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
9859
9860This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
9861
9862This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
9863`,v3=`---
9864title: "TypeScript Unit Tests with Debugging"
9865slug: "typescript-unit-tests-with-debugging"
9866date: "2024-01-16"
9867description: "A short guide on how to set up TypeScript unit tests with debugging on VsCode, allowing you to set breakpoints on your unit tests."
9868author: "Johnny Reilly"
9869authorImage: "/images/blog/authors/656921b4178a0f7b8b8add38_johnny-reilly.jpeg"
9870heroImage: "/images/blog/typescript-unit-tests-with-debugging/656a1916ec3f99329fd2ef53_jest-typescript.png"
9871heroImageAlt: "TypeScript Unit Tests with Debugging"
9872categories: ["TypeScript", "Jest"]
9873showTableOfContents: true
9874showPromo: true
9875hidden: true
9876---
9877
9878Unit tests are an important part of the development process and are used to verify that the code is working as intended. This post will outline how to write [unit tests](https://www.meticulous.ai/blog/frontend-unit-testing-best-practices) using [TypeScript](https://www.meticulous.ai/blog/safer-exhaustive-switch-statements-in-typescript) and how to debug them.
9879
9880## Unit Tests
9881
9882When we are writing unit tests to verify system behaviour, we have to make choices. We need to choose the test framework that we'll use to run our tests. In the [JavaScript](https://www.meticulous.ai/blog/javascript-reference-errors) world we'll be choosing from options including [Jest](https://www.meticulous.ai/blog/how-to-use-jest-spyon), Mocha, tape, Jasmine and others. There are numerous other testing tools like Cypress and Playwright which cover broader automated testing needs, but we're intentionally just thinking about unit tests right now and so we'll exclude those.
9883
9884Of the various choices available, Jest is (at time of writing) very much the most popular. Since we have do not have a particular reason for favouring one of the frameworks that isn't as popular as Jest, that's what we'll use.
9885
9886Tests are a wonderful tool for asserting system behaviour. However, they can fail for mysterious reasons. When that happens, it can be helpful to see what the computer can see. It can be helpful to be able to debug your tests in the way you might hope to debug your other code.
9887
9888In this post:
9889
98901.  We'll set up a TypeScript Node.js project, containing some code we'd like to test.
98912.  We'll configure our project to work with Jest and we'll write a test.
98923.  We'll debug our unit test.
9893
9894Let's begin.
9895
9896## Setting up our TypeScript Project
9897
9898First we'll create ourselves a new Node.js project:
9899
9900\`\`\`
9901mkdir typescript-unit-tests-with-debug-support
9902cd typescript-unit-tests-with-debug-support
9903npm init --yes
9904\`\`\`
9905
9906At this point we have an empty Node.js project. Let's add TypeScript to it as a dependency and initialise our TypeScript project:
9907
9908\`\`\`
9909npm install typescript
9910npx -p typescript tsc --init
9911\`\`\`
9912
9913We now have a fully working TypeScript Node.js project and we're ready to start writing some code!
9914
9915This is a post about demonstrating unit testing with TypeScript. So n
9915aturally we need something to test. We're going write a simple module called greeter.ts which has the following content:
9916
9917\`\`\`
9918export function makeGreeting(name: string): string {
9919  const lengthOfName = name.length;
9920  const greeting = \`Well hello there \${name}, I see your name is \${lengthOfName} characters long!\`;
9921  return greeting;
9922}
9923\`\`\`
9924
9925greeter.ts is a TypeScript file that contains a single simple function. The makeGreeting function takes a string parameter and, over a number of lines, constructs a greeting string which the function returns. The nature of the greeting is inconsequential. However, remember later we want to be able to debug our test. We've intentionally written a function featuring more than one line of code. We've done this so we can demonstrate the benefits of debugging by showing the program state as it is in the process of executing.
9926
9927## Setting up the Jest Project
9928
9929The next step after setting up our TypeScript Node.js project, is adding tests, and the ability to run them, using Jest.
9930
9931First of all we're going to need to add Jest to our project and initially configure it:
9932
9933\`\`\`
9934npm install --save-dev jest
9935npx jest --init
9936\`\`\`
9937
9938As part of the initialisation you should be prompted with a number of questions:
9939
9940\`\`\`
9941npx jest --init
9942
9943The following questions will help Jest to create a suitable configuration for your project
9944
9945✔ Would you like to use Typescript for the configuration file? … no
9946✔ Choose the test environment that will be used for testing › node
9947✔ Do you want Jest to add coverage reports? … no
9948✔ Which provider should be used to instrument code for coverage? › v8
9949✔ Automatically clear mock calls, instances, contexts and results before every test? … no
9950\`\`\`
9951
9952We'll select all the defaults; including not using TypeScript for the configuration file. We don't require a configuration file written in TypeScript to be able to write TypeScript tests. The initialisation will create a jest.config.js file which contains the configuration used to run our tests.
9953
9954Next, we'll update the scripts section of our package.json to invoke Jest:
9955
9956\`\`\`
9957  "scripts": {
9958    "test": "jest"
9959  },
9960\`\`\`
9961
9962At this point we're in a place where we can run tests written in JavaScript. But we want to run tests written in TypeScript. [Jest supports this scenario well](https://jestjs.io/docs/getting-started#using-typescript), using Babel. So we'll add the dependencies we need:
9963
9964\`\`\`
9965npm install --save-dev babel-jest @babel/core @babel/preset-env @babel/preset-typescript @types/jest
9966\`\`\`
9967
9968With all that done, let's see if we can write a test. We'll create a greeter.test.ts file to sit alongside greeter.ts:
9969
9970\`\`\`
9971import { makeGreeting } from './greeter';
9972
9973test('given a name produces the expected greeting', () => {
9974  expect(makeGreeting('George')).toBe(
9975    'Well hello there George, I see your name is 6 characters long!'
9976  );
9977});
9978\`\`\`
9979
9980This simple test, invokes the makeGreeting function in our greeter.ts file and asserts the return value is as expected. Let us see if we can run our test with npm run test:
9981
9982![Screenshot of tests passing](/images/blog/typescript-unit-tests-with-debugging/6569218253363e298b084f8d_screenshot-of-tests-passing.png)
9983
9984Success! We've now created a TypeScript project, written a function, written a test for that function and we have the ability to run it.
9985
9986## Set up Debugging Support
9987
9988The final thing we wanted to tackle was adding debug support. In times past, this was often quite tricky to configure. However, debugging has become much easier due to the excellent [vscode-jest](https://github.com/jest-community/vscode-jest) project, which is dedicated to making "testing more intuitive and fun". In fact, with this extension the experience is now very "plug and play".
9989
9990Inside VS Code, we will install the vscode-jest extension:
9991
9992![Screenshot of vscode jest extension](/images/blog/typescript-unit-tests-with-debugging/65692182d4036d9bcea8e2b6_screenshot-of-vscode-jest.png)
9993
9994Once it's installed, we'll need to restart VS Code, and we may also need to enter the Jest: Start All Runners command in VS Codes power bar:
9995
9996![Screenshot of jest start all runners command](/images/blog/typescript-unit-tests-with-debugging/656921825de0eb8b8d3edd7e_screenshot-jest-start-all-runners.png)
9997
9998Once the Jest runners have started, we start to see the benefits that the VS Code Jest plugin offers. Where tests exist in our 
9998code, they are detected by the plugin and run. Depending upon whether tests are passing or failing we will be presented with a red cross or a green tick denoting failure or success directly alongside the code:
9999
10000![Screenshot of jest test explorer](/images/blog/typescript-unit-tests-with-debugging/656921821be83eca40ab3746_screenshot-jest-test-explorer.png)
10001
10002Using the test explorer, it's possible to run tests on demand and it's now possible to debug them too. If you examine the test explorer and right / command click on a given test, you'll be presented with the option to debug a test:
10003
10004![Screenshot of test explorer to debug a test](/images/blog/typescript-unit-tests-with-debugging/65692182178a0f7b8b8ac741_screenshot-jest-test-explorer-debug-test.png)
10005
10006We can now debug and introspect each test that runs:
10007
10008![Screenshot debug test](/images/blog/typescript-unit-tests-with-debugging/65692182a0e399077eb26033_screenshot-jest-debug-test.png)
10009
10010If you look at the screenshot above you'll see we've stopped on a breakpoint, we're able to examine the context of the program at the point that it has paused. We can step further on in our code, we can do all the useful things that debugging affords us.
10011
10012## Conclusion
10013
10014In this piece we've taken a look at how to get up and running with a unit testable TypeScript project. Beyond that, we've demonstrated how we can debug our TypeScript tests using the VS Code editor.
10015
10016## Meticulous
10017
10018Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
10019
10020This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
10021
10022This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
10023`,_3=`---
10024title: "Visual Regression Testing | 6 Tools to Use"
10025slug: "visual-regression-testing-tools"
10026date: "2024-01-16"
10027description: "A guide on choosing the right visual regression testing tool."
10028author: "Geshan Manandhar"
10029authorImage: "/images/blog/authors/6568d05a61cacbddd7afcac0_geshan-manandhar.jpeg"
10030heroImage: "/images/blog/visual-regression-testing-tools/656a1c5a262bc9e761f0d471_visual-regression-testing-tools.png"
10031heroImageAlt: "Visual Regression Testing | 6 Tools to Use"
10032categories: ["Testing", "Tools"]
10033showTableOfContents: true
10034showPromo: true
10035hidden: true
10036---
10037
10038Visual regression testing ensures that your application’s user interface is not adversely affected when you release code changes. In this guide, we’ll discuss six of the many open-source and software as a service (SaaS) visual regression testing tools your team can start using today. Let’s dive in!
10039
10040## What Is Visual Regression Testing?
10041
10042In brief, [visual regression testing](https://www.meticulous.ai/blog/the-beginner-guide-to-visual-regression-testing/) verifies that a webpage’s elements are rendered properly from a visual point of view. They’re automated tests, so they can be run as part of a CI/CD pipeline; with a mature CI/CD pipeline, the correctness of the visual elements is checked with each code change pushed to the code revision server.
10043
10044Visual regression tests help you catch visual discrepancies in the development stage, before a bug reaches customers and negatively affects user experience, and before it becomes more costly to fix.
10045
10046Visual regression testing can also catch bugs that might slip through functional testing. For instance, a functional test will verify the existence of a button and whether it can be clicked. If the button is the same color as the background it’s on, the functional test won’t catch this visual problem, because the element is rendered on the screen and is clickable. On the other hand, a visual regression test will detect that those pixels have changed color in a way that was unintended. This is the power of visual regression testing, compared with other flavors of testing. In the next section, you’ll learn about some useful visual regression testing tools.
10047
10048## Which Visual Regression Testing Will Work Best for Your Team?
10049
10050Depending on what your team wants to achieve, there are multiple visual regression testing tools to choose from. You can find a tool that meets the requirements of your budget, your need for convenience, and your coding expertise. These tools can be divided into two broad categories: open-source tools and SaaS offerings. (These groups are not mutually exclusive, as an open-source tool might have a cloud-based SaaS offering as well.) Let’s start with the open-source options:
10051
10052### Open-Source Visual Regression Testing Tools
10053
10054Having a high number of variables like web browsers, screen sizes, operating systems, and responsive design makes visual regression testing complex. With existing open-source projects, this complexity can be managed to a great degree. Below are three popular open-source browser automation and testing tools that support visual testing in some form:
10055
10056- Selenium
10057- Cypress
10058- Playwright
10059
10060#### Selenium
10061
10062Selenium
10063
10064[Selenium](https://www.selenium.dev/) is the oldest and [one of the most popular](https://meticulous.ai/blog/is-selenium-worth-it/) browser automation tools. It’s built with Java and has an enviable [ecosystem](https://www.selenium.dev/ecosystem/) of browsers and frameworks, as well as big sponsors like BrowserStack and Sauce Labs. Selenium’s tagline is “Selenium automates browsers. That’s it! What you do with that power is entirely up to you.” Its code and SDKs for other languages like JavaScript can be found on [GitHub](https://github.com/SeleniumHQ/selenium).
10065
10066![Selenium website](/images/blog/visual-regression-testing-tools/6568e66d8815fbded00efeed_selenium-website.jpeg)
10067
10068To drive a browser, you need to use [Selenium WebDriver](https://www.selenium.dev/documentation/webdriver/). In the context of visual regression testing with Selenium, if you know Java, you can sew up a solution with [Selenium and Ashot](https://trailheadtechnology.com/how-to-automate-visual-testing-using-java-selenium-webdriver-and-ashot/). As with any other open-source tool, you’ll need to wire multiple projects together and create a solution that fits your needs.
10069
10070#### Cypress
10071
10072[Cypress](https://www.cypress.io/) is a JavaScript-based front-end testing tool that [claims to be built for the modern web](https://www.meticulous.ai/blog/cypress-testing). The useful [features of the Cypress framework](https://www.cypress.io/features) are automatic waiting, time travel, and screenshots. In the context of visual regression testing, the screenshot feature comes in very handy. A 2022 State of JavaScript survey found that Cypress was used by [42 percent of respondents](https://2022.stateofjs.com/en-US/libraries/testing/).
10073
10074![Cypress website](/images/blog/visual-regression-testing-tools/6568e66e0d89b7585c751d93_cypress-website.jpeg)
10075
10076Cypress has a useful ecosystem of [plugins](https://docs.cypress.io/plugins/directory). There are multiple community-supported plugins like [Cypress Visual Regression](https://github.com/cypress-visual-regression/cypress-visual-regression) and [cypress-image-diff](https://github.com/uktrade/cy
10076press-image-diff) that you can use to create your own visual regression testing suite. Compared with Selenium, Cypress and Cypress’s plugins are updated more frequently and have a more vibrant community behind them. If you’re already familiar with JavaScript, Cypress is a great tool for writing and maintaining your visual regression test suite.
10077
10078#### Playwright
10079
10080[Playwright](https://playwright.dev/) is a framework for web testing and automation developed by Microsoft for end-to-end testing. It can run your tests on Chromium, Firefox, and WebKit (Safari) using a single API. Unlike with Cypress, you can use JavaScript, TypeScript, Python, .NET, or Java to write tests with Playwright. It also supports testing for the mobile web, which is a bonus.
10081
10082![Playwright website](/images/blog/visual-regression-testing-tools/6568e66dd87e81eeff576386_playwright-website.jpeg)
10083
10084In terms of visual regression testing, Playwright’s official documentation has a page for [visual comparisons](https://playwright.dev/docs/test-snapshots). It mentions that Playwright includes the ability to produce and visually compare screenshots. This page has simple code examples for Type
10084Script and JavaScript, too. Playwright uses the [pixelmatch library](https://github.com/mapbox/pixelmatch) in the background to compare screenshots. You can pass in [options](https://playwright.dev/docs/test-assertions#page-assertions-to-have-screenshot-2), such as max diff pixels, threshold, and max diff pixel ratio, to fine-tune the visual difference delta for well-adjusted visual regression tests.
10085
10086Other open-source projects and tools for visual regression testing include [Puppeteer](https://developer.chrome.com/docs/puppeteer/) and [BackstopJS](https://github.com/garris/BackstopJS). They have their own place in the visual regression testing realm; however, the aforementioned three options are the most useful ones. 
10087
10088In the next section, we’ll take a look at some SaaS offerings for running visual regression testing.
10089
10090### SaaS Visual Regression Testing Tools
10091
10092You’ll need to write some code when you use open-source tools for visual regression testing. Your team will be responsible for managing configuration options, deciding where to run the tests, and determining how to execute tests. As you can with other software and tools, you can outsource all of these responsibilities and focus on writing and maintaining top-class visual regression tests; this is where the SaaS offerings shine.
10093
10094And as with any other area of software engineering, testing, and CI/CD, there are many notable players in the visual regression testing game. Below are three that will help you write meaningful visual regression tests for your web applications:
10095
10096- Meticulous
10097- Applitools
10098- Percy
10099
10100#### Meticulous
10101
10102[Meticulous](https://meticulous.ai/) helps you catch visual regressions without writing or maintaining UI tests. It replays user traffic to automatically catch visual regressions on your web application, with no side effects. It is the easiest way to create end-to-end tests without writing any code — and no code to write means no code to maintain.
10103
10104![](/images/blog/visual-regression-testing-tools/67179bf514105e4248299b60_656f1bd9c03b057f9de4bccd_meticulous-site.png)
10105
10106Meticulous is funded by [Y Combinator](https://www.ycombinator.com/companies/meticulous), the CTO of GitHub, the CPO of Adobe, and the founder of Vercel, to name a few. It can be set up in a minute by injecting a code snippet. Meticulous can be easily integrated with your CI/CD pipeline with one line of code and two simple clicks. To learn more about Meticulous, you can [book a demo](https://calendly.com/gabriel-h/meticulous-demo-booking) or [sign up](https://app.meticulous.ai/) to take it for a spin.
10107
10108Meticulous is not a layer on top of existing tools like Playwright or Selenium. It’s a different way of thinking about visual testing. It sets you free from writing code while still providing you with the power of visual tests. It eliminates the need to write or maintain end-to-end tests in the first-place, by generating tests from recorded user sessions and automatically maintaining them.
10109
10110#### Applitools
10111
10112[Applitools](https://applitools.com/) is a SaaS tool for visual testing and monitoring. It helps both development and QA teams reduce the amount of code used to write tests, and reduce the amount of time spent running and fixing them. It claims to be an AI-powered visual testing tool that is easy to set up and that integrates with all modern test frameworks. It has clear pricing plans that include Starter, Eyes, and Ultrafast Test Cloud, but the prices are not shown on the Applitools site. The free plan gives you one user and 100 checkpoints per month.
10113
10114![Applitools website](/images/blog/visual-regression-testing-tools/6568e66d4f55fc65a5a12c48_applitools-website.jpeg)
10115
10116Applitools runs tests in a scalable way with reporting for visual tests written in the tool or framework of your choice. It also lets you monitor the results of the tests in a dashboard. [Applitools Eyes](https://applitools.com/platform/eyes/) gives you a screen with the diff in screenshots, and you can accept or reject changes. It covers a wide [range of tools](https://applitools.com/tutorials/), from Cypress and Playwright on the web to Storybook and Appium for Native mobile apps.
10117
10118As shown in the tutorial for [Playwright with Typescript](https://applitools.com/tutorials/quickstart/web/playwright/typescript), Applitools’ SDK provides a layer of functionality that helps run the tests on multiple browsers and at various resolutions. The SDK also sends the results to the Applitools server. Applitools supports a commendable range of frameworks. If you’re looking for a visual and managed layer on top of your existing open-source visual regression testing tools, such as Playwright, Puppeteer, Selenium, and Watir, then Applitools is a good option.
10119
10120#### Percy
10121
10122[Percy](https://percy.io/) is a visual testing tool that supports cross-browser visual testing. It lists the visual changes as screenshot diffs on a dashboard, where they can be approved or denied. It has been [acquired by BrowserStack](https://blog.percy.io/exciting-news-3b10e7500605), which provides access to more than 3,000 browsers and iOS and Android devices for cross-browser testing. Percy has a free plan with 5,000 screenshots per month and unlimited team members included. The paid plans start at $149 per month; the lowest-price paid plan includes 25,000 screenshots and other [features](https://percy.io/features).
10123
10124![Percy website](/images/blog/visual-regression-testing-tools/6568e66de6e36b1c79c62618_percy-website.jpeg)
10125
10126Percy [integrates](https://docs.percy.io/docs/sdks) with many frameworks, such as Ember and Rails. It also has integrations with [Storybook for React](https://www.meticulous.ai/blog/testing-react-components-with-storybook), Angular, Vue.js, and Ember. Percy supports end-to-end testing frameworks including Cypress, TestCafe, Playwright, Selenium, and Nightwatch. The [Percy Snapshot CLI tool](https://docs.percy.io/docs/cli-snapshot) is the easiest way to get started with Percy. It can also be used with static sites like those built with [Jekyll](https://docs.percy.io/docs/jekyll).
10127
10128As shown in the official documentation for [Playwright](https://docs.percy.io/docs/playwright), the Percy SDK adds an extra layer to Playwright tests for taking screenshots. If you follow the steps in this [tutorial](https://docs.percy.io/docs/playwright-tutorial) after you run the tests, the screenshots are available on Percy. Then you can go through the review and approval process.
10129
10130You can [run Percy locally](https://docs.percy.io/docs/local-development), and it integrates with popular [source code management tools](https://docs.percy.io/docs/source-code-integrations) like GitHub and GitLab. Similar to Applitools, Percy acts as a bridge between visual testing runs and the evaluation and management of results.
10131
10132## What You Need to Know About Visual Regression Testing
10133
10134We hope this summary of what visual regression testing is, and why to use it, is a helpful resource. To recap, we highlighted three popular open-source tools that can do visual regression testing — namely, Selenium, Cypress, and Playwright. We also explored some SaaS offerings that provide an extra layer of test result aggregation and monitoring on top of existing open-source tools.
10135
10136The different tool in the SaaS bucket is Meticulous, which doesn’t add an extra tier to the existing testing tools but provides a completely different way of doing visual regression testing with configuration files and replaying user traffic.
10137
10138This means you don’t have to write any test code, so there’s nothing to maintain in the future. You can [book a demo today](https://calendly.com/gabriel-h/meticulous-demo-booking) or [sign up](https://app.meticulous.ai/signup) and try it out on your own.
10139
10140The choice of the right tool from the open-source or SaaS category will be determined by your team’s preferences. It will also depend on your budget and what your team wants to get out of visual regression testing. If your budget is low, then starting with an open-source tool and integrating it into your current CI/CD pipeline would be an option. If you have some money to spend, then rather than just building a layer on top of existing tools, consider the paradigm shift offered by Meticulous.
10141
10142Choose a tool or service that fits your needs, fold up your sleeves, and get started with visual regression testing today!
10143
10144## Meticulous
10145
10146Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
10147
10148This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
10149
10150This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
10151`,k3=`---
10152title: "Visual Regressions and How to Prevent Them"
10153slug: "visual-regressions-and-how-to-prevent-them"
10154date: "2024-01-16"
10155description: "An overview of visual regression testing, where it fits into the testing hierarchy and the trade-offs of employing it as part of your testing strategy."
10156author: "Charly Poly"
10157authorImage: "/images/blog/authors/656906c69c90ec66afecebf9_charly-poly.jpeg"
10158heroImage: "/images/blog/visual-regressions-and-how-to-prevent-them/656a59397568783cf404401f_visual-regressions-and-how-to-prevent-them.png"
10159heroImageAlt: "Visual Regressions and How to Prevent Them"
10160categories: ["Resources"]
10161showTableOfContents: true
10162showPromo: true
10163---
10164
10165From widgets to complete applications, the front-end has become a more complex and essential piece in most products’ success, particularly those which include UI and UX in their value proposition.
10166
10167For this reason, protecting the health of the user experience by ensuring that no regression has been introduced is critical.
10168
10169While end-to-end tests (E2E) are a complete - but costly - way to assess an application’s health, visual regression testing is a complementary and faster strategy for maintaining product quality. This article will guide you through the many visual regression testing solutions available, from technical libraries to SaaS products.
10170
10171## What is Visual Regression Testing
10172
10173[Visual regression](https://www.meticulous.ai/blog/the-beginner-guide-to-visual-regression-testing) tests ensure that an application displays the proper UI in a given user scenario. For example, visit the login page and ensure that all expected visual elements are present (input, buttons, etc.). These visual checks can be performed at two levels:
10174
10175- At the DOM level: by performing snapshots of the output DOM
10176- At the screen level: by performing screenshots to verify the actual pixel rendered on the screen
10177
10178An updated version of the well-known [pyramid of tests](https://martinfowler.com/articles/practical-test-pyramid.html#TheTestPyramid) would now be the following:
10179
10180![Pyramid of tests](/images/blog/visual-regressions-and-how-to-prevent-them/656922ba1baf9f0a659075e0_pyramid-of-tests.png)
10181
10182Unlike E2E tests, [visual regression tests](https://www.meticulous.ai/blog/the-beginner-guide-to-visual-regression-testing) do not necessarily require interacting with the page, making them faster to run and easier to maintain.
10183
10184## How to Use Visual Regression Tests
10185
10186Both types of visual regression testing (snapshot and screen) should be performed against the critical path of the application. An application’s critical path is the set of pages and features that define your product experience's core, for example, a login page, a register page, or a cart checkout page.
10187
10188## Snapshot Tests
10189
10190Snapshot tests, mainly performed with Jest, are commonly considered part of integration tests. They consist of rendering a component in a given scenario, and saving a snapshot of the resulting DOM. When the test gets updated, the new DOM result gets compared with the existing one.
10191
10192If any change is found, an error is raised to notify of the regression.
10193
10194![Setting up snapshot tests](/images/blog/visual-regressions-and-how-to-prevent-them/6569056a522396bb11419376_setting-up-snapshot-tests.png)
10195
10196Because snapshots tests rely on DOM comparison, they should test the structure of a given page or set of components.
10197
10198For example, snapshots ensure that:
10199
10200- A set of actions is available on a page (ex: filtering, searching)
10201- The proper information text is displayed
10202- The login page does not submit the form if the inputs are empty
10203
10204In short, snapshots tests are perfect in preventing any visual regression in the structure of your application (actions, text, etc).
10205
10206While snapshots tests are easier to set up and maintain, and fast to run (< 2s), they cannot be used to assess pure visual aspects such as CSS overflow issues or colors of UI elements. You can read more about how to implement snapshot tests [here](https://meticulous.ai/blog/how-to-set-up-visual-regression-testing/).
10207
10208## Screen Tests
10209
10210Screen tests give us a finer visual regression detecting by comparing screenshots:
10211
10212![Example of images failing to load](/images/blog/visual-regressions-and-how-to-prevent-them/656906794908530b2b34919a_visual-regression-testing-examples.png)
10213
10214With a bit more setup work, Screen tests prevent any kind of visual regressions, from structure (action buttons, text) to styling (overflow, colors, padding).
10215
10216Testing advanced interactions or image loading scenarios is possible, thanks to tools like Cypress, which rely on a virtual test browser. However, running a virtual test browser comes with its drawbacks: a more complex CI setup and slower tests (>1min).
10217
10218For this reason, screen tests should be used to assess the final rendering of a given page part of the critical path or UI-Kit components. For example, ensuring that the onboarding pages are rendered correctly or that all components of a UI-Kit are correctly rendered. You can read more about how to set up screen tests [here](https://meticulous.ai/blog/how-to-set-up-visual-regression-testing/).
10219
10220## SaaS Solutions
10221
10222The previously mentioned technical solutions fit well for small teams where only technical users need to access the results of the visual tests.
10223
10224SaaS solutions exist for bigger companies or ones where product/designers are in the visual testing process, such as [Meticulous](https://www.meticulous.ai/), [AppliTools](https://applitools.com/) and [Percy](https://percy.io/).
10225
10226Covering the features offered by the major visual regression SaaS solutions would require an article on its own. Still, most SaaS solutions offer the following features that ease the setup of advanced visual regressions tests and enhance the collaboration on failing tests:
10227
10228- Page and component testing
10229- Dedicated cloud worker for faster execution
10230- A platform for reviewing regressions (for technical and non-technical people)
10231- Advanced Workflow management
10232- AI features to avoid false-positive results
10233- Workflow integration: Github, Jenkins, Slack, and more
10234
10235## Conclusion
10236
10237Front-end is now a critical part of any web-product-based business.
10238
10239While maintaining the quality of their products by implementing many E2E tests, we see that visual regression testing is a faster solution.
10240
10241Snapshots tests as integration tests are helpful to ensure that the visual structure of a page is maintained over changes. Screen tests are slower to run and should avoid any visual regression in essential components of an application, for example UI-Kits.
10242
10243Finally, depending on the product complexity and the size of your organization, choosing SaaS solutions to enhance collaboration and set up more advanced tests might be the way to go.
10244
10245## Meticulous
10246
10247Meticulous creates and maintains an exhaustive suite of e2e ui tests with **zero** developer effort.
10248
10249This quote from the CTO of Traba sums the product up best: "Meticulous has fundamentally changed the way we approach frontend testing in our web applications, fully eliminating the need to write any frontend tests. The software gives us confidence that every change will be completely regression tested, allowing us to ship more quickly with significantly fewer bugs in our code. The platform is easy to use and reduces the barrier to entry for backend-focused devs to contribute to our frontend codebase."
10250
10251This [post](https://www.meticulous.ai/blog/lessons-from-a-decade) from our CTO (formerly lead of Palantir's main engineering group) sets out the context of why exhaustive testing can double engineering velocity. Learn more about the product [here.](/)
10252`,x3={"alex-langdon":{bio:"Alex is an experienced Software Engineer with experience working for Currencycloud, Morgan Stanley, and Bloomberg. He has a Masters in Engineering degree from the University of Warwick.",linkedinUrl:"https://www.linkedin.com/in/alex-langdon-82b628a5/"},"alex-merced":{bio:"Alex is an experienced engineer, technical author, and developer advocate. He is currently working as a Developer Advocate for Dremio, and prior to that, was an Instru
10252ctor at General Assembly. Alex has a Bachelor of Arts degree from Bowling Green State University, as well as a Software Engineering certificate from General Assembly.",linkedinUrl:"https://www.linkedin.com/in/alexmerced/"},"aniket-bhattacharyea":{bio:"Aniket is an experienced engineer and author, and he is currently working as a Developer Advocate at Draft.dev. Before that, Aniket held roles including Head of Technology at PiParadox. He has a Master of Science degree in Mathematics from the Ramakrishna Mission Vivekananda Educational and Research Institute.",linkedinUrl:"https://www.linkedin.com/in/heraldofsolace/",twitterUrl:"https://twitter.com/heraldofsolace"},"charly-poly":{bio:"Charly is an experienced engineer with previous roles at Cycle App and The Guild Software. Today, he is the Co-founder and CEO of a company called Defer, which is backed by Y Combinator. Charly received a degree in Computer Science from Teesside University, as well a degree from IUT Orléans Département Informatique.",linkedinUrl:"https://www.linkedin.com/in/charly-poly/"},"daniel-li":{bio:"Daniel is an experienced engineer having held roles as a Staff Software Engineer at Zinc Work and as a DevOps Engineer at Vonage. He has a Bachelor of Science in Biochemistry from the Imperial College of London.",linkedinUrl:"https://www.linkedin.com/in/d4nyll/",twitterUrl:"https://twitter.com/d4nyll"},"david-herbert":{bio:"David is an experienced Technical Author with a number of bylines, including those from CoverPad and FlyCode, which is backed by Y Combinator. He has a degree in Computer Science from Rivers State University of Science and Technology.",linkedinUrl:"https://www.linkedin.com/in/daveyhert/",twitterUrl:"https://twitter.com/DaveyHert"},"elly-obare":{bio:"Ely is a technical author and software engineer, with experience in a number of languages and frameworks. He is currently working as a Software Quality Assurance Engineer at KCB Bank Group. Ely received a Bachelor of Science degree from Kenyatta University.",linkedinUrl:"https://www.linkedin.com/in/elly-o-110956164/"},"faraz-kelhini":{bio:"Faraz is a technical author with deep experience in a number of topics, including Javascript. He has written extensively about Javascript, including work on best practices for UI testing.",linkedinUrl:"https://www.linkedin.com/in/faraz-kelhini-a0b04a1b5/",twitterUrl:"https://twitter.com/FarazKelhini"},"gabriel-spencer-harper":{bio:"Gabriel is the CEO and co-founder of Meticulous, previously worked in engineering at Dropbox and Opendoor.",linkedinUrl:"https://www.linkedin.com/in/gabrielsharper/",twitterUrl:"https://twitter.com/meticulousgabe"},"geshan-manandhar":{bio:"Geshan is an experienced Senior Software Engineer with experience working for companies like Simply Wall Street and The Iconic. He has a Masters in Information Systems Development from the HAN University of Applied Sciences, Arnhem, Netherlands.",linkedinUrl:"https://www.linkedin.com/in/geshan/"},"goodness-chris-ugari":{bio:"Goodness is an experienced software engineer and has worked in a number of roles ranging from frontend development to site reliability engineering. She has a Bachelor of Science in Computer Science from University Of Portharcour.",linkedinUrl:"https://www.linkedin.com/in/goodness-chris-ugari/",twitterUrl:"https://twitter.com/goodnesschris"},"ibas-majid":{bio:"Ibas is an experienced developer and technical author. In addition, he has taught a variety of courses on JavaScript, React, GraphQL, Gatsby, and WordPress.",linkedinUrl:"https://ibaslogic.com/"},"jacob-goldfarb":{bio:"Jacob is an experienced software engineer and has held roles at a number of leading companies including Affirm, Level, Lime, and Zynga. He has a Bachelor of Applied Science degree from the University of Waterloo.",linkedinUrl:"https://www.linkedin.com/in/hire-jacob/"},"james-walker":{bio:"James is an experienced software engineer and technical author. In his full-time role, James is a Director at Heron Web, where he builds bespoke software solutions. He has been published in a number of places, including at ContainIQ, where he has written extensively about Kubernetes.",linkedinUrl:"https://www.linkedin.com/in/jh-walker/"},"johann-michael-thiebaut":{bio:"Johann has held a number of engineering roles at companies like Google and Swyg. He has Master's degree in Computer Science from École Polytechnique.",linkedinUrl:"https://www.linkedin.com/in/jm-thiebaut/"},"johnny-reilly":{bio:"Johnny has held a number of Senior Developer roles, including at Pantheon Ventures, and Trayport. Prior to his current role at Trayport, Johnny was a Software Developer for Microsoft and Commerzbank.",linkedinUrl:"https://www.linkedin.com/in/johnnyreilly/",twitterUrl:"https://twitter.com/johnny_reilly"},"kasper-siig":{bio:"Kasper is a seasoned technical professional with experience ranging from DevOps engineering to specialized content creation, currently leveraging his deep understanding of technology to lead Siig Marketing, a firm dedicated to crafting high-converting, developer-focused marketing content.",linkedinUrl:"https://www.linkedin.com/in/ksiig/"},"marie-starck":{bio:"Marie is an experienced engineer and technical author. In her current role, she works as a Full Stack Engineer at Doctolib. She has a Bachelor's Degree in Computer Science from The University of British Columbia.",linkedinUrl:"https://www.linkedin.com/in/marie-starck/",twitterUrl:"https://twitter.com/MStarckJS"},"myles-scolnick":{bio:"Myles is an experienced engineer with previous roles at Cloud Kitchens and Palantir.",linkedinUrl:"https://www.linkedin.com/in/mscolnick/"},"oluseun-falodun":{bio:"Oluseun is a Software Developer who enjoys building applications that solve real-life problems."},"quentin-spencer-harper":{bio:"Quentin is CTO of Meticulous. Prior to Meticulous he spent a decade helping lead frontend engineering for Palantir Foundry, Palantir's main product.",linkedinUrl:"https://www.linkedin.com/in/qsh/"},"rhuan-souza":{bio:"Rhuan is an experienced Full Stack Software Engineer, with a current role at Ewally. He has a Bachelor of Science in Computer Science from Federal University of Ceara.",linkedinUrl:"https://www.linkedin.com/in/rhuan-souza-1b7b37116/?locale=en_US"},"subha-chanda":{bio:"Subha is an experienced Developer and Technical Author. In his full-time role, he works as a Full Stack Developer for Geekflare. He has a Master of Science in Computer Science from University Of North Bengal.",linkedinUrl:"https://www.linkedin.com/in/subha-chanda/",twitterUrl:"https://twitter.com/Ami_Subha"},"tahanima-chowdhury":{bio:"Tahanima is an experienced software engineer and technical author. She has held a number of roles including as a Software Engineer II at Therap, and at HackerRank. She has a Bachelor of Science in Computer Science from BRAC University.",linkedinUrl:"https://www.linkedin.com/in/tahanima-chowdhury/",twitterUrl:"https://twitter.com/TahanimaC"}},Mm="---",jn=a=>{const i=a.trim();return!i.startsWith('"')||!i.endsWith('"')?i:i.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\")},qd=(a,i)=>{if(a==="true")return!0;
10252if(a==="false")return!1;throw new Error(`Blog post frontmatter field ${i} must be true or false`)},T3=a=>{const i=a.trim();if(!i.startsWith("[")||!i.endsWith("]"))return[];const s=i.slice(1,-1).trim();return s===""?[]:s.split(",").map(r=>jn(r.trim()))},S3=a=>{const i=a.split(`
10253`);if(i[0]?.trim()!==Mm)throw new Error("Blog post markdown is missing its frontmatter block");const s=i.indexOf(Mm,1);if(s===-1)throw new Error("Blog post frontmatter block is not closed");const r={};for(const h of i.slice(1,s)){const f=h.indexOf(":");if(f===-1)continue;const m=h.slice(0,f).trim();r[m]=h.slice(f+1).trim()}const c={title:jn(r.title??""),slug:jn(r.slug??""),date:jn(r.date??""),description:jn(r.description??""),author:jn(r.author??""),authorImage:jn(r.authorImage??""),heroImage:jn(r.heroImage??""),heroImageAlt:jn(r.heroImageAlt??""),categories:T3(r.categories??"[]"),showTableOfContents:qd(r.showTableOfContents,"showTableOfContents"),showPromo:qd(r.showPromo,"showPromo"),hidden:r.hidden===void 0?!1:qd(r.hidden,"hidden")},u=i.slice(s+1).join(`
10254`).trim();return{...c,bodyMarkdown:u}},A3=Object.assign({"../../content/blog/a-beginners-guide-to-debugging-react-testing-library-tests.md":M4,"../../content/blog/browserstack-vs-saucelabs.md":U4,"../../content/blog/cannot-read-property-split-of-undefined.md":D4,"../../content/blog/cypress-testing.md":B4,"../../content/blog/fetch-vs-axios.md":L4,"../../content/blog/frontend-unit-testing-best-practices.md":q4,"../../content/blog/getting-started-with-indexeddb.md":z4,"../../content/blog/getting-started-with-react-logging.md":N4,"../../content/blog/how-to-do-visual-regression-testing-in-a-react-app-a-step-by-step-tutorial.md":H4,"../../content/blog/how-to-prevent-javascript-syntax-errors.md":P4,"../../content/blog/how-to-prevent-the-error-cannot-read-property-0-of-undefined.md":F4,"../../content/blog/how-to-prevent-typeerror-cannot-read-property-map-of-undefined.md":J4,"../../content/blog/how-to-set-up-visual-regression-testing.md":G4,"../../content/blog/how-to-use-jest-spyon.md":Y4,"../../content/blog/how-to-use-react-testing-library-to-wait-for-async-elements-a-step-by-step-guide.md":V4,"../../content/blog/is-selenium-worth-it.md":W4,"../../content/blog/javascript-reference-errors.md":Q4,"../../content/blog/javascript-regressions-and-how-to-prevent-them.md":$4,"../../content/blog/javascript-try-catch-guide.md":X4,"../../content/blog/javascript-typeerrors-and-techniques-to-prevent-them.md":K4,"../../content/blog/javascript-ui-testing-best-practices.md":Z4,"../../content/blog/javascript-unshift-complete-guide.md":e3,"../../content/blog/jest-typescript.md":t3,"../../content/blog/lessons-from-a-decade.md":n3,"../../content/blog/let-users-write-tests-for-you.md":a3,"../../content/blog/localstorage-complete-guide.md":i3,"../../content/blog/meticulous-announces-4m-seed-round.md":s3,"../../content/blog/mocking-a-javascript-class-with-jest-two-ways-to-make-it-easier.md":o3,"../../content/blog/power-meticulous-case-study.md":r3,"../../content/blog/rainforest-qa-alternatives.md":l3,"../../content/blog/range-errors-and-how-to-prevent-them.md":c3,"../../content/blog/react-error-boundaries-complete-guide.md":d3,"../../content/blog/reflect-alternatives.md":u3,"../../content/blog/safer-exhaustive-switch-statements-in-typescript.md":h3,"../../content/blog/saucelabs-alternatives.md":f3,"../../content/blog/series-a.md":g3,"../../content/blog/smoke-testing.md":p3,"../../content/blog/speed-up-your-frontend-monorepo-build.md":m3,"../../content/blog/testing-pyramid-for-frontend.md":b3,"../../content/blog/testing-react-components-with-storybook.md":w3,"../../content/blog/the-beginner-guide-to-visual-regression-testing.md":y3,"../../content/blog/typescript-unit-tests-with-debugging.md":v3,"../../content/blog/visual-regression-testing-tools.md":_3,"../../content/blog/visual-regressions-and-how-to-prevent-them.md":k3}),E3=(a,i)=>i.date.localeCompare(a.date),E0=Object.values(A3).map(a=>S3(a)).sort(E3),Zs=E0.filter(a=>!a.hidden),I7=()=>Zs,j3=a=>E0.find(i=>i.slug===a),C3=()=>{const a=new Set;for(const i of Zs)for(const s of i.categories)a.add(s);return[...a].sort()},I3=a=>C3().find(i=>al(i)===a),R3=a=>{const i=Zs.find(s=>al(s.author)===a);if(i!==void 0)return{name:i.author,image:i.authorImage,...x3[a]}},O3=a=>Zs.filter(i=>i.categories.some(s=>al(s)===a)),M3=a=>Zs.filter(i=>al(i.author)===a),U3=()=>lt(()=>import("./_slug-DnIFOLoz.js"),__vite__mapDeps([10,1,2,11,12,13,14])),D3=ot()({loader:({params:a})=>{const i=R3(a.slug);
10254if(i===void 0)throw Zd();return{author:i,authorSlug:a.slug,posts:M3(a.slug)}},head:({loaderData:a})=>Qe(a===void 0?{title:"Blog",canonicalPath:"/blog"}:{title:a.author.name,canonicalPath:`/author/${a.authorSlug}`}),component:rt(U3,"component")}),B3=()=>lt(()=>import("./_slug-D6sFLFDh.js"),__vite__mapDeps([15,1,11,12,13,2,14])),L3=ot()({loader:({params:a})=>{const i=I3(a.slug);if(i===void 0)throw Zd();return{category:i,categorySlug:a.slug,posts:O3(a.slug)}},head:({loaderData:a})=>Qe(a===void 0?{title:"Blog",canonicalPath:"/blog"}:{title:a.category,canonicalPath:`/blog-categories/${a.categorySlug}`}),component:rt(B3,"component")}),q3=()=>lt(()=>import("./index-DF16s_l1.js"),__vite__mapDeps([16,1,13,2,14])),z3=ot()({head:()=>Qe({title:"Blog",canonicalPath:"/blog"}),component:rt(q3,"component")}),N3=()=>lt(()=>import("./_slug-BEa5Vw5q.js"),__vite__mapDeps([17,1,2,11,14])),H3="100vw",P3=ot()({loader:({params:a})=>{const i=j3(a.slug);if(i===void 0)throw Zd();return i},head:({loaderData:a})=>{if(a===void 0)return Qe({title:"Blog",canonicalPath:"/blog"});const i=Qe({title:a.title,description:a.description,canonicalPath:`/blog/${a.slug}`,...a.heroImage===""?{}:{ogImage:`https://www.meticulous.ai${a.heroImage}`}}),s=a.heroImage===""?void 0:E9(a.heroImage,H3);return s===void 0?i:{...i,links:[...i.links,s]}},component:rt(N3,"component")}),F3=()=>lt(()=>import("./cases-tabs-element-BDLKGJFq.js"),__vite__mapDeps([18,1])),J3=ot()({head:()=>Qe({title:"Cases Tabs Element",canonicalPath:"/cases/cases-tabs-element"}),component:rt(F3,"component")}),G3=()=>lt(()=>import("./engine-CixdYneU.js"),__vite__mapDeps([19,20,1])),Y3=ot()({head:()=>Qe({title:"Engine",canonicalPath:"/cases/engine"}),component:rt(G3,"component")}),V3=()=>lt(()=>import("./launchdarkly-B0rD7a2c.js"),__vite__mapDeps([21,20,1])),W3=ot()({head:()=>Qe({title:"LaunchDarkly",canonicalPath:"/cases/launchdarkly"}),component:rt(V3,"component")}),Q3=()=>lt(()=>import("./notion-Bp0okpt1.js"),__vite__mapDeps([22,20,1])),$3=ot()({head:()=>Qe({title:"Notion",canonicalPath:"/cases/notion"}),component:rt(Q3,"component")}),X3=D9.update({id:"/",path:"/",getParentRoute:()=>et}),K3=L9.update({id:"/401",path:"/401",getParentRoute:()=>et}),Z3=z9.update({id:"/404",path:"/404",getParentRoute:()=>et}),e7=H9.update({id:"/about-us",path:"/about-us",getParentRoute:()=>et}),t7=F9.update({id:"/book-a-demo",path:"/book-a-demo",getParentRoute:()=>et}),n7=G9.update({id:"/case-studies",path:"/case-studies",getParentRoute:()=>et}),a7=V9.update({id:"/how-it-works",path:"/how-it-works",getParentRoute:()=>et}),i7=Q9.update({id:"/privacy-policy",path:"/privacy-policy",getParentRoute:()=>et}),s7=X9.update({id:"/terms-conditions",path:"/terms-conditions",getParentRoute:()=>et}),o7=D3.update({id:"/author/$slug",path:"/author/$slug",getParentRoute:()=>et}),r7=L3.update({id:"/blog-categories/$slug",path:"/blog-categories/$slug",getParentRoute:()=>et}),l7=z3.update({id:"/blog/",path:"/blog/",getParentRoute:()=>et}),c7=P3.update({id:"/blog/$slug",path:"/blog/$slug",getParentRoute:()=>et}),d7=J3.update({id:"/cases/cases-tabs-element",path:"/cases/cases-tabs-element",getParentRoute:()=>et}),u7=Y3.update({id:"/cases/engine",path:"/cases/engine",getParentRoute:()=>et}),h7=W3.update({id:"/cases/launchdarkly",path:"/cases/launchdarkly",getParentRoute:()=>et}),f7=$3.update({id:"/cases/notion",path:"/cases/notion",getParentRoute:()=>et}),g7={IndexRoute:X3,R401Route:K3,R404Route:Z3,AboutUsRoute:e7,BookADemoRoute:t7,CaseStudiesRoute:n7,HowItWorksRoute:a7,PrivacyPolicyRoute:i7,TermsConditionsRoute:s7,AuthorSlugRoute:o7,BlogCategoriesSlugRoute:r7,BlogSlugRoute:c7,CasesCasesTabsElementRoute:d7,CasesEngineRoute:u7,CasesLaunchdarklyRoute:h7,CasesNotionRoute:f7,BlogIndexRoute:l7},p7=et._addFileChildren(g7),m7=()=>h8({routeTree:p7,scrollRestoration:!0,defaultPreload:"intent",defaultPreloadStaleTime:0});async function b7(){const a=await m7();let i;return i=[],window.__TSS_START_OPTIONS__={serializationAdapters:i},i.push(_2),a.options.serializationAdapters&&i.push(...a.options.serializationAdapters),a.update({basepath:"",serializationAdapters:i}
10254),a.stores.ids.get().length||await hv(a),a}var w7=b7;function y7(){return w7().finally(()=>window.$_TSR?.h())}var zd;function v7(){return zd||(zd=y7()),Q.jsx(x2,{promise:zd,children:a=>Q.jsx(p8,{router:a})})}se.startTransition(()=>{r6.hydrateRoot(document,Q.jsx(se.StrictMode,{children:Q.jsx(v7,{})}))});export{o0 as L,I4 as M,D3 as R,za as S,x7 as a,S7 as b,A7 as c,D8 as d,k7 as e,L3 as f,j8 as g,I7 as h,j7 as i,Q as j,C7 as k,T9 as l,Oe as m,w9 as n,E7 as o,P3 as p,R3 as q,se as r,al as s,vu as t,T7 as u};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.