momentic-mobile 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +4 -4
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -3884,15 +3884,15 @@ Available pages:${JSON.stringify(n.map(i=>i.url))}`);if(!_n(o.url,this.logger)){
|
|
|
3884
3884
|
`),tokenLength:m}),s.forEach((h,f)=>{t.debug({chunk:h},`Chunk for page filtering (index ${f+1}/${s.length})`)}),{chunks:s}}var pl=4e4,gl=8e4;async function zn(r){let{serializedTree:e,logger:t,aiPageFiltering:n,softTokenLimit:o=pl}=r;if(So(e)<o)return e;let a;n?a=ul({serializedTree:e,options:ug,logger:t,maxCharacterLength:1e6}):a=ul({serializedTree:e,options:mg,logger:t,maxCharacterLength:3e6});try{return n?await F(rC({...r,chunks:a.chunks}),{milliseconds:2e4}):await F(iC({...r,chunkResult:a}),{milliseconds:15e3})}catch(s){t.warn({err:s},"Error executing page filtering, attempting AI keyword fallback");try{return await oC({...r,chunks:a.chunks})}catch(l){return t.warn({err:l},"Error executing page filtering using keyword matching, using naive truncation"),e.slice(0,gl*Er)}}}async function rC({chunks:r,generator:e,description:t,type:n,logger:o,signal:i,tree:a,softTokenLimit:s=pl,hardTokenLimit:l=gl}){let c=await e.rankChunksWithAi({chunks:r,description:t,type:n,softTokenLimit:s,hardTokenLimit:l},{abortSignal:i,logger:o,loggerTags:ce(o)}),d=[];r.forEach((u,g)=>{c.indices.includes(g)&&(d=d.concat(u.ids))});let m=a.pruneUsingRelevantIds(new Set(d)).serialize();return o.debug({description:t,type:n,selectedChunks:c,tree:m},"Pruned a11y tree with AI page filtering"),m}async function oC(r){let{type:e,description:t,generator:n,tree:o,logger:i,hardTokenLimit:a=gl}=r;if(!t.trim())throw new Error("Empty description passed to page filtering");let s=await n.getExtractedKeywords({goal:t},{logger:r.logger,loggerTags:ce(r.logger)});for(let l of s.keywords){let c=r.chunks.filter(g=>g.content.toLowerCase().includes(l.toLowerCase()));if(!c.length||c.reduce((g,p)=>g+p.tokenLength,0)>a&&c.length>1)continue;let m=c.flatMap(g=>g.ids),u=o.pruneUsingRelevantIds(new Set(m)).serialize();return i.debug({description:t,type:e,selectedChunks:c,tree:u},"Pruned a11y tree with keyword page filtering"),u}throw new Error("No keywords were unique enough for page filtering")}async function iC(r){let{description:e,generator:t,tree:n,logger:o,signal:i,chunkResult:a,softTokenLimit:s=pl}=r,l=await t.rankChunksWithRag({description:e,chunks:a.chunks,tokenLimit:s},{abortSignal:i,logger:o,loggerTags:ce(o)});if(l.ids.length===0)throw new Error("RAG returned no important ids");{let c=n.pruneUsingRelevantIds(new Set(l.ids.map(d=>`${d}`))).serialize();return o.debug({browserState:c},"Pruned a11y tree with RAG"),c}}async function fl(r,e){if(!r.description)throw new T("UserConfigurationError","Cannot locate element with empty description");return Gt({action:async()=>aC(r,e),frameConfig:r.iframeUrl?{type:"url",url:r.iframeUrl}:void 0,browser:e.browser,logger:r.logger})}async function aC(r,e){let{disableCache:t,testContext:n,filterByViewport:o,skipWait:i,source:a,memory:s,aiPageFiltering:l,logger:c,allowZeroOpacityOverride:d}=r,{ctx:m,orgId:u,browser:g,localCodeEvalTools:p,generator:h,abortSignal:f}=e,y=r.description,w=r.useMemory&&!t;n&&(y=await Ms({orgId:u,s:y,context:n,localTools:p,signal:f,logger:c})),a&&(y=lC(y,a));let{serializedTree:A,tree:I}=await fn(g,{allowZeroOpacityOverride:d,filterByViewport:o,abortSignal:f,skipWait:i,logger:c}),C,R=Date.now(),_;for(;!C&&Date.now()-R<3e3;){f.throwIfAborted();try{C=await g.screenshot({clearHighlights:!0,respectActiveFrame:!0,retries:2})}catch(Xe){_=Xe}}if(!C)throw new T("ActionFailureError",`Failed to take screenshot of page to locate element. The page may be unresponsive, or your machine might be severely resource constrained. Error: ${_?.message}`);let V=A,Q=!1;V=await zn({type:"locator",description:y,serializedTree:A,aiPageFiltering:l,tree:I,logger:c,generator:h}),V!==A&&(Q=!0);let Re=`data:image/jpeg;base64,${C.toString("base64")}`,D=await h.getElementLocation({browserState:V,goal:y,screenshot:Re,source:a,memory:w?s:void 0},{disableCache:t,abortSignal:f,loggerTags:ce(c),useMemory:w});c.debug({usedRag:Q,result:D},"Got locator result");let q=D.id>0;if(m?.details?.push({type:"AI_LOCATION",matched:q,pageState:V,ragUsed:Q,thoughts:D.thoughts}),!q)throw new pr(`Could not find any relevant element: ${D.thoughts}`,D.updatedMemory?{type:"GCS_TRACES",traces:D.updatedMemory}:void 0);let{resolution:M,target:te,frameConfig:Ee}=await g.createTargetFromA11yId({id:D.id,description:y,targetSource:"AI",logger:c});if(M.a11yNode?.properties?.hidden&&M.a11yNode?.properties?.hidden!=="false")throw new T("ActionFailureError",`Momentic's AI found a relevant element to interact with, but it is explicitly marked with an 'aria-hidden' attribute. Please remove this attribute or adjust the element description to locate a different element. Element chosen: ${M.displayString}`);return w&&(D.updatedMemory?te.memory={type:"GCS_TRACES",traces:D.updatedMemory}:s&&(te.memory=s)),{thoughts:D.thoughts,target:te,resolution:M,frameConfig:Ee,screenshot:Re}}var sC=["Element exactly matching the description below. Interpret the description narrowly and do not assume there are any typos or errors. Err on the side of returning -1 unless there is a perfect match. Description:","Element closely matching the description below. Interpret the description narrowly and do not return elements that are merely loosely related. Description:","Element matching the description below. This element is being located as part of a negative check step (i.e. we are trying to verify the element does not exist). Therefore, interpret the description narrowly, do not assume there are typos, and err on the side of returning -1 unless there is a perfect match. Description:"],gg="<select> element:",hg="text input or contenteditable element:",fg="Element matching the description below. It is possible the element is hidden or doesn't exist. Interpret the description narrowly and do not assume there are typos. Return -1 unless there is an straightforward match. Description:",Sg="Element matching the description below. This element is being located as part of a check step (i.e. we are trying to verify certain properties about the element). Interpret the description narrowly and do not return elements that are merely loosely related. Description:",hl=[gg,hg,fg,Sg,...sC];function bg(r,e){if(r===e)return!0;for(let t of hl){if(!r.startsWith(t))continue;let n=r.slice(t.length).trim();if(hl.some(o=>e.startsWith(o)&&e.slice(o.length).trim()===n)||n===e.trim())return!0}return!!hl.some(t=>e.startsWith(t)&&e.slice(t.length).trim()===r.trim())}function lC(r,e){if(!r||!e)return r;switch(e){case"SELECT_OPTION":return`${gg} ${r}`;case"TYPE":return`${hg} ${r}`;case"NEGATED_ELEMENT_VISIBLE_CHECK":return`${fg}
|
|
3885
3885
|
${r}`;case"ELEMENT_CHECK":return`${Sg}
|
|
3886
3886
|
${r}`;default:return r}}var cC=15;async function Vi({command:r,aiPageFiltering:e,logger:t,fixtures:n,source:o,useMemory:i,maxRetries:a=cC}){if(!r.assertion.trim())throw new T("ActionFailureError","Assertion command is missing the assertion content");let{browser:s}=n,l=r.timeout?r.timeout*1e3:s.smartWaitingTimeout,c=Ii(l),d=0,m=Date.now(),u,g,p;try{await Gt({action:()=>s.clearHighlights(),frameConfig:r.iframeUrl?{type:"url",url:r.iframeUrl}:void 0,browser:s,logger:t})}catch(f){t.warn({err:f},"Failed to clear highlights before AI check, continuing...")}let h;for(;d<a&&(!h||h-m<l);){n.abortSignal.throwIfAborted(),d!==0&&await j(c,n.abortSignal),h=Date.now();try{if(u=await Gt({action:async()=>{let y=await yg(s,t,n.abortSignal);return g&&g.serializedTree===y.serializedTree&&g.screenshotBuff.equals(y.screenshotBuff)?u:(g=y,wg({command:r,state:y,fixtures:n,useMemory:i,useConsensus:!1,highlightElementsOnFailure:!1,attemptNumber:d,aiPageFiltering:e,logger:t,source:o}))},frameConfig:r.iframeUrl?{type:"url",url:r.iframeUrl}:void 0,logger:t,browser:s}),u?.updatedMemory&&(Ri(r,u.updatedMemory),t.info({updatedMemory:u.updatedMemory,command:r},"Wrote new memory to assertion command")),u?.success)break;throw u?.thoughts?new T("AssertionFailureError",u.thoughts):new T("InternalPlatformError","No thoughts were provided for AI assertion failure")}catch(f){n.abortSignal.throwIfAborted(),p=f instanceof Error?f:new Error(`${f}`),t.info({err:f},`AI check assert attempt ${d} failed, retrying...`)}finally{d++}}if(!u?.success)try{u=await Gt({action:async()=>wg({command:r,state:await yg(s,t,n.abortSignal),fixtures:n,useMemory:i,useConsensus:!0,highlightElementsOnFailure:!0,attemptNumber:d,aiPageFiltering:e,logger:t}),frameConfig:r.iframeUrl?{type:"url",url:r.iframeUrl}:void 0,logger:t,browser:s})}catch(f){n.abortSignal.throwIfAborted(),p=f instanceof Error?f:new Error(`${f}`)}finally{d++}if(!u?.success){let f=`AI check still failing after ${d} attempts.`;throw p&&(f+=` Latest result: ${p.message}`),new T("AssertionFailureError",f)}return{...u,succeedImmediately:!1,urlAfterCommand:s.url()}}async function yg(r,e,t){let[n,o]=await Promise.all([fn(r,{abortSignal:t,skipWait:!0,skipWaitForPageLoad:!0,logger:e}),r.screenshot({retries:1,respectActiveFrame:!0})]);return{...n,screenshotBuff:o}}async function wg({command:r,state:e,fixtures:t,useConsensus:n,useMemory:o,highlightElementsOnFailure:i,aiPageFiltering:a,attemptNumber:s,source:l,logger:c}){let{browser:d,generator:m,abortSignal:u}=t,g={type:"ASSERTION"},{serializedTree:p,tree:h}=e,f=e.screenshotBuff,y=d.url(),w=r.contextChoice??"MULTIMODAL",A=p;w!=="VISION_ONLY"&&(A=await zn({type:"assertion",serializedTree:p,description:r.assertion,aiPageFiltering:a,tree:h,generator:m,logger:c,signal:u}),A!==p&&(g.ragUsed=!0),g.pageState=A);let I={goal:r.assertion,url:y,memory:o?r.cache?.memory:void 0,browserState:A,screenshot:f.toString("base64"),contextChoice:w,source:l},R=await(w==="VISION_ONLY"?(_,V)=>m.getVisualAssertionResult(_,V):(_,V)=>m.getAssertionResult(_,V))(I,{useConsensus:n,attemptNumber:s,useMemory:o,disableCache:!!r.disableCache,abortSignal:u,logger:c,loggerTags:ce(c)});return(R.result||i)&&R.relevantElements&&(g.relevantElementsSerialized=R.relevantElements.map(_=>d.getSerializedFormFromA11yId(_)).filter(_=>!!_),await dC(R.relevantElements,d,c)),{success:R.result,thoughts:R.thoughts,afterScreenshotOverride:f,updatedMemory:o?R.updatedMemory:void 0}}async function dC(r,e,t){let n=Date.now();for(let o of r){if(Date.now()-n>2e3){t.debug("Highlighting relevant elements took over 2s, aborting...");return}try{let i=new AbortController;await F(e.highlightA11yId(o),{milliseconds:1e3,fallback:()=>{throw i.abort(),new Error("Timed out waiting for highlighting to complete")}})}catch(i){t.debug({err:i},"Failed to highlight relevant element after assertion, continuing...");return}}}async function Tg(r){let{command:e,tracer:t,timeoutMs:n,targetingWrapper:o,disableCache:i,fixtures:a}=r,{logger:s,abortSignal:l}=a;if(e.target&&!en(e.target))throw new Error("Element assertion with x/y is not supported yet");let c=uC(e.assertion),d=Tc(e.cache)?e.cache:void 0,m=Date.now(),u=0,g,p=500;for(;u<2||Date.now()-m<n;){u++,u>1&&await j(p,l),l?.throwIfAborted();try{let{elementInteractedDisplayString:h,result:f}=await o({ctx:a.ctx,tracer:t,command:e,target:e.target,cache:d?.target,action:async y=>mC(y.locator,r),options:{...e,disableCache:i,disableGlobalLocatorRedirect:!0,source:Xr(e),allowZeroOpacityOverride:!0,targetName:"target"}});if(g={success:f.success,data:f.data,err:f.err,elementInteractedDisplayString:h},!f.success){g=f,p=Math.min(p*2,1e4);continue}return g}catch(h){if(l?.throwIfAborted(),c)return{success:!0,thoughts:`The element described does not exist on the page: ${h.message}`};if(s.warn({err:h},"Element assertion ended in error, retrying..."),!(h instanceof T)||h.reason!="ActionFailureError")throw h;g={success:!1,err:h}}}if(!g)throw new Error(`Failed to evaluate manual element assertion in ${n}ms.`);return g}async function mC(r,{command:e,fixtures:t}){let n=e.assertion;await t.browser.highlight(r);let o=!0,i,a;switch(n.type){case"ELEMENT_CONTENT":{let l=await r.textContent()??"";if(a={elementTextContent:rt(l,500,!0)},!qi(l,n.value,n.operation,!!n.negated)){let c=n.negated?On[n.operation]:Nn[n.operation];o=!1,i=new T("AssertionFailureError",`The content ${c} '${n.value}': ${l}`)}break}case"ELEMENT_ATTRIBUTE":{a={elementOuterHtml:rt(await r.evaluate(c=>c.cloneNode(!1).outerHTML),500,!0)};let l;try{l=await r.getAttribute(n.attr,{timeout:3e3})??""}catch(c){i=new T("AssertionFailureError",`The element does not have an attribute named ${n.attr}: ${c}`),o=!1;break}if(!qi(l,n.value,n.operation,!!n.negated)){let c=n.negated?On[n.operation]:Nn[n.operation];o=!1,n.operation==="EXISTS"?i=new T("AssertionFailureError",`The attribute ${n.attr} ${c}`):i=new T("AssertionFailureError",`The attribute ${n.attr} ${c} '${n.value}': ${l}`)}break}case"ELEMENT_EXISTENCE":{switch(n.condition){case"VISIBLE":{o=await r.evaluate(async(c,d)=>{let m=Date.now();for(;Date.now()-m<d;){await new Promise(g=>setTimeout(g,250));let u=c.getBoundingClientRect();if(!(u.width===0||u.height===0)&&window.getComputedStyle(c).visibility!=="hidden"&&window.getComputedStyle(c).display!=="none")return!0}return!1},nn*1e3);break}case"EDITABLE":{o=await r.isEditable({timeout:nn*1e3});break}case"EXISTS":{o=!0;break}case"ENABLED":{o=await r.isEnabled({timeout:nn*1e3});break}case"FOCUSED":{o=await r.evaluate(c=>c===document.activeElement);break}default:return(c=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(n.condition)}if(o=n.negated?!o:o,!o){let l=n.negated?hu[n.condition]:fu[n.condition];i=new T("AssertionFailureError",`The element ${l}`)}break}case"ELEMENT_NAME":{let l=await r.evaluate(c=>c.tagName);if(!qi(l,n.value,n.operation,!!n.negated)){let c=n.negated?On[n.operation]:Nn[n.operation];o=!1,i=new T("AssertionFailureError",`The element tag name ${c} '${n.value}': ${l}`)}break}case"ELEMENT_STYLE":{let l=await r.evaluate((c,d)=>window.getComputedStyle(c).getPropertyValue(d),n.property);if(!qi(l,n.value,n.operation,!!n.negated)){let c=n.negated?On[n.operation]:Nn[n.operation];o=!1,n.operation==="EXISTS"?i=new T("AssertionFailureError",`The style property ${n.property} ${c}`):i=new T("AssertionFailureError",`The style property ${n.property} ${c} '${n.value}': ${l}`)}break}default:return(l=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(n)}return{success:o,data:a,err:i}}function qi(r,e,t,n){let o;switch(t){case"CONTAINS":{o=r.includes(e);break}case"EQUALS":{o=r.trim()===e.trim();break}case"STARTS_WITH":{o=r.trim().startsWith(e);break}case"EXISTS":{o=r.trim().length>0;break}default:return(a=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(t)}return n?!o:o}function uC(r){return r.type==="ELEMENT_EXISTENCE"&&r.negated&&(r.condition==="EXISTS"||r.condition==="VISIBLE")}function Eg(r){return r.type==="ELEMENT_EXISTENCE"&&r.negated&&r.condition==="EXISTS"}import{Jimp as pC}from"jimp";async function bo(r,e){let t=await r.screenshot(e),n=await pC.fromBuffer(t);return{buffer:t,width:Math.ceil(n.bitmap.width??0),height:Math.ceil(n.bitmap.height??0)}}import{Jimp as Cg}from"jimp";import Sl from"jpeg-js";import gC from"pixelmatch";async function vg({ctx:r,tracer:e,command:t,disableCache:n,browser:o,targetingWrapper:i,logger:a,screenshotStorage:s}){if(t.target&&!en(t.target))throw new Error("Visual Diff with x/y is not supported yet");await o.waitForDOMStability({logger:a});let l={clearHighlights:!0,hideCaret:!0},c;t.target?.elementDescriptor?c=(await i({ctx:r,tracer:e,command:t,target:t.target,cache:t.cache?.target,action:async Re=>bo(o,{locator:Re.locator,...l}),options:{...t,disableCache:n,disableGlobalLocatorRedirect:!0,targetName:"target"}})).result:c=await bo(o,l);let d=await s.prepareGoldenScreenshotForComparison(a,t,c);if((c.height!==d.height||c.width!==d.width)&&a.warn({currHeight:c.height,currWidth:c.width,savedHeight:d.height,savedWidth:d.width},"Mismatched before and after visual diff screenshot sizes"),Math.abs(c.height-d.height)>10||Math.abs(c.width-d.width)>10){let ee=`${c.width}x${c.height}`,Re=`${d.width}x${d.height}`;return{fail:!0,thoughts:`Current screenshot (${ee}) does not match saved screenshot dimensions (${Re}) - did you change the size of the target or the viewport?`,beforeScreenshotOverride:d.buffer,afterScreenshotOverride:c.buffer,succeedImmediately:!1,urlAfterCommand:o.url()}}let m=await Cg.fromBuffer(c.buffer),u={width:c.width,height:c.height},g=await Cg.fromBuffer(d.buffer),p={width:d.width,height:d.height},h,f=u.width*u.height,y=p.width*p.height,w=Math.abs(u.height-p.height),A=Math.abs(u.width-p.width);if(f>y){let ee=m.cover({w:p.width,h:p.height});c.buffer=await ee.getBuffer("image/jpeg"),h="current",c.width=p.width,c.height=p.height}else if(y>f){let ee=g.cover({w:u.width,h:u.height});d.buffer=await ee.getBuffer("image/jpeg"),h="saved"}let I={data:Buffer.alloc(c.width*c.height*4),width:c.width,height:c.height},C=t.threshold??.1,_=gC(Sl.decode(d.buffer).data,Sl.decode(c.buffer).data,I.data,c.width,c.height,{threshold:C,diffColorAlt:[0,255,0]})/(c.width*c.height)*100,V=_>C*100,Q=`Visual diff of ${_.toFixed(2)}% detected, which is ${V?"over":"under"} the threshold of ${C*100}%.`;if(h&&(Q+=` The ${h} screenshot was cropped since it was taller by ${w} pixels and wider by ${A} pixels.`),V)throw new T("ActionFailureError",Q);return{fail:V,thoughts:Q,beforeScreenshotOverride:c.buffer,afterScreenshotOverride:Sl.encode(I,75).data,succeedImmediately:!1,urlAfterCommand:o.url()}}var hC=3e4;async function Rg({command:r,logger:e,baseUrl:t,fetchImplementation:n=fetch}){let o=r.timeout??hC/1e3,i=Object.fromEntries(Object.entries(r.headers||{}).filter(([g,p])=>g&&p)),a=new URLSearchParams;Object.entries(r.params||{}).filter(([g,p])=>g&&p).forEach(([g,p])=>{a.append(g,p)});let s=a.toString(),l;if(Sr(r.url)&&(l=r.url),t&&br(r.url,t)&&(l=new URL(r.url,t).toString()),!l)throw new T("ActionFailureError",`Invalid URL: ${r.url}`);e.info({url:l,searchParams:s,headers:i,body:r.body,method:r.method},"Making HTTP request");let d=await F((async()=>{let g=s?`${l}?${s}`:l;try{return await n(g,{headers:i,method:r.method,body:r.body})}catch(p){throw e.error({err:p},"Failed to make HTTP request"),new Error(`Failed to make HTTP request: ${p}`)}})(),{milliseconds:o*1e3,fallback:()=>{throw new T("ActionFailureError",`Fetch request timed out after ${o} seconds`)}});if(!d.ok){let g;try{g=await d.text()}catch(p){g=`Failed to read response body: ${p}`}throw new T("ActionFailureError",`Fetch request failed with status ${d.status}: ${g}`)}let m={};d.headers.forEach((g,p)=>{m[p]=g});let u={status:d.status,headers:m};if(d.headers.get("content-type")?.includes("json"))try{u.json=await d.json()}catch{}else d.headers.get("content-type")?.includes("text")&&(u.text=await d.text());return u}var fC=5e3;async function Ig({timeout:r=nn,...e}){let t=Date.now(),n=r*1e3,o=n+1e4,i,a=0,s=500;for(;a-t<n;){if(Date.now()-t>o){e.logger.warn("Exceeded max system timeout for page assertion, exiting...");break}e.signal.throwIfAborted();let l=Date.now();i=await Ag(e),a=Date.now();let c=a-l;if(c>1e3&&e.logger.warn({pageAssertDuration:c},"Page assertion took longer than expected"),!i.success)await j(s,e.signal),s=Math.min(Math.floor(s*1.5),fC);else return i}return i=await Ag(e),i}async function Ag({assertion:r,browser:e,autoExpandIframes:t}){switch(r.type){case"CONTENT":{let o,i=!1,a;try{let s;if(t){let l=await e.evaluateFunctionInAllFrames(xg,{value:r.value,negated:!!r.negated,returnHtml:!1});i=r.negated?l.every(c=>c.evaluation):l.some(c=>c.evaluation),s=l.find(c=>c.pageHtml)?.pageHtml}else({evaluation:i,pageHtml:s}=await e.evaluateFunctionInPage(xg,{value:r.value,negated:!!r.negated,returnHtml:!0},"checking page content"));if(!i){let l=r.negated?On.CONTAINS:Nn.CONTAINS;a=new T("AssertionFailureError",`The page ${l} '${r.value}'.`),o=s}}catch(s){a=new T("AssertionFailureError",`Failed to evaluate page content assertion: ${s instanceof Error?s.message:`${s}`}`)}return{success:i,err:a,data:i||!o?void 0:{pageContent:o}}}default:return(o=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(r.type)}}function xg({value:r,negated:e,returnHtml:t}){let n=document.documentElement.outerHTML,o=n.includes(r)===!e;return n.length>1e4&&(n=n.slice(0,1e4)+"...TRUNCATED"),{evaluation:o,pageHtml:!o&&t?n:void 0}}var SC=3e4;async function Pg({command:r,logger:e,baseUrl:t,fetchImplementation:n=fetch}){let o=r.timeout??SC/1e3,i=new AbortController,a=Object.fromEntries(Object.entries(r.headers||{}).filter(([m,u])=>m&&u));a["Content-Type"]="application/json";let s;if(Sr(r.url)&&(s=r.url),t&&br(r.url,t)&&(s=new URL(r.url,t).toString()),!s)throw new T("ActionFailureError",`Invalid URL: ${r.url}`);let c=await F((async()=>{try{return await n(s,{headers:a,method:"POST",body:JSON.stringify({query:r.query,variables:r.variables}),signal:i.signal})}catch(m){e.error({err:m},"Failed to make HTTP request")}})(),{milliseconds:o*1e3});if(!c)throw new T("ActionFailureError",`GraphQL request timed out after ${o} seconds`);if(!c.ok){let m,u=await c.text();try{m=JSON.parse(u)}catch{throw new T("ActionFailureError",`GraphQL request failed with status ${c.status}: ${u}`)}throw m?.errors?.length&&m?.errors[0]?.message?new T("ActionFailureError",`GraphQL request failed with status ${c.status}: ${m.errors[0].message}`):new T("ActionFailureError",`GraphQL request failed with status ${c.status}: ${u}`)}let d={};return c.headers.forEach((m,u)=>{d[u]=m}),{status:c.status,headers:d,json:await c.json()}}function Ki(r){try{return new RegExp(r)}catch(e){throw new T("UserConfigurationError",`The provided regex expression was invalid: ${e}`)}}async function bC(r){let e=r.postData();if(e){if((await r.headerValue("content-type"))?.includes("json"))try{return{json:r.postDataJSON()}}catch{return{}}else if((await r.headerValue("content-type"))?.includes("text"))return{text:e}}return{}}async function yC(r){let e=await r.text();if(e){if((await r.headerValue("content-type"))?.includes("json"))try{return{json:await r.json()}}catch{return{}}else if((await r.headerValue("content-type"))?.includes("text"))return{text:e}}return{}}function bl(r){return{request:{url:r.request.url,method:r.request.method,headers:r.request.headers.reduce((e,t)=>({...e,[t.name]:t.value}),{}),...r.request.postData?r.request.postData.mimeType.includes("json")?{json:JSON.parse(r.request.postData.text)}:{text:r.request.postData.text}:{}},response:r.response?{status:r.response.status,headers:r.response.headers.reduce((e,t)=>({...e,[t.name]:t.value}),{}),...r.response.content?r.response.content.mimeType?.includes("json")&&r.response.content.text?{json:JSON.parse(r.response.content.text)}:{text:r.response.content.text}:{}}:void 0}}async function Mg(r){let e=r.request(),t=r.status(),n=r.headers(),o=await yC(r);return{request:{url:e.url(),method:e.method(),headers:e.headers(),...await bC(e)},response:{status:t,headers:n,...o},status:t,headers:n,...o}}var Yi=class{flagStore;orgId;options;storage;localCodeEvalTools;uploadedFileStorage;visualDiffScreenshotStorage;browser;generator;executeAbortController=new AbortController;logger;recordAbortController=null;registeredListeners={};recordedRequests={};constructor({browser:e,generator:t,logger:n,flagStore:o,storage:i,orgId:a,localCodeEvalTools:s,uploadedFileStorage:l,visualDiffScreenshotStorage:c,options:d}){this.orgId=a,this.options=d,this.browser=e,this.browser.registerAbortSignal(this.executeAbortController.signal),this.storage=i,this.uploadedFileStorage=l,this.visualDiffScreenshotStorage=c,this.localCodeEvalTools=s,this.generator=t,this.logger=n,this.flagStore=o}setOpen(){this.executeAbortController=new AbortController,this.browser.registerAbortSignal(this.executeAbortController.signal)}setClosed(){this.executeAbortController.abort()}throwIfClosed(){this.executeAbortController.signal.throwIfAborted()}get closed(){return this.executeAbortController.signal.aborted}async evaluateAiAction({goal:e,startingScreenshot:t,history:n,disableCache:o,langfuseSessionId:i,lastError:a,logger:s=this.logger}){let[l,c]=await Promise.all([fn(this.browser,{abortSignal:this.executeAbortController.signal,skipWait:!0,skipWaitForPageLoad:!0,logger:s}),this.browser.screenshot({retries:1,clearHighlights:!0})]),d=await zn({type:"ai-action",description:e,serializedTree:l.serializedTree,tree:l.tree,logger:s,generator:this.generator,aiPageFiltering:!!this.options?.aiPageFiltering,softTokenLimit:25e3,hardTokenLimit:5e4}),m=`data:image/jpeg;base64,${c.toString("base64")}`,u={url:this.browser.url(),browserState:d,startingScreenshot:t,history:n,goal:e,screenshot:m,lastError:a};return await this.generator.getMultiturnAiActionEvaluation(u,{disableCache:o,abortSignal:this.executeAbortController.signal,loggerTags:{...ce(s)},langfuseSessionId:i})}async promptToCommand({goal:e,startingScreenshot:t,history:n,actionHint:o,disableCache:i,logger:a=this.logger,langfuseSessionId:s}){let l=this.browser.url(),[c,d]=await Promise.all([fn(this.browser,{abortSignal:this.executeAbortController.signal,skipWait:!0,skipWaitForPageLoad:!0,logger:a}),this.browser.screenshot({retries:1,clearHighlights:!0})]),m=`data:image/jpeg;base64,${d.toString("base64")}`,u=await zn({type:"ai-action",description:e,serializedTree:c.serializedTree,tree:c.tree,logger:a,generator:this.generator,aiPageFiltering:!!this.options?.aiPageFiltering,softTokenLimit:25e3,hardTokenLimit:5e4}),g={url:l,browserState:u,startingScreenshot:t,history:n,goal:e,actionHint:o,screenshot:m};try{return await this.generator.getMultiturnAiActionCommand(g,{disableCache:i,abortSignal:this.executeAbortController.signal,loggerTags:{...ce(a)},langfuseSessionId:s})}catch(p){throw new T("InternalWebAgentError",`Error generating command: ${p instanceof Error?p.message:p}`,{errOptions:{cause:p}})}}async getBrowserState(e){return fn(this.browser,e)}async locateElement(e){return await fl({...e,aiPageFiltering:!!this.options?.aiPageFiltering},this.getControllerFixtures())}async locateElementWithSelector(e,t){return Gt({action:async()=>{let n=await this.browser.resolveHardcodedCssSelector({ctx:null,selector:e,timeoutMs:2e3,logger:this.logger});return{thoughts:"Located element with selector",target:{id:-1,selector:e,targetSource:"USER_CSS_SELECTOR",targetUpdateTime:new Date().toUTCString()},resolution:n}},frameConfig:t?{type:"url",url:t}:void 0,browser:this.browser,logger:this.logger})}getControllerFixtures(e){return{ctx:e??null,browser:this.browser,generator:this.generator,logger:this.logger,orgId:this.orgId,flagStore:this.flagStore,storage:this.storage,localCodeEvalTools:this.localCodeEvalTools,abortSignal:this.executeAbortController.signal}}shouldUseMemory(){return this.options?.useMemory??(this.orgId==="org_01HMSCJQBCCG51M2ZF65YC5B8W"||this.orgId==="org_01HMJTX4GT1KG94KZRCT8MZ6YB")}async wrapMultiElementTargetingCommand({ctx:e,tracer:t,command:n,targetNames:o,descriptions:i,caches:a,action:s,options:l,retriesWithAI:c=1}){let d=[];for(let m=0;m<i.length;m++){let u=i[m],g=await this.wrapElementTargetingCommand({ctx:e,tracer:t,command:n,target:u,cache:a[m],action:async p=>p,options:{...l,targetName:o[m]}});d.push(g)}try{let m=await s(...d.map(p=>p.result)),u=p=>p==="fromTarget"?"From Target":p==="toTarget"?"To Target":"Target",g=d.map((p,h)=>p.thoughts?`${u(o[h])}: ${p.thoughts}`:void 0).filter(p=>!!p).join(" -------------- ")||void 0;return{result:m,elementInteractedDisplayStrings:d.map(p=>p.elementInteractedDisplayString),thoughts:g}}catch(m){if(this.throwIfClosed(),c>0)return this.logger.warn({err:m},"Failed to execute action with multiple cached targets, retrying with AI"),this.wrapMultiElementTargetingCommand({ctx:e,tracer:t,command:n,targetNames:o,descriptions:i,caches:i.map(()=>{}),action:s,options:l,retriesWithAI:c-1});throw new T("ActionFailureError",m.message,{errOptions:{cause:m}})}}async wrapElementTargetingCommand(e){let t=this.logger.child({commandId:e.command.id}),n;for(let o=0;o<2;o++)try{return await Gt({action:()=>this.wrapElementTargetingCommandHelper({...e,originalCache:e.originalCache??e.cache}),frameConfig:e.options.iframeUrl?{type:"url",url:e.options.iframeUrl}:void 0,browser:this.browser,logger:t})}catch(i){if(n=i,this.browser.userBrowserSettings.visualActions&&gs(i)){t.warn({err:i},"Invalid mpath error, retrying element targeting command");continue}if(!this.browser.userBrowserSettings.visualActions&&so(i)){t.warn({err:i},"Invalid momentic id error, retrying element targeting command");continue}if(hs(i)&&t.warn({err:i},"Invalid backend node id error, retrying element targeting command"),i instanceof Dt&&i.retryableWithAI){t.warn({err:i},"Element cache disqualification error, retrying element targeting command");continue}throw i}throw n instanceof T?n:new T("ActionFailureError",n?.message??"An unknown error occurred during element targeting")}async wrapHardcodedCssTargetingCommandHelper({ctx:e,target:t,action:n,options:o,command:i}){let a=this.logger.child({commandId:i.id}),{targetName:s}=o;if(t.type!=="description")throw new T("ActionFailureError","Cannot use selector with non-description target");let l,c=Date.now(),d=Date.now();for(;Date.now()-d<this.browser.smartWaitingTimeout;){c=Date.now();try{let m=await this.browser.resolveHardcodedCssSelector({ctx:e,selector:t.elementDescriptor,targetName:s,logger:a});return{result:await n({locator:m.locator}),elementInteractedDisplayString:m.displayString}}catch(m){if(m.name==="AbortError")throw m;l=m,a.warn({err:m},"Failed to action on hardcoded css selector"),Date.now()-c<500&&await j(500)}}throw l}async wrapElementTargetingCommandHelper(e){let{ctx:t,tracer:n,target:o,originalCache:i,action:a,options:s,command:l}=e,{disableCache:c,useSelector:d,targetName:m,targetHealingInProgress:u,source:g}=s,p=this.logger.child({commandId:l.id}),h=this.shouldUseMemory(),f=s.retriesWithAI??1,y=!1,w=Lg(e.cache);if((!w||c)&&!Aa(o))throw new T("ActionFailureError","Cannot target element with no cached data or element descriptor");if(d)return this.wrapHardcodedCssTargetingCommandHelper(e);c&&(p.info("Cache explicitly disabled for this step"),y=!0,w=void 0),w&&this.browser.userBrowserSettings.disableSecondaryCacheResolution&&w.targetSource==="HEURISTIC_HEALED"&&(y=!0,w=void 0),w?.inputDescription&&!bg(o.elementDescriptor,w.inputDescription)&&(p.warn({old:w.inputDescription,new:o.elementDescriptor},"Target cache was generated with a different description, clearing it automatically"),y=!0,w=void 0);let A=C=>!!C&&gc(C),I=!0;if(!A(w)){I=!1,p.info({description:o.elementDescriptor,targetHealingInProgress:u,cacheBustedBeforeAction:y,memory:s.memory,useMemory:h},"Prompting AI for an updated element location"),y&&await j(this.browser.smartWaitingTimeout,this.executeAbortController.signal),f--;let C;try{C=await fl({description:o.elementDescriptor,disableCache:!!s.disableCache,iframeUrl:s.iframeUrl,source:g,useMemory:h,memory:h?s.memory:void 0,aiPageFiltering:!!this.options?.aiPageFiltering,allowZeroOpacityOverride:s.allowZeroOpacityOverride,logger:p},this.getControllerFixtures(t))}catch(V){if(V instanceof pr&&V.updatedLocatorMemory){let Q={id:-1,...i,memory:V.updatedLocatorMemory};vi({cmd:l,key:m,newTarget:Q,logger:p})}throw new T("ActionFailureError",V.message)}C.frameConfig&&this.browser.setActiveFrameConfig(C.frameConfig);let R=s.disableGlobalLocatorRedirect?{locator:C.resolution.locator}:await this.attemptLocatorRedirect(C.resolution.locator,p),_=await a(R);return vi({cmd:l,key:m,newTarget:C.target,logger:p}),u&&(n.heal({healType:"AI"}),C.target.targetSource="AI_HEALED",C.target.targetUpdateTime=new Date().toUTCString(),C.target.targetUpdateLoggerTags=ce(p)),{result:_,elementInteractedDisplayString:C.resolution.displayString,thoughts:C.thoughts}}try{let C=await this.browser.resolveTarget(t,w,{allowZeroOpacityOverride:s.allowZeroOpacityOverride,targetName:m,logger:p,signal:this.executeAbortController.signal});(this.browser.userBrowserSettings.visualActions||this.browser.userBrowserSettings.globalLocatorRedirect)&&await this.browser.scrollIntoViewIfNeeded(C.locator);let R=s.disableGlobalLocatorRedirect?{locator:C.locator}:await this.attemptLocatorRedirect(C.locator,p),_=await a(R);if(vi({cmd:l,key:m,newTarget:w,logger:p}),I){let V=C.decisions.filter(Q=>Q.matched);if(V.length!==1)p.warn({decisions:C.decisions},"Expected exactly 1 matching method for element location, got more or less");else{let Q=V[0].type;n.heal({healType:Q})}}return{result:_,elementInteractedDisplayString:C.displayString}}catch(C){this.throwIfClosed();let R=!1;if((C instanceof Dt||gs(C)||so(C)||hs(C)||Yd(C)||Xd(C))&&(R=!0),C instanceof T&&!R)throw p.error({err:C},"Failed to execute action with cached target (fatal)"),C;if(f>0&&o){p.info({err:C},"Failed to execute action with cached target, retrying with AI");let _;return w.memory&&hc(w.memory)&&(_=w.memory),this.wrapElementTargetingCommand({ctx:t,tracer:n,command:l,target:o,cache:void 0,originalCache:i,action:a,options:{...s,memory:_,retriesWithAI:f,targetHealingInProgress:!0}})}throw new T("ActionFailureError",C.message,{errOptions:{cause:C}})}}async attemptLocatorRedirect(e,t){return this.browser.userBrowserSettings.globalLocatorRedirect?this.browser.performTargetRedirection(e,t):{locator:e}}async screenshotWithDimensions(e){return bo(this.browser,e)}async executePresetCommand(e,t,n,o,i){this.options?.slowMoMs&&await j(this.options.slowMoMs);let a=await this.browser.getOpenPages(),s=this.browser.url(),l;try{l=await this.resolveCommandTemplateStrings(n,o)}catch(c){throw this.throwIfClosed(),new T("ActionFailureError",`Failed to substitute template strings in command: ${c.message}`,{errOptions:{cause:c}})}try{let c=await this.executePresetCommandHelper(e,t,n,o,i);return this.browser.userBrowserSettings.visualActions&&Rc(n)?await this.browser.waitForDOMStability({timeout:2e3}):!this.browser.userBrowserSettings.visualActions&&["PRESS","TYPE"].includes(n.type)&&await this.browser.waitForDOMStability({timeout:1e3}),this.options?.autoFollowNewTabs&&await cg({beforeUrl:s,command:n,beforePages:a.map(d=>d.url),browser:this.browser,logger:this.logger}),c}catch(c){throw c.name!=="AbortError"&&this.logger.error({err:c},"Error thrown in action controller"),c}finally{Ci(n,l)}}createCallbacksForBrowser(e){return{createIsolatedFolder:()=>Vs(e)}}async resolveCommandTemplateStrings(e,t){return Ei({obj:e,context:t,bannedKeys:["type","a11yData","thoughts","cache","code"],orgId:this.orgId,logger:this.logger,signal:this.executeAbortController.signal,localTools:this.localCodeEvalTools})}async executePresetCommandHelper(e,t,n,o,i){i=i||"disableCache"in n&&!!n.disableCache;let a=this.logger.child({commandId:n.id});switch(n.type){case"SUCCESS":let s=n.condition;return s?.assertion.trim()?Vi({command:s,fixtures:this.getControllerFixtures(e),useMemory:this.shouldUseMemory(),aiPageFiltering:!!this.options?.aiPageFiltering,logger:a}):{succeedImmediately:!1,urlAfterCommand:this.browser.url()};case"AI_ASSERTION":{if(!n.assertion.trim())throw new T("ActionFailureError","Missing assertion");if(n.timeout&&n.timeout>1800)throw new T("AssertionFailureError",`AI check timeout of ${n.timeout} exceeds the maximum allowed value of 30 minutes.`);return Vi({command:n,fixtures:this.getControllerFixtures(e),useMemory:this.shouldUseMemory(),aiPageFiltering:!!this.options?.aiPageFiltering,logger:a})}case"AI_EXTRACT":{if(!n.goal.trim())throw new T("ActionFailureError","Cannot perform AI extraction without goal");if(n.schema){let f=Km(n.schema);if(f)throw new T("UserConfigurationError",f)}let p=await this.browser.getCondensedHtml(),h=await this.browser.screenshot({retries:2});try{let f=await this.generator.getTextExtraction({goal:n.goal,browserState:p,returnSchema:n.schema,screenshot:`data:image/jpeg;base64,${h.toString("base64")}`},{disableCache:i,abortSignal:this.executeAbortController.signal,loggerTags:ce(a)});if(f.result==="NOT_FOUND")throw new T("ActionFailureError","No relevant data found for extraction goal on this page");if(f.thoughts?.includes("MaxGenerationLengthExceededError"))throw new T("UserConfigurationError",f.thoughts);return{thoughts:f.thoughts||void 0,data:f.result,succeedImmediately:!1,urlAfterCommand:this.browser.url()}}catch(f){let y=f.message;throw y.includes("MaxGenerationLengthExceededError")?new T("UserConfigurationError","You tried to extract too much data. Please rephrase your query to limit the results returned or use a JavaScript step in the browser instead."):y.includes("AIProviderError")&&y.includes("time")?new T("AIProviderError","The AI provider responded with an error. This may be because you tried to extract too much data. Please limit extraction results to 2000 characters.",{errOptions:{cause:f}}):f}}case"NAVIGATE":if(!Sr(n.url)&&!br(n.url,this.browser.baseUrl))throw new T("ActionFailureError",`Invalid URL provided to navigate command: ${n.url}`);await this.browser.navigate({url:n.url,loadTimeoutMs:n.loadTimeout?n.loadTimeout*1e3:void 0});break;case"DIALOG":this.browser.registerDialogHandler(n.action);break;case"CAPTCHA":if(!this.browser.canSolveCaptchas())break;let l=await this.browser.solveCaptcha();l&&(await this.wrapElementTargetingCommand({ctx:e,tracer:t,command:n,target:{type:"description",elementDescriptor:"the captcha image solution input"},cache:void 0,action:p=>this.browser.click(p,this.createCallbacksForBrowser(this.orgId),{}),options:{...n,targetName:"target",disableCache:i}}),await this.browser.type(l,{clearContent:!0,pressEnter:!0},!0));break;case"GO_BACK":await this.browser.goBack();break;case"GO_FORWARD":await this.browser.goForward();break;case"SCROLL_LEFT":case"SCROLL_RIGHT":case"SCROLL_DOWN":case"SCROLL_UP":{let p,h;if(n.target&&kt(n.target))await this.browser.hoverUsingVisualCoordinates(n.target.pixels);else if(n.target&&n.target.elementDescriptor.trim()){let{elementInteractedDisplayString:w,thoughts:A}=await this.wrapElementTargetingCommand({ctx:e,tracer:t,command:n,target:n.target,cache:n.cache?.target,action:I=>this.browser.hover(I),options:{...n,targetName:"target",disableGlobalLocatorRedirect:!0,disableCache:i}});p=w,h=A}let f=this.browser.getViewport()?.height??an.height,y=this.browser.getViewport()?.width??an.width;switch(n.type){case"SCROLL_UP":await this.browser.scrollVertical(-(n.deltaY??f));break;case"SCROLL_DOWN":await this.browser.scrollVertical(n.deltaY??f);break;case"SCROLL_LEFT":await this.browser.scrollHorizontal(-(n.deltaX??y));break;case"SCROLL_RIGHT":await this.browser.scrollHorizontal(n.deltaX??y);break}return{succeedImmediately:!1,urlAfterCommand:this.browser.url(),elementInteracted:p,thoughts:h}}case"WAIT_FOR_URL":{if(n.timeout&&n.timeout>1800)throw new T("UserConfigurationError",`Wait for URL timeout of ${n.timeout} exceeds the maximum allowed value of 30 minutes.`);let p=n.matcher;await this.browser.waitForUrl({beforeUrl:this.browser.url(),matcher:p},{timeout:n.timeout?n.timeout*1e3:void 0,negated:n.negated,caseInsensitive:n.caseInsensitive});break}case"WAIT":if(n.delay>1800)throw new T("UserConfigurationError",`Wait timeout of ${n.delay} seconds exceeds the maximum allowed value of 30 minutes`);let c=n.delay*1e3;await j(c,this.executeAbortController.signal);break;case"REFRESH":await this.browser.refresh({loadTimeoutMs:n.loadTimeout?n.loadTimeout*1e3:void 0});break;case"CLICK":{if(kt(n.target)){await this.browser.clickUsingVisualCoordinates(n.target.pixels,n);break}let p=this.browser.url(),{elementInteractedDisplayString:h,result:f,thoughts:y}=await this.wrapElementTargetingCommand({ctx:e,tracer:t,target:n.target,command:n,cache:n.cache?.target,action:A=>this.browser.click(A,this.createCallbacksForBrowser(this.orgId),n),options:{disableCache:i,targetName:"target",...n}}),w={urlAfterCommand:this.browser.url(),succeedImmediately:!1,elementInteracted:h,thoughts:y,data:f};return Pi(p,w.urlAfterCommand)&&(w.succeedImmediately=!0,w.succeedImmediatelyReason="URL changed"),w}case"COPY":return await this.browser.copy(n.value),{succeedImmediately:!1,data:n.value,urlAfterCommand:this.browser.url()};case"PASTE":{await this.browser.paste();break}case"DRAG":{if(kt(n.fromTarget)&&kt(n.toTarget)){await this.browser.dragAndDropUsingVisualCoordinates(n.fromTarget.pixels,n.toTarget.pixels,{hoverSeconds:n.hoverSeconds});break}if(kt(n.fromTarget)||kt(n.toTarget))throw new Error("Drag and drop targets must be both coordinates or both descriptions");let{elementInteractedDisplayStrings:p,thoughts:h}=await this.wrapMultiElementTargetingCommand({ctx:e,tracer:t,command:n,targetNames:["fromTarget","toTarget"],descriptions:[n.fromTarget,n.toTarget],caches:[n.cache?.fromTarget,n.cache?.toTarget],action:(f,y)=>this.browser.dragAndDrop(f.locator,y.locator,{hoverSeconds:n.hoverSeconds,steps:n.steps}),options:{useSelector:!!n.useSelector,disableCache:i}});return{succeedImmediately:!1,urlAfterCommand:this.browser.url(),elementInteracted:p[0],thoughts:h}}case"MOUSE_DRAG":{let p=parseInt(n.deltaX),h=parseInt(n.deltaY),f=n.steps??5;if(isNaN(p)||isNaN(h))throw new T("ActionFailureError",`Invalid pixel values passed to mouse drag command: (${n.deltaX}, ${n.deltaY})`);if(n.target&&kt(n.target)){await this.browser.mouseDragUsingVisualCoordinates(p,h,f,n.target.pixels,{force:n.force});break}let y,w,A;if(n.target?.elementDescriptor){let{elementInteractedDisplayString:I,result:C,thoughts:R}=await this.wrapElementTargetingCommand({ctx:e,tracer:t,command:n,target:n.target,cache:n.cache?.target,action:async _=>_.locator,options:{disableCache:i,targetName:"target",...n}});y=C,w=I,A=R}return await this.browser.mouseDrag(p,h,f,y,{force:n.force}),{succeedImmediately:!1,urlAfterCommand:this.browser.url(),elementInteracted:w,thoughts:A}}case"SELECT_OPTION":{if(!en(n.target))throw new Error("Select with x/y is not supported yet");let p=n.target.elementDescriptor,h=n.choice,{elementInteractedDisplayString:f,thoughts:y}=await this.wrapElementTargetingCommand({ctx:e,tracer:t,command:n,target:{type:"description",elementDescriptor:p},cache:n.cache?.target,action:w=>this.browser.selectOption(w,h,n.force),options:{...n,targetName:"target",disableCache:i,source:Xr(n)}});return{succeedImmediately:!1,urlAfterCommand:this.browser.url(),elementInteracted:f,thoughts:y}}case"TAB":{let p={loadTimeoutMs:n.loadTimeout?n.loadTimeout*1e3:void 0,retry:!0};await this.browser.switchToPage(n.action,p);break}case"NEW_TAB":await this.browser.createNewTab(n.url,{loadTimeoutMs:n.loadTimeout?n.loadTimeout*1e3:void 0});break;case"COOKIE":if(!n.value)break;let d=await this.browser.setCookie(n.value);a.debug({results:d},"Set cookies");break;case"LOCAL_STORAGE":if(!n.value||!n.key)break;await this.browser.setLocalStorage(n.key,n.value);break;case"JAVASCRIPT":{let p;try{n.environment==="BROWSER"?(p=await this.browser.evaluateCodeInPage({code:n.code,fragment:n.fragment??!1,context:o.toObjectCopy(),timeoutMs:n.timeout?n.timeout*1e3:void 0}),a.info({result:p},"Executed JavaScript in browser")):p=await Ln({orgId:this.orgId,code:n.code,fragment:!!n.fragment,context:o,timeoutMs:n.timeout?n.timeout*1e3:void 0,logger:a,localTools:this.localCodeEvalTools,signal:this.executeAbortController.signal,callbacks:{onPersistentVariableUpdates:async h=>{if(!this.options?.scratchPadId){a.warn({updates:h},"Got persistent variable updates but scratch pad is not available");return}await this.storage.savePersistentVariables?.({scratchPadId:this.options?.scratchPadId,orgId:this.orgId,updates:h,logger:a})}}})}catch(h){throw this.throwIfClosed(),new T("ActionFailureError",h instanceof Error?h.message:`${h}`,{errOptions:{cause:h}})}try{JSON.stringify(p)}catch(h){throw new T("ActionFailureError",`Return value is not serializable: ${h instanceof Error?h.message:`${h}`}`,{errOptions:{cause:h}})}return{urlAfterCommand:this.browser.url(),succeedImmediately:!1,data:p}}case"TYPE":{if(n.target&&kt(n.target)){await this.browser.clickUsingVisualCoordinates(n.target.pixels,n),await this.browser.type(n.value,{force:n.force,clearContent:n.clearContent,forceClearContent:n.forceClearContent,delay:n.delay,pressEnter:n.pressEnter},!0);break}let p=this.browser.url(),h,f,y=Lg(n.target);if(y){let{elementInteractedDisplayString:A,thoughts:I}=await this.wrapElementTargetingCommand({ctx:e,tracer:t,command:n,target:y,cache:n.cache?.target,action:C=>this.browser.typeIntoTarget(n.value,C,{force:n.force,clearContent:n.clearContent,forceClearContent:n.forceClearContent,delay:n.delay,pressEnter:n.pressEnter}),options:{...n,targetName:"target",disableCache:i,disableGlobalLocatorRedirect:!0,source:Xr(n)}});h=A,f=I}else await this.browser.type(n.value,{force:n.force,clearContent:n.clearContent,forceClearContent:n.forceClearContent,delay:n.delay,pressEnter:n.pressEnter},!0);let w={urlAfterCommand:this.browser.url(),succeedImmediately:!1,elementInteracted:h,thoughts:f};return Pi(p,w.urlAfterCommand)&&(w.succeedImmediately=!0,w.succeedImmediatelyReason="URL changed"),w}case"HOVER":{if(kt(n.target)){await this.browser.hoverUsingVisualCoordinates(n.target.pixels);break}let{elementInteractedDisplayString:p,thoughts:h}=await this.wrapElementTargetingCommand({ctx:e,tracer:t,command:n,target:n.target,cache:n.cache?.target,action:f=>this.browser.hover(f),options:{...n,targetName:"target",disableCache:i}});return{succeedImmediately:!1,urlAfterCommand:this.browser.url(),elementInteracted:p,thoughts:h}}case"FOCUS":{if(!en(n.target))throw new Error("Focus with x/y is not supported yet");let{elementInteractedDisplayString:p,thoughts:h}=await this.wrapElementTargetingCommand({ctx:e,tracer:t,command:n,target:n.target,cache:n.cache?.target,action:f=>this.browser.focus(f),options:{...n,targetName:"target",disableCache:i}});return{succeedImmediately:!1,urlAfterCommand:this.browser.url(),elementInteracted:p,thoughts:h}}case"BLUR":{if(n.target&&!en(n.target))throw new Error("Blur with x/y is not supported yet");if(!n.target||!n.target.elementDescriptor)return await this.browser.blur(null),{succeedImmediately:!1,urlAfterCommand:this.browser.url()};let{elementInteractedDisplayString:p,thoughts:h}=await this.wrapElementTargetingCommand({ctx:e,tracer:t,target:n.target,command:n,cache:n.cache?.target,action:f=>this.browser.blur(f),options:{...n,targetName:"target",disableCache:i}});return{succeedImmediately:!1,urlAfterCommand:this.browser.url(),elementInteracted:p,thoughts:h}}case"PRESS":let m=this.browser.url();await this.browser.press(n.value,{repeat:n.repeat,convertMeta:n.convertMeta??!0,delayMs:n.delayMs});let u={urlAfterCommand:this.browser.url(),succeedImmediately:!1};return Pi(m,u.urlAfterCommand)&&(u.succeedImmediately=!0,u.succeedImmediatelyReason="URL changed"),u;case"KEY_DOWN":return await this.browser.keyDown(n.value,{convertMeta:n.convertMeta??!0}),{urlAfterCommand:this.browser.url(),succeedImmediately:!1};case"KEY_UP":return await this.browser.keyUp(n.value,{convertMeta:n.convertMeta??!0}),{urlAfterCommand:this.browser.url(),succeedImmediately:!1};case"REQUEST":{let p=new TC,h=wC(fetch,p),f;try{f=new URL(n.url).hostname}catch{}return{data:{...await Rg({command:n,baseUrl:this.browser.baseUrl,logger:a,fetchImplementation:h}),cookies:Ad(p,f)},succeedImmediately:!1,urlAfterCommand:this.browser.url()}}case"GRAPHQL_REQUEST":return{data:await Pg({command:n,baseUrl:this.browser.baseUrl,logger:a}),succeedImmediately:!1,urlAfterCommand:this.browser.url()};case"VISUAL_DIFF":return vg({ctx:e,tracer:t,command:n,disableCache:i,browser:this.browser,logger:a,storage:this.storage,screenshotStorage:this.visualDiffScreenshotStorage,targetingWrapper:p=>this.wrapElementTargetingCommand(p)});case"FILE_UPLOAD":{let p,h;if(n.fileSource.type==="URL"?(h=n.fileSource.url,p=await ip({uri:n.fileSource.url,logger:a,orgId:this.orgId})):n.fileSource.type==="USER_FILE"&&(h=n.fileSource.name,p=await this.uploadedFileStorage?.getFileForUpload(n.fileSource.name,this.orgId)),!p)throw new T("UserConfigurationError",`Attempted to use non-existent file for upload step: ${h}`);await this.browser.setFileChooserHandler({...p,filename:n.filename});break}case"AUTH_SAVE":return{data:await this.browser.saveAuthState(),succeedImmediately:!1,urlAfterCommand:this.browser.url()};case"AUTH_LOAD":{let p;if(!n.storageState.trim())p=void 0;else if(p=await Ln({orgId:this.orgId,code:n.storageState,fragment:!1,context:o,logger:a,localTools:this.localCodeEvalTools,signal:this.executeAbortController.signal}),typeof p!="object")throw new T("ActionFailureError",`Credentials must evaluate to an object (received ${typeof p} instead)`);let h;try{h=Rd.optional().parse(p)}catch(f){throw new T("ActionFailureError",`Credentials provided do not follow the required format: ${f}`)}await this.browser.loadAuthState(h);break}case"ELEMENT_CHECK":{let p=(n.timeout??nn)*1e3,h=this.generator.getAgentConfig()?.assertion;if(Eg(n.assertion)&&!n.useSelector&&n.target.type==="description"&&h&&h!=="v1"){let y={id:n.id,type:"AI_ASSERTION",assertion:`There is no element on the page closely matches the following description. If the description has single quotes, remember that requires an exact text substring match. Description: ${n.target.elementDescriptor}`,iframeUrl:n.iframeUrl,timeout:n.timeout,cache:n.cache&&"memory"in n.cache?{memory:n.cache?.memory}:void 0};try{let w=await Vi({command:y,logger:a,aiPageFiltering:!!this.options?.aiPageFiltering,fixtures:this.getControllerFixtures(e),useMemory:this.shouldUseMemory(),source:"NEGATED_CHECK"});return{succeedImmediately:!1,thoughts:`The element described does not exist on the page: ${w.thoughts}`,urlAfterCommand:this.browser.url(),afterScreenshotOverride:w.afterScreenshotOverride}}finally{y.cache?.memory&&Ri(n,y.cache?.memory.traces)}}let f=await Tg({command:n,tracer:t,timeoutMs:p,targetingWrapper:y=>this.wrapElementTargetingCommand(y),fixtures:this.getControllerFixtures(e),disableCache:i});return{fail:!f.success,data:f.data,elementInteracted:f.elementInteractedDisplayString,thoughts:f.err?.message??f.thoughts??`Element assertion ${f.success?"succeeded":"failed"}.`,succeedImmediately:!1,urlAfterCommand:this.browser.url()}}case"PAGE_CHECK":{let p=await Gt({action:async()=>Ig({assertion:n.assertion,browser:this.browser,logger:a,timeout:n.timeout,signal:this.executeAbortController.signal,autoExpandIframes:!!this.browser.userBrowserSettings.autoExpandIframes}),frameConfig:n.iframeUrl?{type:"url",url:n.iframeUrl}:void 0,browser:this.browser,logger:a});return{fail:!p.success,data:p.data,thoughts:p.success?"Page assertion passed.":p.err?.message??`Page assertion still failing after ${n.timeout} seconds.`,urlAfterCommand:this.browser.url(),succeedImmediately:!1}}case"REGISTER_REQUEST_LISTENER":{let p=Ki(n.pattern),h=this.browser.registerRequestListener(p);return this.registeredListeners[n.key]=h.then(async f=>await Mg(f)).catch(f=>{a.error({err:f},"Failed to get request listener response")}),{succeedImmediately:!1,urlAfterCommand:this.browser.url()}}case"AWAIT_LISTENER":{let p=this.registeredListeners[n.key];if(!p)throw new T("ActionFailureError",`No listener registered with key: ${n.key}`);let h=n.timeout??10;return{data:await F(p,{milliseconds:h*1e3,message:`Request listener timed out after ${h} seconds`}),succeedImmediately:!1,urlAfterCommand:this.browser.url()}}case"RECORD_REQUESTS":{let p=Ki(n.pattern);return this.recordedRequests[n.key]={},this.browser.registerRequestRecorder(n.key,p,{onRequestStart:(h,f)=>{this.recordedRequests[n.key][h]=bl(f)},onRequestComplete:(h,f)=>{this.recordedRequests[n.key][h]=bl(f)}}),{succeedImmediately:!1,urlAfterCommand:this.browser.url()}}case"GET_RECORDED_REQUESTS":{let p=this.recordedRequests[n.key];if(!p)throw new T("ActionFailureError",`No recorder registered with key: ${n.key}`);return delete this.recordedRequests[n.key],{data:Object.values(p),succeedImmediately:!1,urlAfterCommand:this.browser.url()}}case"SET_HEADER":{let p;return n.urlPattern&&(p=Ki(n.urlPattern)),this.browser.setHeader(n.name,n.value,p),{succeedImmediately:!1,urlAfterCommand:this.browser.url()}}case"OFFLINE_MODE":return await this.browser.setOfflineMode(n.enable),{succeedImmediately:!1,urlAfterCommand:this.browser.url()};default:return(p=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(n)}return{succeedImmediately:!1,urlAfterCommand:this.browser.url()}}async getReverseMappedDescription({browserState:e,targetId:t,disableCache:n,screenshot:o}){return(await this.generator.getReverseMappedDescription({browserState:e,target:t,screenshot:o},{disableCache:n,abortSignal:this.executeAbortController.signal,loggerTags:ce(this.logger)})).phrase}async stopRecordMode(){this.recordAbortController?.abort(),await this.browser.clearAllCdpHighlights()}async startRecordMode({params:e,abortController:t,isClickToRecord:n}){this.recordAbortController=t;let o=new Hi({signal:t.signal,...e});return await this.browser.startRecording(this.recordAbortController.signal,o,n),o}async runSectionAutohealing(e){return this.generator.getAutohealingProposal(e,{disableCache:!0,abortSignal:this.executeAbortController.signal,loggerTags:ce(this.logger)})}async getFailureRecoveryPlan(e){return this.generator.getFailureRecoveryPlan(e,{disableCache:!0,abortSignal:this.executeAbortController.signal,loggerTags:ce(this.logger)})}};import{z as At}from"zod";var x5=At.object({useSelector:At.boolean().optional(),disableCache:At.boolean().optional(),iframeUrl:At.string().optional(),retriesWithAI:At.number().optional(),targetName:At.union([At.literal("target"),At.literal("fromTarget"),At.literal("toTarget")]),source:qo.optional(),disableGlobalLocatorRedirect:At.boolean().optional(),allowZeroOpacityOverride:At.boolean().optional(),targetHealingInProgress:At.boolean().optional(),memory:jn.optional()});import{z as ve}from"zod";var Og=ve.array(ve.object({proc:ve.string(),webview:ve.string(),info:ve.object({"Android-Package":ve.string(),Browser:ve.string(),"Protocol-Version":ve.string(),"User-Agent":ve.string(),"V8-Version":ve.string(),"WebKit-Version":ve.string(),webSocketDebuggerUrl:ve.string()}),pages:ve.array(ve.object({description:ve.string(),devtoolsFrontendUrl:ve.string(),id:ve.string(),title:ve.string(),type:ve.string(),url:ve.string(),webSocketDebuggerUrl:ve.string()})),webviewName:ve.string()}));var Xi=class r{driver;device;currentContext;logger;webviews=new Map;fixtures;orgId;options;aborter;constructor({driver:e,context:t,logger:n,device:o,fixtures:i,orgId:a,options:s,aborter:l}){this.driver=e,this.device=o,this.currentContext=t,this.logger=n,this.fixtures=i,this.orgId=a,this.options=s,this.aborter=l;for(let c of["SIGINT","SIGTERM"])process.on(c,async()=>{await this.cleanupWebviews()})}static async init({driver:e,logger:t,fixtures:n,orgId:o,adbPort:i,options:a,aborter:s}){let l=await e.getContext(),c=typeof l=="string"?l:l.id,d=(await CC.devices()).filter(g=>g._initializer?.serial?.endsWith(i.toString()));if(d.length!==1)throw new Error(`Playwright detected ${d.length} Android devices, expected 1. Please stop all other emulators and try again.`);let m=d[0];return new r({driver:e,context:c,logger:t,device:m,fixtures:n,orgId:o,options:a,aborter:s})}async getContexts(){let e=await this.driver.getContexts({}),t=Array.from(this.webviews.values());return{contexts:e,webviews:t.map(n=>({contextId:n.contextId,packageName:n.packageName,active:n.active,hasPlaywright:!!n.browserController,lastSeen:n.lastSeen,socketName:n.socketName}))}}async getCurrentScreenshot(){return`data:image/png;base64,${await this.driver.takeScreenshot()}`}async waitForScreenshotStability({timeoutMs:e,signal:t,reason:n,tracer:o}){await o.startAsyncSpan("WAIT_FOR_STABILITY",async()=>{let i=Date.now(),a,s=!1;for(;Date.now()-i<e;){if(t?.throwIfAborted(),!a){a=await this.getCurrentScreenshot();continue}if(await this.getCurrentScreenshot()===a){s=!0;break}await j(250,t)}s||this.logger.warn({reason:n},"Timed out waiting for screenshot stability")})}async getDomState(e){let t;if(e?.includeWebviews)try{let i=await this.getActiveWebview();i&&i.browserController&&(t=(await i.browserController.getBrowserState({abortSignal:this.aborter.controller?.signal})).serializedTree)}catch(i){this.logger.error({err:i},"Could not get webview info to get the nested DOM state")}this.throwIfAborted();let n=await this.driver.getPageSource();return this.throwIfAborted(),{graph:await qu(n,{injectedWebviewContent:t,disallowAccessibilityBridgeForWebviews:this.options.disableDefaultWebviewAccessibilityTree}),context:this.currentContext}}async refreshWebviews(){await this.driver.getPageSource(),this.throwIfAborted();let e=await this.getDetailedContexts();if(!e){this.logger.warn("No context details, not proceeding with refreshing webviews");return}let t=Date.now(),n=new Set;for(let o of e){if(this.throwIfAborted(),o.webview==="NATIVE_APP")continue;let i=o.info["Android-Package"];if(n.add(o.webview),!this.webviews.has(o.webview)||!this.webviews.get(o.webview)?.active){let a={contextId:o.webview,packageName:i,active:!1,lastSeen:t,socketName:o.proc.startsWith("@")?o.proc.substring(1):o.proc};this.logger.info(`New webview detected: ${JSON.stringify(a)}`),this.webviews.set(o.webview,a);let s;if(i==="com.android.chrome"?(s=await this.connectPlaywrightToChrome(a),this.logger.info(`Connected Playwright to Android Chrome: ${o.webview}`)):(this.logger.info(`Attempting to connect Playwright to webview: ${o.webview}`),s=await this.connectPlaywrightToWebview(i,a.socketName),this.logger.info(`Connected Playwright to webview ${o.webview}`)),!s)continue;a.browserController=s,a.active=!0}}for(let[o,i]of this.webviews.entries())!n.has(o)&&i.active&&(this.logger.info(`Disconnecting dead webview ${o}`),this.disconnectPlaywrightFromWebview(i),i.active=!1)}async getActiveWebview(){await this.refreshWebviews();let e=await this.driver.getCurrentPackage(),t=[];for(let n of this.webviews.values())n.packageName===e&&n.active&&t.push(n);if(t.length!==0){if(t.length>1)throw new Error("Multiple active webviews in a single package is currently not supported");return t[0]}}async executeRawADBCommand(e){let t=this.driver.capabilities,n=t.deviceUDID||t.udid||t["appium:udid"]||t["appium:deviceUDID"],o=process.env.ANDROID_HOME?`${process.env.ANDROID_HOME}/platform-tools/adb`:"adb",i=n?`-s ${n}`:"",a=`${o} ${i} ${e}`;this.logger.info(`Executing ADB command: ${a}`);let s=Ng(a,{encoding:"utf8"});return this.logger.info(`ADB command result: ${s}`),s}async cleanupWebviews(){for(let[e,t]of this.webviews.entries())this.logger.info(`Disconnecting webview ${e}`),this.disconnectPlaywrightFromWebview(t),t.active=!1}async getDetailedContexts(){let e=await this.driver.execute("mobile: getContexts");return this.aborter.controller?.signal.throwIfAborted(),Og.parse(e)}async createBrowserController({context:e}){let t=await fo.fromExistingContext({context:e,logger:this.logger,userBrowserSettings:{visualActions:!0},storage:this.fixtures.storage,flagStore:Ss,enricher:this.fixtures.browserEnricher});await t.getActivePage().waitForLoadState();let n=await t.getBrowserState({skipWait:!0,skipWaitForPageLoad:!0});return new Yi({browser:t,generator:this.fixtures.browserGenerator,logger:this.logger,flagStore:Ss,orgId:this.orgId,storage:this.fixtures.storage,localCodeEvalTools:this.fixtures.localCodeEvalTools,visualDiffScreenshotStorage:new wi})}async connectPlaywrightToWebview(e,t){let o=this.device.webViews().find(a=>!(a.pkg()!==e||"_socketName"in a&&typeof a._socketName=="string"&&a._socketName!==t));if(!o){this.logger.warn(`[WebviewManager] Could not find webview for ${e} with socket name ${t}`);return}return this.logger.info({packageName:e,socketName:t},"Connecting Playwright to app webview"),await this.createBrowserController({context:(await o.page()).context()})}async connectPlaywrightToChrome(e){let{contextId:t,socketName:n}=e;try{this.logger.info({contextId:t,socketName:n},"Connecting to Chrome webview");let o;e.forwardedPort?(o=`http://127.0.0.1:${e.forwardedPort}`,this.logger.info({contextId:t,endpoint:o,socketName:n},"Using existing forwarded port")):(o=`http://127.0.0.1:${await this.forwardDevToolsSocket(e.socketName)}`,this.logger.info({contextId:t,endpoint:o,socketName:n},`Forwarding DevTools socket for ${e.socketName}`)),this.logger.info({endpoint:o,socketName:n},"Connecting to CDP endpoint");let i=await vC.connectOverCDP({endpointURL:o,timeout:12e3}),a=i.contexts()[0];if(!a){this.logger.warn("No browser context available after CDP connection"),await i.close();return}return this.createBrowserController({context:a})}catch(o){this.logger.error({err:o},`Error connecting Playwright to webview ${e.contextId}:`);return}}async disconnectPlaywrightFromWebview(e){try{e.browserController&&(this.logger.info(`Disconnecting Playwright from webview: ${e.contextId}`),await e.browserController.browser.cleanup(),e.browserController=void 0),e.forwardedPort&&await this.removeDevToolsForwarding(e.forwardedPort)}catch(t){this.logger.error({err:t},`Error disconnecting Playwright from webview ${e.contextId}:`)}}async forwardDevToolsSocket(e){this.logger.info("Setting up port forwarding for DevTools");let t=1e4+Math.floor(Math.random()*1e4),n=this.driver.capabilities,o=n.deviceUDID||n.udid||n["appium:udid"]||n["appium:deviceUDID"];this.logger.info({deviceId:o},`Device ID from capabilities: ${o||"not found"}`);let i=process.env.ANDROID_HOME?`${process.env.ANDROID_HOME}/platform-tools/adb`:"adb",a=o?`-s ${o}`:"",s=`${i} ${a} forward tcp:${t} ${`localabstract:${e}`||"localabstract:chrome_devtools_remote"}`;this.logger.info(`Executing command: ${s}`);let l=Ng(s,{encoding:"utf8"});return this.logger.info(`ADB command result: ${l}`),this.logger.info(`Successfully forwarded DevTools socket to port ${t}`),t}async removeDevToolsForwarding(e){await this.executeRawADBCommand(`forward --remove tcp:${e}`),this.logger.info("Successfully removed port forwarding")}throwIfAborted(){this.aborter.controller?.signal.throwIfAborted()}};var Cr=class r{stateManager;generator;driver;logger;fixtures;options;aborter;appsWithGrantedPermissions=new Set;orgId;adbPort;constructor({driver:e,generator:t,stateManager:n,logger:o,fixtures:i,options:a,aborter:s,orgId:l,adbPort:c}){this.driver=e,this.generator=t,this.stateManager=n,this.logger=o,this.fixtures=i,this.options=a??{},this.aborter=s,this.orgId=l,this.adbPort=c}static async init({driver:e,generator:t,logger:n,fixtures:o,orgId:i,options:a,abortController:s,adbPort:l}){let c={controller:s},d=await Xi.init({driver:e,logger:n,fixtures:o,orgId:i,options:a?.emulator??{},aborter:c,adbPort:l}),m=new r({driver:e,generator:t,stateManager:d,logger:n,fixtures:o,options:a,aborter:c,orgId:i,adbPort:l});return await m.initializeSettings(),m}async installApp(e){await this.stateManager.executeRawADBCommand(`install ${yl.resolve(e)}`)}throwIfAborted(){this.aborter.controller?.signal.throwIfAborted()}async wrapTargetingAction(e){let{action:t,description:n,command:o,cacheKey:i="cache",targetName:a="target",cacheIsInvalidAfterResolution:s,tracer:l}=e,c=i in o&&o[i]&&a in o[i]?o[i][a]:void 0,d=e.retriesWithAI??1,m=!1,u=IC(c),g;if(o.disableCache&&(this.logger.debug({command:o},"Cache explicitly disabled for command"),m=!0,u=void 0),s&&(m=!0,u=void 0),u&&u?.resolvedDescription!==n&&(this.logger.info({cache:u,description:n},"Cache description mismatch, clearing it automatically"),m=!0,u=void 0),!u){d--,this.logger.info({description:n,cacheBustedBeforeAction:m},"Prompting AI for a new element location");let h=await this.findElement({description:n,tracer:l});return g=h.thoughts,Us({command:o,cacheKey:i,targetName:a,updatedCache:h.resolvedTarget}),{result:await t(h.resolvedTarget),thoughts:g}}let p;try{await this.stateManager.waitForScreenshotStability({timeoutMs:5e3,signal:this.aborter.controller?.signal,tracer:l,reason:"Waiting for stability before cache resolution"}),p=await l.startAsyncSpan("GET_EMULATOR_STATE",async()=>{try{await this.stateManager.refreshWebviews()}catch(y){this.logger.warn({err:y},"Failed to refresh webviews, continuing...")}return await this.stateManager.getDomState()});let h=await l.startAsyncSpan("RESOLVE_TARGET_CACHE",async y=>{let{resolvedTarget:w}=await Ju({target:u,domState:p,stateManager:this.stateManager,logger:this.logger}),A=Qu(w),I=AC(u,A);return this.logger.info({cacheDiffs:I},"Successfully resolved and updated target"),Us({command:o,cacheKey:i,targetName:a,updatedCache:A}),y.result={serializedElement:w.type==="WEBVIEW"?w.browserCache?.nodeOnlySerializedHtml??"Unknown HTML element in webview":w.elementOnlySerializedXml},w});return{result:await t(h),thoughts:"Successfully executed preset action with cache."}}catch(h){if(this.throwIfAborted(),d>0)return this.logger.warn({err:h,domState:p?.graph.originalXml},"Failed to resolve target cache, retrying with AI"),this.wrapTargetingAction({...e,cacheIsInvalidAfterResolution:!0,retriesWithAI:d-1});throw new Error(`ActionFailureError: ${h instanceof Error?h.message:h}`,{cause:h})}}async findElement({description:e,tracer:t}){await this.stateManager.waitForScreenshotStability({timeoutMs:5e3,signal:this.aborter.controller?.signal,tracer:t,reason:"Waiting for stability before locating an element with AI"});let n=await t.startAsyncSpan("GET_EMULATOR_STATE",async()=>{try{await this.stateManager.refreshWebviews()}catch(m){this.throwIfAborted(),this.logger.warn({err:m},"Failed to refresh webviews, continuing...")}let c=await this.stateManager.getDomState(),d=await this.stateManager.getCurrentScreenshot();return{emulatorState:c,screenshot:d}}),o=Date.now(),i;try{i=await this.generator.getElementLocation({description:e,screenXml:n.emulatorState.graph.xml,screenshot:n.screenshot},{logger:this.logger,loggerTags:ce(this.logger),abortSignal:this.aborter.controller?.signal})}catch(c){throw this.throwIfAborted(),this.logger.error({err:c},"Failed to locate element"),new Error(`ActionFailureError: Failed to locate element: ${c instanceof Error?c.message:c}`)}if(i.id===-1)throw t.addSpan({type:"AI_LOCATOR_CALL",startTime:o,endTime:Date.now(),error:`No matching element found: ${i.thoughts}`}),new Error(`ActionFailureError: No matching element found: ${i.thoughts}`);let a=Xu({aiResponse:i,description:e,emulatorState:n.emulatorState});if(a.type==="NATIVE")return t.addSpan({type:"AI_LOCATOR_CALL",startTime:o,endTime:Date.now(),result:{serializedElement:a.elementOnlySerializedXml,id:i.id,thoughts:i.thoughts}}),{resolvedTarget:a,thoughts:i.thoughts};let s=await this.stateManager.getActiveWebview();if(!s||!s.browserController)throw new Error("No browser controller is attached to the requested webview");let l=await s.browserController.locateElement({description:e,disableCache:!1,logger:this.logger});return t.addSpan({type:"AI_LOCATOR_CALL",startTime:o,endTime:Date.now(),result:{serializedElement:l.target.nodeOnlySerializedHtml??"Unknown HTML element in webview",id:l.target.id,thoughts:l.thoughts}}),{resolvedTarget:{...a,controller:s.browserController,resolution:l.resolution,browserCache:l.target},thoughts:l.thoughts}}async executeCommand(e){let t=["type","a11yData","thoughts","cache","code"],n;try{n=await Ei({obj:e.command,context:this.fixtures.testContext,bannedKeys:t,orgId:this.orgId,logger:this.logger,signal:this.aborter.controller?.signal,localTools:this.fixtures.localCodeEvalTools})}catch(o){throw this.throwIfAborted(),new Error(`ActionFailureError: Failed to substitute template strings in command: ${o.message}`,{cause:o})}try{return await this.executeCommandHelper(e)}catch(o){throw o.name!=="AbortError"&&this.logger.error({err:o},"Error thrown in action controller"),o}finally{Ci(e.command,n)}}async executeCommandHelper({command:e,tracer:t}){switch(e.type){case"STATE":{await this.stateManager.refreshWebviews();let n=await this.stateManager.getDomState(),o=await this.stateManager.getContexts();return{output:{xml:n.graph.xml,contexts:o},success:!0}}case"TAP":{if(e.target.type==="coordinates"){let o=await this.driver.getWindowSize(),i=e.target.xPercent*o.width,a=e.target.yPercent*o.height;return await this.driver.tap({x:i,y:a}),{success:!0,message:`Tapped at ${i}, ${a}`}}let{thoughts:n}=await this.wrapTargetingAction({command:e,tracer:t,action:async o=>{if("type"in o&&o.type==="NATIVE"){let[i,a,s,l]=o.bounds,c=i+(s-i)/2,d=a+(l-a)/2;await this.driver.tap({x:c,y:d})}else{let{controller:i}=o;await i.browser.click(o.resolution,{createIsolatedFolder:()=>{let a=Math.random().toString(36).substring(4),s=yl.join(PC(),"momentic","downloads"),l=yl.join(s,this.orgId,a);return xC(l,{recursive:!0}),l}})}},description:e.target.description});return{success:!0,message:n}}case"TYPE":{let n;if(e.target){let o={type:"TAP",id:RC(),target:e.target,cache:e.cache};try{n=(await this.executeCommand({command:o,tracer:t})).message}finally{e.cache=o.cache}}return await this.stateManager.waitForScreenshotStability({timeoutMs:2e3,signal:this.aborter.controller?.signal,tracer:t,reason:"Waiting for keyboard to appear before typing"}),await this.driver.keys(e.text),{success:!0,message:n}}case"AI_CHECK":{let o=e.timeoutSecs?e.timeoutSecs*1e3:5e3,i=Ii(o),a=Date.now(),s=0,l,c=!1,d;for(;s<15&&!c;){this.throwIfAborted(),c||l&&l-a>=o&&(c=!0),s!==0&&await j(i,this.aborter.controller?.signal),l=Date.now();try{let m="",u="";await t.startAsyncSpan("GET_EMULATOR_STATE",async()=>{m=(await this.stateManager.getDomState({includeWebviews:!0})).graph.xml,u=await this.stateManager.getCurrentScreenshot()});let g=await t.startAsyncSpan("AI_ASSERTION_CALL",async p=>{let h=await this.generator.evaluateAssertion({assertion:e.assertion,screenXml:m,screenshot:u},{logger:this.logger,loggerTags:ce(this.logger)});return p.result={thoughts:h.thoughts,result:h.result},h});if(g.result)return{success:g.result,message:g.thoughts};{let p=`AssertionFailureError: ${g.thoughts}`;d=new Error(p)}}catch(m){this.throwIfAborted(),this.logger.info({err:m},`AI check assert attempt ${s} failed, retrying...`),d=m instanceof Error?m:new Error(`${m}`)}finally{s++}}return{success:!1,message:d?.message}}case"JAVASCRIPT":{let n=await Ln({orgId:this.orgId,code:e.code,fragment:!1,context:this.fixtures.testContext,timeoutMs:e.timeout?e.timeout*1e3:void 0,logger:this.logger,localTools:this.fixtures.localCodeEvalTools,signal:this.aborter.controller?.signal});try{JSON.stringify(n)}catch(o){throw new Error(`UserConfigurationError: Return value is not serializable: ${o instanceof Error?o.message:`${o}`}`)}return{success:!0,output:n}}case"OPEN_NOTIFICATION_DRAWER":return await this.driver.pressKeyCode(83),{success:!0};case"OPEN_APP":{let n=e.activityName;if(!n){let o=await F(this.driver.execute("mobile: shell",{command:"pm",args:["resolve-activity","--brief","-a","android.intent.action.MAIN","-c","android.intent.category.LAUNCHER",e.packageName]}),{signal:this.aborter.controller?.signal,milliseconds:1e4});if(typeof o!="string")throw new Error(`Could not find main activity name for package ${e.packageName}. Unexpected output: ${o}`);if(o.includes("No activity found"))throw new Error(`No activity found for package ${e.packageName}.`);if(n=o.trim().split(`
|
|
3887
|
-
`).pop()?.replace(/^.*\//,""),!n)throw new Error(`Could not parse main activity name for package ${e.packageName}. Raw output: ${o}`)}if(this.options.emulator?.autoGrantPermissions&&!this.appsWithGrantedPermissions.has(e.packageName)){let o={permissions:"all",appPackage:e.packageName,action:"grant"};await this.driver.executeScript("mobile: changePermissions",[o]),this.appsWithGrantedPermissions.add(e.packageName)}return await this.driver.execute("mobile: shell",{command:"am",args:["start","-n",`${e.packageName}/${n}`]}),await this.stateManager.waitForScreenshotStability({timeoutMs:5e3,signal:this.aborter.controller?.signal,tracer:t,reason:"Waiting for stability after app launch"}),{success:!0}}case"PRESS":switch(e.key){case"HOME":await this.driver.pressKeyCode(3);break;case"BACK":await this.driver.pressKeyCode(4);break;case"APP_SWITCHER":await this.driver.pressKeyCode(187);break;case"POWER":await this.driver.pressKeyCode(26);break;case"SEARCH":await this.driver.pressKeyCode(84);break;case"VOLUME_UP":await this.driver.pressKeyCode(24);break;case"VOLUME_DOWN":await this.driver.pressKeyCode(25);break;case"VOLUME_MUTE":await this.driver.pressKeyCode(164);break}return{success:!0};case"ADB":return{success:!0,output:await this.driver.execute("mobile: shell",{command:e.command}),message:"ADB command executed successfully"};default:{let n=e;return{success:!0}}}}async initializeSettings(){}async screenshot(){return this.driver.takeScreenshot()}resetAbortController(e){this.aborter.controller=e??new AbortController}isAborted(){return this.aborter.controller?.signal.aborted}abort(){this.aborter.controller?.abort()}get context(){return this.fixtures.testContext}};async function kg({socket:r,logger:e,androidDriverFactory:t,getOrgId:n,mobileGeneratorFactory:o,browserGeneratorFactory:i,browserEnricherFactory:a,storageFactory:s,localToolsFactory:l,globalStateManager:c}){let d=r.id,m=r.handshake.query?.testMetadata,u=r.handshake.query?.fileName,g=u?.endsWith($e.TEST)?u.slice(0,-$e.TEST.length):u,p=Pn.parse(JSON.parse(m??"")),h=p.id,f=p.settings.defaultChannel,y=p.settings.defaultTag,w=await n({testId:h});e=e.child({sessionId:d,testId:h,orgId:w});let A=Date.now(),{driver:I,cleanup:C,limbarToken:R,limbarUrl:_,emulatorName:V,adbTunnelPort:Q}=await t({socket:r,logger:e,creationOpts:{apkToInstall:f?{channel:f,tag:y}:void 0}});e.info({adbTunnelPort:Q,apkChannel:f,apkTag:y,duration:Date.now()-A,emulatorName:V},"Android emulator session initiated"),e=e.child({emulator:V});let ee=await o(w,e),Re=await i(w,e),D=await a(w,e),q=await s(w),M=l?await l(w):void 0,te={};p.settings.defaultEnv&&(te=(await q.fetchEnvironment(p.settings.defaultEnv,e))?.variables??{});let Ee=new cr({variablesFromEnvironment:te,envName:p.settings.defaultEnv,testName:g}),Xe=await Cr.init({driver:I,generator:ee,logger:e,options:{emulator:p.settings.emulator},fixtures:{storage:q,browserEnricher:D,browserGenerator:Re,localCodeEvalTools:M,testContext:Ee},orgId:w,adbPort:Q,abortController:new AbortController});if(!r.connected)throw await C(),new Error("Socket not connected anymore, not proceeding with Android session setup");let ze=MC({socket:r,controller:Xe,testContext:Ee}),pt=async()=>{try{await C()}catch(Je){e.warn({err:Je},"Failed to clean up emulator in socket server")}clearInterval(ze)};return c.registerSession(d,{controller:Xe,cleanup:pt,emulatorName:V}),r.emit("session",{testId:h,sessionId:d,limbarUrl:_,limbarToken:R}),{sessionId:d,testId:h,orgId:w,emulatorName:V,logger:e}}function MC({socket:r,controller:e,testContext:t}){return setInterval(()=>{let n=t.toEditorDisplayCopy();r.emit("emulatorState",{context:n})},3e3)}async function _g({socket:r,logger:e,globalStateManager:t}){await t.removeSession(r.id,e)}var LC=({socket:r,globalStateManager:e})=>async()=>{let t=e.getSession(r.id);if(!t)throw new Error("No active Android session found");t.controller.abort()},Dg={event:"cancel",createHandler:LC};import{diff as OC}from"deep-object-diff";import{cloneDeep as NC}from"lodash-es";import{randomUUID as Fg}from"crypto";async function Ug({fixtures:r,step:e,inputs:t,stepTracer:n}){let o=Date.now(),{logger:i,controller:a}=r,s;if(!t.interactive)try{s=await a.screenshot()}catch(c){i.warn({err:c},"Failed to take before screenshot")}let l=n.startCommand();try{let c=await a.executeCommand({command:e.command,tracer:l}),d=l.getRootSpan(),m={...e,status:c.success?"SUCCESS":"FAILED",message:c.message,trace:d,startTime:o,endTime:Date.now(),data:c.output};if(!t.interactive){try{let u=Fg(),g=await a.screenshot();m.afterSnapshot=u,n.attachAfterScreenshot({logger:i,snapshotId:u,screenshot:Buffer.from(g,"base64")})}catch(u){i.warn({err:u},"Failed to take after screenshot")}if(s){let u=Fg();m.beforeSnapshot=u,n.attachBeforeScreenshot({logger:i,snapshotId:u,screenshot:Buffer.from(s,"base64")})}}return m}catch(c){let d=l.getRootSpan();return c instanceof Error&&c.name==="AbortError"||a.isAborted()?{...e,status:"CANCELLED",message:"Step cancelled.",startTime:o,endTime:Date.now(),trace:d}:(i.error({err:c},"Failed to execute preset step"),{...e,status:"FAILED",message:c instanceof Error?c.message:String(c),startTime:o,endTime:Date.now(),trace:d})}}async function zg({fixtures:r,inputs:e,tracer:t}){let{logger:n,controller:o}=r,i=[],a,s="SUCCESS",{fromStep:l,toStep:c}=e,d=!!l;for(let m=0;m<e.steps.length;m++){let u=e.steps[m];if(n.info({step:u},`Executing step ${m+1} of ${e.steps.length}: ${bu(u.command)}`),d&&l){let h=JSON.stringify([]);if(u.id===l.fromStepId&&h===JSON.stringify(l.parentStepIdChain))d=!1;else continue}let g=await t.startStep({logger:n,step:u,attempt:1,parentStepIdChain:[]}),p=await Ug({fixtures:r,inputs:e,step:u,stepTracer:g});if(i.push(p),await g.finish({parentStepIdChain:[],result:p}),p.status!=="SUCCESS"){s=p.status,a=p;break}if(u.envKey&&o.context.setVariable(u.envKey,p.data),c){let h=JSON.stringify([]);if(u.id===c.toStepId&&h===JSON.stringify(c.parentStepIdChain))break}}return{results:i,status:s,terminalResult:a}}async function Qi(r){let{tracer:e}=r,{steps:t,testMetadata:n}=r.inputs,{cacheStorage:o,logger:i}=r.fixtures;await o.resolveEntries({logger:i,testId:n.id,stepLists:{steps:t}});let a=NC(t);i.info({testMetadata:Pn.parse(n)},"Starting mobile test execution");let s=await e.startMainStepList(),{status:l,results:c}=await zg({...r,tracer:s});if(l==="SUCCESS"){let d=OC(a,t);if(d&&Object.keys(d).length>0){i.debug({diffs:d},"Saving mobile step cache entries post-success");let{cachesToSave:m}=await fr({steps:t,cacheCreationParams:{orgId:r.inputs.orgId,testId:n.id}});await o.saveEntries({logger:i,testId:n.id,entries:m})}return{status:"PASSED",results:c}}return l==="CANCELLED"?{status:"CANCELLED",results:c}:{status:"FAILED",results:c}}var wl=class{parentTracer=null;socket;step;commandTracer;constructor({step:e,socket:t,parentTracer:n}){this.socket=t,this.parentTracer=n,this.step=e,this.commandTracer=new hr("command")}attachBeforeScreenshot(){}attachAfterScreenshot(){}attachBeforeHtmlSnapshot(){}attachAfterHtmlSnapshot(){}async finish(e){switch(e.result.status){case"SUCCESS":this.socket.emit("success",e);break;case"FAILED":this.socket.emit("failure",e);break;case"CANCELLED":this.socket.emit("cancelled",e);break}return this.commandTracer.finish(),{trace:this.commandTracer.getRootSpan()}}startCommand(){return this.commandTracer}async startSubSteps(){return new vr({parentStep:this.step,socket:this.socket,parentTracer:this})}},vr=class{parentTracer;parentStep;socket;constructor({parentStep:e,socket:t,parentTracer:n}){this.parentTracer=n,this.parentStep=e,this.socket=t}async startStep(e){return this.socket.emit("started",{stepId:e.step.id,parentStepIdChain:e.parentStepIdChain,attempt:e.attempt}),new wl({step:e.step,parentTracer:this,socket:this.socket})}},Zi=class{constructor(e){this.socket=e}attachConsoleLogs(){}attachNetworkLogs(){}async finish(){this.socket.emit("finished")}async startBeforeStepList(){return new vr({parentStep:null,parentTracer:null,socket:this.socket})}async startMainStepList(){return new vr({parentStep:null,parentTracer:null,socket:this.socket})}async startAfterStepList(){return new vr({parentStep:null,parentTracer:null,socket:this.socket})}};var kC=({metadata:r,logger:e,globalStateManager:t,socket:n,cacheStorageFactory:o})=>async(i,a)=>{let s=t.getSession(n.id);if(!s)throw new Error("No active Android session found");let l=await o(r.orgId),c=s.controller;c.resetAbortController();let d=new Zi(n),m=await Qi({fixtures:{controller:c,logger:e,cacheStorage:l},inputs:{steps:i.steps,fromStep:i.fromStep,toStep:i.toStep,orgId:r.orgId,testMetadata:i.testMetadata,interactive:!0},tracer:d});await d.finish(),a?.({results:m.results,status:m.status})},Bg={event:"execute",createHandler:kC};import{debounce as _C}from"ts-debounce";var DC=({socket:r,globalStateManager:e,keepSessionAlive:t})=>{let n=_C(t,3e4,{maxWait:6e4});return async()=>{let o=e.getSession(r.id);if(!o)throw new Error("No active Android session found");o.emulatorName&&n(o.emulatorName)}},Hg={event:"keepalive",createHandler:DC};var Gg=[Bg,Dg,Hg];function $g(r){let{logger:e,globalStateManager:t}=r,n=new FC(r.baseServer,{cors:{origin:"*",methods:["GET","POST"]},pingTimeout:15*60*1e3,pingInterval:15*60*1e3,maxHttpBufferSize:1e7,perMessageDeflate:!0}),o=async()=>{await t.removeAllSessions(e)};n.on("connection",async a=>{e.info({event:"connection",transport:a.conn.transport.name},"Android websocket connection initiated"),a.on("disconnect",async()=>{await _g({...r,socket:a,globalStateManager:t})});let s;try{s=await kg({...r,socket:a,globalStateManager:t})}catch(l){e.error({err:l},"Failed to setup Android connection"),a.emit("error",{message:l instanceof Error?l.message:JSON.stringify(l)}),a.disconnect(!0);return}Gg.forEach(l=>i(l,{...r,socket:a,metadata:s,logger:e}))});let i=(a,s)=>{let l=a.createHandler(s),c=(...d)=>{s.logger.debug({...s.metadata,event:a.event,args:d},`Websocket event (${a.event})`);let m=u=>{s.logger.error({event:a.event,type:"websocket",args:d,err:u instanceof Error?u:new Error(`${u}`)},"Unhandled exception in socket handler"),s.socket.emit("error",{message:u instanceof Error?u.message:`${u}`})};try{let u=l.apply(this,d);u&&typeof u.catch=="function"&&u.catch(m)}catch(u){m(u)}};s.socket.on(a.event,c)};return{server:n,dispose:o}}import{cloneDeep as UC}from"lodash-es";import zC from"truncate-json";var jg=1e3,Wg=3e6;function BC(r,e){for(let t=0;t<r.length;t++){let n=r[t];try{if(n.data){let{jsonString:o}=zC(JSON.stringify(n.data),1e3);n.data=JSON.parse(o)}}catch(o){e.error({err:o},"Failed to serialize individual result output data"),n.data=`Result output data could not be serialized: ${o}`}switch(n.type){case"MOBILE_PRESET_STEP":HC(n);break;default:return(()=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})()}}}function HC(r){let e=r.command;"cache"in e&&e.cache&&(e.cache=void 0)}function Vg(r,e){let t=JSON.stringify(r),n=t.replaceAll("\\u0000","");if(t.length!==n.length){let o=t.indexOf("\\u0000");e.warn({input:t.slice(Math.max(0,o-500),Math.min(t.length,o+500))},"Database content violation: stripped unicode character from input")}return n}function ea(r,e){let t=UC(r);if(BC(t,e),t.length>jg)return e.error("Database content violation: results too long, truncating before insertion"),t.slice(0,jg);let n=Vg(t,e);if(n.length>Wg)for(e.error({serializedLength:n.length,resultsArrayLength:t.length},"Database content violation: results too large, truncating before insertion");n.length>Wg;)t.pop(),n=Vg(t,e);try{return mr.array().parse(JSON.parse(n))}catch(o){throw e.error({serialized:n,err:o},"Could not parse serialized results into JSON structure after processing"),o}}var yo=!!process.env.CI||!!process.env.GITHUB_ACTIONS||!!process.env.GITHUB_RUN_ID||!!process.env.GITLAB_CI||!!process.env.CI_COMMIT_SHA||!!process.env.JENKINS_URL||!!process.env.BUILD_NUMBER||!!process.env.JENKINS_HOME||!!process.env.CIRCLECI||!!process.env.CIRCLE_BUILD_NUM||!!process.env.TRAVIS||!!process.env.TRAVIS_BUILD_NUMBER||!!process.env.BITBUCKET_BUILD_NUMBER||!!process.env.BITBUCKET_COMMIT||!!process.env.CODEBUILD_BUILD_ID||!!process.env.TF_BUILD||!!process.env.BUILD_BUILDID||!!process.env.AGENT_ID||!!process.env.BUILDER_SA_EMAIL||!!process.env.HEROKU_TEST_RUN_ID||!!process.env.CI_NODE_INDEX||!!process.env.TEAMCITY_VERSION||!!process.env.BUILD_VCS_NUMBER||!!process.env.BUILDKITE||!!process.env.BUILDKITE_BUILD_NUMBER||!!process.env.APPVEYOR||!!process.env.APPVEYOR_BUILD_NUMBER||!!process.env.DRONE||!!process.env.DRONE_BUILD_NUMBER||!!process.env.SHIPPABLE||!!process.env.BUILD_URL||!!process.env.SEMAPHORE||!!process.env.SEMAPHORE_EXECUTABLE_UUID||!!process.env.WOODPECKER||!!process.env.CI_BUILD_NUMBER||!!process.env.WERCKER_MAIN_PIPELINE_STARTED||!!process.env.BUDDY_EXECUTION_ID;import{confirm as XC,input as JC}from"@inquirer/prompts";import{existsSync as qJ,mkdirSync as KJ,statSync as QC}from"fs";import Rr,{supportsColor as YC}from"chalk";import{Console as qg}from"console";import{format as wo}from"util";var Tl=class extends Error{constructor(e,t,n){let o=Error.stackTraceLimit;n&&(Error.stackTraceLimit=Math.max(n,o||10)),super(e),Error.captureStackTrace&&Error.captureStackTrace(this,t),Error.stackTraceLimit=o}},ta=class r extends qg{_buffer=[];_groupDepth=0;Console=qg;constructor(){super({write:e=>(r.write(this._buffer,"log",e),!0)})}static write(e,t,n,o=2){let i=new Tl(void 0,r.write).stack;if(!i)return e;let a=i.split(`
|
|
3887
|
+
`).pop()?.replace(/^.*\//,""),!n)throw new Error(`Could not parse main activity name for package ${e.packageName}. Raw output: ${o}`)}if(this.options.emulator?.autoGrantPermissions&&!this.appsWithGrantedPermissions.has(e.packageName)){let o={permissions:"all",appPackage:e.packageName,action:"grant"};await this.driver.executeScript("mobile: changePermissions",[o]),this.appsWithGrantedPermissions.add(e.packageName)}return await this.driver.execute("mobile: shell",{command:"am",args:["start","-n",`${e.packageName}/${n}`]}),await this.stateManager.waitForScreenshotStability({timeoutMs:5e3,signal:this.aborter.controller?.signal,tracer:t,reason:"Waiting for stability after app launch"}),{success:!0}}case"PRESS":switch(e.key){case"HOME":await this.driver.pressKeyCode(3);break;case"BACK":await this.driver.pressKeyCode(4);break;case"APP_SWITCHER":await this.driver.pressKeyCode(187);break;case"POWER":await this.driver.pressKeyCode(26);break;case"SEARCH":await this.driver.pressKeyCode(84);break;case"VOLUME_UP":await this.driver.pressKeyCode(24);break;case"VOLUME_DOWN":await this.driver.pressKeyCode(25);break;case"VOLUME_MUTE":await this.driver.pressKeyCode(164);break}return{success:!0};case"ADB":return{success:!0,output:await this.driver.execute("mobile: shell",{command:e.command}),message:"ADB command executed successfully"};default:{let n=e;return{success:!0}}}}async initializeSettings(){}async screenshot(){return this.driver.takeScreenshot()}resetAbortController(e){this.aborter.controller=e??new AbortController}isAborted(){return this.aborter.controller?.signal.aborted}abort(){this.aborter.controller?.abort()}get context(){return this.fixtures.testContext}};async function kg({socket:r,logger:e,androidDriverFactory:t,getOrgId:n,mobileGeneratorFactory:o,browserGeneratorFactory:i,browserEnricherFactory:a,storageFactory:s,localToolsFactory:l,globalStateManager:c}){let d=r.id,m=r.handshake.query?.testMetadata,u=r.handshake.query?.fileName,g=u?.endsWith($e.TEST)?u.slice(0,-$e.TEST.length):u,p=Pn.parse(JSON.parse(m??"")),h=p.id,f=p.settings.defaultChannel,y=p.settings.defaultTag,w=await n({testId:h});e=e.child({sessionId:d,testId:h,orgId:w});let A=Date.now(),{driver:I,cleanup:C,limbarToken:R,limbarUrl:_,emulatorName:V,adbTunnelPort:Q}=await t({socket:r,logger:e,creationOpts:{apkToInstall:f?{channel:f,tag:y}:void 0}});e.info({adbTunnelPort:Q,apkChannel:f,apkTag:y,duration:Date.now()-A,emulatorName:V},"Android emulator session initiated"),e=e.child({emulator:V});let ee=await o(w,e),Re=await i(w,e),D=await a(w,e),q=await s(w),M=l?await l(w):void 0,te={};p.settings.defaultEnv&&(te=(await q.fetchEnvironment(p.settings.defaultEnv,e))?.variables??{});let Ee=new cr({variablesFromEnvironment:te,envName:p.settings.defaultEnv,testName:g}),Xe=await Cr.init({driver:I,generator:ee,logger:e,options:{emulator:p.settings.emulator},fixtures:{storage:q,browserEnricher:D,browserGenerator:Re,localCodeEvalTools:M,testContext:Ee},orgId:w,adbPort:Q,abortController:new AbortController});if(!r.connected)throw await C(),new Error("Socket not connected anymore, not proceeding with Android session setup");let ze=MC({socket:r,controller:Xe,testContext:Ee}),pt=async()=>{try{await C()}catch(Je){e.warn({err:Je},"Failed to clean up emulator in socket server")}clearInterval(ze)};return c.registerSession(d,{controller:Xe,cleanup:pt,emulatorName:V}),r.emit("session",{testId:h,sessionId:d,limbarUrl:_,limbarToken:R}),{sessionId:d,testId:h,orgId:w,emulatorName:V,logger:e}}function MC({socket:r,controller:e,testContext:t}){return setInterval(()=>{let n=t.toEditorDisplayCopy();r.emit("emulatorState",{context:n})},3e3)}async function _g({socket:r,logger:e,globalStateManager:t}){await t.removeSession(r.id,e)}var LC=({socket:r,globalStateManager:e})=>async()=>{let t=e.getSession(r.id);if(!t)throw new Error("No active Android session found");t.controller.abort()},Dg={event:"cancel",createHandler:LC};import{diff as OC}from"deep-object-diff";import{cloneDeep as NC}from"lodash-es";import{randomUUID as Fg}from"crypto";async function Ug({fixtures:r,step:e,inputs:t,stepTracer:n}){let o=Date.now(),{logger:i,controller:a}=r,s;if(!t.interactive)try{s=await a.screenshot()}catch(c){i.warn({err:c},"Failed to take before screenshot")}let l=n.startCommand();try{let c=await a.executeCommand({command:e.command,tracer:l}),d=l.getRootSpan(),m={...e,status:c.success?"SUCCESS":"FAILED",message:c.message,trace:d,startTime:o,endTime:Date.now(),data:c.output};if(!t.interactive){try{let u=Fg(),g=await a.screenshot();m.afterSnapshot=u,n.attachAfterScreenshot({logger:i,snapshotId:u,screenshot:Buffer.from(g,"base64")})}catch(u){i.warn({err:u},"Failed to take after screenshot")}if(s){let u=Fg();m.beforeSnapshot=u,n.attachBeforeScreenshot({logger:i,snapshotId:u,screenshot:Buffer.from(s,"base64")})}}return m}catch(c){let d=l.getRootSpan();return c instanceof Error&&c.name==="AbortError"||a.isAborted()?{...e,status:"CANCELLED",message:"Step cancelled.",startTime:o,endTime:Date.now(),trace:d}:(i.error({err:c},"Failed to execute preset step"),{...e,status:"FAILED",message:c instanceof Error?c.message:String(c),startTime:o,endTime:Date.now(),trace:d})}}async function zg({fixtures:r,inputs:e,tracer:t}){let{logger:n,controller:o}=r,i=[],a,s="SUCCESS",{fromStep:l,toStep:c}=e,d=!!l;for(let m=0;m<e.steps.length;m++){let u=e.steps[m];if(n.info({step:u},`Executing step ${m+1} of ${e.steps.length}: ${bu(u.command)}`),d&&l){let h=JSON.stringify([]);if(u.id===l.fromStepId&&h===JSON.stringify(l.parentStepIdChain))d=!1;else continue}let g=await t.startStep({logger:n,step:u,attempt:1,parentStepIdChain:[]}),p=await Ug({fixtures:r,inputs:e,step:u,stepTracer:g});if(i.push(p),await g.finish({parentStepIdChain:[],result:p}),p.status!=="SUCCESS"){s=p.status,a=p;break}if(u.envKey&&o.context.setVariable(u.envKey,p.data),c){let h=JSON.stringify([]);if(u.id===c.toStepId&&h===JSON.stringify(c.parentStepIdChain))break}}return{results:i,status:s,terminalResult:a}}async function Qi(r){let{tracer:e}=r,{steps:t,testMetadata:n}=r.inputs,{cacheStorage:o,logger:i}=r.fixtures;await o.resolveEntries({logger:i,testId:n.id,stepLists:{steps:t}});let a=NC(t);i.info({testMetadata:Pn.parse(n)},"Starting mobile test execution");let s=await e.startMainStepList(),{status:l,results:c}=await zg({...r,tracer:s});if(l==="SUCCESS"){let d=OC(a,t);if(d&&Object.keys(d).length>0){i.debug({diffs:d},"Saving mobile step cache entries post-success");let{cachesToSave:m}=await fr({steps:t,cacheCreationParams:{orgId:r.inputs.orgId,testId:n.id}});await o.saveEntries({logger:i,testId:n.id,entries:m})}return{status:"PASSED",results:c}}return l==="CANCELLED"?{status:"CANCELLED",results:c}:{status:"FAILED",results:c}}var wl=class{parentTracer=null;socket;step;commandTracer;constructor({step:e,socket:t,parentTracer:n}){this.socket=t,this.parentTracer=n,this.step=e,this.commandTracer=new hr("command")}attachBeforeScreenshot(){}attachAfterScreenshot(){}attachBeforeHtmlSnapshot(){}attachAfterHtmlSnapshot(){}async finish(e){switch(e.result.status){case"SUCCESS":this.socket.emit("success",e);break;case"FAILED":this.socket.emit("failure",e);break;case"CANCELLED":this.socket.emit("cancelled",e);break}return this.commandTracer.finish(),{trace:this.commandTracer.getRootSpan()}}startCommand(){return this.commandTracer}async startSubSteps(){return new vr({parentStep:this.step,socket:this.socket,parentTracer:this})}},vr=class{parentTracer;parentStep;socket;constructor({parentStep:e,socket:t,parentTracer:n}){this.parentTracer=n,this.parentStep=e,this.socket=t}async startStep(e){return this.socket.emit("started",{stepId:e.step.id,parentStepIdChain:e.parentStepIdChain,attempt:e.attempt}),new wl({step:e.step,parentTracer:this,socket:this.socket})}},Zi=class{constructor(e){this.socket=e}attachConsoleLogs(){}attachNetworkLogs(){}async finish(){this.socket.emit("finished")}async startBeforeStepList(){return new vr({parentStep:null,parentTracer:null,socket:this.socket})}async startMainStepList(){return new vr({parentStep:null,parentTracer:null,socket:this.socket})}async startAfterStepList(){return new vr({parentStep:null,parentTracer:null,socket:this.socket})}};var kC=({metadata:r,logger:e,globalStateManager:t,socket:n,cacheStorageFactory:o})=>async(i,a)=>{let s=t.getSession(n.id);if(!s)throw new Error("No active Android session found");let l=await o(r.orgId),c=s.controller;c.resetAbortController();let d=new Zi(n),m=await Qi({fixtures:{controller:c,logger:e,cacheStorage:l},inputs:{steps:i.steps,fromStep:i.fromStep,toStep:i.toStep,orgId:r.orgId,testMetadata:i.testMetadata,interactive:!0},tracer:d});await d.finish(),a?.({results:m.results,status:m.status})},Bg={event:"execute",createHandler:kC};import{debounce as _C}from"ts-debounce";var DC=({socket:r,globalStateManager:e,keepSessionAlive:t})=>{let n=_C(t,3e4,{maxWait:6e4});return async()=>{let o=e.getSession(r.id);if(!o)throw new Error("No active Android session found");o.emulatorName&&n(o.emulatorName)}},Hg={event:"keepalive",createHandler:DC};var Gg=[Bg,Dg,Hg];function $g(r){let{logger:e,globalStateManager:t}=r,n=new FC(r.baseServer,{cors:{origin:"*",methods:["GET","POST"]},pingTimeout:15*60*1e3,pingInterval:15*60*1e3,maxHttpBufferSize:1e7,perMessageDeflate:!0}),o=async()=>{await t.removeAllSessions(e)};n.on("connection",async a=>{e.info({event:"connection",transport:a.conn.transport.name},"Android websocket connection initiated"),a.on("disconnect",async()=>{await _g({...r,socket:a,globalStateManager:t})});let s;try{s=await kg({...r,socket:a,globalStateManager:t})}catch(l){e.error({err:l},"Failed to setup Android connection"),a.emit("error",{message:l instanceof Error?l.message:JSON.stringify(l)}),a.disconnect(!0);return}Gg.forEach(l=>i(l,{...r,socket:a,metadata:s,logger:e}))});let i=(a,s)=>{let l=a.createHandler(s),c=(...d)=>{s.logger.debug({event:a.event,args:d},`Websocket event (${a.event})`);let m=u=>{s.logger.error({event:a.event,args:d,err:u instanceof Error?u:new Error(`${u}`)},"Unhandled exception in socket handler"),s.socket.emit("error",{message:u instanceof Error?u.message:`${u}`})};try{let u=l.apply(this,d);u&&typeof u.catch=="function"&&u.catch(m)}catch(u){m(u)}};s.socket.on(a.event,c)};return{server:n,dispose:o}}import{cloneDeep as UC}from"lodash-es";import zC from"truncate-json";var jg=1e3,Wg=3e6;function BC(r,e){for(let t=0;t<r.length;t++){let n=r[t];try{if(n.data){let{jsonString:o}=zC(JSON.stringify(n.data),1e3);n.data=JSON.parse(o)}}catch(o){e.error({err:o},"Failed to serialize individual result output data"),n.data=`Result output data could not be serialized: ${o}`}switch(n.type){case"MOBILE_PRESET_STEP":HC(n);break;default:return(()=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})()}}}function HC(r){let e=r.command;"cache"in e&&e.cache&&(e.cache=void 0)}function Vg(r,e){let t=JSON.stringify(r),n=t.replaceAll("\\u0000","");if(t.length!==n.length){let o=t.indexOf("\\u0000");e.warn({input:t.slice(Math.max(0,o-500),Math.min(t.length,o+500))},"Database content violation: stripped unicode character from input")}return n}function ea(r,e){let t=UC(r);if(BC(t,e),t.length>jg)return e.error("Database content violation: results too long, truncating before insertion"),t.slice(0,jg);let n=Vg(t,e);if(n.length>Wg)for(e.error({serializedLength:n.length,resultsArrayLength:t.length},"Database content violation: results too large, truncating before insertion");n.length>Wg;)t.pop(),n=Vg(t,e);try{return mr.array().parse(JSON.parse(n))}catch(o){throw e.error({serialized:n,err:o},"Could not parse serialized results into JSON structure after processing"),o}}var yo=!!process.env.CI||!!process.env.GITHUB_ACTIONS||!!process.env.GITHUB_RUN_ID||!!process.env.GITLAB_CI||!!process.env.CI_COMMIT_SHA||!!process.env.JENKINS_URL||!!process.env.BUILD_NUMBER||!!process.env.JENKINS_HOME||!!process.env.CIRCLECI||!!process.env.CIRCLE_BUILD_NUM||!!process.env.TRAVIS||!!process.env.TRAVIS_BUILD_NUMBER||!!process.env.BITBUCKET_BUILD_NUMBER||!!process.env.BITBUCKET_COMMIT||!!process.env.CODEBUILD_BUILD_ID||!!process.env.TF_BUILD||!!process.env.BUILD_BUILDID||!!process.env.AGENT_ID||!!process.env.BUILDER_SA_EMAIL||!!process.env.HEROKU_TEST_RUN_ID||!!process.env.CI_NODE_INDEX||!!process.env.TEAMCITY_VERSION||!!process.env.BUILD_VCS_NUMBER||!!process.env.BUILDKITE||!!process.env.BUILDKITE_BUILD_NUMBER||!!process.env.APPVEYOR||!!process.env.APPVEYOR_BUILD_NUMBER||!!process.env.DRONE||!!process.env.DRONE_BUILD_NUMBER||!!process.env.SHIPPABLE||!!process.env.BUILD_URL||!!process.env.SEMAPHORE||!!process.env.SEMAPHORE_EXECUTABLE_UUID||!!process.env.WOODPECKER||!!process.env.CI_BUILD_NUMBER||!!process.env.WERCKER_MAIN_PIPELINE_STARTED||!!process.env.BUDDY_EXECUTION_ID;import{confirm as XC,input as JC}from"@inquirer/prompts";import{existsSync as qJ,mkdirSync as KJ,statSync as QC}from"fs";import Rr,{supportsColor as YC}from"chalk";import{Console as qg}from"console";import{format as wo}from"util";var Tl=class extends Error{constructor(e,t,n){let o=Error.stackTraceLimit;n&&(Error.stackTraceLimit=Math.max(n,o||10)),super(e),Error.captureStackTrace&&Error.captureStackTrace(this,t),Error.stackTraceLimit=o}},ta=class r extends qg{_buffer=[];_groupDepth=0;Console=qg;constructor(){super({write:e=>(r.write(this._buffer,"log",e),!0)})}static write(e,t,n,o=2){let i=new Tl(void 0,r.write).stack;if(!i)return e;let a=i.split(`
|
|
3888
3888
|
`).slice(o).filter(Boolean).join(`
|
|
3889
3889
|
`);return e.push({message:n,origin:a,type:t}),e}_log(e,t){r.write(this._buffer,e," ".repeat(this._groupDepth)+t,3)}debug(e,...t){this._log("debug",wo(e,...t))}error(e,...t){this._log("error",wo(e,...t))}info(e,...t){this._log("info",wo(e,...t))}log(e,...t){this._log("log",wo(e,...t))}warn(e,...t){this._log("warn",wo(e,...t))}getBuffer(){return this._buffer.length>0?this._buffer:void 0}};function Kg(r){let e=globalThis.console,t=new ta;globalThis.console=t;try{r()}finally{let o=t.getBuffer()?.map(i=>i.message).join(`
|
|
3890
3890
|
`);process.stderr.write(`${o}
|
|
3891
|
-
`),globalThis.console=e}}var El=" ".repeat(6);import GC from"os";import Xg,{multistream as $C}from"pino";import jC from"pino-pretty";var Yg=["lastScreenshotBuffer"];var To=new Map,WC=!0,Jg="Log throttle exceeded",VC=100,qC=5e3,Cl=class r{consoleLogger;hostname;bindingAttributes;disableConsoleLogs;minLevelValue=20;logsInCurrentWindow=0;droppedLogsInWindow=!1;lastWindowStart=Date.now();site="https://ingest.us.signoz.cloud:443/logs/json";flushIntervalMs;maxBatchSize;buffer=[];flushTimer;constructor({bindings:e,hostname:t,disableConsoleLogs:n,flushIntervalMs:o,maxBatchSize:i}){this.hostname=t??GC.hostname(),this.disableConsoleLogs=n,this.bindingAttributes={...e,env:"production"},this.flushIntervalMs=o??5e3,this.maxBatchSize=i??10;let a={base:this.bindingAttributes,errorKey:"err",level:"debug"};this.consoleLogger=WC?Xg(a):Xg(a,$C([{stream:jC({colorize:!0})}]))}getLevel(){return bi[this.minLevelValue]}child(e){return new r({bindings:{...this.bindingAttributes,...e},hostname:this.hostname,disableConsoleLogs:this.disableConsoleLogs,flushIntervalMs:this.flushIntervalMs,maxBatchSize:this.maxBatchSize})}flush(e){this.flushBuffer(),this.disableConsoleLogs||this.consoleLogger.flush(e)}scheduleFlush(){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flushTimer=void 0,this.flushBuffer()},this.flushIntervalMs))}async flushBuffer(){if(this.buffer.length===0)return;let e=this.buffer;this.buffer=[];try{let t=await fetch(this.site,{method:"POST",headers:{"Content-Type":"application/json","signoz-access-token":"CumAaTMUcwjt05OddAmefKgshbhfRmWxzxih"},body:JSON.stringify(e)});if(!t.ok)throw new Error(`Got error status (${t.statusText}) from SigNoz`)}catch(t){this.consoleLogger.warn({err:t},"Failed to log to SigNoz")}}shouldAllowLog(e){if(e===Jg)return!0;let t=Date.now();return t-this.lastWindowStart>qC&&(this.logsInCurrentWindow=0,this.droppedLogsInWindow&&this.log("error",void 0,Jg),this.droppedLogsInWindow=!1,this.lastWindowStart=t),this.logsInCurrentWindow<VC?(this.logsInCurrentWindow++,!0):(this.droppedLogsInWindow=!0,!1)}log(e,t,n,...o){if(lo[e]<this.minLevelValue||!this.shouldAllowLog(n))return;if(t&&n===void 0&&(n=`${t}`,t={}),this.disableConsoleLogs||this.consoleLogger[e](e==="error"?t:{testId:t.testId,runId:t.runId},n,...o),typeof t=="object"&&t&&"err"in t&&t.err instanceof Error){let l={};for(let c of Object.getOwnPropertyNames(t.err))Yg.includes(c)||(l[c]=t.err[c]);l.name=t.err.name,t.err=l}let i=Object.assign({},this.bindingAttributes,t&&typeof t=="object"?t:{});o.length>0&&(i.args=o);let a={host:this.hostname,app:this.bindingAttributes.app,env:this.bindingAttributes.env},s={timestamp:Math.round(Date.now()*1e6),severity_text:e.toUpperCase(),resources:a,attributes:{},body:JSON.stringify({...i,message:n||""})};this.buffer.push(s),this.buffer.length>=this.maxBatchSize?(this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=void 0),this.flushBuffer()):this.scheduleFlush()}setApp(e){let t=this.bindingAttributes.app;this.bindingAttributes.app=e,To.set("app",this),To.delete(t)}debug(e,t,...n){this.log("debug",e,t,...n)}info(e,t,...n){this.log("info",e,t,...n)}warn(e,t,...n){this.log("warn",e,t,...n)}error(e,t,...n){this.log("error",e,t,...n)}bindings(){return this.bindingAttributes}addBinding(e,t){this.bindingAttributes[e]=t}setMinLevel(e){typeof e=="number"?(this.minLevelValue=e,this.consoleLogger.level=bi[e]):(this.minLevelValue=lo[e],this.consoleLogger.level=e)}enableConsoleLogs(){this.disableConsoleLogs=!1}},na=({app:r,hostname:e,disableConsoleLogs:t})=>(To.has(r)||To.set(r,new Cl({bindings:{app:r},hostname:e,disableConsoleLogs:t})),To.get(r));import{hostname as KC}from"os";var ot=na({app:"cli",hostname:KC(),disableConsoleLogs:!0}).child({version:"0.0.8"});function vl(r){if(!r)return;r=r.toLowerCase();let e=Si.safeParse(r);if(e.success)return E.setMinLevel(e.data),e.data}function Qg({results:r,startTime:e,entity:t,getDisplayLine:n,onFailed:o}){let i=r.filter(d=>d.status==="PASSED"&&d.quarantined),a=r.filter(d=>d.status==="PASSED"&&!d.quarantined),s=r.filter(d=>d.status==="FAILED"&&d.quarantined),l=r.filter(d=>d.status==="FAILED"&&!d.quarantined),c=r.filter(d=>d.status==="CANCELLED");return Kg(()=>{if(l.forEach(d=>{E.log(""),o(d)}),l.length){E.log("");let d=l.length===1?"":"s";E.error(`${l.length} ${t}${d} failed:`),l.forEach(m=>{E.dimmed(n(m))})}if(c.length){E.log("");let d=c.length===1?"":"s";E.warn(`${c.length} ${t}${d} cancelled:`),c.forEach(m=>{E.dimmed(n(m))})}if(a.length){E.log("");let d=a.length===1?"":"s";E.success(`${a.length} ${t}${d} passed:`),a.forEach(m=>{E.dimmed(n(m))})}if(s.length){E.log("");let d=s.length===1?"":"s";E.warn(`${s.length} quarantined ${t}${d} failed:`),s.forEach(m=>{E.dimmed(n(m))})}if(i.length){E.log("");let d=i.length===1?"":"s";E.warn(`${i.length} quarantined ${t}${d} passed:`),i.forEach(m=>{E.dimmed(n(m))})}E.log(""),E.dimmed(`Total time: ${Math.round((Date.now()-e)/1e3)}s`)}),{quarantinedPassed:i.length,passed:a.length,quarantinedFailed:s.length,failed:l.length,cancelled:c.length}}var Eo=({status:r,testLogRef:e,getRunningTestsCount:t,getTotalTestsCount:n,additionalText:o})=>{r=r.toUpperCase();let i=r,a;r.includes("FAIL")?(i=Rr.bgRed.white("FAIL"),a=3):r.includes("PASS")?(i=Rr.bgGreen.white("PASS"),a=3):r.includes("START")?(i=Rr.bgBlue.white("START"),a=2):r.includes("CANCEL")?(i=Rr.bgRgb(191,68,11).white("CANCEL"),a=1):r.includes("RETRY")?(i=Rr.bgRgb(191,68,11).white("RETRY"),a=2):r.includes("RUN")||r.includes("PROG")?(i=Rr.bgMagenta.white("RUNNING"),a=0):(E.warn(`Unknown status tried to be logged in run test locally: ${r}`),a=0),YC||(i=`${i}`),E.log(`${i}${" ".repeat(a)} ${e} ${o?`${o} `:""}(${t()}/${n()})`)};var ZC=!1,Zg=(()=>{try{return QC("/.dockerenv"),!0}catch{return!1}})();async function ra(r,e){return yo||ZC||Zg?!0:(E.flush(),ot.flush(),await new Promise(n=>setTimeout(n,500)),await XC({message:r}))}async function eh(r,e){return yo||Zg?e:(await JC({message:r,default:e})).trim()||e}import ev from"fs";import{tmpdir as tv}from"os";import nv from"path";import{registry as Co}from"playwright-core/lib/server";import th from"proper-lockfile";var nh=nv.join(tv(),"momenticBrowserInstallation");var Rl=["chrome","chromium","chrome-for-testing"],rv={Chromium:"chromium","Google Chrome":"chrome","Chrome for Testing":"chrome-for-testing"},rh={chrome:"chrome",chromium:"chromium","chrome-for-testing":"chromium-headless-shell"};function oh(r){let e=rh[rv[r]??""]??"",t=Co.findExecutable(e);return!t||t.installType==="none"?!1:Al(t)}function Al(r){let e=r.executablePath();return ev.existsSync(e)}function ov(r,e){let t=rh[r];if(!t)throw new Error(`Requested install of unknown browser type ${r}`);let n=Co.findExecutable(t);if(!n||n.installType==="none")throw new Error(`Requested install of unknown browser type ${r}`);if(!(!e&&Al(n)))return n}async function iv({browser:r,force:e}){let t=ov(r,e);if(!t){E.info(`Browser '${r}' is already installed, skipping...`);return}E.info(`Installing browser '${r}'...`);try{await Co.installDeps([t],!1),await Co.install([t],!1)}catch(n){if(n.message.includes("Lock file is already being held")){E.warn("Another process is installing Playwright browsers. Waiting for completion before proceeding..");let o=Co.findExecutable(r),i=5*60*1e3,a=Date.now();for(;Date.now()-a<i&&!Al(o);)E.info("Waiting for browser to finish installing..."),await new Promise(s=>setTimeout(s,5e3))}else throw n}}async function oa({rawBrowsers:r,force:e=!1,all:t=!1}){let n=t?Rl:Array.from(new Set(r));try{await th.lock(nh,{stale:1e3*60*5,update:1e3*60,realpath:!1,retries:{retries:30,factor:2,maxTimeout:15e3,minTimeout:500}})}catch(i){E.warn(`Failed to acquire lock to install browsers. Please ensure that any other process installing browsers completes within 5 minutes: ${i}. Continuing without installation...`);return}let o;try{for(let i of n)try{await iv({browser:i,force:e})}catch(a){o=a,E.error(`Failed to install the ${i} browser: ${a}`)}}finally{await th.unlock(nh,{realpath:!1})}if(o)throw o}import{randomUUID as eA}from"crypto";import{diff as $6}from"deep-object-diff";import eQ from"yaml";import{z as nQ}from"zod";import{diff as w6}from"deep-object-diff";import{cloneDeep as E6}from"lodash-es";import{v4 as k6}from"uuid";import D6 from"yaml";import lv from"@dotenvx/dotenvx";import cv from"fs";import ih from"path";function dv(r){return r.includes("${")?r.replace(/\$\{([^}]+)\}/g,(e,t)=>{let[n,o]=t.split(/:-|-/,2),i=process.env[n];return t.includes(":-")?i&&i!==""?i:o||"":t.includes("-")?i!==void 0?i:o||"":i||""}):r}function mv(r){let{envVariables:e,project:t}=r;if(!e)return{};let n={};for(let[o,i]of Object.entries(e)){if(typeof i=="string"){let s=dv(i);s&&(n[o]=s);continue}let a;try{a=cv.readFileSync(ih.resolve(t.rootDir,i.fromFile),"utf-8")}catch(s){throw new Error(`Failed to read environment variable '${o}' from file '${i.fromFile}': ${s}`)}if(i.json)try{n[o]=JSON.parse(a)}catch(s){throw new Error(`Failed to parse environment variable '${o}' from file '${i.fromFile}' as JSON: ${s}`)}else n[o]=a}return Object.keys(n).length>0&&E.debug(n,"Set environment variables with interpolation from project configuration"),n}function uv(r){let{project:e,envFile:t,logger:n}=r,o={};if(!t)return o;let i=lv.config({path:ih.resolve(e.rootDir,t),processEnv:o,logLevel:"error",quiet:!0});if(i.error)throw new Error(`Failed to load .env file: ${i.error.message}`);return n.debug(o,"Set environment variables from .env file"),o}function ia(r,e,t){let n=(e.config.environments??[]).find(l=>l.name===r);if(!n)throw new Error(`Environment ${r} not found in local project configuration file`);let o={},i=mv({envVariables:n.envVariables,project:e});Object.assign(o,i);let a=uv({project:e,envFile:n.envFile,logger:t});return Object.assign(o,a),n.inheritFromShell&&(t.debug(process.env,"Inheriting environment variables from shell"),Object.assign(o,process.env)),{name:r,variables:o}}import{existsSync as vv,readFileSync as Rv,readdirSync as Av,writeFileSync as xv}from"fs";import{globSync as Iv}from"glob";import Bn,{dirname as ch}from"path";import{cwd as Ll}from"process";import dh from"yaml";import{z as fe}from"zod";import ah from"fs";import{globSync as pv}from"glob";import vo from"path";import gv from"yaml";import{z as xl}from"zod";var sh=!1,Il=[`**/*.${$e.TEST}`,`**/*.${$e.MODULE}`],Pl=xl.string().refine(r=>/^[a-zA-Z0-9-]+$/.test(r)),Ml=50,hv=xl.object({fileType:xl.nativeEnum(_e)});function aa(r,e){let t={project:r,tests:{},modules:{},mobileTests:{},duplicateEntities:{}},n=r.config.include??Il,o=Array.from(r.config.exclude??[]).concat(hi),i=pv(n,{absolute:!1,cwd:r.rootDir,ignore:o,dotRelative:!1,maxDepth:Ml,nodir:!0});for(let a of i){let s=fv(r.rootDir,a,t,e);s&&(t.duplicateEntities[s.id]=s.paths)}return sh=!0,t}function fv(r,e,t,n){let o=vo.join(r,e),i=Sv(o,n);if(!i)return;let a=bv(i,o,n);if(!a)return;let s=hv.safeParse(a);if(s.success===!1){n.warn(`Possible Momentic file at ${o} does not have a 'fileType', skipping: ${s.error}`);return}let l=s.data.fileType,c=yv(o,n);if(!c)return;let d=wv(e,o,c);switch(l){case _e.TEST:try{return Tv(a,t,d,o,n)}catch(m){n.warn(`Skipping file '${o}' because it is missing Momentic test metadata: ${m}`);return}case _e.MODULE:try{return Ev(a,t,d,o,n)}catch(m){n.warn(`Skipping file '${o}' because it is missing Momentic module metadata: ${m}`);return}case _e.MOBILE_TEST:try{return Cv(a,t,d,o,n)}catch(m){n.warn(`Skipping file '${o}' because it is missing Momentic mobile test metadata: ${m}`);return}default:{let m=l;return}}}function Sv(r,e){try{return ah.readFileSync(r,"utf-8")}catch(t){e.warn(`Could not read possible Momentic file at ${r}, skipping: ${t}`);return}}function bv(r,e,t){try{let n=gv.parse(r);if(typeof n!="object"||n===null)throw new Error("The YAML document should parse as a map with key-value pairs");return n}catch(n){t.warn(`Could not parse possible Momentic file at ${e}, skipping: ${n}`);return}}function yv(r,e){try{return ah.statSync(r)}catch(t){e.warn(`Skipping path '${r}' because it could not be stat, skipping: ${t}`);return}}function wv(r,e,t){return{relativePath:r,fullFilePath:e,platformSep:vo.sep,fullPathSegments:e.split(vo.sep),relativePathSegments:r.split(vo.sep),fileName:vo.basename(e),lastModified:t.mtime,createdAt:t.birthtime}}function Tv(r,e,t,n,o){let i=ur.parse(r),a;if(e.tests[i.id]){let s=e.tests[i.id].fullFilePath;a={id:i.id,paths:[s,n]}}return e.tests[i.id]={type:_e.TEST,name:i.name,id:i.id,description:i.description??void 0,labels:i.labels,...t},a}function Ev(r,e,t,n,o){let i=yt.parse(r),a;if(e.modules[i.moduleId]){let l=e.modules[i.moduleId].fullFilePath;a={id:i.moduleId,paths:[l,n]}}e.modules[i.moduleId]={type:_e.MODULE,name:i.name,id:i.moduleId,description:i.description??void 0,...t};let s=t.fileName.replace(`.${$e.MODULE}`,"");return!sh&&lr(i.name)!==s&&o.warn(`The module with ID ${i.moduleId} has a name (${i.name}) that does not match its file name (${s}). We recommend renaming the module or the file to be consistent to avoid confusion and issues with module resolution.`),a}function Cv(r,e,t,n,o){let i=Pn.parse(r),a;if(e.mobileTests[i.id]){let l=e.mobileTests[i.id].fullFilePath;a={id:i.id,paths:[l,n]}}let s=t.fileName.replace(`.${$e.TEST}`,"");return e.mobileTests[i.id]={type:_e.MOBILE_TEST,name:s,id:i.id,description:i.description??void 0,...t},a}var Ar="momentic.config.yaml",lh="momentic.workspace.yaml",Pv=fe.object({projects:fe.string().array().describe("list of glob patterns to find project (momentic.config.yaml) files")}),Mv=fe.union([fe.string(),fe.object({fromFile:fe.string(),json:fe.boolean().optional()})]),Lv=fe.object({name:Pl,baseUrl:fe.string().optional().describe("Optional for mobile tests"),envFile:fe.string().optional().describe("path to a file on disk to read environment variables from. can be relative to project root or absolute."),envVariables:fe.record(fe.string(),Mv).optional(),inheritFromShell:fe.boolean().optional().describe("inherit all environment variables from the shell - might be noisy"),browser:En.optional().describe("deprecated: use project-level configuration only")}),Ov=fe.object({postSave:fe.string().optional()}),Nv=fe.object({name:Pl,include:fe.string().array().optional().describe("list of glob patterns that match momentic files (optional)"),exclude:fe.string().array().optional().describe("opposite of include, takes precedence over include"),goldenFileDir:fe.string().optional(),reporterDir:fe.string().optional(),outputDir:fe.string().optional(),retries:fe.number().optional().describe("number of retries per test"),parallel:fe.number().optional().describe("degree of parallelism"),environments:fe.array(Lv).optional(),gitMainBranch:fe.string().optional(),gitProtectedBranches:fe.string().array().optional(),ai:Es.optional(),browser:En.optional(),advanced:Cs.optional(),hooks:Ov.optional()});function mh(r,e){let t;try{t=Rv(r,"utf-8")}catch(o){E.warn(`Could not read possible Momentic ${e} file at ${r}: ${o}`);return}let n;try{if(n=dh.parse(t),typeof n!="object"||n===null)throw new Error(`The ${e} file should parse as a map with key-value pairs, but is type ${typeof n} instead`)}catch(o){E.warn(`Possible Momentic ${e} file at ${r} does not parse as valid YAML: ${o}`);return}return n}function Ol(r){let e=mh(r,"project configuration");if(e!==void 0)try{return Nv.parse(e)}catch(t){E.warn(`Possible Momentic project configuration file at ${r} does not adhere to the required schema: ${t}`);return}}function kv(r){let e=mh(r,"workspace configuration");if(e!==void 0)try{return Pv.parse(e)}catch(t){E.warn(`Possible Momentic workspace configuration file at ${r} does not adhere to the required schema: ${t}`);return}}function _v(){let r=[],e=Ll(),t=Bn.parse(e).root,n=15,o=0;for(;o<n;){o++;let i=Bn.basename(e);if(gi.includes(i))return E.warn(`Stopping search for Momentic projects since the current directory name (${i}) is likely a system artifact folder.`),r;for(let a of Av(e))if(a.endsWith(Ar)){let s=Bn.join(e,a),l=Ol(s);l&&r.push({configFilePath:s,config:l,rootDir:ch(s)})}if(r.length)return r;if(e=Bn.dirname(e),e===t)break}return r}function Ro(r={}){let{configFilePath:e,nameFilter:t}=r,n=Fv(e);if(t&&(n=n.filter(o=>o.config.name===t)),n.length>1)throw new Error(`Multiple valid projects were found in the same directory. Please use the '-c / --config' flag to disambiguate:
|
|
3891
|
+
`),globalThis.console=e}}var El=" ".repeat(6);import GC from"os";import Xg,{multistream as $C}from"pino";import jC from"pino-pretty";var Yg=["lastScreenshotBuffer"];var To=new Map,WC=!0,Jg="Log throttle exceeded",VC=100,qC=5e3,Cl=class r{consoleLogger;hostname;bindingAttributes;disableConsoleLogs;minLevelValue=20;logsInCurrentWindow=0;droppedLogsInWindow=!1;lastWindowStart=Date.now();site="https://ingest.us.signoz.cloud:443/logs/json";flushIntervalMs;maxBatchSize;buffer=[];flushTimer;constructor({bindings:e,hostname:t,disableConsoleLogs:n,flushIntervalMs:o,maxBatchSize:i}){this.hostname=t??GC.hostname(),this.disableConsoleLogs=n,this.bindingAttributes={...e,env:"production"},this.flushIntervalMs=o??5e3,this.maxBatchSize=i??10;let a={base:this.bindingAttributes,errorKey:"err",level:"debug"};this.consoleLogger=WC?Xg(a):Xg(a,$C([{stream:jC({colorize:!0})}]))}getLevel(){return bi[this.minLevelValue]}child(e){return new r({bindings:{...this.bindingAttributes,...e},hostname:this.hostname,disableConsoleLogs:this.disableConsoleLogs,flushIntervalMs:this.flushIntervalMs,maxBatchSize:this.maxBatchSize})}flush(e){this.flushBuffer(),this.disableConsoleLogs||this.consoleLogger.flush(e)}scheduleFlush(){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flushTimer=void 0,this.flushBuffer()},this.flushIntervalMs))}async flushBuffer(){if(this.buffer.length===0)return;let e=this.buffer;this.buffer=[];try{let t=await fetch(this.site,{method:"POST",headers:{"Content-Type":"application/json","signoz-access-token":"CumAaTMUcwjt05OddAmefKgshbhfRmWxzxih"},body:JSON.stringify(e)});if(!t.ok)throw new Error(`Got error status (${t.statusText}) from SigNoz`)}catch(t){this.consoleLogger.warn({err:t},"Failed to log to SigNoz")}}shouldAllowLog(e){if(e===Jg)return!0;let t=Date.now();return t-this.lastWindowStart>qC&&(this.logsInCurrentWindow=0,this.droppedLogsInWindow&&this.log("error",void 0,Jg),this.droppedLogsInWindow=!1,this.lastWindowStart=t),this.logsInCurrentWindow<VC?(this.logsInCurrentWindow++,!0):(this.droppedLogsInWindow=!0,!1)}log(e,t,n,...o){if(lo[e]<this.minLevelValue||!this.shouldAllowLog(n))return;if(t&&n===void 0&&(n=`${t}`,t={}),this.disableConsoleLogs||this.consoleLogger[e](e==="error"?t:{testId:t.testId,runId:t.runId},n,...o),typeof t=="object"&&t&&"err"in t&&t.err instanceof Error){let l={};for(let c of Object.getOwnPropertyNames(t.err))Yg.includes(c)||(l[c]=t.err[c]);l.name=t.err.name,t.err=l}let i=Object.assign({},this.bindingAttributes,t&&typeof t=="object"?t:{});o.length>0&&(i.args=o);let a={host:this.hostname,app:this.bindingAttributes.app,env:this.bindingAttributes.env},s={timestamp:Math.round(Date.now()*1e6),severity_text:e.toUpperCase(),resources:a,attributes:{},body:JSON.stringify({...i,message:n||""})};this.buffer.push(s),this.buffer.length>=this.maxBatchSize?(this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=void 0),this.flushBuffer()):this.scheduleFlush()}setApp(e){let t=this.bindingAttributes.app;this.bindingAttributes.app=e,To.set("app",this),To.delete(t)}debug(e,t,...n){this.log("debug",e,t,...n)}info(e,t,...n){this.log("info",e,t,...n)}warn(e,t,...n){this.log("warn",e,t,...n)}error(e,t,...n){this.log("error",e,t,...n)}bindings(){return this.bindingAttributes}addBinding(e,t){this.bindingAttributes[e]=t}setMinLevel(e){typeof e=="number"?(this.minLevelValue=e,this.consoleLogger.level=bi[e]):(this.minLevelValue=lo[e],this.consoleLogger.level=e)}enableConsoleLogs(){this.disableConsoleLogs=!1}},na=({app:r,hostname:e,disableConsoleLogs:t})=>(To.has(r)||To.set(r,new Cl({bindings:{app:r},hostname:e,disableConsoleLogs:t})),To.get(r));import{hostname as KC}from"os";var ot=na({app:"cli",hostname:KC(),disableConsoleLogs:!0}).child({version:"0.0.9"});function vl(r){if(!r)return;r=r.toLowerCase();let e=Si.safeParse(r);if(e.success)return E.setMinLevel(e.data),e.data}function Qg({results:r,startTime:e,entity:t,getDisplayLine:n,onFailed:o}){let i=r.filter(d=>d.status==="PASSED"&&d.quarantined),a=r.filter(d=>d.status==="PASSED"&&!d.quarantined),s=r.filter(d=>d.status==="FAILED"&&d.quarantined),l=r.filter(d=>d.status==="FAILED"&&!d.quarantined),c=r.filter(d=>d.status==="CANCELLED");return Kg(()=>{if(l.forEach(d=>{E.log(""),o(d)}),l.length){E.log("");let d=l.length===1?"":"s";E.error(`${l.length} ${t}${d} failed:`),l.forEach(m=>{E.dimmed(n(m))})}if(c.length){E.log("");let d=c.length===1?"":"s";E.warn(`${c.length} ${t}${d} cancelled:`),c.forEach(m=>{E.dimmed(n(m))})}if(a.length){E.log("");let d=a.length===1?"":"s";E.success(`${a.length} ${t}${d} passed:`),a.forEach(m=>{E.dimmed(n(m))})}if(s.length){E.log("");let d=s.length===1?"":"s";E.warn(`${s.length} quarantined ${t}${d} failed:`),s.forEach(m=>{E.dimmed(n(m))})}if(i.length){E.log("");let d=i.length===1?"":"s";E.warn(`${i.length} quarantined ${t}${d} passed:`),i.forEach(m=>{E.dimmed(n(m))})}E.log(""),E.dimmed(`Total time: ${Math.round((Date.now()-e)/1e3)}s`)}),{quarantinedPassed:i.length,passed:a.length,quarantinedFailed:s.length,failed:l.length,cancelled:c.length}}var Eo=({status:r,testLogRef:e,getRunningTestsCount:t,getTotalTestsCount:n,additionalText:o})=>{r=r.toUpperCase();let i=r,a;r.includes("FAIL")?(i=Rr.bgRed.white("FAIL"),a=3):r.includes("PASS")?(i=Rr.bgGreen.white("PASS"),a=3):r.includes("START")?(i=Rr.bgBlue.white("START"),a=2):r.includes("CANCEL")?(i=Rr.bgRgb(191,68,11).white("CANCEL"),a=1):r.includes("RETRY")?(i=Rr.bgRgb(191,68,11).white("RETRY"),a=2):r.includes("RUN")||r.includes("PROG")?(i=Rr.bgMagenta.white("RUNNING"),a=0):(E.warn(`Unknown status tried to be logged in run test locally: ${r}`),a=0),YC||(i=`${i}`),E.log(`${i}${" ".repeat(a)} ${e} ${o?`${o} `:""}(${t()}/${n()})`)};var ZC=!1,Zg=(()=>{try{return QC("/.dockerenv"),!0}catch{return!1}})();async function ra(r,e){return yo||ZC||Zg?!0:(E.flush(),ot.flush(),await new Promise(n=>setTimeout(n,500)),await XC({message:r}))}async function eh(r,e){return yo||Zg?e:(await JC({message:r,default:e})).trim()||e}import ev from"fs";import{tmpdir as tv}from"os";import nv from"path";import{registry as Co}from"playwright-core/lib/server";import th from"proper-lockfile";var nh=nv.join(tv(),"momenticBrowserInstallation");var Rl=["chrome","chromium","chrome-for-testing"],rv={Chromium:"chromium","Google Chrome":"chrome","Chrome for Testing":"chrome-for-testing"},rh={chrome:"chrome",chromium:"chromium","chrome-for-testing":"chromium-headless-shell"};function oh(r){let e=rh[rv[r]??""]??"",t=Co.findExecutable(e);return!t||t.installType==="none"?!1:Al(t)}function Al(r){let e=r.executablePath();return ev.existsSync(e)}function ov(r,e){let t=rh[r];if(!t)throw new Error(`Requested install of unknown browser type ${r}`);let n=Co.findExecutable(t);if(!n||n.installType==="none")throw new Error(`Requested install of unknown browser type ${r}`);if(!(!e&&Al(n)))return n}async function iv({browser:r,force:e}){let t=ov(r,e);if(!t){E.info(`Browser '${r}' is already installed, skipping...`);return}E.info(`Installing browser '${r}'...`);try{await Co.installDeps([t],!1),await Co.install([t],!1)}catch(n){if(n.message.includes("Lock file is already being held")){E.warn("Another process is installing Playwright browsers. Waiting for completion before proceeding..");let o=Co.findExecutable(r),i=5*60*1e3,a=Date.now();for(;Date.now()-a<i&&!Al(o);)E.info("Waiting for browser to finish installing..."),await new Promise(s=>setTimeout(s,5e3))}else throw n}}async function oa({rawBrowsers:r,force:e=!1,all:t=!1}){let n=t?Rl:Array.from(new Set(r));try{await th.lock(nh,{stale:1e3*60*5,update:1e3*60,realpath:!1,retries:{retries:30,factor:2,maxTimeout:15e3,minTimeout:500}})}catch(i){E.warn(`Failed to acquire lock to install browsers. Please ensure that any other process installing browsers completes within 5 minutes: ${i}. Continuing without installation...`);return}let o;try{for(let i of n)try{await iv({browser:i,force:e})}catch(a){o=a,E.error(`Failed to install the ${i} browser: ${a}`)}}finally{await th.unlock(nh,{realpath:!1})}if(o)throw o}import{randomUUID as eA}from"crypto";import{diff as $6}from"deep-object-diff";import eQ from"yaml";import{z as nQ}from"zod";import{diff as w6}from"deep-object-diff";import{cloneDeep as E6}from"lodash-es";import{v4 as k6}from"uuid";import D6 from"yaml";import lv from"@dotenvx/dotenvx";import cv from"fs";import ih from"path";function dv(r){return r.includes("${")?r.replace(/\$\{([^}]+)\}/g,(e,t)=>{let[n,o]=t.split(/:-|-/,2),i=process.env[n];return t.includes(":-")?i&&i!==""?i:o||"":t.includes("-")?i!==void 0?i:o||"":i||""}):r}function mv(r){let{envVariables:e,project:t}=r;if(!e)return{};let n={};for(let[o,i]of Object.entries(e)){if(typeof i=="string"){let s=dv(i);s&&(n[o]=s);continue}let a;try{a=cv.readFileSync(ih.resolve(t.rootDir,i.fromFile),"utf-8")}catch(s){throw new Error(`Failed to read environment variable '${o}' from file '${i.fromFile}': ${s}`)}if(i.json)try{n[o]=JSON.parse(a)}catch(s){throw new Error(`Failed to parse environment variable '${o}' from file '${i.fromFile}' as JSON: ${s}`)}else n[o]=a}return Object.keys(n).length>0&&E.debug(n,"Set environment variables with interpolation from project configuration"),n}function uv(r){let{project:e,envFile:t,logger:n}=r,o={};if(!t)return o;let i=lv.config({path:ih.resolve(e.rootDir,t),processEnv:o,logLevel:"error",quiet:!0});if(i.error)throw new Error(`Failed to load .env file: ${i.error.message}`);return n.debug(o,"Set environment variables from .env file"),o}function ia(r,e,t){let n=(e.config.environments??[]).find(l=>l.name===r);if(!n)throw new Error(`Environment ${r} not found in local project configuration file`);let o={},i=mv({envVariables:n.envVariables,project:e});Object.assign(o,i);let a=uv({project:e,envFile:n.envFile,logger:t});return Object.assign(o,a),n.inheritFromShell&&(t.debug(process.env,"Inheriting environment variables from shell"),Object.assign(o,process.env)),{name:r,variables:o}}import{existsSync as vv,readFileSync as Rv,readdirSync as Av,writeFileSync as xv}from"fs";import{globSync as Iv}from"glob";import Bn,{dirname as ch}from"path";import{cwd as Ll}from"process";import dh from"yaml";import{z as fe}from"zod";import ah from"fs";import{globSync as pv}from"glob";import vo from"path";import gv from"yaml";import{z as xl}from"zod";var sh=!1,Il=[`**/*.${$e.TEST}`,`**/*.${$e.MODULE}`],Pl=xl.string().refine(r=>/^[a-zA-Z0-9-]+$/.test(r)),Ml=50,hv=xl.object({fileType:xl.nativeEnum(_e)});function aa(r,e){let t={project:r,tests:{},modules:{},mobileTests:{},duplicateEntities:{}},n=r.config.include??Il,o=Array.from(r.config.exclude??[]).concat(hi),i=pv(n,{absolute:!1,cwd:r.rootDir,ignore:o,dotRelative:!1,maxDepth:Ml,nodir:!0});for(let a of i){let s=fv(r.rootDir,a,t,e);s&&(t.duplicateEntities[s.id]=s.paths)}return sh=!0,t}function fv(r,e,t,n){let o=vo.join(r,e),i=Sv(o,n);if(!i)return;let a=bv(i,o,n);if(!a)return;let s=hv.safeParse(a);if(s.success===!1){n.warn(`Possible Momentic file at ${o} does not have a 'fileType', skipping: ${s.error}`);return}let l=s.data.fileType,c=yv(o,n);if(!c)return;let d=wv(e,o,c);switch(l){case _e.TEST:try{return Tv(a,t,d,o,n)}catch(m){n.warn(`Skipping file '${o}' because it is missing Momentic test metadata: ${m}`);return}case _e.MODULE:try{return Ev(a,t,d,o,n)}catch(m){n.warn(`Skipping file '${o}' because it is missing Momentic module metadata: ${m}`);return}case _e.MOBILE_TEST:try{return Cv(a,t,d,o,n)}catch(m){n.warn(`Skipping file '${o}' because it is missing Momentic mobile test metadata: ${m}`);return}default:{let m=l;return}}}function Sv(r,e){try{return ah.readFileSync(r,"utf-8")}catch(t){e.warn(`Could not read possible Momentic file at ${r}, skipping: ${t}`);return}}function bv(r,e,t){try{let n=gv.parse(r);if(typeof n!="object"||n===null)throw new Error("The YAML document should parse as a map with key-value pairs");return n}catch(n){t.warn(`Could not parse possible Momentic file at ${e}, skipping: ${n}`);return}}function yv(r,e){try{return ah.statSync(r)}catch(t){e.warn(`Skipping path '${r}' because it could not be stat, skipping: ${t}`);return}}function wv(r,e,t){return{relativePath:r,fullFilePath:e,platformSep:vo.sep,fullPathSegments:e.split(vo.sep),relativePathSegments:r.split(vo.sep),fileName:vo.basename(e),lastModified:t.mtime,createdAt:t.birthtime}}function Tv(r,e,t,n,o){let i=ur.parse(r),a;if(e.tests[i.id]){let s=e.tests[i.id].fullFilePath;a={id:i.id,paths:[s,n]}}return e.tests[i.id]={type:_e.TEST,name:i.name,id:i.id,description:i.description??void 0,labels:i.labels,...t},a}function Ev(r,e,t,n,o){let i=yt.parse(r),a;if(e.modules[i.moduleId]){let l=e.modules[i.moduleId].fullFilePath;a={id:i.moduleId,paths:[l,n]}}e.modules[i.moduleId]={type:_e.MODULE,name:i.name,id:i.moduleId,description:i.description??void 0,...t};let s=t.fileName.replace(`.${$e.MODULE}`,"");return!sh&&lr(i.name)!==s&&o.warn(`The module with ID ${i.moduleId} has a name (${i.name}) that does not match its file name (${s}). We recommend renaming the module or the file to be consistent to avoid confusion and issues with module resolution.`),a}function Cv(r,e,t,n,o){let i=Pn.parse(r),a;if(e.mobileTests[i.id]){let l=e.mobileTests[i.id].fullFilePath;a={id:i.id,paths:[l,n]}}let s=t.fileName.replace(`.${$e.TEST}`,"");return e.mobileTests[i.id]={type:_e.MOBILE_TEST,name:s,id:i.id,description:i.description??void 0,...t},a}var Ar="momentic.config.yaml",lh="momentic.workspace.yaml",Pv=fe.object({projects:fe.string().array().describe("list of glob patterns to find project (momentic.config.yaml) files")}),Mv=fe.union([fe.string(),fe.object({fromFile:fe.string(),json:fe.boolean().optional()})]),Lv=fe.object({name:Pl,baseUrl:fe.string().optional().describe("Optional for mobile tests"),envFile:fe.string().optional().describe("path to a file on disk to read environment variables from. can be relative to project root or absolute."),envVariables:fe.record(fe.string(),Mv).optional(),inheritFromShell:fe.boolean().optional().describe("inherit all environment variables from the shell - might be noisy"),browser:En.optional().describe("deprecated: use project-level configuration only")}),Ov=fe.object({postSave:fe.string().optional()}),Nv=fe.object({name:Pl,include:fe.string().array().optional().describe("list of glob patterns that match momentic files (optional)"),exclude:fe.string().array().optional().describe("opposite of include, takes precedence over include"),goldenFileDir:fe.string().optional(),reporterDir:fe.string().optional(),outputDir:fe.string().optional(),retries:fe.number().optional().describe("number of retries per test"),parallel:fe.number().optional().describe("degree of parallelism"),environments:fe.array(Lv).optional(),gitMainBranch:fe.string().optional(),gitProtectedBranches:fe.string().array().optional(),ai:Es.optional(),browser:En.optional(),advanced:Cs.optional(),hooks:Ov.optional()});function mh(r,e){let t;try{t=Rv(r,"utf-8")}catch(o){E.warn(`Could not read possible Momentic ${e} file at ${r}: ${o}`);return}let n;try{if(n=dh.parse(t),typeof n!="object"||n===null)throw new Error(`The ${e} file should parse as a map with key-value pairs, but is type ${typeof n} instead`)}catch(o){E.warn(`Possible Momentic ${e} file at ${r} does not parse as valid YAML: ${o}`);return}return n}function Ol(r){let e=mh(r,"project configuration");if(e!==void 0)try{return Nv.parse(e)}catch(t){E.warn(`Possible Momentic project configuration file at ${r} does not adhere to the required schema: ${t}`);return}}function kv(r){let e=mh(r,"workspace configuration");if(e!==void 0)try{return Pv.parse(e)}catch(t){E.warn(`Possible Momentic workspace configuration file at ${r} does not adhere to the required schema: ${t}`);return}}function _v(){let r=[],e=Ll(),t=Bn.parse(e).root,n=15,o=0;for(;o<n;){o++;let i=Bn.basename(e);if(gi.includes(i))return E.warn(`Stopping search for Momentic projects since the current directory name (${i}) is likely a system artifact folder.`),r;for(let a of Av(e))if(a.endsWith(Ar)){let s=Bn.join(e,a),l=Ol(s);l&&r.push({configFilePath:s,config:l,rootDir:ch(s)})}if(r.length)return r;if(e=Bn.dirname(e),e===t)break}return r}function Ro(r={}){let{configFilePath:e,nameFilter:t}=r,n=Fv(e);if(t&&(n=n.filter(o=>o.config.name===t)),n.length>1)throw new Error(`Multiple valid projects were found in the same directory. Please use the '-c / --config' flag to disambiguate:
|
|
3892
3892
|
${n.map(o=>o.configFilePath)}`);if(n.length===0)throw new Error("No valid Momentic project file available.");return E.debug(`Found valid project configuration at ${n[0].configFilePath}`),n[0]}function Dv(r){let e=kv(r);if(!e||!e.projects||!e.projects.length)return;let t=e.projects.map(i=>(i.endsWith("/")||(i+="/"),`${i}*${Ar}`)),n=Iv(t,{absolute:!1,cwd:Ll(),dotRelative:!1,maxDepth:Ml,nodir:!0}),o=[];for(let i of n){let a=Bn.join(Ll(),i),s=Ol(a);s&&o.push({configFilePath:a,config:s,rootDir:ch(a)})}return o}function Fv(r){if(r){r=Bn.resolve(r);let t=Ol(r);return t||(console.error(`No valid Momentic project file found at ${r}.`),process.exit(1)),[{config:t,configFilePath:r,rootDir:Bn.dirname(r)}]}if(vv(lh)){let t=Dv(lh);if(t)return t}return _v()}function uh(r,e){let t=dh.stringify(r);xv(e,t)}import{z as Nl}from"zod";var ph="test-results";var BQ=Nl.object({width:Nl.number(),height:Nl.number()});import{execFile as Uv}from"node:child_process";import{promisify as zv}from"node:util";import Bv from"simple-git";var me=Bv(),gh=zv(Uv);async function Hv(r){let e=await ue(r,me.raw(["config","--list"])),t={};if(!e)return t;for(let n of e.split(`
|
|
3893
3893
|
`)){let o=n.indexOf("=");if(o===-1)continue;let i=n.slice(0,o),a=n.slice(o+1).trim();t[i]=a}return t}async function Gv(r,e,t){try{let o=t["github.user"]||void 0;if(o)return o}catch{}let n;try{if(e?.startsWith("http://")||e?.startsWith("https://"))n=new URL(e).host;else if(e?.startsWith("git@")){let o=e.indexOf("@"),i=e.indexOf(":",o+1);o!==-1&&i!==-1&&(n=e.slice(o+1,i))}}catch{}if(n=n?.toLowerCase(),!!n){try{if(e?.startsWith("git@")&&n?.includes("github")){let{stdout:o,stderr:i}=await gh("ssh",["-T","-o","BatchMode=yes",`git@${n}`],{timeout:5e3}),s=`${o??""}${i??""}`.trim().match(/Hi\s+([A-Za-z0-9_-]+)!/);if(s?.[1])return s[1]}}catch{}try{let o=n&&n!=="github.com"?["api","--hostname",n,"user","-q",".login"]:["api","user","-q",".login"],{stdout:i}=await gh("gh",o,{timeout:5e3}),a=i?.toString().trim();if(a)return a}catch{}}}async function $v(r,e,t){let n=e?.includes("github.com"),o=e?.includes("gitlab.com");try{if(n)return Gv(r,e,t);if(o)return}catch{}}function sa(r){if(r.startsWith("git@")){let e=r.split(":");if(e.length===2){let t=e[1].replace(".git","").split("/");if(t.length===2){let n=t[0],o=t[1];return`${n}/${o}`}}}else if(r.startsWith("http")||r.startsWith("https")){let t=new URL(r).pathname.split("/").filter(Boolean);if(t.length>=2){let n=t[0],o=t[1].replace(".git","");return`${n}/${o}`}}}async function ue(r,e){try{return(await e).trim()}catch(t){r.error({err:t},"Failed to run git command");return}}function jv(){if(process.env.GITHUB_ACTION)return"GithubActions";if(process.env.GITLAB_CI)return"GitlabCI";if(process.env.CIRCLECI)return"CircleCI";if(process.env.BUILDKITE)return"Buildkite";if(process.env["System.CollectionUri"]?.includes("azure"))return"AzureDevOps";if(process.env.PROJECT_ID&&process.env.BUILD_ID)return"GCPCloudBuild"}async function Wv(r){let[e,t,n,o]=await Promise.all([ue(r,me.show(["--no-patch","--format=%ci"])),ue(r,me.listRemote(["--get-url","origin"])),ue(r,me.show(["-s","--pretty=%B"])),ue(r,me.show(["-s","--pretty=%an"]))]);return{gitCommitSha:process.env.GITHUB_SHA,gitCommitShaShort:process.env.GITHUB_SHA?.slice(0,6),gitCommitTimestamp:e?new Date(e):void 0,gitBranchName:process.env.GITHUB_HEAD_REF||process.env.GITHUB_REF_NAME,gitOriginUrl:t,gitCommitMessage:n,gitCommitAuthorName:o,githubRepository:process.env.GITHUB_REPOSITORY,pipelineId:process.env.GITHUB_RUN_ID}}async function Vv(r){let[e,t,n]=await Promise.all([ue(r,me.listRemote(["--get-url","origin"])),ue(r,me.show(["-s","--pretty=%B"])),ue(r,me.show(["-s","--pretty=%an"]))]);return{gitCommitSha:process.env.CI_COMMIT_SHA,gitCommitShaShort:process.env.CI_COMMIT_SHORT_SHA,gitCommitTimestamp:process.env.CI_COMMIT_TIMESTAMP?new Date(process.env.CI_COMMIT_TIMESTAMP):void 0,gitBranchName:process.env.CI_COMMIT_BRANCH||process.env.CI_COMMIT_REF_NAME,gitOriginUrl:e,gitCommitMessage:t,gitCommitAuthorName:n,gitlabProjectPath:process.env.CI_PROJECT_PATH,pipelineId:`${process.env.CI_PIPELINE_ID}:${process.env.CI_JOB_ID}`}}async function qv(r){let[e,t,n,o]=await Promise.all([ue(r,me.show(["--no-patch","--format=%ci"])),ue(r,me.listRemote(["--get-url","origin"])),ue(r,me.show(["-s","--pretty=%B"])),ue(r,me.show(["-s","--pretty=%an"]))]),i=t?.includes("github.com"),a=t?.includes("gitlab.com"),s=t?sa(t):void 0;return{gitCommitSha:process.env.CIRCLE_SHA1,gitCommitShaShort:process.env.CIRCLE_SHA1?.slice(0,6),gitCommitTimestamp:e?new Date(e):void 0,gitBranchName:process.env.CIRCLE_BRANCH,gitOriginUrl:process.env.CIRCLE_REPOSITORY_URL,gitCommitMessage:n,gitCommitAuthorName:o,githubRepository:i?s:void 0,gitlabProjectPath:a?s:void 0,pipelineId:process.env.CIRCLE_PIPELINE_ID}}async function Kv(r){let[e,t,n]=await Promise.all([ue(r,me.show(["--no-patch","--format=%ci"])),ue(r,me.show(["-s","--pretty=%B"])),ue(r,me.show(["-s","--pretty=%an"]))]),o=process.env.BUILDKITE_REPO,i=o?.includes("github.com"),a=o?.includes("gitlab.com"),s=o?sa(o):void 0;return{gitCommitSha:process.env.BUILDKITE_COMMIT,gitCommitShaShort:process.env.BUILDKITE_COMMIT?.slice(0,6),gitCommitTimestamp:e?new Date(e):void 0,gitBranchName:process.env.BUILDKITE_BRANCH,gitOriginUrl:o,gitCommitMessage:t,gitCommitAuthorName:n,githubRepository:i?s:void 0,gitlabProjectPath:a?s:void 0,pipelineId:`${process.env.BUILDKITE_PIPELINE_ID}:${process.env.BUILDKITE_BUILD_ID}:${process.env.BUILDKITE_JOB_ID}`}}async function Yv(r){let[e,t,n]=await Promise.all([ue(r,me.show(["--no-patch","--format=%ci"])),ue(r,me.show(["-s","--pretty=%B"])),ue(r,me.show(["-s","--pretty=%an"]))]),o=process.env["Build.Repository.Uri"],i=o?.includes("github.com"),a=o?.includes("gitlab.com"),s=o?sa(o):void 0;return{gitCommitSha:process.env["Build.SourceVersion"],gitCommitShaShort:process.env["Build.SourceVersion"]?.slice(0,6),gitCommitTimestamp:e?new Date(e):void 0,gitBranchName:process.env["Build.SourceBranchName"],gitOriginUrl:o,gitCommitMessage:t,gitCommitAuthorName:n,githubRepository:i?s:void 0,gitlabProjectPath:a?s:void 0,pipelineId:`${process.env["System.JobId"]}:${process.env["System.JobAttempt"]}`}}async function Xv(r,e){let[t,n,o,i,a,s,l,c,d]=await Promise.all([ue(r,me.revparse(["HEAD"])),ue(r,me.revparse(["--short","HEAD"])),ue(r,me.revparse(["--abbrev-ref","HEAD"])),ue(r,me.listRemote(["--get-url","origin"])),ue(r,me.show(["--no-patch","--format=%ci"])),ue(r,me.show(["-s","--pretty=%B"])),ue(r,me.show(["-s","--pretty=%an"])),e?ue(r,me.raw(["merge-base","--fork-point",e])):Promise.resolve(void 0),Hv(r)]),m=c?await ue(r,me.show(["--no-patch","--format=%ci",c])):void 0,u=i?.includes("github.com"),g=i?.includes("gitlab.com"),p=i?sa(i):void 0,h=d["user.email"]||void 0,f=d["user.name"]||void 0,y=d["user.username"]||void 0,w=await $v(r,i,d)??y??void 0;return r.debug({userUsername:w,gitLocalUserLogin:y,gitLocalUserEmail:h,gitLocalUserName:f},"Resolved local git identity"),{gitCommitSha:t,gitCommitShaShort:n,gitBranchName:o,gitOriginUrl:i,gitCommitTimestamp:a?new Date(a):void 0,gitCommitMessage:s,gitCommitAuthorName:l,gitLocalUsername:w,gitLocalEmail:h,gitLocalName:f,lastCommitOnMainSha:c,lastCommitOnMainTimestamp:m?new Date(m):void 0,githubRepository:u?p:void 0,gitlabProjectPath:g?p:void 0,pipelineId:void 0}}async function Jv(){let r=process.env._HEAD_REPO_URL;return{gitCommitSha:process.env.COMMIT_SHA,gitCommitShaShort:process.env.COMMIT_SHA?.slice(0,6),gitBranchName:process.env.BRANCH_NAME,gitOriginUrl:r?process.env._HEAD_REPO_URL:void 0,gitCommitTimestamp:void 0,gitCommitMessage:void 0,gitCommitAuthorName:void 0,githubRepository:r?process.env.REPO_FULL_NAME:void 0,pipelineId:`${process.env.PROJECT_ID}:${process.env.BUILD_ID}`}}async function Qv(r){let e=r.config.gitProtectedBranches??[];return r.config.gitMainBranch&&e.push(r.config.gitMainBranch),{gitMainBranch:r.config.gitMainBranch,gitProtectedBranches:e}}async function Zv(r,e){let t=jv();if(!t)return Xv(r,e);switch(t){case"GithubActions":return Wv(r);case"GitlabCI":return Vv(r);case"CircleCI":return qv(r);case"Buildkite":return Kv(r);case"AzureDevOps":return Yv(r);case"GCPCloudBuild":return Jv()}}async function eR(r,e,t,n){let o=n;if(!n.gitCommitSha)return o;if(n.gitMainBranch&&(!o.lastCommitOnMainSha||!o.lastCommitOnMainTimestamp)){let i=await e.getMergeBaseCommitFromGitlab(t,n.gitMainBranch,n.gitCommitSha);o={...o,lastCommitOnMainSha:i.sha,lastCommitOnMainTimestamp:i.committer.date}}if(!o.gitCommitTimestamp||!o.gitCommitAuthorName||!o.gitCommitMessage||!o.gitCommitAuthorName){let i=await e.getCommitFromGitlab(t,n.gitCommitSha);i&&(o={...o,gitCommitTimestamp:o.gitCommitTimestamp??i.committer.date,gitCommitAuthorName:o.gitCommitAuthorName??i.author.name,gitCommitMessage:o.gitCommitMessage??i.message})}return o}async function tR(r,e,t,n,o){let i=o;if(!o.gitCommitSha)return i;if(o.gitMainBranch&&(!i.lastCommitOnMainSha||!i.lastCommitOnMainTimestamp)){let a=await e.getMergeBaseCommitFromGithub(t,n,o.gitMainBranch,o.gitCommitSha);i={...i,lastCommitOnMainSha:a.sha,lastCommitOnMainTimestamp:a.committer.date}}if(!i.gitCommitTimestamp||!i.gitCommitAuthorName||!i.gitCommitMessage||!i.gitCommitAuthorName){let a=await e.getCommitFromGithub(t,n,o.gitCommitSha);a&&(i={...i,gitCommitTimestamp:i.gitCommitTimestamp??a.committer.date,gitCommitAuthorName:i.gitCommitAuthorName??a.author.name,gitCommitMessage:i.gitCommitMessage??a.message})}return i}async function nR(r,e,t){try{if(t.githubRepository){let[n,o]=t.githubRepository.split("/");return await tR(r,e,n,o,t)}else if(t.gitlabProjectPath)return await eR(r,e,t.gitlabProjectPath,t)}catch(n){r.warn({err:n},"Failed to get remote git metadata")}return t}async function la(r,e,t){let n=await Qv(t),o=await Zv(r,n.gitMainBranch),i={...n,...o};(!i.lastCommitOnMainSha||!i.lastCommitOnMainTimestamp)&&i.gitBranchName===n.gitMainBranch&&(i.lastCommitOnMainSha=i.gitCommitSha,i.lastCommitOnMainTimestamp=i.gitCommitTimestamp);let a=await nR(r,e,i);return{...n,...o,...a}}import{randomUUID as rR}from"crypto";import Hn from"fs";import ca from"path";import da from"yaml";function hh({name:r,description:e="",steps:t=[],settings:n={},folder:o}){let a=`${lr(r)}.${$e.TEST}`,s=ca.join(o,a);if(Hn.existsSync(s))throw new Error(`A test named '${r}' already exists at path '${s}'. Choose a different name.`);let l={fileType:_e.MOBILE_TEST,id:rR(),description:e,schemaVersion:Vo,settings:n,steps:t},c=da.stringify(l);return Hn.writeFileSync(s,c,"utf-8"),s}function kl(r,e=process.cwd()){let t=ca.join(e,r);if(!Hn.existsSync(t))throw new Error(`Test file not found: ${t}`);let n=Hn.readFileSync(t,"utf-8").replace(/\r\n|\r/g,`
|
|
3894
3894
|
`),o=da.parse(n),i=Mn.parse(o);if(i.fileType!==_e.MOBILE_TEST)throw new Error(`File '${r}' is not a mobile test (fileType=${i.fileType}).`);return i}async function fh({filePath:r,steps:e,settings:t,cwd:n=process.cwd()}){let o=ca.isAbsolute(r)?r:ca.join(n,r);if(!Hn.existsSync(o))throw new Error(`Test file not found: ${o}`);let i=Hn.readFileSync(o,"utf-8").replace(/\r\n|\r/g,`
|
|
3895
|
-
`),a=da.parse(i),s=Mn.parse(a);if(s.fileType!==_e.MOBILE_TEST)throw new Error(`File at '${o}' is not a mobile test (fileType=${s.fileType}).`);let l;e&&(l=(await fr({steps:e})).stepsToSave);let c={...s,...l!==void 0?{steps:l}:{},...t!==void 0?{settings:t}:{}},d=da.stringify(Mn.parse(c));Hn.writeFileSync(o,d,"utf-8")}import{randomUUID as oR}from"crypto";import Jt from"fs";import Gn from"path";var Sh={status:(r,e)=>{if(r.status===e.status)return r.status;if(r.status==="FAILED"||e.status==="FAILED")return"FAILED";if(r.status==="CANCELLED"||e.status==="CANCELLED")return"CANCELLED";if(r.status==="RETRYING"||e.status==="RETRYING")return"RETRYING";if(r.status==="RUNNING"||e.status==="RUNNING")return"RUNNING";if(r.status==="PENDING"||e.status==="PENDING")return"PENDING";throw new Error(`Invalid run status merge: ${r.status} and ${e.status}`)},startedAt:(r,e)=>r.startedAt<e.startedAt?r.startedAt:e.startedAt,updatedAt:(r,e)=>r.updatedAt>e.updatedAt?r.updatedAt:e.updatedAt,finishedAt:(r,e)=>!r.finishedAt||!e.finishedAt?new Date:r.finishedAt>e.finishedAt?r.finishedAt:e.finishedAt,gitCommitTimestamp:(r,e)=>{if(!(!r&&!e)){if(!r.gitCommitTimestamp||!e.gitCommitTimestamp||r.gitCommitTimestamp.getTime()!==e.gitCommitTimestamp.getTime())throw new Error(`Git commit timestamps must match to be merged: ${r.gitCommitTimestamp} and ${e.gitCommitTimestamp}`);return r.gitCommitTimestamp}},pipelineId:(r,e)=>r.pipelineId===e.pipelineId?r.pipelineId:!r.pipelineId&&e.pipelineId?e.pipelineId:!e.pipelineId&&r.pipelineId?r.pipelineId:r.startedAt<e.startedAt?e.pipelineId:r.pipelineId,labels:(r,e)=>{let t=new Set([...r.labels??[],...e.labels??[]]);return Array.from(t)}};function iR(r,e,t){if(Sh[t]){let i=Sh[t];return i(r,e)}let n=r[t],o=e[t];if(n!==o)throw new Error(`Metadata values for key "${t}" do not match: "${n}" vs "${o}"`);return n}var bh=new Set([".DS_Store","__MACOSX"]);function yh(r,e,t){let n=oR(),o=r.child({runGroupId:n});Jt.rmSync(e,{recursive:!0,force:!0});let i=Jt.readdirSync(t).filter(c=>!bh.has(c)).map(c=>Gn.join(t,c));if(i.length===0)throw new Error(`No run groups found in results path: ${t}`);Jt.mkdirSync(e,{recursive:!0});let a=Gn.join(i[0],"metadata.json"),s={...uo.parse(JSON.parse(Jt.readFileSync(a,"utf-8"))),id:n};for(let c of i){let d=Gn.join(c,"runs");if(!Jt.existsSync(d))continue;let m=Jt.readdirSync(d);for(let p of m){if(bh.has(p))continue;let h=Gn.join(d,p),f=Gn.join(e,"runs",p);Jt.cpSync(h,f,{recursive:!0})}let u=Gn.join(c,"metadata.json"),g=uo.parse(JSON.parse(Jt.readFileSync(u,"utf-8")));o.info({oldRunGroupId:g.id},"Merging metadata from run groups");for(let p in g){if(p==="id")continue;let h=p;s[h]=iR(s,g,h)}}let l=Gn.join(e,"metadata.json");Jt.writeFileSync(l,JSON.stringify(s,null,2))}import Th from"adm-zip";import zl from"fs";import{z as Dl}from"zod";var k="v1",Ao="0.0.8";var aR=9e4,sR=3,lR=1500,cR=15e3,Lt=class extends Error{status;rawError;constructor(e,t,n,o={}){super(n,o),this.status=e,this.rawError=t}};async function dR(r){return r.text().then(e=>{try{return JSON.parse(e).error}catch{return e}})}var _l=class{baseUrl;logger;constructor(e){this.baseUrl=e.baseUrl,this.logger=e.logger}getHeaders(){let e={"Content-Type":"application/json"};return Ao&&(e[lm]=Ao),e}async sendRequest(e,t){let{retries:n=sR,requestTimeoutMs:o=aR,initialRetryDelayMs:i=lR,maxRetryDelayMs:a=cR}=t,s=n,l=n,c,d={path:e,baseUrl:this.baseUrl,method:t.method};for(;s>0;)try{return s--,await this.sendSingleRequestHelper(e,t,o)}catch(m){if(c=m,m instanceof Lt&&m.status>=400&&m.status<500)throw m;if(m instanceof Error&&m.name==="AbortError"&&(c=new pn),s===0)throw c;let u=l-s,g=Math.min(i*Math.pow(2,u-1),a);await new Promise(p=>setTimeout(p,g))}throw this.logger.warn({...d,err:c},"Got fatal error response from Momentic server"),c}async sendSingleRequestHelper(e,t,n){let o={path:e,baseUrl:this.baseUrl,method:t.method},i=new AbortController,a=setTimeout(()=>i.abort(),n),s=()=>i.abort();t.signal&&t.signal.addEventListener("abort",s);let l={...this.getHeaders(),...t.extraHeaders};try{let c=await fetch(`${this.baseUrl}${e}`,{method:t.method,body:t.body?JSON.stringify(t.body):void 0,headers:l,signal:i.signal});if(!c.ok){let m=await dR(c);throw new Lt(c.status,m,`Request to ${t.method} ${e} failed with status ${c.status}: ${m}`)}let d;if(c.status===204)d={};else{let m=await c.text();try{d=JSON.parse(m)}catch{d=m}}return this.logger&&t.logResponse===!0&&d&&this.logger.debug({result:d,status:c.status,...o},"Got response from Momentic server"),d}finally{clearTimeout(a),t.signal&&t.signal.removeEventListener("abort",s)}}},$t=class extends _l{apiKey;constructor(e){super(e),this.apiKey=e.apiKey}getHeaders(){return{...super.getHeaders(),Authorization:`Bearer ${this.apiKey}`}}};var Ot=class extends $t{constructor(e){super(e)}getAppUrl(){return this.baseUrl==="http://localhost:8000"?"http://localhost:3000":this.baseUrl.replace(/\/\/api/,"//app")}async getAuthInfo(){let e=await this.sendRequest(`/${k}/auth/check`,{method:"GET",retries:10,requestTimeoutMs:5e3});return Rm.parse(e)}async bulkGetRunStatus(e){let t=await this.sendRequest(`/${k}/runs/status`,{method:"POST",body:e,retries:3,requestTimeoutMs:1e4});return Tm.parse(t)}async getTestYAMLExport(e){let t=await this.sendRequest(`/${k}/tests/export`,{method:"POST",body:e,retries:3,requestTimeoutMs:3e4});return gm.parse(t)}async updateStepCaches(e,t){await this.sendRequest(`/${k}/cache`,{method:"PATCH",body:e,extraHeaders:t,retries:3,requestTimeoutMs:1e4,initialRetryDelayMs:3e3})}async getStepCacheForTest(e,t){let n=await this.sendRequest(`/${k}/cache`,{method:"POST",body:e,extraHeaders:t,retries:10,requestTimeoutMs:3e4,initialRetryDelayMs:3e3});return fm.parse(n)}async updateMobileStepCaches(e,t){await this.sendRequest(`/${k}/mobile-cache`,{method:"PATCH",body:e,extraHeaders:t,retries:3,requestTimeoutMs:1e4,initialRetryDelayMs:3e3})}async getMobileStepCacheForTest(e,t){let n=await this.sendRequest(`/${k}/mobile-cache`,{method:"POST",body:e,extraHeaders:t,retries:10,logResponse:!0,requestTimeoutMs:3e4,initialRetryDelayMs:3e3});return Sm.parse(n)}async queueTests(e){let t=await this.sendRequest(`/${k}/tests/queue`,{method:"POST",body:e,retries:3,requestTimeoutMs:1e4});return pm.parse(t)}async uploadScreenshot(e){let t=await this.sendRequest(`/${k}/screenshots`,{method:"POST",body:e,retries:3,requestTimeoutMs:5e3});return vm.parse(t)}async getAllEnvironments(){let e=await this.sendRequest(`/${k}/environments`,{method:"GET",retries:3,requestTimeoutMs:5e3});return Am.parse(e)}async acquireCacheLock(e,t){let n=await this.sendRequest(`/${k}/result-cache/lock`,{method:"POST",body:e,signal:t,retries:3,requestTimeoutMs:3e4});return Bm.parse(n)}async releaseCacheLock(e){await this.sendRequest(`/${k}/result-cache/lock`,{method:"DELETE",body:{key:e},retries:3,requestTimeoutMs:5e3})}async deleteCacheResult(e){await this.sendRequest(`/${k}/result-cache/entry`,{method:"DELETE",body:e,retries:3,requestTimeoutMs:5e3})}async setCacheResult(e){await this.sendRequest(`/${k}/result-cache/entry`,{method:"PATCH",body:e,retries:3,requestTimeoutMs:5e3})}async getCacheResult(e){try{return await this.sendRequest(`/${k}/result-cache/entry`,{method:"POST",body:e,retries:3,requestTimeoutMs:5e3})}catch(t){if(t instanceof Error&&t.message.includes("404"))return null;throw t}}async queueSuiteRuns(e){let t=await this.sendRequest(`/${k}/suites/queue`,{method:"POST",body:e,retries:3,requestTimeoutMs:5e3});return xm.parse(t)}async bulkGetRunGroupStatus(e){let t={runGroupIds:e},n=await this.sendRequest(`/${k}/run-groups/status`,{method:"POST",body:t,retries:3,requestTimeoutMs:5e3});return td.array().parse(n)}async uploadProposedSteps(e,t){try{await this.sendRequest(`/${k}/test-fragments/`,{method:"POST",body:e,retries:3,requestTimeoutMs:1e4})}catch(n){t.error({err:n},"Failed to upload proposed steps")}}async reportBillableEvents(e,t){try{await this.sendRequest(`/${k}/billing/events`,{method:"POST",body:t,retries:10,requestTimeoutMs:1e4})}catch(n){e.error({err:n},"Failed to report billable event")}}async fetchTestFragment(e){let t=await this.sendRequest(`/${k}/test-fragments/${e}`,{method:"GET",retries:3,requestTimeoutMs:1e4});return Im.parse(t)}async patchTestFragment(e,t){await this.sendRequest(`/${k}/test-fragments/${e}`,{method:"PATCH",body:t,retries:3,requestTimeoutMs:1e4})}async getPastTestResults(e,t){let n=await this.sendRequest(`/${k}/results/tests/${e}`,{method:"POST",body:t,retries:3,requestTimeoutMs:1e4});return Pm.parse(n)}async generateTestResultsUploadUrl(){let e=await this.sendRequest(`/${k}/results/uploads`,{method:"POST",retries:3,requestTimeoutMs:1e4});return Mm.parse(e)}async startProcessingResultsUpload(e,t){let n=await this.sendRequest(`/${k}/results/uploads/${e}/process`,{method:"POST",body:t,retries:3,requestTimeoutMs:1e4});return Lm.parse(n)}async fetchIconKnowledgeBase(e){try{let t=await this.sendRequest(`/${k}/knowledge-base/icons`,{method:"GET",retries:3,requestTimeoutMs:5e3});return $m.parse(t)}catch(t){return e.error({err:t},"Failed to fetch icon knowledge base"),null}}async saveNewIcons(e,t){try{await this.sendRequest(`/${k}/knowledge-base/icons`,{method:"POST",body:e,retries:3,requestTimeoutMs:5e3})}catch(n){t.error({err:n},"Failed to save new icons to icon knowledge base")}}async getMergeBaseCommitFromGithub(e,t,n,o){let i=new URLSearchParams;i.set("base",n),i.set("head",o);let a=await this.sendRequest(`/${k}/git/github/${e}/${t}/merge-base-commit?${i.toString()}`,{method:"GET",retries:3,requestTimeoutMs:1e4});return mo.parse(a)}async getCommitFromGithub(e,t,n){let o=await this.sendRequest(`/${k}/git/github/${e}/${t}/commits/${n}`,{method:"GET",retries:3,requestTimeoutMs:1e4});return mo.parse(o)}async getMergeBaseCommitFromGitlab(e,t,n){let o=new URLSearchParams;o.set("base",t),o.set("head",n);let i=await this.sendRequest(`/${k}/git/gitlab/${e}/merge-base-commit?${o.toString()}`,{method:"GET",retries:3,requestTimeoutMs:5e3});return mo.parse(i)}async getCommitFromGitlab(e,t){let n=await this.sendRequest(`/${k}/git/gitlab/${e}/commits/${t}`,{method:"GET",retries:3,requestTimeoutMs:1e4});return mo.parse(n)}async getAgentConfig(){let e=await this.sendRequest(`/${k}/web-agent/agent-config`,{method:"GET",retries:3,requestTimeoutMs:5e3});return Dl.record(Dl.string(),Dl.string()).parse(e)}async getQuarantinedTests(){let e=await this.sendRequest(`/${k}/quarantine`,{method:"GET"});return Om.parse(e)}async quarantineTest(e,t,n){await this.sendRequest(`/${k}/quarantine`,{method:"POST",body:{testId:e.id,testName:e.name,reason:t,...n??{}},retries:3,requestTimeoutMs:1e4})}async unquarantineTest(e,t,n){await this.sendRequest(`/${k}/quarantine/${e.id}`,{method:"DELETE",body:{testName:e.name,reason:t,...n??{}},retries:3,requestTimeoutMs:1e4})}async createAndroidEmulator(e){let t=await this.sendRequest(`/${k}/limbar/android`,{method:"POST",retries:3,body:e,requestTimeoutMs:9e4,initialRetryDelayMs:5e3,maxRetryDelayMs:15e3});return Nm.parse(t)}async extendAndroidEmulatorTtl(e){try{await this.sendRequest(`/${k}/limbar/android/${e}/keepalive`,{method:"POST",retries:3,requestTimeoutMs:15e3})}catch{}}async generateAndroidAssetUrls({channel:e,tag:t,md5:n}){let o={channel:e,tag:t,md5:n},i=await this.sendRequest(`/${k}/limbar/android/upload-url`,{method:"POST",retries:3,body:o,requestTimeoutMs:15e3,logResponse:!0});return km.parse(i)}async deleteAndroidEmulator(e){await this.sendRequest(`/${k}/limbar/android/${e}`,{method:"DELETE",retries:3,requestTimeoutMs:3e4})}async getAndroidAssets(){let e=await this.sendRequest(`/${k}/limbar/assets`,{method:"GET",retries:3,requestTimeoutMs:1e4});return _m.parse(e)}async deleteAndroidAsset(e,t){await this.sendRequest(`/${k}/limbar/assets/${e}/${t}`,{method:"DELETE",retries:3,requestTimeoutMs:1e4})}};async function Fl(r){let e=process.versions.node,t=parseInt(e.split(".")[0]);(isNaN(t)||t<18)&&(E.error(`Node.js version 20 or higher is required to run the CLI. Detected: ${process.versions.node}.`),process.exit(1)),E.debug(`Identified node version ${e}`);let n=await r.client.getAuthInfo();return E.debug("Got auth info from API"),n}var xr=class{constructor(e,t){this.client=e;this.orgId=t}async acquireCacheLock(e,t){return this.client.acquireCacheLock(e,t)}async uploadScreenshot(e){return(await this.client.uploadScreenshot({screenshot:e.toString("base64")})).key}async releaseCacheLock(e){return this.client.releaseCacheLock(e)}async deleteCacheResult(e){return this.client.deleteCacheResult(e)}async setCacheResult(e){return this.client.setCacheResult(e)}async getCacheResult(e){return this.client.getCacheResult(e)}fetchIconKnowledgeBase(e){return this.client.fetchIconKnowledgeBase(e)}saveNewIcons(e,t){return this.client.saveNewIcons(e,t)}};import{Faker as mR,en as uR}from"@faker-js/faker";var Ir="v1",Pr=class{httpClient;fakerInstance;type="API_CLIENT";sms={send:this.sendSms.bind(this),fetchLatest:this.fetchLatestSms.bind(this)};email={send:this.sendEmail.bind(this),fetchLatest:this.fetchLatestEmail.bind(this),fetchAll:this.fetchAllEmails.bind(this)};ai={generate:this.sendAiGenerate.bind(this)};constructor(e){this.httpClient=e.httpClient,e.fakerSeed&&(this.fakerInstance=new mR({locale:uR}),this.fakerInstance.seed(e.fakerSeed))}async sendAiGenerate(e){let t=typeof e=="string"?{input:e}:e;return this.httpClient.sendRequest(`/${Ir}/tools/ai/generate`,{method:"POST",body:t}).catch(n=>{throw n instanceof Lt?new Error(n.rawError):new Error(`Failed to send AI generation: ${n.message}`)})}async sendSms(e){return this.httpClient.sendRequest(`/${Ir}/tools/sms/send`,{method:"POST",body:e}).then(()=>{}).catch(t=>{throw t instanceof Lt?new Error(t.rawError):new Error(`Failed to send sms: ${t.message}`)})}async fetchLatestSms(e){return this.httpClient.sendRequest(`/${Ir}/tools/sms/fetchLatest`,{method:"POST",body:e}).catch(t=>{throw t instanceof Lt?new Error(t.rawError):t})}async sendEmail(e){return this.httpClient.sendRequest(`/${Ir}/tools/email/send`,{method:"POST",body:e}).then(()=>{}).catch(t=>{throw t instanceof Lt?new Error(t.rawError):new Error(`Failed to send email: ${t.message}`)})}async fetchAllEmails(e){return this.httpClient.sendRequest(`/${Ir}/tools/email/fetchAll`,{method:"POST",body:e}).catch(t=>{throw t instanceof Lt?new Error(t.rawError):new Error(`Failed to fetch all emails: ${t.message}`)})}async fetchLatestEmail(e){return this.httpClient.sendRequest(`/${Ir}/tools/email/fetchLatest`,{method:"POST",body:e}).catch(t=>{throw t instanceof Lt?new Error(t.rawError):new Error(`Failed to fetch latest emails: ${t.message}`)})}};import{z as pR}from"zod";var $n=class extends $t{agentConfig;constructor(e,t){super(t),this.agentConfig=e}getAgentConfig(){return this.agentConfig}async rankChunksWithAi(e,t){let n={...e,loggerTags:t.loggerTags},o=await this.sendRequest(`/${k}/web-agent/recommend-chunks-ai`,{method:"POST",body:n,signal:t.abortSignal});return ad.parse(o)}async rankChunksWithRag(e,t){let n=await this.sendRequest(`/${k}/web-agent/recommend-chunks`,{method:"POST",body:{cliVersion:Ao,...e},signal:t.abortSignal});return id.parse(n)}async getScreenshotFromS3(e){let t=await this.sendRequest(`/${k}/s3/visual-diff-screenshot`,{method:"POST",body:{url:e}});return pR.string().parse(t)}async getElementLocation(e,t){let n={...e,disableCache:t.disableCache,loggerTags:t.loggerTags,useMemory:t.useMemory,agentConfigVersion:this.agentConfig?.locator},o=await this.sendRequest(`/${k}/web-agent/locate-element`,{method:"POST",body:n,signal:t.abortSignal});return mm.parse(o)}async getAssertionResult(e,t){let n={...e,disableCache:!!t.disableCache,useConsensus:!!t.useConsensus,attemptNumber:t.attemptNumber,loggerTags:t.loggerTags,useMemory:t.useMemory,agentConfigVersion:this.agentConfig?.assertion},o=await this.sendRequest(`/${k}/web-agent/assertion`,{method:"POST",body:n,signal:t.abortSignal});return vs.parse(o)}async getLintStepResult(e,t){let n={...e,disableCache:!!t.disableCache,loggerTags:t.loggerTags},o=await this.sendRequest(`/${k}/web-agent/lint/step`,{method:"POST",body:n,signal:t.abortSignal});return dm.parse(o)}async getVisualAssertionResult(e,t){let n={...e,disableCache:!!t.disableCache,useConsensus:!!t.useConsensus,attemptNumber:t.attemptNumber,loggerTags:t.loggerTags,useMemory:t.useMemory,agentConfigVersion:this.agentConfig?.["visual-assertion"]},o=await this.sendRequest(`/${k}/web-agent/visual-assertion`,{method:"POST",body:n,signal:t.abortSignal});return vs.parse(o)}async getAiActionCommand(e,t){let n=await this.sendRequest(`/${k}/web-agent/next-command-dynamic`,{method:"POST",body:{...e,disableCache:t.disableCache,loggerTags:t.loggerTags},signal:t.abortSignal});return cm.parse(n)}async getMultiturnAiActionCommand(e,t){return await this.sendRequest(`/${k}/web-agent/ai-action/next-command`,{method:"POST",body:{...e,disableCache:t.disableCache,loggerTags:t.loggerTags},signal:t.abortSignal})}async getMultiturnAiActionEvaluation(e,t){let n=await this.sendRequest(`/${k}/web-agent/ai-action/evaluate`,{method:"POST",body:{...e,disableCache:t.disableCache,loggerTags:t.loggerTags},signal:t.abortSignal});return Ha.parse(n)}async getReverseMappedDescription(e,t){let n=await this.sendRequest(`/${k}/web-agent/reverse-mapped-description`,{method:"POST",body:{...e,disableCache:t.disableCache,loggerTags:t.loggerTags},signal:t.abortSignal});return um.parse(n)}async getTextExtraction(e,t){let n={...e,disableCache:t.disableCache,loggerTags:t.loggerTags,agentConfigVersion:this.agentConfig?.["text-extraction"]},o=await this.sendRequest(`/${k}/web-agent/text-extraction`,{method:"POST",body:n,signal:t.abortSignal});return za.parse(o)}async getTestResultClassification(e,t){let n=await this.sendRequest(`/${k}/web-agent/result-classification`,{method:"POST",body:{...e,loggerTags:t.loggerTags},signal:t.abortSignal});return Va.parse(n)}async getExtractedKeywords(e,t){let n=await this.sendRequest(`/${k}/web-agent/extract-keywords`,{method:"POST",body:{goal:e,disableCache:t.disableCache,context:e},signal:t.abortSignal});return hd.parse(n)}async getAutohealingProposal(e,t){let n=await this.sendRequest(`/${k}/web-agent/autoheal-section`,{method:"POST",body:{...e,loggerTags:t.loggerTags},signal:t.abortSignal});return Dc.parse(n)}async getFailureRecoveryProposal(e,t){let n=await this.sendRequest(`/${k}/web-agent/failure-recovery`,{method:"POST",body:{...e,loggerTags:t.loggerTags},signal:t.abortSignal});return zc.parse(n)}async getFailureRecoveryPlan(e,t){let n=await this.sendRequest(`/${k}/web-agent/failure-recovery-plan`,{method:"POST",body:{...e,loggerTags:t.loggerTags},signal:t.abortSignal});return Uc.parse(n)}async getIframeRegex(e,t){let n=await this.sendRequest(`/${k}/web-agent/iframe-regex`,{method:"POST",body:e,signal:t.abortSignal});return uc.parse(n)}};var Mr=class extends $t{generator;constructor(e,t){super(e),this.generator=t}async runTemplateMatching(e,t={}){let n=await this.sendRequest(`/${k}/web-agent/template-matching`,{method:"POST",body:e,signal:t?.signal});return pc.parse(n)}async constructIframeRegex(e,t={}){return this.generator.getIframeRegex(e,{abortSignal:t.signal})}};function wh(r,e,t){return fetch(r,{method:"PUT",body:t,headers:{"Content-Type":e}})}var ma=class{constructor(e){this.client=e}async uploadResultsArchive(e,t){let{uploadUrl:n,id:o}=await this.client.generateTestResultsUploadUrl(),i=await wh(n,"application/zip",t);if(!i.ok)throw new Error(`Failed to upload test results: ${await i.text()}`);let{runGroupId:a}=await this.client.startProcessingResultsUpload(o,{runGroupId:e});return a}};var Lr=class extends $t{constructor(e){super(e)}async getElementLocation(e,t){let n={...e,loggerTags:t.loggerTags},o=await this.sendRequest(`/${k}/mobile-agent/locate-element`,{method:"POST",body:n,signal:t.abortSignal});return Sd.parse(o)}async evaluateAssertion(e,t){let n={...e,loggerTags:t.loggerTags},o=await this.sendRequest(`/${k}/mobile-agent/assertion`,{method:"POST",body:n,signal:t.abortSignal});return wd.parse(o)}};function ua({orgId:r,client:e,gitMetadata:t,alwaysSaveCache:n,noCache:o}){return o?new Ti:new Ul(r,e,t,n)}var Ul=class{constructor(e,t,n,o){this.orgId=e;this.client=t;let{gitBranchName:i,gitProtectedBranches:a}=n;this.cacheHeaders=Rs(n),o?this.writeCaches=!0:i?this.writeCaches=!a.includes(i):this.writeCaches=!0}cacheHeaders;writeCaches;async saveEntries({entries:e,testId:t,logger:n}){if(!this.writeCaches){n.debug("Skipping cache storage because branch is protected");return}try{await this.client.updateMobileStepCaches({entries:e,testId:t},this.cacheHeaders)}catch(o){n.error({err:o},"Failed to save mobile step cache entries")}}async resolveEntries(e){let{steps:t}=e.stepLists,n=await this.client.getMobileStepCacheForTest({testId:e.testId},this.cacheHeaders);if(!this.writeCaches){e.logger.debug("Skipping mobile cache last used at update because branch is protected");return}for(let i of[t])i&&gu({steps:i,stepCacheEntries:n,logger:e.logger});let{cachesToSave:o}=await fr({steps:t,cacheCreationParams:{testId:e.testId,orgId:this.orgId}});this.client.updateMobileStepCaches({entries:o,testId:e.testId},this.cacheHeaders)}};import pa from"path";function gR(r){let e=new Th,t=pa.join(r,"metadata.json"),n=uo.parse(JSON.parse(zl.readFileSync(t,"utf-8")));e.addLocalFile(t);for(let o of zl.readdirSync(pa.join(r,"runs"))){if(!o.endsWith(".zip"))continue;let i=o.replace(/\.zip$/,""),a=new Th(pa.join(r,"runs",o));for(let s of a.getEntries())s.isDirectory||e.addFile(pa.join("runs",i,s.entryName),s.getData())}return{runGroupId:n.id,buffer:e.toBuffer()}}async function ga(r){let{client:e,consoleLogger:t,resultsPath:n}=r;if(!zl.existsSync(n)){t.warn("Results path does not exist, skipping upload.");return}let o=new ma(e);try{let{runGroupId:i,buffer:a}=gR(n),s=await o.uploadResultsArchive(i,a),l=`${e.getAppUrl()}/run-groups/${s}`;t.success(`Successfully uploaded test results. Once processed, your results can be found at ${l}`);return}catch(i){let a;i instanceof Error?a=i.message:typeof i=="string"?a=i:a="Unknown error",t.error(`Failed to upload test results: ${a}.`);return}}import hR from"adm-zip";import it from"fs";import Nt from"path";var xo=class r{constructor(e){this.filePath=e;it.rmSync(this.filePath,{recursive:!0,force:!0}),it.mkdirSync(this.filePath,{recursive:!0})}cd(e){return new r(Nt.join(this.filePath,e))}mkdir(e){it.mkdirSync(Nt.join(this.filePath,e),{recursive:!0})}readFile(e){let t=Nt.join(this.filePath,e);if(it.existsSync(t))return it.readFileSync(t)}storeFile(e){let{name:t,contents:n}=e,o=Nt.join(this.filePath,t);try{it.writeFileSync(o,n)}catch{}}createRunArchive(e){return new Bl(Nt.join(this.filePath,"runs"),e)}},Bl=class{constructor(e,t){this.filePath=e;this.tempPath=Nt.join(e,`.${t}`),this.finalPath=Nt.join(e,`${t}.zip`),it.rmSync(this.tempPath,{recursive:!0,force:!0}),it.rmSync(this.finalPath,{recursive:!0,force:!0}),it.mkdirSync(this.tempPath,{recursive:!0})}tempPath;finalPath;readFile(e){let t=Nt.join(this.tempPath,e);if(it.existsSync(t))return it.readFileSync(t)}mkdir(e){it.mkdirSync(Nt.join(this.tempPath,e),{recursive:!0})}cd(e){return new xo(Nt.join(this.tempPath,e))}storeFile(e){let{name:t,contents:n}=e,o=Nt.join(this.tempPath,t);it.writeFileSync(o,n)}close(){let e=new hR;e.addLocalFolder(this.tempPath,void 0,n=>n!==".DS_Store");let t=e.toBuffer();it.writeFileSync(this.finalPath,t),it.rmSync(this.tempPath,{recursive:!0,force:!0})}};import Ea from"fs";import Nh from"body-parser";import LR from"cors";import OR from"dedent";import{Router as yR}from"express";import st from"fs";import{globSync as wR}from"glob";import je from"path";import Gl from"fs";import fR from"path";var SR=new Ni(30,60*1e3),Wl="https://api.momentic.ai",$l,Ch=r=>{Wl=r},Vl=()=>Wl,ql=()=>$l;var Or,jl,Eh,vh=async r=>{if($l&&Or&&Eh)return Or;let e=new Ot({baseUrl:Wl,apiKey:r,logger:E});$l=e;try{let t=await e.getAuthInfo();return Or=t.orgId,jl=t.userId,Eh=r,Or}catch(t){throw new Error(`Error checking API key against server: ${t}`)}},Io=()=>{if(!Or)throw new Error("Your organization ID is invalid.");return Or},ha=()=>{if(!jl)throw new Error("Your user ID is invalid.");return jl};var Kl,Hl,Rh=(r,e)=>{Kl=r,Hl?.abort(),Hl=new AbortController;let t=Hl.signal,n=[r.configFilePath];r.config.environments?.forEach(o=>{if(!o.envFile)return;let i=fR.resolve(r.rootDir,o.envFile);try{if(Gl.lstatSync(i).isSymbolicLink())return;Gl.existsSync(i)&&n.push(i)}catch(a){E.warn({err:a},`Failed to check if env file ${i} exists`)}});try{bR({filesToWatch:n,revalidator:e,signal:t,project:r})}catch(o){E.error({err:o},"Failed to start config file watchers")}},xt=()=>Kl;function bR({filesToWatch:r,revalidator:e,signal:t,project:n}){r.forEach(o=>{Gl.watch(o,{signal:t,persistent:!1,recursive:!1},(i,a)=>{a&&(SR.increment("setLocalProject")&&E.warn(`A file change under the ${n.rootDir} directory has caused Momentic to reload its configuration more than 30 times in the last minute. Rapid changes to files may indicate your momentic.config.yaml 'include' glob is incorrect. Please ensure temporary, library, and auto-generated files are not included in Momentic's context.`),Kl=e(n.configFilePath))})})}function at(r){return function(...e){let t=e[e.length-1],n=r(...e);Promise.resolve(n).catch(t)}}var Mo=yR();function Po(r){let e=xt(),t=je.dirname(e.configFilePath);return je.join(t,...r)}function TR(r){let e=xt(),t=je.dirname(e.configFilePath),n=je.relative(t,r);return n?n.split(je.sep):[]}function ER(r,e){let t=st.statSync(r),n=TR(r);return ys.parse({name:e,absolutePath:r,relativePath:n.join(je.sep),pathSegments:n,isDirectory:t.isDirectory(),size:t.size,createdAt:t.birthtime,modifiedAt:t.mtime,accessedAt:t.atime})}Mo.post("/",at(async(r,e,t)=>{let n;try{n=em.parse(r.body).pathSegments}catch(m){e.status(400).json({error:`Failed to parse folder read body: ${m}`});return}let o=Po(n);if(!st.existsSync(o)){e.status(404).json({error:`Path not found: ${n.join(je.sep)}`});return}if(!st.statSync(o).isDirectory()){e.status(400).json({error:`Path is not a directory: ${n.join(je.sep)}`});return}let a=xt(),s=Array.from(a.config.exclude??[]).concat(hi),c=wR("*",{absolute:!1,cwd:o,ignore:s,dotRelative:!1,maxDepth:1,nodir:!1}).map(m=>{let u=je.join(o,m);return ER(u,m)}),d={absolutePath:o,pathSegments:n,contents:c};e.status(200).json(d)}));Mo.put("/",at(async(r,e,t)=>{let n;try{n=tm.parse(r.body).pathSegments}catch(a){e.status(400).json({error:`Failed to parse folder create body: ${a}`});return}let o=Po(n);if(st.existsSync(o)){e.status(200).json({success:!0,message:`Folder already exists: ${n.join(je.sep)}`,pathSegments:n});return}st.mkdirSync(o,{recursive:!0});let i={success:!0,message:`Folder created: ${n.join(je.sep)}`,pathSegments:n};e.status(201).json(i)}));Mo.patch("/",at(async(r,e,t)=>{let n,o;try{let c=nm.parse(r.body);n=c.pathSegments,o=c.newPathSegments}catch(c){e.status(400).json({error:`Failed to parse folder update body: ${c}`});return}let i=Po(n),a=Po(o);if(!st.existsSync(i)){e.status(400).json({error:`Folder not found: ${n.join(je.sep)}`});return}if(st.existsSync(a)){e.status(400).json({error:`Destination already exists: ${o.join(je.sep)}`});return}let s=je.dirname(a);st.existsSync(s)||st.mkdirSync(s,{recursive:!0}),st.renameSync(i,a);let l={success:!0,message:`Folder moved from ${n.join(je.sep)} to ${o.join(je.sep)}`,pathSegments:o};e.status(200).json(l)}));Mo.delete("/",at(async(r,e,t)=>{let n,o=!0;try{let l=rm.parse(r.body);n=l.pathSegments,o=l.recursive??!0}catch(l){e.status(400).json({error:`Failed to parse folder delete body: ${l}`});return}let i=Po(n);if(!st.existsSync(i)){e.status(200).json({success:!0,message:`Folder not found: ${n.join(je.sep)}`,pathSegments:n});return}if(!st.statSync(i).isDirectory()){e.status(400).json({error:`Path is not a directory: ${n.join(je.sep)}`});return}if(o)st.rmSync(i,{recursive:!0,force:!0});else{if(st.readdirSync(i).length>0){e.status(409).json({error:`Cannot delete non-empty directory without recursive flag: ${n.join("/")}`});return}st.rmdirSync(i)}let s={success:!0,message:`Folder deleted: ${n.join("/")}`,pathSegments:n};e.status(200).json(s)}));var Yl=Mo;import NR from"events";import kh,{Router as kR}from"express";import _R from"http";import DR from"path";var fa=class{sessions=new Map;registerSession(e,t){this.sessions.set(e,t)}getSession(e){return this.sessions.get(e)}async removeSession(e,t){let n=this.sessions.get(e);if(n)try{await n.cleanup?.(),t.info({sessionId:e,emulatorName:n.emulatorName},"Android emulator cleaned up")}catch(o){t.error({err:o},"Error during Android session cleanup")}finally{this.sessions.delete(e)}}async removeAllSessions(e){let t=Array.from(this.sessions.keys());await Promise.all(t.map(n=>this.removeSession(n,e)))}};var Ah=new fa;import{Router as RR}from"express";import{existsSync as AR}from"fs";import xR from"path";import{hostname as CR}from"os";var vR="0.0.8",Sn=na({app:"mobile-desktop-server",hostname:CR(),disableConsoleLogs:!0}).child({cliVersion:vR});var Xl=RR();Xl.get("/",at(async(r,e)=>{let t=ql();if(!t){e.status(500).json({message:"API client not initialized"});return}let n=await t.getAndroidAssets();e.status(200).json(n)}));Xl.post("/upload-url",at(async(r,e)=>{let t;try{t=am.parse(r.body)}catch(i){e.status(400).json({error:`Invalid request body: ${i}`});return}let n=ql();if(!n){e.status(500).json({error:"API client not initialized"});return}let o=xR.resolve(t.filePath);if(!AR(o)){e.status(400).json({error:`File not found: ${o}`});return}await Do({tag:t.tag,channel:t.channel,filePath:o,apiClient:n,logger:Sn}),e.sendStatus(204)}));var xh=Xl;import{Router as IR}from"express";var Ih=IR();Ih.get("/",at(async(r,e)=>{let t=xt(),n=aa(t,E),o=new Set;n?.tests&&Object.values(n.tests).forEach(l=>{l.labels?.forEach(c=>o.add(c))});let i=Array.from(o).sort(),a=Object.values(n.mobileTests),s={labels:i,modules:[],tests:a};e.status(200).json(s)}));var Ph=Ih;import{Router as PR}from"express";var Mh=PR();Mh.get("/",(r,e)=>{e.status(200).json({userId:ha(),orgId:Io()})});var Lh=Mh;import{Router as MR}from"express";import Jl from"path";var Sa=MR();Sa.patch("/:testPath",at(async(r,e)=>{let t=r.params.testPath;if(!t){e.status(400).json({error:"Missing testPath in path"});return}let n;try{n=im.parse(r.body)}catch(a){e.status(400).json({error:`Invalid request body: ${a}`});return}if(n.steps===void 0&&n.settings===void 0){e.status(400).json({error:"At least one of steps or settings is required"});return}let o=t.endsWith(`.${$e.TEST}`)?t:`${t}.${$e.TEST}`;await fh({filePath:o,steps:n.steps,settings:n.settings});let i={message:"ok"};e.status(200).json(i)}));Sa.post("/",at((r,e)=>{let t;try{t=om.parse(r.body)}catch(h){e.status(400).json({error:`Invalid request body: ${h}`});return}let{name:n,description:o,steps:i,settings:a,pathSegments:s}=t;if(!n||typeof n!="string"){e.status(400).json({error:"Missing or invalid 'name' in body"});return}try{as(n)}catch(h){e.status(400).json({error:h.message});return}let l=xt(),c=Jl.join(l.rootDir,...s),d=hh({name:n,description:o,steps:i,settings:a,folder:c}),m=Jl.basename(d),u=kl(m,c),g=Jl.relative(l.rootDir,d),p={id:u.id,fileName:m,fullPath:d,relativeFilePath:g};e.status(201).json(p)}));Sa.get("/:fileName",at((r,e)=>{let t=r.params.fileName;if(!t){e.status(400).json({error:"Missing fileName in path"});return}let n=t.endsWith(`.${$e.TEST}`)?t:`${t}.${$e.TEST}`,i=kl(n);e.status(200).json(i)}));var Oh=Sa;var ba=class extends xr{constructor(t,n){super(t,n);this.client=t;this.orgId=n}async fetchEnvironment(t,n){let o=xt();return ia(t,o,E)}};var _h="10mb";async function Dh(r){let{serverPort:e,apiKey:t,momenticServerUrl:n,staticDir:o,initialProject:i,logLevel:a}=r;n&&Ch(n),await vh(t);let s=Io(),l=ha(),c=r.logger.child({orgId:s,userId:l});Rh(i,w=>Ro({configFilePath:w}));let d=kh();d.use(LR()),d.use(Nh.json({limit:_h})),d.use(Nh.urlencoded({extended:!1,limit:_h}));let m=kR();if(m.use("/folders",Yl),m.use("/entities",Ph),m.use("/identify",Lh),m.use("/mobile-tests",Oh),m.use("/assets",xh),d.use("/api",m),d.use((w,A,I)=>{E.debug({url:w.url,path:w.path,query:w.query,method:w.method,body:w.body,headers:w.rawHeaders,client:w.ip},"Incoming request on mobile-desktop-server"),A.on("close",()=>{A.statusCode>=400&&(E.error({url:w.url,method:w.method,statusCode:A.statusCode},"Request completed in error on mobile-desktop-server"),Sn.error({url:w.url,method:w.method,statusCode:A.statusCode},"Request completed in error on mobile-desktop-server"))}),I()}),d.use((w,A,I,C)=>{if(w instanceof Error&&w.message.includes("BadRequestError: request aborted")){I.status(400).send("Client disconnected");return}E.error({stack:w.stack,msg:w.message,err:w,url:A.url,method:A.method},"Unhandled exception leading to 500 on mobile-desktop-server"),Sn.error({stack:w.stack,msg:w.message,err:w,url:A.url,method:A.method},"Unhandled exception leading to 500 on mobile-desktop-server"),I.status(500).send(`Internal Server Error: ${w.message}`)}),o){let w=kh.static(o,{setHeaders:A=>{A.setHeader("Cache-Control","no-cache")},redirect:!1});d.use(w),d.use("*",(A,I)=>{I.sendFile(DR.join(o,"index.html"))})}let u=_R.createServer(d),g=`http://localhost:${e}`;await new Promise(w=>{try{u.listen(e,()=>{c.info(`Mobile desktop server is running at ${g}`),w()})}catch(A){A.message.includes("EADDRINUSE")?FR(e):E.error(`An unexpected error occurred while starting the server: ${A.message}`),process.exit(1)}});let h={type:"API_KEY",baseUrl:Vl(),apiKey:t,logger:c},f=new Ot(h),{dispose:y}=$g({baseServer:u,logger:c,authorization:h,globalStateManager:Ah,getOrgId:()=>Io(),androidDriverFactory:({socket:w,logger:A,creationOpts:I})=>_o({logger:A,logLevel:a,apiClient:f,creationOpts:I,socket:w,onStatusUpdate:C=>{w.emit("connectionStatusUpdate",{message:C})}}),mobileGeneratorFactory:async w=>new Lr({baseUrl:Vl(),apiKey:t,logger:c}),browserGeneratorFactory:async w=>new $n({},h),browserEnricherFactory:async w=>new Mr(h,new $n({},h)),storageFactory:async w=>new ba(f,w),cacheStorageFactory:async w=>{let A=xt(),I=await la(c,f,A);return ua({orgId:w,client:f,gitMetadata:I})},localToolsFactory:async()=>new Pr({httpClient:f,fakerSeed:void 0}),keepSessionAlive:w=>f.extendAndroidEmulatorTtl(w)});process.once("SIGUSR2",async()=>{return;try{await y()}catch(w){E.error({err:w},"Error during session dispose on SIGUSR2")}try{u.close(()=>{process.kill(process.pid,"SIGUSR2")})}catch{process.kill(process.pid,"SIGUSR2")}}),process.once("SIGTERM",async()=>{E.info("SIGTERM in Momentic app received");try{await y()}finally{u.close(()=>process.exit(0))}}),process.once("SIGINT",async()=>{E.info("SIGINT in Momentic app received");try{await y()}finally{u.close(()=>process.exit(0))}})}NR.setMaxListeners(50);process.on("warning",r=>{Sn.warn({err:r},`Node warning received on desktop-server: ${r.message}`)});process.on("uncaughtException",r=>{Sn.error({err:r},"Uncaught exception leading to exit on desktop-server"),E.error(`Oh no! The Momentic desktop app encountered a fatal error \u{1F61E}. Error logs: ${r.message}`)});process.on("unhandledRejection",(r,e)=>{Sn.error({reason:`${r}`,promise:`${e}`,stack:r?.stack},"Uncaught exception leading to exit on desktop-server (promise rejection)"),E.error(`Oh no! The Momentic desktop app encountered an asynchronous error \u{1F61E}. Error logs: ${r}`)});function FR(r){E.error(OR`Port ${r} is already in use by another process. Please close the other process and try again.
|
|
3895
|
+
`),a=da.parse(i),s=Mn.parse(a);if(s.fileType!==_e.MOBILE_TEST)throw new Error(`File at '${o}' is not a mobile test (fileType=${s.fileType}).`);let l;e&&(l=(await fr({steps:e})).stepsToSave);let c={...s,...l!==void 0?{steps:l}:{},...t!==void 0?{settings:t}:{}},d=da.stringify(Mn.parse(c));Hn.writeFileSync(o,d,"utf-8")}import{randomUUID as oR}from"crypto";import Jt from"fs";import Gn from"path";var Sh={status:(r,e)=>{if(r.status===e.status)return r.status;if(r.status==="FAILED"||e.status==="FAILED")return"FAILED";if(r.status==="CANCELLED"||e.status==="CANCELLED")return"CANCELLED";if(r.status==="RETRYING"||e.status==="RETRYING")return"RETRYING";if(r.status==="RUNNING"||e.status==="RUNNING")return"RUNNING";if(r.status==="PENDING"||e.status==="PENDING")return"PENDING";throw new Error(`Invalid run status merge: ${r.status} and ${e.status}`)},startedAt:(r,e)=>r.startedAt<e.startedAt?r.startedAt:e.startedAt,updatedAt:(r,e)=>r.updatedAt>e.updatedAt?r.updatedAt:e.updatedAt,finishedAt:(r,e)=>!r.finishedAt||!e.finishedAt?new Date:r.finishedAt>e.finishedAt?r.finishedAt:e.finishedAt,gitCommitTimestamp:(r,e)=>{if(!(!r&&!e)){if(!r.gitCommitTimestamp||!e.gitCommitTimestamp||r.gitCommitTimestamp.getTime()!==e.gitCommitTimestamp.getTime())throw new Error(`Git commit timestamps must match to be merged: ${r.gitCommitTimestamp} and ${e.gitCommitTimestamp}`);return r.gitCommitTimestamp}},pipelineId:(r,e)=>r.pipelineId===e.pipelineId?r.pipelineId:!r.pipelineId&&e.pipelineId?e.pipelineId:!e.pipelineId&&r.pipelineId?r.pipelineId:r.startedAt<e.startedAt?e.pipelineId:r.pipelineId,labels:(r,e)=>{let t=new Set([...r.labels??[],...e.labels??[]]);return Array.from(t)}};function iR(r,e,t){if(Sh[t]){let i=Sh[t];return i(r,e)}let n=r[t],o=e[t];if(n!==o)throw new Error(`Metadata values for key "${t}" do not match: "${n}" vs "${o}"`);return n}var bh=new Set([".DS_Store","__MACOSX"]);function yh(r,e,t){let n=oR(),o=r.child({runGroupId:n});Jt.rmSync(e,{recursive:!0,force:!0});let i=Jt.readdirSync(t).filter(c=>!bh.has(c)).map(c=>Gn.join(t,c));if(i.length===0)throw new Error(`No run groups found in results path: ${t}`);Jt.mkdirSync(e,{recursive:!0});let a=Gn.join(i[0],"metadata.json"),s={...uo.parse(JSON.parse(Jt.readFileSync(a,"utf-8"))),id:n};for(let c of i){let d=Gn.join(c,"runs");if(!Jt.existsSync(d))continue;let m=Jt.readdirSync(d);for(let p of m){if(bh.has(p))continue;let h=Gn.join(d,p),f=Gn.join(e,"runs",p);Jt.cpSync(h,f,{recursive:!0})}let u=Gn.join(c,"metadata.json"),g=uo.parse(JSON.parse(Jt.readFileSync(u,"utf-8")));o.info({oldRunGroupId:g.id},"Merging metadata from run groups");for(let p in g){if(p==="id")continue;let h=p;s[h]=iR(s,g,h)}}let l=Gn.join(e,"metadata.json");Jt.writeFileSync(l,JSON.stringify(s,null,2))}import Th from"adm-zip";import zl from"fs";import{z as Dl}from"zod";var k="v1",Ao="0.0.9";var aR=9e4,sR=3,lR=1500,cR=15e3,Lt=class extends Error{status;rawError;constructor(e,t,n,o={}){super(n,o),this.status=e,this.rawError=t}};async function dR(r){return r.text().then(e=>{try{return JSON.parse(e).error}catch{return e}})}var _l=class{baseUrl;logger;constructor(e){this.baseUrl=e.baseUrl,this.logger=e.logger}getHeaders(){let e={"Content-Type":"application/json"};return Ao&&(e[lm]=Ao),e}async sendRequest(e,t){let{retries:n=sR,requestTimeoutMs:o=aR,initialRetryDelayMs:i=lR,maxRetryDelayMs:a=cR}=t,s=n,l=n,c,d={path:e,baseUrl:this.baseUrl,method:t.method};for(;s>0;)try{return s--,await this.sendSingleRequestHelper(e,t,o)}catch(m){if(c=m,m instanceof Lt&&m.status>=400&&m.status<500)throw m;if(m instanceof Error&&m.name==="AbortError"&&(c=new pn),s===0)throw c;let u=l-s,g=Math.min(i*Math.pow(2,u-1),a);await new Promise(p=>setTimeout(p,g))}throw this.logger.warn({...d,err:c},"Got fatal error response from Momentic server"),c}async sendSingleRequestHelper(e,t,n){let o={path:e,baseUrl:this.baseUrl,method:t.method},i=new AbortController,a=setTimeout(()=>i.abort(),n),s=()=>i.abort();t.signal&&t.signal.addEventListener("abort",s);let l={...this.getHeaders(),...t.extraHeaders};try{let c=await fetch(`${this.baseUrl}${e}`,{method:t.method,body:t.body?JSON.stringify(t.body):void 0,headers:l,signal:i.signal});if(!c.ok){let m=await dR(c);throw new Lt(c.status,m,`Request to ${t.method} ${e} failed with status ${c.status}: ${m}`)}let d;if(c.status===204)d={};else{let m=await c.text();try{d=JSON.parse(m)}catch{d=m}}return this.logger&&t.logResponse===!0&&d&&this.logger.debug({result:d,status:c.status,...o},"Got response from Momentic server"),d}finally{clearTimeout(a),t.signal&&t.signal.removeEventListener("abort",s)}}},$t=class extends _l{apiKey;constructor(e){super(e),this.apiKey=e.apiKey}getHeaders(){return{...super.getHeaders(),Authorization:`Bearer ${this.apiKey}`}}};var Ot=class extends $t{constructor(e){super(e)}getAppUrl(){return this.baseUrl==="http://localhost:8000"?"http://localhost:3000":this.baseUrl.replace(/\/\/api/,"//app")}async getAuthInfo(){let e=await this.sendRequest(`/${k}/auth/check`,{method:"GET",retries:10,requestTimeoutMs:5e3});return Rm.parse(e)}async bulkGetRunStatus(e){let t=await this.sendRequest(`/${k}/runs/status`,{method:"POST",body:e,retries:3,requestTimeoutMs:1e4});return Tm.parse(t)}async getTestYAMLExport(e){let t=await this.sendRequest(`/${k}/tests/export`,{method:"POST",body:e,retries:3,requestTimeoutMs:3e4});return gm.parse(t)}async updateStepCaches(e,t){await this.sendRequest(`/${k}/cache`,{method:"PATCH",body:e,extraHeaders:t,retries:3,requestTimeoutMs:1e4,initialRetryDelayMs:3e3})}async getStepCacheForTest(e,t){let n=await this.sendRequest(`/${k}/cache`,{method:"POST",body:e,extraHeaders:t,retries:10,requestTimeoutMs:3e4,initialRetryDelayMs:3e3});return fm.parse(n)}async updateMobileStepCaches(e,t){await this.sendRequest(`/${k}/mobile-cache`,{method:"PATCH",body:e,extraHeaders:t,retries:3,requestTimeoutMs:1e4,initialRetryDelayMs:3e3})}async getMobileStepCacheForTest(e,t){let n=await this.sendRequest(`/${k}/mobile-cache`,{method:"POST",body:e,extraHeaders:t,retries:10,logResponse:!0,requestTimeoutMs:3e4,initialRetryDelayMs:3e3});return Sm.parse(n)}async queueTests(e){let t=await this.sendRequest(`/${k}/tests/queue`,{method:"POST",body:e,retries:3,requestTimeoutMs:1e4});return pm.parse(t)}async uploadScreenshot(e){let t=await this.sendRequest(`/${k}/screenshots`,{method:"POST",body:e,retries:3,requestTimeoutMs:5e3});return vm.parse(t)}async getAllEnvironments(){let e=await this.sendRequest(`/${k}/environments`,{method:"GET",retries:3,requestTimeoutMs:5e3});return Am.parse(e)}async acquireCacheLock(e,t){let n=await this.sendRequest(`/${k}/result-cache/lock`,{method:"POST",body:e,signal:t,retries:3,requestTimeoutMs:3e4});return Bm.parse(n)}async releaseCacheLock(e){await this.sendRequest(`/${k}/result-cache/lock`,{method:"DELETE",body:{key:e},retries:3,requestTimeoutMs:5e3})}async deleteCacheResult(e){await this.sendRequest(`/${k}/result-cache/entry`,{method:"DELETE",body:e,retries:3,requestTimeoutMs:5e3})}async setCacheResult(e){await this.sendRequest(`/${k}/result-cache/entry`,{method:"PATCH",body:e,retries:3,requestTimeoutMs:5e3})}async getCacheResult(e){try{return await this.sendRequest(`/${k}/result-cache/entry`,{method:"POST",body:e,retries:3,requestTimeoutMs:5e3})}catch(t){if(t instanceof Error&&t.message.includes("404"))return null;throw t}}async queueSuiteRuns(e){let t=await this.sendRequest(`/${k}/suites/queue`,{method:"POST",body:e,retries:3,requestTimeoutMs:5e3});return xm.parse(t)}async bulkGetRunGroupStatus(e){let t={runGroupIds:e},n=await this.sendRequest(`/${k}/run-groups/status`,{method:"POST",body:t,retries:3,requestTimeoutMs:5e3});return td.array().parse(n)}async uploadProposedSteps(e,t){try{await this.sendRequest(`/${k}/test-fragments/`,{method:"POST",body:e,retries:3,requestTimeoutMs:1e4})}catch(n){t.error({err:n},"Failed to upload proposed steps")}}async reportBillableEvents(e,t){try{await this.sendRequest(`/${k}/billing/events`,{method:"POST",body:t,retries:10,requestTimeoutMs:1e4})}catch(n){e.error({err:n},"Failed to report billable event")}}async fetchTestFragment(e){let t=await this.sendRequest(`/${k}/test-fragments/${e}`,{method:"GET",retries:3,requestTimeoutMs:1e4});return Im.parse(t)}async patchTestFragment(e,t){await this.sendRequest(`/${k}/test-fragments/${e}`,{method:"PATCH",body:t,retries:3,requestTimeoutMs:1e4})}async getPastTestResults(e,t){let n=await this.sendRequest(`/${k}/results/tests/${e}`,{method:"POST",body:t,retries:3,requestTimeoutMs:1e4});return Pm.parse(n)}async generateTestResultsUploadUrl(){let e=await this.sendRequest(`/${k}/results/uploads`,{method:"POST",retries:3,requestTimeoutMs:1e4});return Mm.parse(e)}async startProcessingResultsUpload(e,t){let n=await this.sendRequest(`/${k}/results/uploads/${e}/process`,{method:"POST",body:t,retries:3,requestTimeoutMs:1e4});return Lm.parse(n)}async fetchIconKnowledgeBase(e){try{let t=await this.sendRequest(`/${k}/knowledge-base/icons`,{method:"GET",retries:3,requestTimeoutMs:5e3});return $m.parse(t)}catch(t){return e.error({err:t},"Failed to fetch icon knowledge base"),null}}async saveNewIcons(e,t){try{await this.sendRequest(`/${k}/knowledge-base/icons`,{method:"POST",body:e,retries:3,requestTimeoutMs:5e3})}catch(n){t.error({err:n},"Failed to save new icons to icon knowledge base")}}async getMergeBaseCommitFromGithub(e,t,n,o){let i=new URLSearchParams;i.set("base",n),i.set("head",o);let a=await this.sendRequest(`/${k}/git/github/${e}/${t}/merge-base-commit?${i.toString()}`,{method:"GET",retries:3,requestTimeoutMs:1e4});return mo.parse(a)}async getCommitFromGithub(e,t,n){let o=await this.sendRequest(`/${k}/git/github/${e}/${t}/commits/${n}`,{method:"GET",retries:3,requestTimeoutMs:1e4});return mo.parse(o)}async getMergeBaseCommitFromGitlab(e,t,n){let o=new URLSearchParams;o.set("base",t),o.set("head",n);let i=await this.sendRequest(`/${k}/git/gitlab/${e}/merge-base-commit?${o.toString()}`,{method:"GET",retries:3,requestTimeoutMs:5e3});return mo.parse(i)}async getCommitFromGitlab(e,t){let n=await this.sendRequest(`/${k}/git/gitlab/${e}/commits/${t}`,{method:"GET",retries:3,requestTimeoutMs:1e4});return mo.parse(n)}async getAgentConfig(){let e=await this.sendRequest(`/${k}/web-agent/agent-config`,{method:"GET",retries:3,requestTimeoutMs:5e3});return Dl.record(Dl.string(),Dl.string()).parse(e)}async getQuarantinedTests(){let e=await this.sendRequest(`/${k}/quarantine`,{method:"GET"});return Om.parse(e)}async quarantineTest(e,t,n){await this.sendRequest(`/${k}/quarantine`,{method:"POST",body:{testId:e.id,testName:e.name,reason:t,...n??{}},retries:3,requestTimeoutMs:1e4})}async unquarantineTest(e,t,n){await this.sendRequest(`/${k}/quarantine/${e.id}`,{method:"DELETE",body:{testName:e.name,reason:t,...n??{}},retries:3,requestTimeoutMs:1e4})}async createAndroidEmulator(e){let t=await this.sendRequest(`/${k}/limbar/android`,{method:"POST",retries:3,body:e,requestTimeoutMs:9e4,initialRetryDelayMs:5e3,maxRetryDelayMs:15e3});return Nm.parse(t)}async extendAndroidEmulatorTtl(e){try{await this.sendRequest(`/${k}/limbar/android/${e}/keepalive`,{method:"POST",retries:3,requestTimeoutMs:15e3})}catch{}}async generateAndroidAssetUrls({channel:e,tag:t,md5:n}){let o={channel:e,tag:t,md5:n},i=await this.sendRequest(`/${k}/limbar/android/upload-url`,{method:"POST",retries:3,body:o,requestTimeoutMs:15e3,logResponse:!0});return km.parse(i)}async deleteAndroidEmulator(e){await this.sendRequest(`/${k}/limbar/android/${e}`,{method:"DELETE",retries:3,requestTimeoutMs:3e4})}async getAndroidAssets(){let e=await this.sendRequest(`/${k}/limbar/assets`,{method:"GET",retries:3,requestTimeoutMs:1e4});return _m.parse(e)}async deleteAndroidAsset(e,t){await this.sendRequest(`/${k}/limbar/assets/${e}/${t}`,{method:"DELETE",retries:3,requestTimeoutMs:1e4})}};async function Fl(r){let e=process.versions.node,t=parseInt(e.split(".")[0]);(isNaN(t)||t<18)&&(E.error(`Node.js version 20 or higher is required to run the CLI. Detected: ${process.versions.node}.`),process.exit(1)),E.debug(`Identified node version ${e}`);let n=await r.client.getAuthInfo();return E.debug("Got auth info from API"),n}var xr=class{constructor(e,t){this.client=e;this.orgId=t}async acquireCacheLock(e,t){return this.client.acquireCacheLock(e,t)}async uploadScreenshot(e){return(await this.client.uploadScreenshot({screenshot:e.toString("base64")})).key}async releaseCacheLock(e){return this.client.releaseCacheLock(e)}async deleteCacheResult(e){return this.client.deleteCacheResult(e)}async setCacheResult(e){return this.client.setCacheResult(e)}async getCacheResult(e){return this.client.getCacheResult(e)}fetchIconKnowledgeBase(e){return this.client.fetchIconKnowledgeBase(e)}saveNewIcons(e,t){return this.client.saveNewIcons(e,t)}};import{Faker as mR,en as uR}from"@faker-js/faker";var Ir="v1",Pr=class{httpClient;fakerInstance;type="API_CLIENT";sms={send:this.sendSms.bind(this),fetchLatest:this.fetchLatestSms.bind(this)};email={send:this.sendEmail.bind(this),fetchLatest:this.fetchLatestEmail.bind(this),fetchAll:this.fetchAllEmails.bind(this)};ai={generate:this.sendAiGenerate.bind(this)};constructor(e){this.httpClient=e.httpClient,e.fakerSeed&&(this.fakerInstance=new mR({locale:uR}),this.fakerInstance.seed(e.fakerSeed))}async sendAiGenerate(e){let t=typeof e=="string"?{input:e}:e;return this.httpClient.sendRequest(`/${Ir}/tools/ai/generate`,{method:"POST",body:t}).catch(n=>{throw n instanceof Lt?new Error(n.rawError):new Error(`Failed to send AI generation: ${n.message}`)})}async sendSms(e){return this.httpClient.sendRequest(`/${Ir}/tools/sms/send`,{method:"POST",body:e}).then(()=>{}).catch(t=>{throw t instanceof Lt?new Error(t.rawError):new Error(`Failed to send sms: ${t.message}`)})}async fetchLatestSms(e){return this.httpClient.sendRequest(`/${Ir}/tools/sms/fetchLatest`,{method:"POST",body:e}).catch(t=>{throw t instanceof Lt?new Error(t.rawError):t})}async sendEmail(e){return this.httpClient.sendRequest(`/${Ir}/tools/email/send`,{method:"POST",body:e}).then(()=>{}).catch(t=>{throw t instanceof Lt?new Error(t.rawError):new Error(`Failed to send email: ${t.message}`)})}async fetchAllEmails(e){return this.httpClient.sendRequest(`/${Ir}/tools/email/fetchAll`,{method:"POST",body:e}).catch(t=>{throw t instanceof Lt?new Error(t.rawError):new Error(`Failed to fetch all emails: ${t.message}`)})}async fetchLatestEmail(e){return this.httpClient.sendRequest(`/${Ir}/tools/email/fetchLatest`,{method:"POST",body:e}).catch(t=>{throw t instanceof Lt?new Error(t.rawError):new Error(`Failed to fetch latest emails: ${t.message}`)})}};import{z as pR}from"zod";var $n=class extends $t{agentConfig;constructor(e,t){super(t),this.agentConfig=e}getAgentConfig(){return this.agentConfig}async rankChunksWithAi(e,t){let n={...e,loggerTags:t.loggerTags},o=await this.sendRequest(`/${k}/web-agent/recommend-chunks-ai`,{method:"POST",body:n,signal:t.abortSignal});return ad.parse(o)}async rankChunksWithRag(e,t){let n=await this.sendRequest(`/${k}/web-agent/recommend-chunks`,{method:"POST",body:{cliVersion:Ao,...e},signal:t.abortSignal});return id.parse(n)}async getScreenshotFromS3(e){let t=await this.sendRequest(`/${k}/s3/visual-diff-screenshot`,{method:"POST",body:{url:e}});return pR.string().parse(t)}async getElementLocation(e,t){let n={...e,disableCache:t.disableCache,loggerTags:t.loggerTags,useMemory:t.useMemory,agentConfigVersion:this.agentConfig?.locator},o=await this.sendRequest(`/${k}/web-agent/locate-element`,{method:"POST",body:n,signal:t.abortSignal});return mm.parse(o)}async getAssertionResult(e,t){let n={...e,disableCache:!!t.disableCache,useConsensus:!!t.useConsensus,attemptNumber:t.attemptNumber,loggerTags:t.loggerTags,useMemory:t.useMemory,agentConfigVersion:this.agentConfig?.assertion},o=await this.sendRequest(`/${k}/web-agent/assertion`,{method:"POST",body:n,signal:t.abortSignal});return vs.parse(o)}async getLintStepResult(e,t){let n={...e,disableCache:!!t.disableCache,loggerTags:t.loggerTags},o=await this.sendRequest(`/${k}/web-agent/lint/step`,{method:"POST",body:n,signal:t.abortSignal});return dm.parse(o)}async getVisualAssertionResult(e,t){let n={...e,disableCache:!!t.disableCache,useConsensus:!!t.useConsensus,attemptNumber:t.attemptNumber,loggerTags:t.loggerTags,useMemory:t.useMemory,agentConfigVersion:this.agentConfig?.["visual-assertion"]},o=await this.sendRequest(`/${k}/web-agent/visual-assertion`,{method:"POST",body:n,signal:t.abortSignal});return vs.parse(o)}async getAiActionCommand(e,t){let n=await this.sendRequest(`/${k}/web-agent/next-command-dynamic`,{method:"POST",body:{...e,disableCache:t.disableCache,loggerTags:t.loggerTags},signal:t.abortSignal});return cm.parse(n)}async getMultiturnAiActionCommand(e,t){return await this.sendRequest(`/${k}/web-agent/ai-action/next-command`,{method:"POST",body:{...e,disableCache:t.disableCache,loggerTags:t.loggerTags},signal:t.abortSignal})}async getMultiturnAiActionEvaluation(e,t){let n=await this.sendRequest(`/${k}/web-agent/ai-action/evaluate`,{method:"POST",body:{...e,disableCache:t.disableCache,loggerTags:t.loggerTags},signal:t.abortSignal});return Ha.parse(n)}async getReverseMappedDescription(e,t){let n=await this.sendRequest(`/${k}/web-agent/reverse-mapped-description`,{method:"POST",body:{...e,disableCache:t.disableCache,loggerTags:t.loggerTags},signal:t.abortSignal});return um.parse(n)}async getTextExtraction(e,t){let n={...e,disableCache:t.disableCache,loggerTags:t.loggerTags,agentConfigVersion:this.agentConfig?.["text-extraction"]},o=await this.sendRequest(`/${k}/web-agent/text-extraction`,{method:"POST",body:n,signal:t.abortSignal});return za.parse(o)}async getTestResultClassification(e,t){let n=await this.sendRequest(`/${k}/web-agent/result-classification`,{method:"POST",body:{...e,loggerTags:t.loggerTags},signal:t.abortSignal});return Va.parse(n)}async getExtractedKeywords(e,t){let n=await this.sendRequest(`/${k}/web-agent/extract-keywords`,{method:"POST",body:{goal:e,disableCache:t.disableCache,context:e},signal:t.abortSignal});return hd.parse(n)}async getAutohealingProposal(e,t){let n=await this.sendRequest(`/${k}/web-agent/autoheal-section`,{method:"POST",body:{...e,loggerTags:t.loggerTags},signal:t.abortSignal});return Dc.parse(n)}async getFailureRecoveryProposal(e,t){let n=await this.sendRequest(`/${k}/web-agent/failure-recovery`,{method:"POST",body:{...e,loggerTags:t.loggerTags},signal:t.abortSignal});return zc.parse(n)}async getFailureRecoveryPlan(e,t){let n=await this.sendRequest(`/${k}/web-agent/failure-recovery-plan`,{method:"POST",body:{...e,loggerTags:t.loggerTags},signal:t.abortSignal});return Uc.parse(n)}async getIframeRegex(e,t){let n=await this.sendRequest(`/${k}/web-agent/iframe-regex`,{method:"POST",body:e,signal:t.abortSignal});return uc.parse(n)}};var Mr=class extends $t{generator;constructor(e,t){super(e),this.generator=t}async runTemplateMatching(e,t={}){let n=await this.sendRequest(`/${k}/web-agent/template-matching`,{method:"POST",body:e,signal:t?.signal});return pc.parse(n)}async constructIframeRegex(e,t={}){return this.generator.getIframeRegex(e,{abortSignal:t.signal})}};function wh(r,e,t){return fetch(r,{method:"PUT",body:t,headers:{"Content-Type":e}})}var ma=class{constructor(e){this.client=e}async uploadResultsArchive(e,t){let{uploadUrl:n,id:o}=await this.client.generateTestResultsUploadUrl(),i=await wh(n,"application/zip",t);if(!i.ok)throw new Error(`Failed to upload test results: ${await i.text()}`);let{runGroupId:a}=await this.client.startProcessingResultsUpload(o,{runGroupId:e});return a}};var Lr=class extends $t{constructor(e){super(e)}async getElementLocation(e,t){let n={...e,loggerTags:t.loggerTags},o=await this.sendRequest(`/${k}/mobile-agent/locate-element`,{method:"POST",body:n,signal:t.abortSignal});return Sd.parse(o)}async evaluateAssertion(e,t){let n={...e,loggerTags:t.loggerTags},o=await this.sendRequest(`/${k}/mobile-agent/assertion`,{method:"POST",body:n,signal:t.abortSignal});return wd.parse(o)}};function ua({orgId:r,client:e,gitMetadata:t,alwaysSaveCache:n,noCache:o}){return o?new Ti:new Ul(r,e,t,n)}var Ul=class{constructor(e,t,n,o){this.orgId=e;this.client=t;let{gitBranchName:i,gitProtectedBranches:a}=n;this.cacheHeaders=Rs(n),o?this.writeCaches=!0:i?this.writeCaches=!a.includes(i):this.writeCaches=!0}cacheHeaders;writeCaches;async saveEntries({entries:e,testId:t,logger:n}){if(!this.writeCaches){n.debug("Skipping cache storage because branch is protected");return}try{await this.client.updateMobileStepCaches({entries:e,testId:t},this.cacheHeaders)}catch(o){n.error({err:o},"Failed to save mobile step cache entries")}}async resolveEntries(e){let{steps:t}=e.stepLists,n=await this.client.getMobileStepCacheForTest({testId:e.testId},this.cacheHeaders);if(!this.writeCaches){e.logger.debug("Skipping mobile cache last used at update because branch is protected");return}for(let i of[t])i&&gu({steps:i,stepCacheEntries:n,logger:e.logger});let{cachesToSave:o}=await fr({steps:t,cacheCreationParams:{testId:e.testId,orgId:this.orgId}});this.client.updateMobileStepCaches({entries:o,testId:e.testId},this.cacheHeaders)}};import pa from"path";function gR(r){let e=new Th,t=pa.join(r,"metadata.json"),n=uo.parse(JSON.parse(zl.readFileSync(t,"utf-8")));e.addLocalFile(t);for(let o of zl.readdirSync(pa.join(r,"runs"))){if(!o.endsWith(".zip"))continue;let i=o.replace(/\.zip$/,""),a=new Th(pa.join(r,"runs",o));for(let s of a.getEntries())s.isDirectory||e.addFile(pa.join("runs",i,s.entryName),s.getData())}return{runGroupId:n.id,buffer:e.toBuffer()}}async function ga(r){let{client:e,consoleLogger:t,resultsPath:n}=r;if(!zl.existsSync(n)){t.warn("Results path does not exist, skipping upload.");return}let o=new ma(e);try{let{runGroupId:i,buffer:a}=gR(n),s=await o.uploadResultsArchive(i,a),l=`${e.getAppUrl()}/run-groups/${s}`;t.success(`Successfully uploaded test results. Once processed, your results can be found at ${l}`);return}catch(i){let a;i instanceof Error?a=i.message:typeof i=="string"?a=i:a="Unknown error",t.error(`Failed to upload test results: ${a}.`);return}}import hR from"adm-zip";import it from"fs";import Nt from"path";var xo=class r{constructor(e){this.filePath=e;it.rmSync(this.filePath,{recursive:!0,force:!0}),it.mkdirSync(this.filePath,{recursive:!0})}cd(e){return new r(Nt.join(this.filePath,e))}mkdir(e){it.mkdirSync(Nt.join(this.filePath,e),{recursive:!0})}readFile(e){let t=Nt.join(this.filePath,e);if(it.existsSync(t))return it.readFileSync(t)}storeFile(e){let{name:t,contents:n}=e,o=Nt.join(this.filePath,t);try{it.writeFileSync(o,n)}catch{}}createRunArchive(e){return new Bl(Nt.join(this.filePath,"runs"),e)}},Bl=class{constructor(e,t){this.filePath=e;this.tempPath=Nt.join(e,`.${t}`),this.finalPath=Nt.join(e,`${t}.zip`),it.rmSync(this.tempPath,{recursive:!0,force:!0}),it.rmSync(this.finalPath,{recursive:!0,force:!0}),it.mkdirSync(this.tempPath,{recursive:!0})}tempPath;finalPath;readFile(e){let t=Nt.join(this.tempPath,e);if(it.existsSync(t))return it.readFileSync(t)}mkdir(e){it.mkdirSync(Nt.join(this.tempPath,e),{recursive:!0})}cd(e){return new xo(Nt.join(this.tempPath,e))}storeFile(e){let{name:t,contents:n}=e,o=Nt.join(this.tempPath,t);it.writeFileSync(o,n)}close(){let e=new hR;e.addLocalFolder(this.tempPath,void 0,n=>n!==".DS_Store");let t=e.toBuffer();it.writeFileSync(this.finalPath,t),it.rmSync(this.tempPath,{recursive:!0,force:!0})}};import Ea from"fs";import Nh from"body-parser";import LR from"cors";import OR from"dedent";import{Router as yR}from"express";import st from"fs";import{globSync as wR}from"glob";import je from"path";import Gl from"fs";import fR from"path";var SR=new Ni(30,60*1e3),Wl="https://api.momentic.ai",$l,Ch=r=>{Wl=r},Vl=()=>Wl,ql=()=>$l;var Or,jl,Eh,vh=async r=>{if($l&&Or&&Eh)return Or;let e=new Ot({baseUrl:Wl,apiKey:r,logger:E});$l=e;try{let t=await e.getAuthInfo();return Or=t.orgId,jl=t.userId,Eh=r,Or}catch(t){throw new Error(`Error checking API key against server: ${t}`)}},Io=()=>{if(!Or)throw new Error("Your organization ID is invalid.");return Or},ha=()=>{if(!jl)throw new Error("Your user ID is invalid.");return jl};var Kl,Hl,Rh=(r,e)=>{Kl=r,Hl?.abort(),Hl=new AbortController;let t=Hl.signal,n=[r.configFilePath];r.config.environments?.forEach(o=>{if(!o.envFile)return;let i=fR.resolve(r.rootDir,o.envFile);try{if(Gl.lstatSync(i).isSymbolicLink())return;Gl.existsSync(i)&&n.push(i)}catch(a){E.warn({err:a},`Failed to check if env file ${i} exists`)}});try{bR({filesToWatch:n,revalidator:e,signal:t,project:r})}catch(o){E.error({err:o},"Failed to start config file watchers")}},xt=()=>Kl;function bR({filesToWatch:r,revalidator:e,signal:t,project:n}){r.forEach(o=>{Gl.watch(o,{signal:t,persistent:!1,recursive:!1},(i,a)=>{a&&(SR.increment("setLocalProject")&&E.warn(`A file change under the ${n.rootDir} directory has caused Momentic to reload its configuration more than 30 times in the last minute. Rapid changes to files may indicate your momentic.config.yaml 'include' glob is incorrect. Please ensure temporary, library, and auto-generated files are not included in Momentic's context.`),Kl=e(n.configFilePath))})})}function at(r){return function(...e){let t=e[e.length-1],n=r(...e);Promise.resolve(n).catch(t)}}var Mo=yR();function Po(r){let e=xt(),t=je.dirname(e.configFilePath);return je.join(t,...r)}function TR(r){let e=xt(),t=je.dirname(e.configFilePath),n=je.relative(t,r);return n?n.split(je.sep):[]}function ER(r,e){let t=st.statSync(r),n=TR(r);return ys.parse({name:e,absolutePath:r,relativePath:n.join(je.sep),pathSegments:n,isDirectory:t.isDirectory(),size:t.size,createdAt:t.birthtime,modifiedAt:t.mtime,accessedAt:t.atime})}Mo.post("/",at(async(r,e,t)=>{let n;try{n=em.parse(r.body).pathSegments}catch(m){e.status(400).json({error:`Failed to parse folder read body: ${m}`});return}let o=Po(n);if(!st.existsSync(o)){e.status(404).json({error:`Path not found: ${n.join(je.sep)}`});return}if(!st.statSync(o).isDirectory()){e.status(400).json({error:`Path is not a directory: ${n.join(je.sep)}`});return}let a=xt(),s=Array.from(a.config.exclude??[]).concat(hi),c=wR("*",{absolute:!1,cwd:o,ignore:s,dotRelative:!1,maxDepth:1,nodir:!1}).map(m=>{let u=je.join(o,m);return ER(u,m)}),d={absolutePath:o,pathSegments:n,contents:c};e.status(200).json(d)}));Mo.put("/",at(async(r,e,t)=>{let n;try{n=tm.parse(r.body).pathSegments}catch(a){e.status(400).json({error:`Failed to parse folder create body: ${a}`});return}let o=Po(n);if(st.existsSync(o)){e.status(200).json({success:!0,message:`Folder already exists: ${n.join(je.sep)}`,pathSegments:n});return}st.mkdirSync(o,{recursive:!0});let i={success:!0,message:`Folder created: ${n.join(je.sep)}`,pathSegments:n};e.status(201).json(i)}));Mo.patch("/",at(async(r,e,t)=>{let n,o;try{let c=nm.parse(r.body);n=c.pathSegments,o=c.newPathSegments}catch(c){e.status(400).json({error:`Failed to parse folder update body: ${c}`});return}let i=Po(n),a=Po(o);if(!st.existsSync(i)){e.status(400).json({error:`Folder not found: ${n.join(je.sep)}`});return}if(st.existsSync(a)){e.status(400).json({error:`Destination already exists: ${o.join(je.sep)}`});return}let s=je.dirname(a);st.existsSync(s)||st.mkdirSync(s,{recursive:!0}),st.renameSync(i,a);let l={success:!0,message:`Folder moved from ${n.join(je.sep)} to ${o.join(je.sep)}`,pathSegments:o};e.status(200).json(l)}));Mo.delete("/",at(async(r,e,t)=>{let n,o=!0;try{let l=rm.parse(r.body);n=l.pathSegments,o=l.recursive??!0}catch(l){e.status(400).json({error:`Failed to parse folder delete body: ${l}`});return}let i=Po(n);if(!st.existsSync(i)){e.status(200).json({success:!0,message:`Folder not found: ${n.join(je.sep)}`,pathSegments:n});return}if(!st.statSync(i).isDirectory()){e.status(400).json({error:`Path is not a directory: ${n.join(je.sep)}`});return}if(o)st.rmSync(i,{recursive:!0,force:!0});else{if(st.readdirSync(i).length>0){e.status(409).json({error:`Cannot delete non-empty directory without recursive flag: ${n.join("/")}`});return}st.rmdirSync(i)}let s={success:!0,message:`Folder deleted: ${n.join("/")}`,pathSegments:n};e.status(200).json(s)}));var Yl=Mo;import NR from"events";import kh,{Router as kR}from"express";import _R from"http";import DR from"path";var fa=class{sessions=new Map;registerSession(e,t){this.sessions.set(e,t)}getSession(e){return this.sessions.get(e)}async removeSession(e,t){let n=this.sessions.get(e);if(n)try{await n.cleanup?.(),t.info({sessionId:e,emulatorName:n.emulatorName},"Android emulator cleaned up")}catch(o){t.error({err:o},"Error during Android session cleanup")}finally{this.sessions.delete(e)}}async removeAllSessions(e){let t=Array.from(this.sessions.keys());await Promise.all(t.map(n=>this.removeSession(n,e)))}};var Ah=new fa;import{Router as RR}from"express";import{existsSync as AR}from"fs";import xR from"path";import{hostname as CR}from"os";var vR="0.0.9",Sn=na({app:"mobile-desktop-server",hostname:CR(),disableConsoleLogs:!0}).child({cliVersion:vR});var Xl=RR();Xl.get("/",at(async(r,e)=>{let t=ql();if(!t){e.status(500).json({message:"API client not initialized"});return}let n=await t.getAndroidAssets();e.status(200).json(n)}));Xl.post("/upload-url",at(async(r,e)=>{let t;try{t=am.parse(r.body)}catch(i){e.status(400).json({error:`Invalid request body: ${i}`});return}let n=ql();if(!n){e.status(500).json({error:"API client not initialized"});return}let o=xR.resolve(t.filePath);if(!AR(o)){e.status(400).json({error:`File not found: ${o}`});return}await Do({tag:t.tag,channel:t.channel,filePath:o,apiClient:n,logger:Sn}),e.sendStatus(204)}));var xh=Xl;import{Router as IR}from"express";var Ih=IR();Ih.get("/",at(async(r,e)=>{let t=xt(),n=aa(t,E),o=new Set;n?.tests&&Object.values(n.tests).forEach(l=>{l.labels?.forEach(c=>o.add(c))});let i=Array.from(o).sort(),a=Object.values(n.mobileTests),s={labels:i,modules:[],tests:a};e.status(200).json(s)}));var Ph=Ih;import{Router as PR}from"express";var Mh=PR();Mh.get("/",(r,e)=>{e.status(200).json({userId:ha(),orgId:Io()})});var Lh=Mh;import{Router as MR}from"express";import Jl from"path";var Sa=MR();Sa.patch("/:testPath",at(async(r,e)=>{let t=r.params.testPath;if(!t){e.status(400).json({error:"Missing testPath in path"});return}let n;try{n=im.parse(r.body)}catch(a){e.status(400).json({error:`Invalid request body: ${a}`});return}if(n.steps===void 0&&n.settings===void 0){e.status(400).json({error:"At least one of steps or settings is required"});return}let o=t.endsWith(`.${$e.TEST}`)?t:`${t}.${$e.TEST}`;await fh({filePath:o,steps:n.steps,settings:n.settings});let i={message:"ok"};e.status(200).json(i)}));Sa.post("/",at((r,e)=>{let t;try{t=om.parse(r.body)}catch(h){e.status(400).json({error:`Invalid request body: ${h}`});return}let{name:n,description:o,steps:i,settings:a,pathSegments:s}=t;if(!n||typeof n!="string"){e.status(400).json({error:"Missing or invalid 'name' in body"});return}try{as(n)}catch(h){e.status(400).json({error:h.message});return}let l=xt(),c=Jl.join(l.rootDir,...s),d=hh({name:n,description:o,steps:i,settings:a,folder:c}),m=Jl.basename(d),u=kl(m,c),g=Jl.relative(l.rootDir,d),p={id:u.id,fileName:m,fullPath:d,relativeFilePath:g};e.status(201).json(p)}));Sa.get("/:fileName",at((r,e)=>{let t=r.params.fileName;if(!t){e.status(400).json({error:"Missing fileName in path"});return}let n=t.endsWith(`.${$e.TEST}`)?t:`${t}.${$e.TEST}`,i=kl(n);e.status(200).json(i)}));var Oh=Sa;var ba=class extends xr{constructor(t,n){super(t,n);this.client=t;this.orgId=n}async fetchEnvironment(t,n){let o=xt();return ia(t,o,E)}};var _h="10mb";async function Dh(r){let{serverPort:e,apiKey:t,momenticServerUrl:n,staticDir:o,initialProject:i,logLevel:a}=r;n&&Ch(n),await vh(t);let s=Io(),l=ha(),c=r.logger.child({orgId:s,userId:l});Rh(i,w=>Ro({configFilePath:w}));let d=kh();d.use(LR()),d.use(Nh.json({limit:_h})),d.use(Nh.urlencoded({extended:!1,limit:_h}));let m=kR();if(m.use("/folders",Yl),m.use("/entities",Ph),m.use("/identify",Lh),m.use("/mobile-tests",Oh),m.use("/assets",xh),d.use("/api",m),d.use((w,A,I)=>{E.debug({url:w.url,path:w.path,query:w.query,method:w.method,body:w.body,headers:w.rawHeaders,client:w.ip},"Incoming request on mobile-desktop-server"),A.on("close",()=>{A.statusCode>=400&&(E.error({url:w.url,method:w.method,statusCode:A.statusCode},"Request completed in error on mobile-desktop-server"),Sn.error({url:w.url,method:w.method,statusCode:A.statusCode},"Request completed in error on mobile-desktop-server"))}),I()}),d.use((w,A,I,C)=>{if(w instanceof Error&&w.message.includes("BadRequestError: request aborted")){I.status(400).send("Client disconnected");return}E.error({stack:w.stack,msg:w.message,err:w,url:A.url,method:A.method},"Unhandled exception leading to 500 on mobile-desktop-server"),Sn.error({stack:w.stack,msg:w.message,err:w,url:A.url,method:A.method},"Unhandled exception leading to 500 on mobile-desktop-server"),I.status(500).send(`Internal Server Error: ${w.message}`)}),o){let w=kh.static(o,{setHeaders:A=>{A.setHeader("Cache-Control","no-cache")},redirect:!1});d.use(w),d.use("*",(A,I)=>{I.sendFile(DR.join(o,"index.html"))})}let u=_R.createServer(d),g=`http://localhost:${e}`;await new Promise(w=>{try{u.listen(e,()=>{c.info(`Mobile desktop server is running at ${g}`),w()})}catch(A){A.message.includes("EADDRINUSE")?FR(e):E.error(`An unexpected error occurred while starting the server: ${A.message}`),process.exit(1)}});let h={type:"API_KEY",baseUrl:Vl(),apiKey:t,logger:c},f=new Ot(h),{dispose:y}=$g({baseServer:u,logger:c,authorization:h,globalStateManager:Ah,getOrgId:()=>Io(),androidDriverFactory:({socket:w,logger:A,creationOpts:I})=>_o({logger:A,logLevel:a,apiClient:f,creationOpts:I,socket:w,onStatusUpdate:C=>{w.emit("connectionStatusUpdate",{message:C})}}),mobileGeneratorFactory:async w=>new Lr({baseUrl:Vl(),apiKey:t,logger:c}),browserGeneratorFactory:async w=>new $n({},h),browserEnricherFactory:async w=>new Mr(h,new $n({},h)),storageFactory:async w=>new ba(f,w),cacheStorageFactory:async w=>{let A=xt(),I=await la(c,f,A);return ua({orgId:w,client:f,gitMetadata:I})},localToolsFactory:async()=>new Pr({httpClient:f,fakerSeed:void 0}),keepSessionAlive:w=>f.extendAndroidEmulatorTtl(w)});process.once("SIGUSR2",async()=>{return;try{await y()}catch(w){E.error({err:w},"Error during session dispose on SIGUSR2")}try{u.close(()=>{process.kill(process.pid,"SIGUSR2")})}catch{process.kill(process.pid,"SIGUSR2")}}),process.once("SIGTERM",async()=>{E.info("SIGTERM in Momentic app received");try{await y()}finally{u.close(()=>process.exit(0))}}),process.once("SIGINT",async()=>{E.info("SIGINT in Momentic app received");try{await y()}finally{u.close(()=>process.exit(0))}})}NR.setMaxListeners(50);process.on("warning",r=>{Sn.warn({err:r},`Node warning received on desktop-server: ${r.message}`)});process.on("uncaughtException",r=>{Sn.error({err:r},"Uncaught exception leading to exit on desktop-server"),E.error(`Oh no! The Momentic desktop app encountered a fatal error \u{1F61E}. Error logs: ${r.message}`)});process.on("unhandledRejection",(r,e)=>{Sn.error({reason:`${r}`,promise:`${e}`,stack:r?.stack},"Uncaught exception leading to exit on desktop-server (promise rejection)"),E.error(`Oh no! The Momentic desktop app encountered an asynchronous error \u{1F61E}. Error logs: ${r}`)});function FR(r){E.error(OR`Port ${r} is already in use by another process. Please close the other process and try again.
|
|
3896
3896
|
Using Bash on MacOS or Linux:
|
|
3897
3897
|
lsof -t -i :58888 | xargs kill -9
|
|
3898
3898
|
|
|
@@ -3902,6 +3902,6 @@ ${n.map(o=>o.configFilePath)}`);if(n.length===0)throw new Error("No valid Moment
|
|
|
3902
3902
|
- ${r.join(`
|
|
3903
3903
|
- `)}
|
|
3904
3904
|
`),r.forEach(m=>{if(!ya.existsSync(m))throw new Error(`Path '${m}' does not exist.`);let u,g;try{u=ya.statSync(m),g=u.isDirectory()}catch(h){a.warn({err:h},`Skipping path ${m} because it cannot be read`);return}let p=No.resolve(m);Object.values(e.mobileTests).filter(h=>g?h.fullFilePath.startsWith(p):h.fullFilePath===p).forEach(h=>{s.add(h.fullFilePath)})})):(a.info("The arguments provided don't appear to be valid paths. Treating them as substrings instead... "),Object.values(e.mobileTests).forEach(m=>{r.some(u=>m.relativePath.includes(u))&&s.add(m.fullFilePath)}))}else{!n&&!await ra("No test paths or substrings were provided. Do you want to run all tests?")&&(a.error("Cancelled by user."),process.exit(1));let c=Object.values(e.mobileTests);a.info(`Reading all ${c.length} mobile tests in the project from local disk.`),c.forEach(d=>{s.add(d.fullFilePath)})}for(let c of Array.from(s)){let d=No.relative(t.rootDir,c);o&&!o.some(m=>new RegExp(m).test(d))&&s.delete(c),i&&i.some(m=>new RegExp(m).test(d))&&s.delete(c)}return(await Promise.all(Array.from(s).map(async c=>{try{let d=ya.readFileSync(c,"utf-8").replace(/\r\n|\r/g,`
|
|
3905
|
-
`),u=Mn.parse(GR.parse(d));HR(u.schemaVersion,Vo)&&a.warn(`Test ${c} has schema version ${u.schemaVersion}, which is greater than what is currently supported by this SDK. Please update your momentic package version to avoid unexpected behavior.`);let g=No.relative(t.rootDir,c),p=e.mobileTests[u.id]?.name??No.basename(c,No.extname(c));return{id:u.id,name:p,description:u.description,schemaVersion:u.schemaVersion,settings:u.settings,steps:u.steps,fullFilePath:c,relativeFilePath:g}}catch(d){a.error(`Failed to read and resolve mobile test at '${c}': ${d}`),process.exit(1)}}))).filter(c=>!!c)}import{cloneDeep as $R}from"lodash-es";async function Vh(r){let{inputs:e,fixtures:t,metadata:n,options:o}=r,{tracer:i,logger:a}=t,{runId:s}=n,{testDefinition:l,runSigIntHandlers:c}=e,d=new Date,m=await i.startRun({logger:a,testId:l.id,testName:l.name,runId:s,testDescription:l.description,schemaVersion:l.schemaVersion,originalSteps:{steps:$R(l.steps),beforeSteps:void 0,afterSteps:void 0}}),u=a.child(m.loggerBindings||{}),g=1+(l.settings.retries??1);try{for(let p=0;p<g;p++){let h=new Date,f=await jR({runTracer:m,testDefinition:l,metadata:n,constants:{attemptNumber:p,totalAttempts:g},fixtures:{...t,logger:u},inputs:e,options:o}),y=new Date;if(f.status!=="FAILED")return{...f,startedAt:d,lastAttemptStartedAt:h,finishedAt:y,attempts:p+1,testMetadata:l,steps:l.steps,filePath:l.relativeFilePath};if(p!==g-1){a.warn("Retrying failed mobile run");continue}return a.error("Mobile test failed after all exhausting attempts"),{...f,startedAt:d,lastAttemptStartedAt:h,finishedAt:y,attempts:p+1,testMetadata:l,steps:l.steps,filePath:l.relativeFilePath}}throw new Error("This code should not be reachable")}finally{c?.pop()}}async function jR(r){let{runTracer:e,testDefinition:t,metadata:n,constants:o,fixtures:i,inputs:a,options:s}=r,{attemptNumber:l,totalAttempts:c}=o,{orgId:d,gitMetadata:m}=n,{apiClient:u,logger:g}=i,{logUpdate:p,channel:h,tag:f,project:y,envOverride:w}=a,{alwaysSaveCache:A,noCache:I,logLevel:C}=s;l!==0&&p("RETRY",`attempt ${l}/${c}`);let R=ua({orgId:d,client:u,gitMetadata:m,alwaysSaveCache:A,noCache:I}),_=new xr(u,d),V=new Lr(u),Q={type:"API_KEY",baseUrl:u.baseUrl,apiKey:u.apiKey,logger:g},ee=new $n({},Q),Re=new Mr(Q,ee),D=new Pr({httpClient:u,fakerSeed:void 0}),q=await _o({apiClient:u,logger:g,logLevel:C,creationOpts:h?{apkToInstall:{channel:h,tag:f}}:{},onStatusUpdate:Pe=>{g.debug({status:Pe},"Limbar emulator status update")}}),M=await e.startAttempt({emulatorName:q.emulatorName}),te=g.child(M.loggerBindings||{}),Ee=w||t.settings.defaultEnv,Xe;Ee&&(Xe=ia(Ee,y,te).variables);let ze=new cr({variablesFromEnvironment:Xe??{},envName:Ee,testName:t.name}),pt=await Cr.init({driver:q.driver,generator:V,logger:te,orgId:d,adbPort:q.adbTunnelPort,fixtures:{storage:_,browserEnricher:Re,browserGenerator:ee,localCodeEvalTools:D,testContext:ze}}),Je;try{Je=await Qi({fixtures:{controller:pt,logger:te,cacheStorage:R},inputs:{steps:t.steps,orgId:d,testMetadata:t},tracer:M})}finally{await q.cleanup()}return await M.finish({logger:te,results:Je.results,status:Je.status}),Je}import{randomUUID as WR}from"crypto";import{debounce as VR}from"ts-debounce";var Nr="0.0.8";var wa="assets";function qR(r){switch(r){case"PASSED":return"SUCCESS";case"FAILED":return"FAILED";case"CANCELLED":return"CANCELLED";case"RUNNING":case"PENDING":case"RETRYING":case"WAITING_FOR_USER":return"RUNNING"}}function KR(r){switch(r){case"SUCCESS":return"PASSED";case"FAILED":return"FAILED";case"CANCELLED":return"CANCELLED";case"RUNNING":return"RUNNING";case"IDLE":return"PENDING"}}var oc=class{constructor(e,t,n,o){this.keepalive=e;this.testId=t;this.testName=n;this.diskStorage=o;this.commandTracer=new hr("command")}children=[];commandTracer;finished=!1;getParentStepIdChain(){return[]}attachBeforeScreenshot(e){let{snapshotId:t,screenshot:n}=e;this.diskStorage.storeFile({name:`${wa}/${t}.jpeg`,contents:n})}attachAfterScreenshot(e){let{snapshotId:t,screenshot:n}=e;this.diskStorage.storeFile({name:`${wa}/${t}.jpeg`,contents:n})}async finishInternal(e){this.finished||(this.finished=!0,await Promise.all(this.children.map(t=>t.finish({status:KR(e.status),finishedAt:e.finishedAt}))))}async finish(e){return await this.finishInternal({status:e.result.status,finishedAt:new Date(e.result.endTime)}),this.commandTracer.finish(),{trace:this.commandTracer.getRootSpan()}}startCommand(){return this.commandTracer}async startSubSteps(){let e=new kr(this.keepalive,this.testId,this.testName,this.diskStorage);return this.children.push(e),e}},kr=class{constructor(e,t,n,o){this.keepalive=e;this.testId=t;this.testName=n;this.diskStorage=o}children=[];finished=!1;async getScreenshot(e,t){return this.diskStorage.readFile(`${wa}/${t}.jpeg`)}getParentStepIdChain(){return[]}async startStep(e){this.keepalive();let t=new oc(this.keepalive,this.testId,this.testName,this.diskStorage);return this.children.push(t),t}async finish(e){this.finished||(this.finished=!0,await Promise.all(this.children.map(t=>t.finishInternal({status:qR(e.status),finishedAt:e.finishedAt}))))}},ic=class{constructor(e,t,n,o,i,a){this.keepalive=e;this.testId=t;this.testName=n;this.runAttemptId=o;this.metadata=i;this.diskStorage=a;this.diskStorage.mkdir("assets")}finished=!1;children=[];get loggerBindings(){return{runAttemptId:this.runAttemptId}}attachNetworkLogs(e){let{logs:t}=e;this.diskStorage.storeFile({name:"network.har",contents:JSON.stringify(t,null,2)})}attachConsoleLogs(e){let{logs:t}=e;this.diskStorage.storeFile({name:"console.json",contents:JSON.stringify(t,null,2)})}async finish(e){if(this.finished)return;this.finished=!0;let{logger:t,status:n,results:o,beforeResults:i,afterResults:a}=e,s={...this.metadata,status:n,finishedAt:new Date,results:ea(o,t),beforeResults:i?ea(i,t):void 0,afterResults:a?ea(a,t):void 0};await Promise.all(this.children.map(l=>l.finish({status:s.status,finishedAt:s.finishedAt}))),this.diskStorage.storeFile({name:"metadata.json",contents:JSON.stringify(s,null,2)})}async startBeforeStepList(){let e=new kr(this.keepalive,this.testId,this.testName,this.diskStorage);return this.children.push(e),e}async startMainStepList(){let e=new kr(this.keepalive,this.testId,this.testName,this.diskStorage);return this.children.push(e),e}async startAfterStepList(){let e=new kr(this.keepalive,this.testId,this.testName,this.diskStorage);return this.children.push(e),e}async getScreenshot(e,t){return this.diskStorage.readFile(`${wa}/${t}.jpeg`)}},ac=class{constructor(e,t,n,o,i,a){this.client=e;this.testId=t;this.testName=n;this.runId=o;this.metadata=i;this.diskStorage=a}children=[];finished=!1;get loggerBindings(){return{runId:this.runId}}async finish(e){if(this.finished)return;this.finished=!0;let t={...this.metadata,finishedAt:e.finishedAt||new Date,status:e.status,failureDetails:e.failureDetails,failureReason:e.failureReason,flake:e.isFlake||!1,failureRecoveryDetails:e.failureRecoveryDetails};await Promise.all(this.children.map(n=>n.finish({logger:e.logger,status:t.status,results:[],beforeResults:[],afterResults:[]}))),this.diskStorage.storeFile({name:"metadata.json",contents:JSON.stringify(t,null,2)}),this.diskStorage.close()}async startAttempt(e){this.metadata.attempts=this.metadata.attempts+1,this.metadata.status="RUNNING",this.diskStorage.storeFile({name:"metadata.json",contents:JSON.stringify(this.metadata,null,2)});let t=this.diskStorage.cd(`attempts/${this.metadata.attempts}`),n=WR(),o={id:n,schemaVersion:nr,runAttemptSchemaVersion:Jm,startedAt:new Date,status:"RUNNING"};t.storeFile({name:"metadata.json",contents:JSON.stringify(o,null,2)});let i=VR(async()=>{await this.client.extendAndroidEmulatorTtl(e.emulatorName)},3e4,{maxWait:6e4}),a=new ic(()=>void i(),this.testId,this.testName,n,o,t);return this.children.push(a),a}},Ta=class r{constructor(e,t,n,o,i){this.orgId=e;this.runGroupId=t;this.metadata=n;this.client=o;this.diskStorage=i}children=[];finished=!1;get loggerBindings(){return{orgId:this.orgId,runGroupId:this.runGroupId,branch:this.metadata.gitBranchName}}static async start({orgId:e,runGroupId:t,outputDir:n,client:o,gitMetadata:i,labels:a}){let s={...i,id:t,trigger:Et.CLI,startedAt:new Date,status:"RUNNING",cliVersion:Nr,labels:a??[]},l=new xo(n);return l.storeFile({name:"metadata.json",contents:JSON.stringify(s,null,2)}),new r(e,t,s,o,l)}async finish(e){if(this.finished)return;this.finished=!0;let{status:t}=e,n={...this.metadata,status:t,updatedAt:new Date,finishedAt:new Date};await Promise.all(this.children.map(o=>o.finish({logger:e.logger,status:n.status,finishedAt:n.finishedAt}))),this.diskStorage.storeFile({name:"metadata.json",contents:JSON.stringify(n,null,2)})}async startRun(e){let t=this.diskStorage.createRunArchive(e.runId),n={stepsSnapshot:e.originalSteps.steps,runGroupId:this.runGroupId,testId:e.testId,testName:e.testName,testDescription:e.testDescription,labels:e.testLabels,trigger:"CLI",status:"RUNNING",environmentName:e.environmentName,cliVersion:Nr,schemaVersion:e.schemaVersion,startedAt:new Date,attempts:0,quarantined:!1,executionType:"ANDROID"};t.storeFile({name:"metadata.json",contents:JSON.stringify(n,null,2)});let o=new ac(this.client,e.testId,e.testName,e.runId,n,t);return this.children.push(o),o}};async function qh(r){let{options:e,fixtures:t,inputs:n,metadata:o}=r,{project:i,apiClient:a,logger:s}=t,{orgId:l,runGroupId:c,gitMetadata:d}=o,{outputDir:m,parallel:u}=e;XR(m)&&E.warn(`Output directory ${m} already exists, removing before test execution...`);let g=aa(i,E),p=await Wh({tests:n.tests,yes:e.yes,project:i,momenticFiles:g,logger:E}),h=s.child({orgId:l,runGroupId:c,branch:d.gitBranchName}),f=await Ta.start({logger:h,orgId:l,runGroupId:c,outputDir:m,client:a,gitMetadata:d,labels:[]}),y=[],w=new Date,A=new Set,I=async()=>JR({runResults:y,startTime:w,ranTests:A,apiClient:a,uploadResults:e.uploadResults??!1,outputDir:m}),C=[],R=async()=>{E.warn("SIGINT received. Stopping tests and printing latest results..."),await f.finish({logger:h,status:"CANCELLED"}),await I(),await Promise.allSettled(C.map(ee=>ee())),process.exit(1)};process.once("SIGINT",R);let _={};for(let ee=0;ee<p.length;ee++){let Re=Object.values(_);Re.length===u&&await Promise.race(Re.map(M=>M.promise));let D=p[ee],q=`test-${ee}`;_[q]={done:!1,promise:(async({testDefinition:M})=>{A.add(q);let te=M.relativeFilePath.includes("..")?M.fullFilePath:M.relativeFilePath;Eo({status:"START",testLogRef:te,getRunningTestsCount:()=>A.size,getTotalTestsCount:()=>p.length});let Ee=setInterval(()=>Eo({status:"RUN",testLogRef:te,getRunningTestsCount:()=>A.size,getTotalTestsCount:()=>p.length}),5*60*1e3),Xe=YR(),ze=h.child({testId:M.id,runId:Xe});try{let pt=await Vh({metadata:{...o,runId:Xe},fixtures:{...t,tracer:f,logger:ze},inputs:{...n,project:i,testDefinition:M,channel:n.channel??M.settings.defaultChannel,tag:n.tag??M.settings.defaultTag,logUpdate:(Je,Pe)=>Eo({status:Je,testLogRef:te,getRunningTestsCount:()=>A.size,getTotalTestsCount:()=>p.length,additionalText:Pe}),runSigIntHandlers:C},options:e});Eo({status:pt.status,testLogRef:te,getRunningTestsCount:()=>A.size,getTotalTestsCount:()=>p.length}),y.push(pt)}catch(pt){let Je=`Encountered unexpected fatal error when running test '${M.name}': ${pt.message}`;E.error(Je),ze.error(Je)}finally{clearInterval(Ee),_[q].done=!0,delete _[q]}})({testDefinition:D})}}await Promise.allSettled(Object.values(_).map(ee=>ee.promise));let Q=y.some(ee=>ee.status==="FAILED")?"FAILED":"PASSED";return await f.finish({logger:h,status:Q}),process.off("SIGINT",R),I()}async function JR({runResults:r,startTime:e,ranTests:t,apiClient:n,uploadResults:o,outputDir:i}){let a=n.getAppUrl(),s=Qg({results:r,startTime:e.getTime(),onFailed:l=>{},getDisplayLine:l=>{let c=`${El}- ${l.filePath}`;return l.runId&&(c+=` ( link when uploaded: ${a}/runs/${l.runId} )`),c},entity:"mobile test"});return E.log(""),o?(E.success(`Test results have been saved to the folder ${i}. Uploading to Momentic Cloud...`),await ga({client:n,consoleLogger:E,resultsPath:i})):E.success(`Test results have been saved to the folder ${i}. Upload them to Momentic Cloud by running 'npx momentic-mobile results upload ${i}'.`),s}async function sc(){iu(),oh("Chromium")||(yo?(await ra("The Chromium browser is not installed but is required for automating webviews. Install it now?")||process.exit(1),await oa({rawBrowsers:["chromium"],force:!1,all:!1})):(E.error("The Chromium browser is not installed but is required for automating webviews. Please install it using 'momentic-mobile install-browsers chromium'"),process.exit(1)))}ot.setApp("mobile-cli");var bn=new QR;bn.name("momentic-mobile").description("Momentic Mobile CLI").version(Nr);var rA=bn.command("assets").description("Manage mobile testing assets");rA.command("upload").description("Upload an asset for mobile tests").argument("<file>","Path to the asset file to upload").addOption(Lo).addOption(Oo).addOption(tc).addOption(Hh).action(async(r,e)=>{let{apiKey:t,server:n,tag:o,channel:i}=e,a=Ca.resolve(r);Ea.existsSync(a)||(E.error(`File not found: ${a}`),process.exit(1));let s=new Ot({baseUrl:n,apiKey:t,logger:E});/\.apk$/i.test(a)||(E.error(`File is not an APK file: ${a}`),process.exit(1)),await Do({tag:o?.toLowerCase(),channel:i.toLowerCase(),filePath:a,apiClient:s,logger:E})});bn.command("app").addOption(Lo).addOption(Oo).addOption(ec).addOption(zh).addOption(Zl).addOption(rc).action(async r=>{let{apiKey:e,server:t,port:n,yes:o}=r,i=vl(r.logLevel);ot.setApp("mobile-desktop-server");let a=new Ot({baseUrl:t,apiKey:e,logger:ot});await Fl({client:a,skipPrompts:o}),E.debug("API key check complete");let s=nA(import.meta.url),l=Ca.dirname(s),c=Ca.resolve(l,"..","static");await sc(),E.debug({appiumHome:po},"Resolved resource paths");let d=Ro({configFilePath:r.config});await Dh({momenticServerUrl:t,serverPort:n,apiKey:e,staticDir:c,initialProject:d,logLevel:i,logger:ot});let m=`http://localhost:${n}`;E.info(`Local app started on: ${m}`),await tA(m)});var Kh=bn.command("results").description("Merge and upload test results.");Kh.command("merge").description("Merge test results files.").addOption(nc).addArgument(new lc("<resultsPath>","Path to a directory that contains on or more test results archives.").argRequired()).action(async(r,e)=>{let{outputDir:t}=e;t||(E.error("Output directory is required."),process.exit(1)),Ea.existsSync(r)||(E.warn("Results path does not exist, skipping merge."),process.exit(0)),Ea.existsSync(t)&&E.warn(`Output directory ${t} already exists, removing before merging...`),yh(ot,t,r)});var oA=new lc("<results>","Path to the results archive.").argRequired();Kh.command("upload").description("Upload test results to Momentic cloud.").addOption(Lo).addOption(Oo).addArgument(oA).action(async(r,e)=>{let{apiKey:t,server:n}=e,o=ot,i=new Ot({baseUrl:n,apiKey:t,logger:o});await ga({consoleLogger:E,resultsPath:r,client:i}),process.exit(0)});var iA=bn.command("run").alias("test").description("Run tests on the local machine");iA.addOption(Lo).addOption(Oo).addOption(Zl).addOption(ec).addOption($h).addOption(rc).addOption(nc).addOption(jh).addOption(tc).addOption(Bh).addOption(Gh).addArgument(new lc("<tests...>","One or more test file path or folders that exist on the local machine.").argOptional()).action(async(r,e)=>{let{apiKey:t,server:n,tag:o,channel:i,outputDir:a}=e,s=vl(e.logLevel);await sc(),E.debug({appiumHome:po},"Resolved resource paths");let l=Ro({configFilePath:e.config}),c=a??l.config.outputDir??ph,d=e.parallel??l.config.parallel??1;E.debug("Checking API key and dependencies");let m=new Ot({baseUrl:n,apiKey:t,logger:E}),{orgId:u,userId:g}=await Fl({client:m,skipPrompts:e.yes});E.debug("API key check and browser installation complete");let p=eA(),h=ot.child({runGroupId:p,orgId:u,userId:g,cliVersion:Nr,trigger:"CLI"}),f=await la(ot,m,l);h.debug(f,"Got local git metadata");try{(await qh({options:{...e,outputDir:c,parallel:d,logLevel:s},fixtures:{logger:h,project:l,apiClient:m},inputs:{envOverride:e.env,tag:o,channel:i,tests:r},metadata:{gitMetadata:f,runGroupId:p,orgId:u}})).failed>0?process.exit(1):process.exit(0)}catch(y){E.error("Failed to run tests locally. Please check the error message below or run with the --verbose flag."),E.error(y),ot.error({err:y},"Failed to run tests locally"),process.exit(1)}});bn.command("init").description("Initialize an empty Momentic project in the current working directory").addOption(new ZR("--name <name>","Name of the project")).action(async r=>{E.info(`Welcome to the Momentic Mobile project setup wizard! \u{1F680}
|
|
3905
|
+
`),u=Mn.parse(GR.parse(d));HR(u.schemaVersion,Vo)&&a.warn(`Test ${c} has schema version ${u.schemaVersion}, which is greater than what is currently supported by this SDK. Please update your momentic package version to avoid unexpected behavior.`);let g=No.relative(t.rootDir,c),p=e.mobileTests[u.id]?.name??No.basename(c,No.extname(c));return{id:u.id,name:p,description:u.description,schemaVersion:u.schemaVersion,settings:u.settings,steps:u.steps,fullFilePath:c,relativeFilePath:g}}catch(d){a.error(`Failed to read and resolve mobile test at '${c}': ${d}`),process.exit(1)}}))).filter(c=>!!c)}import{cloneDeep as $R}from"lodash-es";async function Vh(r){let{inputs:e,fixtures:t,metadata:n,options:o}=r,{tracer:i,logger:a}=t,{runId:s}=n,{testDefinition:l,runSigIntHandlers:c}=e,d=new Date,m=await i.startRun({logger:a,testId:l.id,testName:l.name,runId:s,testDescription:l.description,schemaVersion:l.schemaVersion,originalSteps:{steps:$R(l.steps),beforeSteps:void 0,afterSteps:void 0}}),u=a.child(m.loggerBindings||{}),g=1+(l.settings.retries??1);try{for(let p=0;p<g;p++){let h=new Date,f=await jR({runTracer:m,testDefinition:l,metadata:n,constants:{attemptNumber:p,totalAttempts:g},fixtures:{...t,logger:u},inputs:e,options:o}),y=new Date;if(f.status!=="FAILED")return{...f,startedAt:d,lastAttemptStartedAt:h,finishedAt:y,attempts:p+1,testMetadata:l,steps:l.steps,filePath:l.relativeFilePath};if(p!==g-1){a.warn("Retrying failed mobile run");continue}return a.error("Mobile test failed after all exhausting attempts"),{...f,startedAt:d,lastAttemptStartedAt:h,finishedAt:y,attempts:p+1,testMetadata:l,steps:l.steps,filePath:l.relativeFilePath}}throw new Error("This code should not be reachable")}finally{c?.pop()}}async function jR(r){let{runTracer:e,testDefinition:t,metadata:n,constants:o,fixtures:i,inputs:a,options:s}=r,{attemptNumber:l,totalAttempts:c}=o,{orgId:d,gitMetadata:m}=n,{apiClient:u,logger:g}=i,{logUpdate:p,channel:h,tag:f,project:y,envOverride:w}=a,{alwaysSaveCache:A,noCache:I,logLevel:C}=s;l!==0&&p("RETRY",`attempt ${l}/${c}`);let R=ua({orgId:d,client:u,gitMetadata:m,alwaysSaveCache:A,noCache:I}),_=new xr(u,d),V=new Lr(u),Q={type:"API_KEY",baseUrl:u.baseUrl,apiKey:u.apiKey,logger:g},ee=new $n({},Q),Re=new Mr(Q,ee),D=new Pr({httpClient:u,fakerSeed:void 0}),q=await _o({apiClient:u,logger:g,logLevel:C,creationOpts:h?{apkToInstall:{channel:h,tag:f}}:{},onStatusUpdate:Pe=>{g.debug({status:Pe},"Limbar emulator status update")}}),M=await e.startAttempt({emulatorName:q.emulatorName}),te=g.child(M.loggerBindings||{}),Ee=w||t.settings.defaultEnv,Xe;Ee&&(Xe=ia(Ee,y,te).variables);let ze=new cr({variablesFromEnvironment:Xe??{},envName:Ee,testName:t.name}),pt=await Cr.init({driver:q.driver,generator:V,logger:te,orgId:d,adbPort:q.adbTunnelPort,fixtures:{storage:_,browserEnricher:Re,browserGenerator:ee,localCodeEvalTools:D,testContext:ze}}),Je;try{Je=await Qi({fixtures:{controller:pt,logger:te,cacheStorage:R},inputs:{steps:t.steps,orgId:d,testMetadata:t},tracer:M})}finally{await q.cleanup()}return await M.finish({logger:te,results:Je.results,status:Je.status}),Je}import{randomUUID as WR}from"crypto";import{debounce as VR}from"ts-debounce";var Nr="0.0.9";var wa="assets";function qR(r){switch(r){case"PASSED":return"SUCCESS";case"FAILED":return"FAILED";case"CANCELLED":return"CANCELLED";case"RUNNING":case"PENDING":case"RETRYING":case"WAITING_FOR_USER":return"RUNNING"}}function KR(r){switch(r){case"SUCCESS":return"PASSED";case"FAILED":return"FAILED";case"CANCELLED":return"CANCELLED";case"RUNNING":return"RUNNING";case"IDLE":return"PENDING"}}var oc=class{constructor(e,t,n,o){this.keepalive=e;this.testId=t;this.testName=n;this.diskStorage=o;this.commandTracer=new hr("command")}children=[];commandTracer;finished=!1;getParentStepIdChain(){return[]}attachBeforeScreenshot(e){let{snapshotId:t,screenshot:n}=e;this.diskStorage.storeFile({name:`${wa}/${t}.jpeg`,contents:n})}attachAfterScreenshot(e){let{snapshotId:t,screenshot:n}=e;this.diskStorage.storeFile({name:`${wa}/${t}.jpeg`,contents:n})}async finishInternal(e){this.finished||(this.finished=!0,await Promise.all(this.children.map(t=>t.finish({status:KR(e.status),finishedAt:e.finishedAt}))))}async finish(e){return await this.finishInternal({status:e.result.status,finishedAt:new Date(e.result.endTime)}),this.commandTracer.finish(),{trace:this.commandTracer.getRootSpan()}}startCommand(){return this.commandTracer}async startSubSteps(){let e=new kr(this.keepalive,this.testId,this.testName,this.diskStorage);return this.children.push(e),e}},kr=class{constructor(e,t,n,o){this.keepalive=e;this.testId=t;this.testName=n;this.diskStorage=o}children=[];finished=!1;async getScreenshot(e,t){return this.diskStorage.readFile(`${wa}/${t}.jpeg`)}getParentStepIdChain(){return[]}async startStep(e){this.keepalive();let t=new oc(this.keepalive,this.testId,this.testName,this.diskStorage);return this.children.push(t),t}async finish(e){this.finished||(this.finished=!0,await Promise.all(this.children.map(t=>t.finishInternal({status:qR(e.status),finishedAt:e.finishedAt}))))}},ic=class{constructor(e,t,n,o,i,a){this.keepalive=e;this.testId=t;this.testName=n;this.runAttemptId=o;this.metadata=i;this.diskStorage=a;this.diskStorage.mkdir("assets")}finished=!1;children=[];get loggerBindings(){return{runAttemptId:this.runAttemptId}}attachNetworkLogs(e){let{logs:t}=e;this.diskStorage.storeFile({name:"network.har",contents:JSON.stringify(t,null,2)})}attachConsoleLogs(e){let{logs:t}=e;this.diskStorage.storeFile({name:"console.json",contents:JSON.stringify(t,null,2)})}async finish(e){if(this.finished)return;this.finished=!0;let{logger:t,status:n,results:o,beforeResults:i,afterResults:a}=e,s={...this.metadata,status:n,finishedAt:new Date,results:ea(o,t),beforeResults:i?ea(i,t):void 0,afterResults:a?ea(a,t):void 0};await Promise.all(this.children.map(l=>l.finish({status:s.status,finishedAt:s.finishedAt}))),this.diskStorage.storeFile({name:"metadata.json",contents:JSON.stringify(s,null,2)})}async startBeforeStepList(){let e=new kr(this.keepalive,this.testId,this.testName,this.diskStorage);return this.children.push(e),e}async startMainStepList(){let e=new kr(this.keepalive,this.testId,this.testName,this.diskStorage);return this.children.push(e),e}async startAfterStepList(){let e=new kr(this.keepalive,this.testId,this.testName,this.diskStorage);return this.children.push(e),e}async getScreenshot(e,t){return this.diskStorage.readFile(`${wa}/${t}.jpeg`)}},ac=class{constructor(e,t,n,o,i,a){this.client=e;this.testId=t;this.testName=n;this.runId=o;this.metadata=i;this.diskStorage=a}children=[];finished=!1;get loggerBindings(){return{runId:this.runId}}async finish(e){if(this.finished)return;this.finished=!0;let t={...this.metadata,finishedAt:e.finishedAt||new Date,status:e.status,failureDetails:e.failureDetails,failureReason:e.failureReason,flake:e.isFlake||!1,failureRecoveryDetails:e.failureRecoveryDetails};await Promise.all(this.children.map(n=>n.finish({logger:e.logger,status:t.status,results:[],beforeResults:[],afterResults:[]}))),this.diskStorage.storeFile({name:"metadata.json",contents:JSON.stringify(t,null,2)}),this.diskStorage.close()}async startAttempt(e){this.metadata.attempts=this.metadata.attempts+1,this.metadata.status="RUNNING",this.diskStorage.storeFile({name:"metadata.json",contents:JSON.stringify(this.metadata,null,2)});let t=this.diskStorage.cd(`attempts/${this.metadata.attempts}`),n=WR(),o={id:n,schemaVersion:nr,runAttemptSchemaVersion:Jm,startedAt:new Date,status:"RUNNING"};t.storeFile({name:"metadata.json",contents:JSON.stringify(o,null,2)});let i=VR(async()=>{await this.client.extendAndroidEmulatorTtl(e.emulatorName)},3e4,{maxWait:6e4}),a=new ic(()=>void i(),this.testId,this.testName,n,o,t);return this.children.push(a),a}},Ta=class r{constructor(e,t,n,o,i){this.orgId=e;this.runGroupId=t;this.metadata=n;this.client=o;this.diskStorage=i}children=[];finished=!1;get loggerBindings(){return{orgId:this.orgId,runGroupId:this.runGroupId,branch:this.metadata.gitBranchName}}static async start({orgId:e,runGroupId:t,outputDir:n,client:o,gitMetadata:i,labels:a}){let s={...i,id:t,trigger:Et.CLI,startedAt:new Date,status:"RUNNING",cliVersion:Nr,labels:a??[]},l=new xo(n);return l.storeFile({name:"metadata.json",contents:JSON.stringify(s,null,2)}),new r(e,t,s,o,l)}async finish(e){if(this.finished)return;this.finished=!0;let{status:t}=e,n={...this.metadata,status:t,updatedAt:new Date,finishedAt:new Date};await Promise.all(this.children.map(o=>o.finish({logger:e.logger,status:n.status,finishedAt:n.finishedAt}))),this.diskStorage.storeFile({name:"metadata.json",contents:JSON.stringify(n,null,2)})}async startRun(e){let t=this.diskStorage.createRunArchive(e.runId),n={stepsSnapshot:e.originalSteps.steps,runGroupId:this.runGroupId,testId:e.testId,testName:e.testName,testDescription:e.testDescription,labels:e.testLabels,trigger:"CLI",status:"RUNNING",environmentName:e.environmentName,cliVersion:Nr,schemaVersion:e.schemaVersion,startedAt:new Date,attempts:0,quarantined:!1,executionType:"ANDROID"};t.storeFile({name:"metadata.json",contents:JSON.stringify(n,null,2)});let o=new ac(this.client,e.testId,e.testName,e.runId,n,t);return this.children.push(o),o}};async function qh(r){let{options:e,fixtures:t,inputs:n,metadata:o}=r,{project:i,apiClient:a,logger:s}=t,{orgId:l,runGroupId:c,gitMetadata:d}=o,{outputDir:m,parallel:u}=e;XR(m)&&E.warn(`Output directory ${m} already exists, removing before test execution...`);let g=aa(i,E),p=await Wh({tests:n.tests,yes:e.yes,project:i,momenticFiles:g,logger:E}),h=s.child({orgId:l,runGroupId:c,branch:d.gitBranchName}),f=await Ta.start({logger:h,orgId:l,runGroupId:c,outputDir:m,client:a,gitMetadata:d,labels:[]}),y=[],w=new Date,A=new Set,I=async()=>JR({runResults:y,startTime:w,ranTests:A,apiClient:a,uploadResults:e.uploadResults??!1,outputDir:m}),C=[],R=async()=>{E.warn("SIGINT received. Stopping tests and printing latest results..."),await f.finish({logger:h,status:"CANCELLED"}),await I(),await Promise.allSettled(C.map(ee=>ee())),process.exit(1)};process.once("SIGINT",R);let _={};for(let ee=0;ee<p.length;ee++){let Re=Object.values(_);Re.length===u&&await Promise.race(Re.map(M=>M.promise));let D=p[ee],q=`test-${ee}`;_[q]={done:!1,promise:(async({testDefinition:M})=>{A.add(q);let te=M.relativeFilePath.includes("..")?M.fullFilePath:M.relativeFilePath;Eo({status:"START",testLogRef:te,getRunningTestsCount:()=>A.size,getTotalTestsCount:()=>p.length});let Ee=setInterval(()=>Eo({status:"RUN",testLogRef:te,getRunningTestsCount:()=>A.size,getTotalTestsCount:()=>p.length}),5*60*1e3),Xe=YR(),ze=h.child({testId:M.id,runId:Xe});try{let pt=await Vh({metadata:{...o,runId:Xe},fixtures:{...t,tracer:f,logger:ze},inputs:{...n,project:i,testDefinition:M,channel:n.channel??M.settings.defaultChannel,tag:n.tag??M.settings.defaultTag,logUpdate:(Je,Pe)=>Eo({status:Je,testLogRef:te,getRunningTestsCount:()=>A.size,getTotalTestsCount:()=>p.length,additionalText:Pe}),runSigIntHandlers:C},options:e});Eo({status:pt.status,testLogRef:te,getRunningTestsCount:()=>A.size,getTotalTestsCount:()=>p.length}),y.push(pt)}catch(pt){let Je=`Encountered unexpected fatal error when running test '${M.name}': ${pt.message}`;E.error(Je),ze.error(Je)}finally{clearInterval(Ee),_[q].done=!0,delete _[q]}})({testDefinition:D})}}await Promise.allSettled(Object.values(_).map(ee=>ee.promise));let Q=y.some(ee=>ee.status==="FAILED")?"FAILED":"PASSED";return await f.finish({logger:h,status:Q}),process.off("SIGINT",R),I()}async function JR({runResults:r,startTime:e,ranTests:t,apiClient:n,uploadResults:o,outputDir:i}){let a=n.getAppUrl(),s=Qg({results:r,startTime:e.getTime(),onFailed:l=>{},getDisplayLine:l=>{let c=`${El}- ${l.filePath}`;return l.runId&&(c+=` ( link when uploaded: ${a}/runs/${l.runId} )`),c},entity:"mobile test"});return E.log(""),o?(E.success(`Test results have been saved to the folder ${i}. Uploading to Momentic Cloud...`),await ga({client:n,consoleLogger:E,resultsPath:i})):E.success(`Test results have been saved to the folder ${i}. Upload them to Momentic Cloud by running 'npx momentic-mobile results upload ${i}'.`),s}async function sc(){iu(),oh("Chromium")||(yo?(await ra("The Chromium browser is not installed but is required for automating webviews. Install it now?")||process.exit(1),await oa({rawBrowsers:["chromium"],force:!1,all:!1})):(E.error("The Chromium browser is not installed but is required for automating webviews. Please install it using 'momentic-mobile install-browsers chromium'"),process.exit(1)))}ot.setApp("mobile-cli");var bn=new QR;bn.name("momentic-mobile").description("Momentic Mobile CLI").version(Nr);var rA=bn.command("assets").description("Manage mobile testing assets");rA.command("upload").description("Upload an asset for mobile tests").argument("<file>","Path to the asset file to upload").addOption(Lo).addOption(Oo).addOption(tc).addOption(Hh).action(async(r,e)=>{let{apiKey:t,server:n,tag:o,channel:i}=e,a=Ca.resolve(r);Ea.existsSync(a)||(E.error(`File not found: ${a}`),process.exit(1));let s=new Ot({baseUrl:n,apiKey:t,logger:E});/\.apk$/i.test(a)||(E.error(`File is not an APK file: ${a}`),process.exit(1)),await Do({tag:o?.toLowerCase(),channel:i.toLowerCase(),filePath:a,apiClient:s,logger:E})});bn.command("app").addOption(Lo).addOption(Oo).addOption(ec).addOption(zh).addOption(Zl).addOption(rc).action(async r=>{let{apiKey:e,server:t,port:n,yes:o}=r,i=vl(r.logLevel);ot.setApp("mobile-desktop-server");let a=new Ot({baseUrl:t,apiKey:e,logger:ot});await Fl({client:a,skipPrompts:o}),E.debug("API key check complete");let s=nA(import.meta.url),l=Ca.dirname(s),c=Ca.resolve(l,"..","static");await sc(),E.debug({appiumHome:po},"Resolved resource paths");let d=Ro({configFilePath:r.config});await Dh({momenticServerUrl:t,serverPort:n,apiKey:e,staticDir:c,initialProject:d,logLevel:i,logger:ot});let m=`http://localhost:${n}`;E.info(`Local app started on: ${m}`),await tA(m)});var Kh=bn.command("results").description("Merge and upload test results.");Kh.command("merge").description("Merge test results files.").addOption(nc).addArgument(new lc("<resultsPath>","Path to a directory that contains on or more test results archives.").argRequired()).action(async(r,e)=>{let{outputDir:t}=e;t||(E.error("Output directory is required."),process.exit(1)),Ea.existsSync(r)||(E.warn("Results path does not exist, skipping merge."),process.exit(0)),Ea.existsSync(t)&&E.warn(`Output directory ${t} already exists, removing before merging...`),yh(ot,t,r)});var oA=new lc("<results>","Path to the results archive.").argRequired();Kh.command("upload").description("Upload test results to Momentic cloud.").addOption(Lo).addOption(Oo).addArgument(oA).action(async(r,e)=>{let{apiKey:t,server:n}=e,o=ot,i=new Ot({baseUrl:n,apiKey:t,logger:o});await ga({consoleLogger:E,resultsPath:r,client:i}),process.exit(0)});var iA=bn.command("run").alias("test").description("Run tests on the local machine");iA.addOption(Lo).addOption(Oo).addOption(Zl).addOption(ec).addOption($h).addOption(rc).addOption(nc).addOption(jh).addOption(tc).addOption(Bh).addOption(Gh).addArgument(new lc("<tests...>","One or more test file path or folders that exist on the local machine.").argOptional()).action(async(r,e)=>{let{apiKey:t,server:n,tag:o,channel:i,outputDir:a}=e,s=vl(e.logLevel);await sc(),E.debug({appiumHome:po},"Resolved resource paths");let l=Ro({configFilePath:e.config}),c=a??l.config.outputDir??ph,d=e.parallel??l.config.parallel??1;E.debug("Checking API key and dependencies");let m=new Ot({baseUrl:n,apiKey:t,logger:E}),{orgId:u,userId:g}=await Fl({client:m,skipPrompts:e.yes});E.debug("API key check and browser installation complete");let p=eA(),h=ot.child({runGroupId:p,orgId:u,userId:g,cliVersion:Nr,trigger:"CLI"}),f=await la(ot,m,l);h.debug(f,"Got local git metadata");try{(await qh({options:{...e,outputDir:c,parallel:d,logLevel:s},fixtures:{logger:h,project:l,apiClient:m},inputs:{envOverride:e.env,tag:o,channel:i,tests:r},metadata:{gitMetadata:f,runGroupId:p,orgId:u}})).failed>0?process.exit(1):process.exit(0)}catch(y){E.error("Failed to run tests locally. Please check the error message below or run with the --verbose flag."),E.error(y),ot.error({err:y},"Failed to run tests locally"),process.exit(1)}});bn.command("init").description("Initialize an empty Momentic project in the current working directory").addOption(new ZR("--name <name>","Name of the project")).action(async r=>{E.info(`Welcome to the Momentic Mobile project setup wizard! \u{1F680}
|
|
3906
3906
|
`),E.info("This wizard will help you bootstrap a new Momentic project."),Ea.existsSync(Ar)&&(E.error("A momentic.config.yaml file already exists in this directory. Please rename or remove it to initialize a new project."),process.exit(1));let t={name:r.name??await eh("Choose an identifier for your project, such as a service, product, or team name (default: 'app'):","app"),include:Il};uh(t,Ar),E.success(`Initialized Momentic project file at ${Ca.resolve(Ar)}`)});bn.command("install-browsers").description("Install browser executables onto the local machine.").option("-f, --force","Force reinstallation even if the browser executables already exist on disk.").option("-a, --all","Install all browsers types.").argument("[browsers...]",`Browsers to install. Available choices: ${Rl.join(", ")}.`).action(async(r,e)=>{!e.all&&r.length===0&&(E.error("No browsers specified"),process.exit(1)),await oa({rawBrowsers:r,force:e.force,all:e.all})});async function aA(){try{await bn.parseAsync(process.argv)}catch(r){ot.error({err:r},"Uncaught error in CLI"),ot.flush(),E.error(r),process.exit(1)}}aA();
|
|
3907
3907
|
//# sourceMappingURL=cli.js.map
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "momentic-mobile",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "momentic-mobile",
|
|
9
|
-
"version": "0.0.
|
|
9
|
+
"version": "0.0.9",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@actions/exec": "^1.1.1",
|
|
12
12
|
"@actions/io": "^1.1.3",
|