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={"&":"&","<":"<",">":">",'"':""","'":"'"},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 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 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 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 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 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 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 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 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 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 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 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 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 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 1378 1379The graph: 1380 1381 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 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 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 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 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 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 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 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 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 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 2995 2996That should be easy to do, then you will be presented with the onboarding. Select an organization name: 2997 2998 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 3003 3004Choose GitHub and add the Meticulous app on GitHub after authenticating it as follows: 3005 3006 3007 3008After that, use the name-nationality-meticulous repository that you had forked before registering on Meticulous: 3009 3010 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 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 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 3025 3026After you have set the project ID on your .env.local file, you can click the âNextâ button on the page. 3027 3028 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 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 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 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 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 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 3063 3064Use the âHobbyâ option and type in your name. Then click continue, after that, you can continue the registration with GitHub. 3065 3066 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 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 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 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 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 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 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 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 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 3288 3289 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 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 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 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 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 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 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 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 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 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 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 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 4246 4247The downloaded .zip file contains all original screenshots along with the diff outputs: 4248 4249 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 8250 8251### BrowserStack: A Cloud-Based Testing Environment 8252 8253 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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&autopause=0&player_id=0&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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 10005 10006We can now debug and introspect each test that runs: 10007 10008 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 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 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 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 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 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 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 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 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 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.