momentic 0.0.92 → 0.0.93
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 +3 -3
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -1343,16 +1343,16 @@ You have already executed the following commands successfully (most recent liste
|
|
|
1343
1343
|
`).forEach(c=>n.push(` ${c}`))):n.push("PAGE CONTENT CHANGE: <TOO_LONG_TO_DISPLAY/>"):n.push("PAGE CONTENT CHANGE: <NONE/>")}n.push("-".repeat(10))}),n.push(`STARTING URL: ${this.browser.baseURL}`),n.join(`
|
|
1344
1344
|
`)}getListHistory(){return Eh`Here are the commands that you have successfully executed:
|
|
1345
1345
|
${this.commandHistory.filter(e=>e.type==="AI_ACTION").map(e=>`- ${e.serializedCommand}`).join(`
|
|
1346
|
-
`)}`}async executeCommand(e,r,o,n=!1){let s=this.commandHistory[this.commandHistory.length-1];if(!n&&(!s||s.state!=="PENDING"))throw new x("InternalWebAgentError","Executing command but there is no pending entry in the history");if(this.closed)throw new Error("Attempting to execute command on a closed controller");let i=Date.now(),a=await this.executePresetStep(e,r,o),c=Date.now()-i;return this.logger.debug({result:a,duration:c},"Got execution result"),a.succeedImmediately&&!n&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(a.succeedImmediately=!1)),a.elementInteracted&&"target"in e&&e.target&&e.target.type==="description"&&!e.target.elementDescriptor&&(e.target.elementDescriptor=a.elementInteracted.trim()),n||(s.generatedStep=e,s.serializedCommand=Ye(e),s.state="DONE"),a}async executeAssertion(e){let r=!1,o,n=async()=>{try{o=await this.browser.screenshot({clearHighlights:!0,scale:"css"}),Kl(this.uploadScreenshotForDebugging("assertion",o),{milliseconds:5e3,fallback:()=>{}})}catch(g){this.logger.warn({err:g},"Failed to take screenshot for assertion, continuing without it")}},s=await this.browser.url(),[i]=await Promise.all([this.getBrowserState({filterByViewport:e.filterByViewport}),n()]),{serializedTree:a}=i,{tree:c}=i,l=.2*Eo["gpt-4o-2024-05-13"],d=os({serializedTree:a,tokenLimit:l});if(d){let w=(await this.generator.getRecommendedChunks({...d,description:e.assertion,tokenLimit:l})).ids;w.length===0?this.logger.debug("RAG returned no important information for assertion"):(a=c.pruneUsingRelevantIds(new Set(w)).serialize(),this.logger.debug({browserState:a},"Pruned a11y tree with RAG"),r=!0)}let u=this.getSerializedHistory(s,a),p={goal:e.assertion,url:s,browserState:a,history:u,lastCommand:this.lastExecutedCommand,screenshot:o?.toString("base64"),numPrevious:this.commandHistory.length},m=await this.generator.getAssertionResult(p,e.disableCache);if(m.relevantElements&&Promise.all(Array.from(new Set(m.relevantElements)).slice(0,5).map(g=>this.browser.highlight({id:g}))),this.logger.debug({usedRag:r,result:m},"Got assertion result"),!m.result)throw new x("AssertionFailureError",m.thoughts);return{succeedImmediately:!1,thoughts:m.thoughts,urlAfterCommand:s,beforeScreenshotOverride:o}}async wrapMultiElementTargetingCommand(e,r,o,n,s=1){let i=await Promise.all(e.map((a,c)=>this.wrapElementTargetingCommand({target:a,cache:r[c],action:async l=>l,options:n})));try{return{result:await o(...i.map(c=>c.result)),caches:i.map(c=>c.cache),elementInteractedDisplayStrings:i.map(c=>c.elementInteractedDisplayString)}}catch(a){if(s>0)return this.logger.debug({err:a},"Failed to execute action with multiple cached targets, retrying with AI"),this.wrapMultiElementTargetingCommand(e,e.map(()=>{}),o,n,s-1);throw new x("ActionFailureError",a.message,{cause:a})}}async wrapFrameUseCommand(e,r){if(!r)return e();let o=this.browser.getActiveFrame();try{return this.logger.debug({frameUrl:r},"Setting parent iframe target"),this.browser.setActiveFrame({type:"url",url:r}),await e()}finally{this.browser.setActiveFrame(o)}}async wrapElementTargetingCommand(e){return this.wrapFrameUseCommand(()=>this.wrapElementTargetingCommandHelper(e),e.options.iframeUrl)}async wrapElementTargetingCommandHelper({target:e,cache:r,action:o,options:n}){let{disableCache:s,filterByViewport:i,useSelector:a}=n,c=n.retriesWithAI??1,l;if((!r||s)&&!Fo(e))throw new x("ActionFailureError","Cannot target element with no cached data or element descriptor");if(a){if(e.type!=="description")throw new x("ActionFailureError","Cannot use selector along with non-description target");let u={id:-1,selector:e.elementDescriptor},p=await this.browser.resolveTarget(u);return{result:await o(p.locator),cache:u,elementInteractedDisplayString:p.displayString}}s&&(this.logger.debug("Cache explicitly disabled for this step"),r=void 0);let d=!!r&&Dr(r);if(!r){this.logger.debug("No cached locator data for target, prompting AI for fresh location"),c--;let u=await this.locateElement({description:e.elementDescriptor,disableCache:s,filterByViewport:i,iframeUrl:n.iframeUrl});r=u.target,l=u.thoughts}try{let u=await this.browser.resolveTarget(r),p=await o(u.locator);return d?this.logger.debug({cache:r},"Successfully used cached target to perform action"):this.logger.debug({cache:r},"Successfully generated and used new target data"),{result:p,cache:r,thoughts:l,elementInteractedDisplayString:u.displayString}}catch(u){if(u instanceof x)throw u;if(c>0&&e)return this.logger.debug({err:u,cache:r},"Failed to execute action with cached target, retrying with AI"),this.wrapElementTargetingCommand({target:e,cache:void 0,action:o,options:{...n,retriesWithAI:c}});throw new x("ActionFailureError",u.message,{cause:u})}}async screenshotWithDimensions(e){let r=await this.browser.screenshot(e),o=await is(r).metadata();return{buffer:r,width:Math.ceil(o.width??0),height:Math.ceil(o.height??0)}}async executePresetStep(e,r,o){let n;try{n=await this.resolveCommandTemplateStrings(e,r)}catch(s){throw new x("ActionFailureError",s.message,{cause:s})}try{let s=this.browser.getOpenPages(),i=await this.browser.url(),a=await this.executePresetActionHelper(e,r,o),c=!0;(e.type==="NAVIGATE"||e.type==="NEW_TAB"||e.type==="TAB"||e.type==="REFRESH")&&(c=!1);let l=this.browser.getOpenPages(),d=await this.browser.url();if(c&&l.length!==s.length)for(let u=l.length-1;u>=0;u--){let p=l[u];if(p!==i&&p!==d){await this.browser.switchToPage(p,u);break}}return a}catch(s){throw this.logger.error({err:s},"Error thrown in action controller"),s}finally{this.restoreCommandTemplateReplacements(e,n)}}restoreCommandTemplateReplacements(e,r={}){for(let[o,n]of Object.entries(r))ns(e,o,n)}async resolveCommandTemplateStrings(e,r,o="",n={}){let s=["type","a11yData","thoughts","cache"];for(let i in e){if(s.includes(i))continue;let a=e[i],c=o?`${o}.${i}`:i;if(typeof a=="string"&&a.includes("{{")){let l=await ho({s:a,context:r,logger:this.logger});if(a===l)continue;n[c]=a,e[i]=l}else typeof a=="object"&&a!==null&&!Array.isArray(a)&&await this.resolveCommandTemplateStrings(a,r,c,n)}return n}async executePresetActionHelper(e,r,o){switch(o=o||"disableCache"in e&&e.disableCache,e.type){case"SUCCESS":let n=e.condition;return n?.assertion.trim()?this.wrapFrameUseCommand(()=>this.executeAssertion(n),n?.iframeUrl):{succeedImmediately:!1,urlAfterCommand:await this.browser.url()};case"AI_ASSERTION":{if(!e.assertion.trim())throw new x("ActionFailureError","Missing assertion");return this.wrapFrameUseCommand(()=>this.executeAssertion(e),e.iframeUrl)}case"AI_WAIT":{if(!e.assertion.trim())throw new x("ActionFailureError","Missing assertion");let u=Date.now();if(e.timeout&&e.timeout>60)throw new x("AssertionFailureError",`AI wait timeout of ${e.timeout} exceeds the maximum allowed timeout of 60s`);let p=(e.timeout??10)*1e3,m,g,w=0;for(;Date.now()-u<p;)try{m=await this.executeAssertion({...e,type:"AI_ASSERTION"});break}catch(f){g=f instanceof Error?f:new Error(`${f}`),this.logger.info({err:f},`AI_WAIT assert attempt ${w} failed, retrying...`),w++,await W(p/10)}if(!m){let f=`AI wait still failing after ${p}ms.`;throw g&&(f+=` Latest result: ${g.message}`),new x("AssertionFailureError",f)}return m}case"AI_EXTRACT":{if(!e.goal.trim())throw new x("ActionFailureError","Cannot perform AI extraction without goal");let u=await this.browser.getCondensedHtml(),p=await this.generator.getTextExtraction({goal:e.goal,browserState:u,returnSchema:e.schema},o);if(p.result==="NOT_FOUND")throw new x("ActionFailureError","No relevant data found for extraction goal on this page");return{data:p.result,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}case"NAVIGATE":if(!Vn(e.url)&&!qn(e.url,this.browser.baseURL))throw new x("ActionFailureError",`Invalid URL provided to navigate command: ${e.url}`);await this.browser.navigate({url:e.url,loadTimeoutMs:e.loadTimeout?e.loadTimeout*1e3:void 0,networkIdleTimeoutMs:e.networkTimeout?e.networkTimeout*1e3:5e3});break;case"DIALOG":this.browser.registerDialogHandler(e.action);break;case"CAPTCHA":if(!this.browser.canSolveCaptchas())break;let s=await this.browser.solveCaptcha();s&&(await this.wrapElementTargetingCommand({target:{type:"description",elementDescriptor:"the captcha image solution input"},cache:void 0,action:u=>this.browser.click(u),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}}),await this.browser.type(s,{clearContent:!0,pressKeysSequentially:!1}));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":if(e.target&&!Ge(e.target))throw new Error("Scroll with x/y is not supported yet");let i,a;if(e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)){let{cache:u,thoughts:p,elementInteractedDisplayString:m}=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.hover(g,!1),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});i=m,u&&(e.cache={target:u}),a=p}switch(e.type){case"SCROLL_UP":await this.browser.scrollUp(e.deltaY);break;case"SCROLL_DOWN":await this.browser.scrollDown(e.deltaY);break;case"SCROLL_LEFT":await this.browser.scrollLeft(e.deltaX);break;case"SCROLL_RIGHT":await this.browser.scrollRight(e.deltaX);break}return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i,thoughts:a};case"WAIT":await this.browser.wait(e.delay*1e3);break;case"REFRESH":await this.browser.refresh({networkIdleTimeoutMs:e.networkTimeout?e.networkTimeout*1e3:void 0,loadTimeoutMs:e.loadTimeout?e.loadTimeout*1e3:void 0});break;case"CLICK":{let u={doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force,waitForUrl:e.waitForUrl};if(at(e.target)){await this.browser.clickUsingVisualCoordinates(e.target.percentXYLocation,u);break}let p=await this.browser.url(),m={disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl},{elementInteractedDisplayString:g,cache:w,thoughts:f}=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target??e.target.a11yData,action:b=>this.browser.click(b,u),options:m});w&&(e.cache={target:w});let h={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:g,thoughts:f};return Ct(p,h.urlAfterCommand)&&(h.succeedImmediately=!0,h.succeedImmediatelyReason="URL changed"),h}case"DRAG":{if(at(e.fromTarget)&&at(e.toTarget)){await this.browser.dragAndDropUsingVisualCoordinates(e.fromTarget.percentXYLocation,e.toTarget.percentXYLocation,{force:e.force,hoverSeconds:e.hoverSeconds});break}if(at(e.fromTarget)||at(e.toTarget))throw new Error("Drag and drop targets must be both coordinates or both descriptions");let{caches:u,elementInteractedDisplayStrings:p}=await this.wrapMultiElementTargetingCommand([e.fromTarget,e.toTarget],[e.cache?.fromTarget,e.cache?.toTarget],(m,g)=>this.browser.dragAndDrop(m,g,{force:e.force,hoverSeconds:e.hoverSeconds}),{filterByViewport:e.filterByViewport,useSelector:e.useSelector,disableCache:o});return u&&u.every(m=>m)&&(e.cache={fromTarget:u[0],toTarget:u[1]}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:p[0]}}case"MOUSE_DRAG":{if(e.target&&!Ge(e.target))throw new Error("Scroll with x/y is not supported yet");let u,p;if(e.target?.elementDescriptor){let w=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target,action:async f=>f,options:{filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl,disableCache:o}});u=w.result,p=w.elementInteractedDisplayString}let m=parseInt(e.deltaX),g=parseInt(e.deltaY);if(isNaN(m)||isNaN(g))throw new x("ActionFailureError",`Invalid pixel values passed to mouse drag command: (${e.deltaX}, ${e.deltaY})`);return await this.browser.mouseDrag(m,g,e.steps,u,{force:e.force}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:p}}case"SELECT_OPTION":{if(!Ge(e.target))throw new Error("Select with x/y is not supported yet");let u=e.target.elementDescriptor,{cache:p,thoughts:m,elementInteractedDisplayString:g}=await this.wrapElementTargetingCommand({target:{type:"description",elementDescriptor:cr(e,u)},cache:e.cache?.target??e.target.a11yData,action:w=>this.browser.selectOption(w,e.option),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});return p&&(e.cache={target:p}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:g,thoughts:m}}case"TAB":await this.browser.switchToPage(e.url,void 0,{networkIdleTimeoutMs:e.networkTimeout?e.networkTimeout*1e3:void 0,loadTimeoutMs:e.loadTimeout?e.loadTimeout*1e3:void 0});break;case"NEW_TAB":await this.browser.createNewTab(e.url,{networkIdleTimeoutMs:e.networkTimeout?e.networkTimeout*1e3:void 0,loadTimeoutMs:e.loadTimeout?e.loadTimeout*1e3:void 0});break;case"COOKIE":if(!e.value)break;await this.browser.setCookie(e.value);break;case"LOCAL_STORAGE":if(!e.value||!e.key)break;await this.browser.setLocalStorage(e.key,e.value);break;case"JAVASCRIPT":{let u;try{e.environment==="BROWSER"?u=await this.browser.executePageFunction(new Hr(e.fragment?`return ${e.code}`:e.code),void 0):u=await vt({code:e.code,fragment:!!e.fragment,context:r,timeoutMs:e.timeout?e.timeout*1e3:void 0,logger:this.logger})}catch(p){throw new x("ActionFailureError",p instanceof Error?p.message:`${p}`,{cause:p})}try{JSON.stringify(u)}catch(p){throw new x("ActionFailureError",`Return value is not serializable: ${p instanceof Error?p.message:`${p}`}`,{cause:p})}return{urlAfterCommand:await this.browser.url(),succeedImmediately:!1,data:u}}case"TYPE":{let u=await this.browser.url(),p,m,g=Ch(e.target);if(g){g.elementDescriptor=cr(e,g.elementDescriptor);let{elementInteractedDisplayString:f,cache:h,thoughts:b}=await this.wrapElementTargetingCommand({target:g,cache:e.cache?.target??g.a11yData,action:T=>this.browser.typeIntoTarget(e.value,T,{force:e.force,clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});m=b,p=f,h&&(e.cache={target:h})}else await this.browser.type(e.value,{force:e.force,clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially});e.pressEnter&&await this.browser.press("Enter");let w={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:p,thoughts:m};return Ct(u,w.urlAfterCommand)&&(w.succeedImmediately=!0,w.succeedImmediatelyReason="URL changed"),w}case"HOVER":{if(at(e.target)){await this.browser.hoverUsingVisualCoordinates(e.target.percentXYLocation);break}let{cache:u,thoughts:p,elementInteractedDisplayString:m}=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.hover(g,e.force),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});return u&&(e.cache={target:u}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:m,thoughts:p}}case"FOCUS":{if(!Ge(e.target))throw new Error("Focus with x/y is not supported yet");let{elementInteractedDisplayString:u,cache:p,thoughts:m}=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.focus(g),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});return p&&(e.cache={target:p}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:u,thoughts:m}}case"BLUR":{if(!Ge(e.target))throw new Error("Blur with x/y is not supported yet");let{cache:u,thoughts:p,elementInteractedDisplayString:m}=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.blur(g),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});return u&&(e.cache={target:u}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:m,thoughts:p}}case"PRESS":let c=await this.browser.url();await this.browser.press(e.value);let l={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return Ct(c,l.urlAfterCommand)&&(l.succeedImmediately=!0,l.succeedImmediatelyReason="URL changed"),l;case"REQUEST":{let u=e.timeout??30,p=null,m=new AbortController,g=Object.fromEntries(Object.entries(e.headers||{}).filter(([v,_])=>v&&_)),w=new URLSearchParams;Object.entries(e.params||{}).filter(([v,_])=>v&&_).forEach(([v,_])=>{w.append(v,_)});let f;if(Vn(e.url)&&(f=e.url),qn(e.url,this.browser.baseURL)&&(f=new URL(e.url,this.browser.baseURL).toString()),!f)throw new x("ActionFailureError",`Invalid URL: ${e.url}`);let h=async()=>{try{p=await fetch(`${f}?${w.toString()}`,{headers:g,method:e.method,body:e.body,signal:m.signal})}catch(v){this.logger.error({err:v},"Failed to make HTTP request")}},b=async()=>{await new Promise(v=>setTimeout(v,u*1e3)),m.abort()};await Promise.race([b(),h()]);let T=p;if(!T)throw new x("ActionFailureError",`Fetch request timed out after ${u} seconds`);if(!T.ok)throw new x("ActionFailureError",`Fetch request failed with status ${T.status}`);let S={};T.headers.forEach((v,_)=>{S[_]=v});let C={status:T.status,headers:S};return T.headers.get("content-type")?.includes("json")?C.json=await T.json():T.headers.get("content-type")?.includes("text")&&(C.text=await T.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:C}}case"VISUAL_DIFF":{if(e.target&&!Ge(e.target))throw new Error("Visual Diff with x/y is not supported yet");if(!e.screenshot)throw new x("ActionFailureError","Cannot execute visual diff without saved screenshot");await this.getBrowserState({skipWait:!1});let u={clearHighlights:!0,scale:"css",hideCaret:!0},p;if(!e.target)p=await this.screenshotWithDimensions(u);else{let K=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target??e.target?.a11yData,action:async G=>{},options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});if(!K.cache)throw new x("ActionFailureError",`Could not find target given description: ${e.target.elementDescriptor}`);p=await this.screenshotWithDimensions({target:K.cache,...u})}let m;if(e.screenshot.data.startsWith("https://")){let K=await fetch(e.screenshot.data);m=Buffer.from(await K.arrayBuffer())}else m=Buffer.from(e.screenshot.data,"base64");if(Math.abs(p.height-e.screenshot.height)>10||Math.abs(p.width-e.screenshot.width)>10){let K=`${p.width}x${p.height}`,G=`${e.screenshot.width}x${e.screenshot.height}`;return{fail:!0,thoughts:`Current screenshot (${K}) does not match saved screenshot dimensions (${G}) - did you change the size of the target or the viewport?`,beforeScreenshotOverride:m,afterScreenshotOverride:p.buffer,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}let g=is(p.buffer),w={width:p.width,height:p.height},f=is(m),h={width:e.screenshot.width,height:e.screenshot.height},b,T=w.width*w.height,S=h.width*h.height,C=Math.abs(w.height-h.height),v=Math.abs(w.width-h.width);T>S?(p.buffer=await g.resize(h.width,h.height,{fit:"cover"}).toBuffer(),b="current",p.width=h.width,p.height=h.height):S>T&&(m=await f.resize(w.width,w.height,{fit:"cover"}).toBuffer(),b="saved");let _={data:Buffer.alloc(p.width*p.height*4),width:p.width,height:p.height},L=Ah(ss.decode(m).data,ss.decode(p.buffer).data,_.data,p.width,p.height,{threshold:e.threshold,diffColorAlt:[0,255,0]})/(p.width*p.height)*100,A=L>e.threshold*100,P=`Visual diff of ${L.toFixed(2)}% detected, which is ${A?"over":"under"} the threshold of ${e.threshold*100}%.`;return b&&(P+=` The ${b} screenshot was cropped since it was taller by ${C} pixels and wider by ${v} pixels.`),{fail:A,thoughts:P,beforeScreenshotOverride:p.buffer,afterScreenshotOverride:ss.encode(_,75).data,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}case"FILE_UPLOAD":{let u=await jl({url:e.fileSource.url,logger:this.logger});await this.browser.setFileChooserHandler(u);break}default:return(u=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(e)}return{succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}async getReverseMappedTarget(e,r,o){return(await this.generator.getReverseMappedDescription({browserState:e.browserState,goal:`${r}`},o)).phrase}stopRecordMode(){this.recordAbortController?.abort()}async startRecordMode(e){this.recordAbortController=new AbortController;let r=new zt({signal:this.recordAbortController.signal,...e});await this.browser.startRecording(this.recordAbortController.signal,r)}};var _h=2;async function Yl({socket:t,logger:e,storage:r,devicePixelRatio:o,generator:n}){let s=t.id,i=t.handshake.query.testId,a=t.handshake.query.baseUrl,c=t.handshake.query.viewport?JSON.parse(decodeURIComponent(t.handshake.query.viewport)):void 0,l=t.handshake.query.testMetadata?J.parse(JSON.parse(decodeURIComponent(t.handshake.query.testMetadata))):void 0;if(!i||!a)throw new Error("Socket connection request is missing testId or baseUrl");let d=await r.getOrgId(i),u=t.handshake.query.environmentVariables?JSON.parse(decodeURIComponent(t.handshake.query.environmentVariables)):{},p=t.handshake.headers["x-forwarded-for"]?.split(",")[0],m=e.child({testId:i,baseUrl:a,orgId:d,sessionId:s});if(m.info({clientIp:p,event:"connect",args:t.handshake.query,sessionId:s},"Websocket event (connect)"),p&&O.getCurrentConnectionsByIp(p)>=_h)throw e.error({clientIp:p,sessions:O.getCurrentSessionsByIp(),...t.handshake.query},"Socket connection browser creation rate limit triggered"),new Error("You have exceeded the maximum number of connections allowed per client. Momentic limits the number of simultaneously open tabs to uphold browser reliability. Please close another tab and retry or contact Momentic Support if you believe this is in error.");O.reserveCapacityByIp(p);try{await Oh({socket:t,baseUrl:a,viewport:c,testMetadata:l,orgId:d,sessionId:s,logger:m,environmentVariables:u,clientIp:p,devicePixelRatio:o,storage:r,generator:n})}catch(g){throw m.debug({err:g},"Error setting up socket session"),O.releaseCapacityByIp(p),g}return{sessionId:s,testId:i,orgId:d}}async function Oh({socket:t,baseUrl:e,viewport:r,devicePixelRatio:o,testMetadata:n,orgId:s,sessionId:i,logger:a,storage:c,generator:l,environmentVariables:d,clientIp:u}){let p=C=>{t.emit("browserState",{...C,url:e})},m={};r&&(m.viewport=r),o&&(m.deviceScaleFactor=o);let g=ir.parse(n?.advanced??{}),[w,f]=await Promise.all([j.init({baseUrl:e,networkSettings:g,waitForLoad:!1,enricher:new So(a),timeout:g.pageLoadTimeoutMs,storage:c,logger:a,onBrowserUpdateDuringLoad:p,contextArgs:m}),Jt.init(s)]),h=new Qt({browser:w,generator:l,config:_r,logger:a,flagStore:f,orgId:s,storage:c}),b=Zi(t,i,a),T=async()=>{clearInterval(b.timer)},S=new Se({baseUrl:e,currentUrl:await h.browser.url(),variablesFromEnvironment:d});if(!t.connected)throw await w.cleanup(),new Error("Socket not connected anymore, not proceeding with session setup");t.emit("session",{url:e,userAgent:j.USER_AGENT,viewport:await h.browser.getViewport(),sessionId:i}),O.registerSession({controller:h,context:S,sessionId:i,cleanup:T,clientIp:u})}var Xl=[Ta,Nl,zl,Gl,Ul,$l,ea,Bl,Fl,pa,ha,ma,fa,ga,ya,wa,ra,ta,ba,oa];var Jl=t=>{let{logger:e}=t,r=new Ih(t.baseServer,{cors:{origin:"*",methods:["GET","POST"]},pingTimeout:3e4,pingInterval:6e4,maxHttpBufferSize:1e7});return r.on("connection",async o=>{let n;try{n=await Yl({...t,socket:o})}catch(s){e.error({event:"connection",type:"websocket",err:s},"Failed to setup connection"),o.emit("error",{message:s instanceof Error?s.message:`${s}`}),o.disconnect(!0);return}Xl.forEach(s=>xh(s,{socket:o,metadata:n,...t}))}),r};var xh=(t,e)=>{let r=t.createHandler(e),o=(...n)=>{["cloudMouseMove","cloudScroll"].includes(t.event)||e.logger.debug({...e.metadata,event:t.event,args:n},`Websocket event (${t.event})`);let s=i=>{e.logger.error({event:t.event,type:"websocket",args:n,err:i instanceof Error?i:new Error(`${i}`)},"Unhandled exception in socket handler"),e.socket.emit("error",{message:i instanceof Error?i.message:`${i}`})};try{let i=r.apply(void 0,n);i&&typeof i.catch=="function"&&i.catch(s)}catch(i){s(i)}};e.socket.on(t.event,o)};import{z as Lh}from"zod";import Ph from"fetch-retry";var kh=Ph(global.fetch),ne=class{static API_VERSION="v1";baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async sendRequest(e,r){let o=await kh(`${this.baseURL}${e}`,{retries:1,retryDelay:1e3,method:"POST",body:JSON.stringify(r),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!o.ok)throw new Error(`Request to ${e} failed with status ${o.status}: ${await o.text()}`);return o.json()}};var _t=class extends ne{constructor(e){super(e)}async getRecommendedChunks(e){let r=await this.sendRequest(`/${ne.API_VERSION}/web-agent/recommend-chunks`,e);return Ai.parse(r)}async getScreenshotFromS3(e){let r=await this.sendRequest(`/${ne.API_VERSION}/s3/visual-diff-screenshot`,{url:e});return Lh.string().parse(r)}async getElementLocation(e,r){let o=await this.sendRequest(`/${ne.API_VERSION}/web-agent/locate-element`,{...e,disableCache:r});return ui.parse(o)}async getAssertionResult(e,r){let o={...e,disableCache:r},n=await this.sendRequest(`/${ne.API_VERSION}/web-agent/assertion`,o);return li.parse(n)}async getProposedCommand(e,r){let o=await this.sendRequest(`/${ne.API_VERSION}/web-agent/next-command`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,screenshot:e.screenshot,disableCache:r});return ci.parse(o)}async getGranularGoals(e,r){let o=await this.sendRequest(`/${ne.API_VERSION}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:r});return di.parse(o)}async getReverseMappedDescription(e,r){let o=await this.sendRequest(`/${ne.API_VERSION}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:r});return pi.parse(o)}async getTextExtraction(e,r){let o={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:r},n=await this.sendRequest(`/${ne.API_VERSION}/web-agent/text-extraction`,o);return Vo.parse(n)}async getTestResultClassification(e){let r=await this.sendRequest(`/${ne.API_VERSION}/web-agent/result-classification`,e);return jo.parse(r)}};import{Router as Mh}from"express";function ge(t){return function(...e){let r=e[e.length-1],o=t(...e);Promise.resolve(o).catch(r)}}var Ql=Mh();Ql.get("/",ge((t,e)=>{let r=Ji();e.status(200).json(r)}));var Zl=Ql;import{Router as Uh}from"express";import eu,{multistream as Dh}from"pino";import Nh from"pino-pretty";var as=new Map,Fh=process.env.NODE_ENV==="production",cs=class t{consoleLogger;ddClientToken;hostname;bindingAttributes;excludeAttributesInConsoleLogs;excludeTimestamps;disableConsoleLogs;site="https://http-intake.logs.us5.datadoghq.com/api/v2/logs";constructor({bindings:e,clientToken:r,hostname:o,excludeAttributesInConsoleLogs:n,excludeTimestamps:s,disableConsoleLogs:i}){this.ddClientToken=r,this.hostname=o,this.excludeAttributesInConsoleLogs=n,this.excludeTimestamps=s,this.disableConsoleLogs=i,this.bindingAttributes={...e,env:process.env.NODE_ENV};let a={base:n?{}:this.bindingAttributes,errorKey:"err",level:"debug",timestamp:!s};this.consoleLogger=Fh?eu(a):eu(a,Dh([{stream:Nh({colorize:!0})}]))}child(e){return new t({clientToken:this.ddClientToken,bindings:{...this.bindingAttributes,...e},hostname:this.hostname,excludeAttributesInConsoleLogs:this.excludeAttributesInConsoleLogs,disableConsoleLogs:this.disableConsoleLogs})}flush(e){this.disableConsoleLogs||this.consoleLogger.flush(e)}log(e,r,o,...n){if(r&&o===void 0&&(o=`${r}`,r={}),this.disableConsoleLogs||this.consoleLogger[e](e==="error"?r:{testId:r.testId,runId:r.runId},o,...n),typeof r=="object"&&r&&"err"in r&&r.err instanceof Error){let i={};for(let a of Object.getOwnPropertyNames(r.err))i[a]=r.err[a];i.name=r.err.name,r.err=i}let s=Object.assign({},this.bindingAttributes,r&&typeof r=="object"?r:{});n.length>0&&(s.args=n),(async()=>{try{let i=await fetch(this.site,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","DD-API-KEY":this.ddClientToken},body:JSON.stringify([{ddsource:this.bindingAttributes.app,hostname:this.hostname??"vercel",service:"momentic",message:{message:o||"",...s,level:e}}])});if(!i.ok)throw new Error(`Failed to log to Datadog: ${i.statusText})}`)}catch(i){this.disableConsoleLogs||this.consoleLogger.warn({obj:r,msg:o,args:n,err:i},"Failed to log to Datadog")}})()}debug(e,r,...o){this.log("debug",e,r,...o)}info(e,r,...o){this.log("info",e,r,...o)}warn(e,r,...o){this.log("warn",e,r,...o)}error(e,r,...o){this.log("error",e,r,...o)}bindings(){return this.bindingAttributes}setMinLevel(e){this.consoleLogger.level=e}enableConsoleLogs(){this.disableConsoleLogs=!1}},Ao=({app:t,clientToken:e,hostname:r,excludeAttributesInConsoleLogs:o,excludeTimestamps:n,disableConsoleLogs:s})=>{if(!process.env.DD_CLIENT_TOKEN&&!process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN&&!e)throw new Error("Missing DD_CLIENT_TOKEN");return as.has(t)||as.set(t,new cs({bindings:{app:t},hostname:r,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN,excludeAttributesInConsoleLogs:o,excludeTimestamps:n,disableConsoleLogs:s})),as.get(t)};import{hostname as Gh}from"os";var Te=Ao({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:Gh(),excludeAttributesInConsoleLogs:!0,excludeTimestamps:!0,disableConsoleLogs:!0});var Or=Uh();Or.get("/",ge(async(t,e)=>{let r=await Hi(Te);e.status(200).json(r)}));Or.get("/metadata",ge((t,e)=>{let r=Bi();e.status(200).json(r)}));Or.post("/",ge(async(t,e)=>{let r;try{r=si.parse(t.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}try{ar(r.name)}catch(n){e.status(400).json({error:`Invalid module name: ${n}`});return}let o=$i(r.name,r.steps);e.status(201).json(o)}));Or.get("/:moduleId",ge(async(t,e)=>{if(!t.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let r=await Wi(t.params.moduleId,Te);e.json(r)}));var tu=Or;import{Router as zh}from"express";import{existsSync as $h}from"fs";import{join as Oo}from"path";import{v4 as Bh}from"uuid";var us="https://api.momentic.ai",ru=t=>{us=t},Ir=()=>us,Ro,ls,ou=async t=>{if(Ro)return;let e=new oe({baseURL:us,apiKey:t});try{Ro=await e.getOrgId(),ls=t}catch(r){throw new Error(`Error checking API key against server: ${r}`)}},_o=()=>{if(!Ro)throw new Error("Your organization ID appears invalid. Please contact Momentic Support.");return Ro},nu=()=>{if(!ls)throw new Error("Your API key appears invalid. Please contact Momentic Support.");return ls};var Ot=zh();Ot.get("/",ge((t,e)=>{let r=gr(Z,Te);e.status(200).json(r)}));Ot.post("/",ge((t,e)=>{let r;try{r=ni.parse(t.body)}catch(a){e.status(400).json({error:`Invalid request body: ${a}`});return}try{ar(r.name)}catch(a){e.status(400).json({error:a.message});return}let n={id:Bh(),name:r.name,baseUrl:r.baseUrl,schemaVersion:X,advanced:{disableAICaching:!1,viewport:r.viewport??ut},retries:0,steps:[]};r.environment&&(n.envs=[{name:r.environment,default:!0}]);let s=Ki(n,r.name),i={...n,testPath:s};e.status(201).json(i)}));Ot.get("/:testPath",ge(async(t,e)=>{if(!t.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let r=Oo(Z,t.params.testPath);if(!$h(r)){e.status(400).json({error:`Test not found at path: ${r}`});return}let o=await no(r,Tt,Te);e.status(200).json(o)}));Ot.patch("/:testPath/metadata",ge(async(t,e)=>{if(!t.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let r;try{r=oi.parse(t.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}let o=Oo(Z,t.params.testPath);mr(r,o),e.status(201).json({message:"ok"})}));Ot.patch("/:testPath",ge(async(t,e)=>{if(!t.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let r;try{r=ri.parse(t.body)}catch(l){e.status(400).json({error:`Invalid request body: ${l}`});return}let o=Oo(Z,t.params.testPath),n;try{n=Yi(o)}catch(l){e.status(400).json({error:`Existing test file on disk is invalid: ${l}`});return}let{stepsToSave:s,moduleUpdates:i,cachesToSave:a}=de({steps:r.steps,testId:n.id,orgId:_o()});i.forEach(l=>zi(l)),mr({steps:s},o),await new oe({apiKey:nu(),baseURL:Ir()}).updateStepCaches({entries:a,testId:n.id}),e.status(201).json({message:"ok"})}));Ot.patch("/:testPath/environments",ge(async(t,e)=>{if(!t.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let r;try{r=ii.parse(t.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}let o=Oo(Z,t.params.testPath);mr({envs:r.defaultEnv?[{name:r.defaultEnv,default:!0}]:[]},o),e.status(201).json({message:"ok"})}));var su=Ot;async function iu({momenticServerUrl:t,apiKey:e,serverPort:r,appPort:o,staticDir:n,devicePixelRatio:s}){if(!jh(Z)||!Vh(Z).isDirectory()){let m=Yh.isAbsolute(Z);throw new Error(`Root folder ${Z} does not exist${m?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}t&&ru(t),Te.info("Checking API key"),await ou(e);let a=`http://localhost:${r}`;o&&(a=`http://localhost:${o}`);let c=Xh(n);await new Promise(m=>{c.listen(r,()=>{Te.info(`Server is running at http://localhost:${r}`),m()})});let d=new _t({baseURL:Ir(),apiKey:e}),u=new oe({baseURL:Ir(),apiKey:e}),p=new Ut(u,_o());Jl({baseServer:c,generator:d,storage:p,logger:Te,devicePixelRatio:s}),await Kh(a)}function Xh(t){let e=ds();e.use(Wh()),e.use(Hh.json({limit:"50mb"}));let r=ds.Router();if(r.use("/tests",su),r.use("/modules",tu),r.use("/environments",Zl),e.use("/api",r),e.use((n,s,i)=>{n.path!=="/healthcheck"&&Te.debug({url:n.url,path:n.path,query:n.query,method:n.method,body:n.body,headers:n.rawHeaders,client:n.ip},"Received desktop-server request"),s.on("close",()=>{s.statusCode>=400&&Te.error({url:n.url,method:n.method,statusCode:s.statusCode},"Request completed in error")}),i()}),e.use((n,s,i,a)=>{n instanceof Error&&n.message.includes("BadRequestError: request aborted")||(Te.error({stack:n.stack,msg:n.message,url:s.url,method:s.method},"Unhandled exception leading to 500 on desktop-server"),i.status(500).send("Internal Server Error"),a(n))}),t){let n=ds.static(t);e.use("/",n),e.use("*",n)}return qh.createServer(e)}import{existsSync as qf}from"fs";import Fu,{dirname as Kf,join as Yf}from"path";import{chdir as Xf}from"process";import{fileURLToPath as Jf}from"url";var au="0.0.92";import xr from"chalk";var It=(...t)=>{Ie(xr.blue(...t))};var Pr=(...t)=>{Ie(xr.dim(...t))};var se=(...t)=>{Ie(xr.green(...t))},Ie=(...t)=>{console.log(...t)},le=(...t)=>{console.error(xr.yellow(...t))},F=(...t)=>{console.error(xr.red(...t))};import{hostname as Qh}from"os";var I=Ao({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:Qh(),excludeAttributesInConsoleLogs:!0,excludeTimestamps:!0,disableConsoleLogs:!0});import{execSync as Zh}from"child_process";import{platform as ef}from"os";function ps(){return cu()?(It("Setting device pixel ratio to 2 automatically since a Mac OS Retina screen was detected."),It("If you are using a low pixel-density monitor, you should manually set --pixel-ratio to 1 to avoid incorrect viewport calculations."),It("Confirm your device's pixel-ratio at https://www.mydevice.io."),2):(It("Setting device pixel ratio to 1."),It("If you are using Momentic on a high-pixel density (HiDPI) monitor, relaunch with the --pixel-ratio option to avoid incorrect viewport calculations"),It("Confirm your device's pixel-ratio at https://www.mydevice.io."),1)}function cu(){return ef()==="darwin"&&Zh("system_profiler SPDisplaysDataType").toString().includes("Retina")}function ms(t){cu()&&t===1&&(le("If you are using Momentic on a Retina screen, relaunch with the --pixel-ratio option to avoid incorrect viewport calculations."),le("Confirm your device's pixel-ratio at https://www.mydevice.io."))}import{createHash as pu}from"crypto";import{existsSync as mu,readFileSync as bs,writeFileSync as gu}from"fs";import{join as hu}from"path";import{parse as mf}from"yaml";import{existsSync as xo,mkdirSync as lu,readdirSync as sf,statSync as fs}from"fs";import{homedir as uu}from"os";import{join as We,resolve as af}from"path";import{registry as gs}from"playwright-core/lib/server";function tf(t){let e=[],r=[];for(let o of t){let n=gs.findExecutable(o);!n||n.installType==="none"?e.push(o):r.push(n)}return r}async function Io(){let t=tf(["chromium"]);await gs.installDeps(t,!1),await gs.install(t,!1)}import{statSync as rf}from"fs";import of from"readline/promises";var hs=!1,nf=(()=>{try{return rf("/.dockerenv"),!0}catch{return!1}})();async function Ae(t,e){if(process.env.CI||hs||!process.stdout.isTTY||nf)return!0;I.flush(),await new Promise(i=>setTimeout(i,500));let r=of.createInterface({input:process.stdin,output:process.stdout}),o=t.split("."),n;if(o.length===1)n=t;else{let i=`${o.slice(0,o.length-1).join(". ").trim()}.`;e?le(i):Ie(i),n=o[o.length-1].trim()}let s=await r.question(`${n} ('y' for yes / n for no / 'A' to accept all) `);return r.close(),s==="A"?(hs=!0,setTimeout(()=>{hs=!1},3e3),!0):s.toLowerCase()==="y"}var cf=[Be,Kr,Ft],je=pf(),xt=We(je,Be),ws=We(je,Kr),Ve=We(je,Ft),lf=!!process.env.CI||!process.stdout.isTTY,ys=We(uu(),"momentic",rn),du=[xt,ws,Ve];function kr(t){try{return xo(t)&&fs(t).isDirectory()}catch(e){return I.error({err:e},`Error reading path ${t} during directory exists check`),!1}}async function rt(t){let e=process.versions.node,r=parseInt(e.split(".")[0]);(isNaN(r)||r<20)&&(F(`Node.js version 20 or higher is required to run the Momentic CLI. Detected: ${process.versions.node}.`),process.exit(1));let o=await t.client.getOrgId();return await Io(),await df(t),o}async function uf(t){kr(je)||!t&&!await Ae(`The root '${Xe}' folder was not found in the current directory. You will need to invoke the Momentic CLI from this directory in the future. Proceed?`)&&(F("Setup cancelled."),process.exit(1)),kr(ys)||(!t&&!await Ae("The Chrome cache folder was not found in your home directory. Proceed with creation?")&&(F("Setup cancelled."),process.exit(1)),lu(ys,{recursive:!0}));for(let e of du)kr(e)||(!t&&!await Ae(`'${e}' folder was not found in the current directory. Create it now?`)&&(F("Setup cancelled."),process.exit(1)),lu(e,{recursive:!0}))}function Lr(t,e,r=new Set){for(let o of t){let n=af(o),s=!1;try{s=xo(n)&&fs(n).isDirectory()}catch(i){I.error({err:i},`Error reading path ${n} during collect paths`)}if(n&&s){let i=sf(n).map(a=>We(n,a));Lr(i,e,r);continue}if(n.endsWith(".yaml")){try{if(!xo(n)||!fs(n).isFile()){I.error(`File not found or unreadable: ${n}`);continue}}catch(i){I.error({err:i},`Error reading file ${n} during collect paths`);continue}if(!e(n))continue;r.add(n)}}return r}async function df({client:t,createFolders:e,skipPrompts:r,pullEnvs:o}){if(uu()||(F("Your home directory could not be found. Please ensure the HOME environment variable is set for POSIX systems, and the USERPROFILE environment variable is set for Windows systems."),process.exit(1)),e)await uf(r);else for(let n of[je,ys,...du])!kr(n)&&!lf&&(F(`The '${n}' folder is required to run the Momentic CLI but was not found in the current directory. Please run 'npx momentic@latest init' to create the required folders.`),F(`The current working directory is: ${process.cwd()}`),process.exit(1));o&&(!r&&!await Ae("Pull environments from Momentic Cloud? Environment definitions may be required to run tests locally.")&&(F("Setup cancelled."),process.exit(1)),await Po({client:t,all:!0,skipPrompts:r}))}function pf(){let t=o=>xo(We(o,Xe))&&cf.some(n=>kr(We(o,Xe,n))),e=process.cwd();for(;e!=="/"&&!t(e);)e=We(e,"..");let r;return t(e)?r=e:r=process.cwd(),r!==process.cwd()?le(`Current working directory does not contain a ${Xe} folder, treating ${r} as the Momentic root folder instead`):I.info(`Identified ${r} as the Momentic root folder`),We(r,Xe)}async function Po({envNames:t,client:e,all:r,skipPrompts:o}){let n=await e.getAllEnvironments();t&&!r&&(n=n.filter(a=>t?.includes(a.name))),mu(Ve)||(F(`${Ve} does not exist in the current directory. Please run 'npx momentic@latest init' first.`),process.exit(1));let s=[],i=[];for(let a of n){let c=hu(Ve,`${a.name}.yaml`);if(!mu(c))gu(c,ln(a)),s.push(c);else{let l=pu("sha256").update(bs(c)).digest("hex"),d=ln(a),u=pu("sha256").update(d).digest("hex");if(l!==u){if(!o&&!await Ae(`Environment ${a.name} already exists on disk but needs to be updated. Overwrite?`))return;gu(c,d),i.push(c)}}}se("Pulled environments successfully!"),s.length&&(se("Created:"),s.forEach(a=>se(" ",a))),i.length&&(se("Updated:"),i.forEach(a=>se(" ",a)))}function gf(t){return t.endsWith(".yaml")?bs(t,"utf8").includes("momentic/environment")?!0:(le(`Skipping YAML that is not a Momentic environment: ${t}`),!1):!1}async function fu({client:t,names:e,all:r,yes:o}){let n;r?n=[Ve]:n=e.map(a=>hu(Ve,`${a.toLowerCase()}.yaml`));let s=Lr(n,gf);Ie(`Found ${s.size} environments to push:`),s.forEach(a=>Ie(" ",a));let i=[];for(let a of s){let c=mf(bs(a,"utf-8"));try{let l=we.parse(c);i.push(l)}catch(l){F(`${a} failed to parse as a valid environment file.`),F(l),process.exit(1)}}!o&&!await Ae("Pushing environments can overwrite variables on cloud and instantly affect scheduled runs. Continue?",!0)||(await t.updateEnvironments(i),se("Pushed environments successfully!"))}import{Argument as ko,Option as Ne}from"commander";import{cpus as yu}from"os";import{z as hf}from"zod";var ot=new Ne("--api-key <key>","API key for authentication. If not supplied, attempts to read the MOMENTIC_API_KEY env var.").env("MOMENTIC_API_KEY").makeOptionMandatory(!0),nt=new Ne("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),st=new Ne("-y, --yes","Skip all confirmation prompts.").env("CI"),wu=new Ne("-w, --wait","Wait for tests to finish running before exiting. Only applicable when running tests remotely").implies({remote:!0}),Ts=new Ne("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").implies({local:!0}),Ss=new Ne("--pixel-ratio <number>","Device pixel ratio of your screen or monitor. Mac OS Retina displays and machines marketed as 'HiDPI' should set this to 2. Visit https://www.mydevice.io/ to find your device's pixel ratio.").argParser(t=>parseInt(t,10)),bu=new Ne("--env <env>","Name of the environment to use when running tests."),Tu=new Ne("--url-override <urlOverride>","Fully qualified url (e.g. https://www.google.com) to start all tests from. Overrides any default starting url set from the test or environment."),Es=new Ne("-a, --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments."),vs=new Ne("-a, --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments."),Su=new ko("<tests...>",`One or more test paths to pull from Momentic Cloud.
|
|
1346
|
+
`)}`}async executeCommand(e,r,o,n=!1){let s=this.commandHistory[this.commandHistory.length-1];if(!n&&(!s||s.state!=="PENDING"))throw new x("InternalWebAgentError","Executing command but there is no pending entry in the history");if(this.closed)throw new Error("Attempting to execute command on a closed controller");let i=Date.now(),a=await this.executePresetStep(e,r,o),c=Date.now()-i;return this.logger.debug({result:a,duration:c},"Got execution result"),a.succeedImmediately&&!n&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(a.succeedImmediately=!1)),a.elementInteracted&&"target"in e&&e.target&&e.target.type==="description"&&!e.target.elementDescriptor&&(e.target.elementDescriptor=a.elementInteracted.trim()),n||(s.generatedStep=e,s.serializedCommand=Ye(e),s.state="DONE"),a}async executeAssertion(e){let r=!1,o,n=async()=>{try{o=await this.browser.screenshot({clearHighlights:!0,scale:"css"}),Kl(this.uploadScreenshotForDebugging("assertion",o),{milliseconds:5e3,fallback:()=>{}})}catch(g){this.logger.warn({err:g},"Failed to take screenshot for assertion, continuing without it")}},s=await this.browser.url(),[i]=await Promise.all([this.getBrowserState({filterByViewport:e.filterByViewport}),n()]),{serializedTree:a}=i,{tree:c}=i,l=.2*Eo["gpt-4o-2024-05-13"],d=os({serializedTree:a,tokenLimit:l});if(d){let w=(await this.generator.getRecommendedChunks({...d,description:e.assertion,tokenLimit:l})).ids;w.length===0?this.logger.debug("RAG returned no important information for assertion"):(a=c.pruneUsingRelevantIds(new Set(w)).serialize(),this.logger.debug({browserState:a},"Pruned a11y tree with RAG"),r=!0)}let u=this.getSerializedHistory(s,a),p={goal:e.assertion,url:s,browserState:a,history:u,lastCommand:this.lastExecutedCommand,screenshot:o?.toString("base64"),numPrevious:this.commandHistory.length},m=await this.generator.getAssertionResult(p,e.disableCache);if(m.relevantElements&&Promise.all(Array.from(new Set(m.relevantElements)).slice(0,5).map(g=>this.browser.highlight({id:g}))),this.logger.debug({usedRag:r,result:m},"Got assertion result"),!m.result)throw new x("AssertionFailureError",m.thoughts);return{succeedImmediately:!1,thoughts:m.thoughts,urlAfterCommand:s,beforeScreenshotOverride:o}}async wrapMultiElementTargetingCommand(e,r,o,n,s=1){let i=await Promise.all(e.map((a,c)=>this.wrapElementTargetingCommand({target:a,cache:r[c],action:async l=>l,options:n})));try{return{result:await o(...i.map(c=>c.result)),caches:i.map(c=>c.cache),elementInteractedDisplayStrings:i.map(c=>c.elementInteractedDisplayString)}}catch(a){if(s>0)return this.logger.debug({err:a},"Failed to execute action with multiple cached targets, retrying with AI"),this.wrapMultiElementTargetingCommand(e,e.map(()=>{}),o,n,s-1);throw new x("ActionFailureError",a.message,{cause:a})}}async wrapFrameUseCommand(e,r){if(!r)return e();let o=this.browser.getActiveFrame();try{return this.logger.debug({frameUrl:r},"Setting parent iframe target"),this.browser.setActiveFrame({type:"url",url:r}),await e()}finally{this.browser.setActiveFrame(o)}}async wrapElementTargetingCommand(e){return this.wrapFrameUseCommand(()=>this.wrapElementTargetingCommandHelper(e),e.options.iframeUrl)}async wrapElementTargetingCommandHelper({target:e,cache:r,action:o,options:n}){let{disableCache:s,filterByViewport:i,useSelector:a}=n,c=n.retriesWithAI??1,l;if((!r||s)&&!Fo(e))throw new x("ActionFailureError","Cannot target element with no cached data or element descriptor");if(a){if(e.type!=="description")throw new x("ActionFailureError","Cannot use selector along with non-description target");let u={id:-1,selector:e.elementDescriptor},p=await this.browser.resolveTarget(u);return{result:await o(p.locator),cache:u,elementInteractedDisplayString:p.displayString}}s&&(this.logger.debug("Cache explicitly disabled for this step"),r=void 0);let d=!!r&&Dr(r);if(!r){this.logger.debug("No cached locator data for target, prompting AI for fresh location"),c--;let u=await this.locateElement({description:e.elementDescriptor,disableCache:s,filterByViewport:i,iframeUrl:n.iframeUrl});r=u.target,l=u.thoughts}try{let u=await this.browser.resolveTarget(r),p=await o(u.locator);return d?this.logger.debug({cache:r},"Successfully used cached target to perform action"):this.logger.debug({cache:r},"Successfully generated and used new target data"),{result:p,cache:r,thoughts:l,elementInteractedDisplayString:u.displayString}}catch(u){if(u instanceof x)throw u;if(c>0&&e)return this.logger.debug({err:u,cache:r},"Failed to execute action with cached target, retrying with AI"),this.wrapElementTargetingCommand({target:e,cache:void 0,action:o,options:{...n,retriesWithAI:c}});throw new x("ActionFailureError",u.message,{cause:u})}}async screenshotWithDimensions(e){let r=await this.browser.screenshot(e),o=await is(r).metadata();return{buffer:r,width:Math.ceil(o.width??0),height:Math.ceil(o.height??0)}}async executePresetStep(e,r,o){let n;try{n=await this.resolveCommandTemplateStrings(e,r)}catch(s){throw new x("ActionFailureError",s.message,{cause:s})}try{let s=this.browser.getOpenPages(),i=await this.browser.url(),a=await this.executePresetActionHelper(e,r,o),c=!0;(e.type==="NAVIGATE"||e.type==="NEW_TAB"||e.type==="TAB"||e.type==="REFRESH")&&(c=!1);let l=this.browser.getOpenPages(),d=await this.browser.url();if(c&&l.length!==s.length)for(let u=l.length-1;u>=0;u--){let p=l[u];if(p!==i&&p!==d){await this.browser.switchToPage(p,u);break}}return a}catch(s){throw this.logger.error({err:s},"Error thrown in action controller"),s}finally{this.restoreCommandTemplateReplacements(e,n)}}restoreCommandTemplateReplacements(e,r={}){for(let[o,n]of Object.entries(r))ns(e,o,n)}async resolveCommandTemplateStrings(e,r,o="",n={}){let s=["type","a11yData","thoughts","cache"];for(let i in e){if(s.includes(i))continue;let a=e[i],c=o?`${o}.${i}`:i;if(typeof a=="string"&&a.includes("{{")){let l=await ho({s:a,context:r,logger:this.logger});if(a===l)continue;n[c]=a,e[i]=l}else typeof a=="object"&&a!==null&&!Array.isArray(a)&&await this.resolveCommandTemplateStrings(a,r,c,n)}return n}async executePresetActionHelper(e,r,o){switch(o=o||"disableCache"in e&&e.disableCache,e.type){case"SUCCESS":let n=e.condition;return n?.assertion.trim()?this.wrapFrameUseCommand(()=>this.executeAssertion(n),n?.iframeUrl):{succeedImmediately:!1,urlAfterCommand:await this.browser.url()};case"AI_ASSERTION":{if(!e.assertion.trim())throw new x("ActionFailureError","Missing assertion");return this.wrapFrameUseCommand(()=>this.executeAssertion(e),e.iframeUrl)}case"AI_WAIT":{if(!e.assertion.trim())throw new x("ActionFailureError","Missing assertion");let u=Date.now();if(e.timeout&&e.timeout>60)throw new x("AssertionFailureError",`AI wait timeout of ${e.timeout} exceeds the maximum allowed timeout of 60s`);let p=(e.timeout??10)*1e3,m,g,w=0;for(;Date.now()-u<p;)try{m=await this.executeAssertion({...e,type:"AI_ASSERTION"});break}catch(f){g=f instanceof Error?f:new Error(`${f}`),this.logger.info({err:f},`AI_WAIT assert attempt ${w} failed, retrying...`),w++,await W(p/10)}if(!m){let f=`AI wait still failing after ${p}ms.`;throw g&&(f+=` Latest result: ${g.message}`),new x("AssertionFailureError",f)}return m}case"AI_EXTRACT":{if(!e.goal.trim())throw new x("ActionFailureError","Cannot perform AI extraction without goal");let u=await this.browser.getCondensedHtml(),p=await this.generator.getTextExtraction({goal:e.goal,browserState:u,returnSchema:e.schema},o);if(p.result==="NOT_FOUND")throw new x("ActionFailureError","No relevant data found for extraction goal on this page");return{data:p.result,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}case"NAVIGATE":if(!Vn(e.url)&&!qn(e.url,this.browser.baseURL))throw new x("ActionFailureError",`Invalid URL provided to navigate command: ${e.url}`);await this.browser.navigate({url:e.url,loadTimeoutMs:e.loadTimeout?e.loadTimeout*1e3:void 0,networkIdleTimeoutMs:e.networkTimeout?e.networkTimeout*1e3:5e3});break;case"DIALOG":this.browser.registerDialogHandler(e.action);break;case"CAPTCHA":if(!this.browser.canSolveCaptchas())break;let s=await this.browser.solveCaptcha();s&&(await this.wrapElementTargetingCommand({target:{type:"description",elementDescriptor:"the captcha image solution input"},cache:void 0,action:u=>this.browser.click(u),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}}),await this.browser.type(s,{clearContent:!0,pressKeysSequentially:!1}));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":if(e.target&&!Ge(e.target))throw new Error("Scroll with x/y is not supported yet");let i,a;if(e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)){let{cache:u,thoughts:p,elementInteractedDisplayString:m}=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.hover(g,!1),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});i=m,u&&(e.cache={target:u}),a=p}switch(e.type){case"SCROLL_UP":await this.browser.scrollUp(e.deltaY);break;case"SCROLL_DOWN":await this.browser.scrollDown(e.deltaY);break;case"SCROLL_LEFT":await this.browser.scrollLeft(e.deltaX);break;case"SCROLL_RIGHT":await this.browser.scrollRight(e.deltaX);break}return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i,thoughts:a};case"WAIT":await this.browser.wait(e.delay*1e3);break;case"REFRESH":await this.browser.refresh({networkIdleTimeoutMs:e.networkTimeout?e.networkTimeout*1e3:void 0,loadTimeoutMs:e.loadTimeout?e.loadTimeout*1e3:void 0});break;case"CLICK":{let u={doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force,waitForUrl:e.waitForUrl};if(at(e.target)){await this.browser.clickUsingVisualCoordinates(e.target.percentXYLocation,u);break}let p=await this.browser.url(),m={disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl},{elementInteractedDisplayString:g,cache:w,thoughts:f}=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target??e.target.a11yData,action:b=>this.browser.click(b,u),options:m});w&&(e.cache={target:w});let h={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:g,thoughts:f};return Ct(p,h.urlAfterCommand)&&(h.succeedImmediately=!0,h.succeedImmediatelyReason="URL changed"),h}case"DRAG":{if(at(e.fromTarget)&&at(e.toTarget)){await this.browser.dragAndDropUsingVisualCoordinates(e.fromTarget.percentXYLocation,e.toTarget.percentXYLocation,{force:e.force,hoverSeconds:e.hoverSeconds});break}if(at(e.fromTarget)||at(e.toTarget))throw new Error("Drag and drop targets must be both coordinates or both descriptions");let{caches:u,elementInteractedDisplayStrings:p}=await this.wrapMultiElementTargetingCommand([e.fromTarget,e.toTarget],[e.cache?.fromTarget,e.cache?.toTarget],(m,g)=>this.browser.dragAndDrop(m,g,{force:e.force,hoverSeconds:e.hoverSeconds}),{filterByViewport:e.filterByViewport,useSelector:e.useSelector,disableCache:o});return u&&u.every(m=>m)&&(e.cache={fromTarget:u[0],toTarget:u[1]}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:p[0]}}case"MOUSE_DRAG":{if(e.target&&!Ge(e.target))throw new Error("Scroll with x/y is not supported yet");let u,p;if(e.target?.elementDescriptor){let w=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target,action:async f=>f,options:{filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl,disableCache:o}});u=w.result,p=w.elementInteractedDisplayString}let m=parseInt(e.deltaX),g=parseInt(e.deltaY);if(isNaN(m)||isNaN(g))throw new x("ActionFailureError",`Invalid pixel values passed to mouse drag command: (${e.deltaX}, ${e.deltaY})`);return await this.browser.mouseDrag(m,g,e.steps,u,{force:e.force}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:p}}case"SELECT_OPTION":{if(!Ge(e.target))throw new Error("Select with x/y is not supported yet");let u=e.target.elementDescriptor,{cache:p,thoughts:m,elementInteractedDisplayString:g}=await this.wrapElementTargetingCommand({target:{type:"description",elementDescriptor:cr(e,u)},cache:e.cache?.target??e.target.a11yData,action:w=>this.browser.selectOption(w,e.option),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});return p&&(e.cache={target:p}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:g,thoughts:m}}case"TAB":await this.browser.switchToPage(e.url,void 0,{networkIdleTimeoutMs:e.networkTimeout?e.networkTimeout*1e3:void 0,loadTimeoutMs:e.loadTimeout?e.loadTimeout*1e3:void 0});break;case"NEW_TAB":await this.browser.createNewTab(e.url,{networkIdleTimeoutMs:e.networkTimeout?e.networkTimeout*1e3:void 0,loadTimeoutMs:e.loadTimeout?e.loadTimeout*1e3:void 0});break;case"COOKIE":if(!e.value)break;await this.browser.setCookie(e.value);break;case"LOCAL_STORAGE":if(!e.value||!e.key)break;await this.browser.setLocalStorage(e.key,e.value);break;case"JAVASCRIPT":{let u;try{e.environment==="BROWSER"?u=await this.browser.executePageFunction(new Hr(e.fragment?`return ${e.code}`:e.code),void 0):u=await vt({code:e.code,fragment:!!e.fragment,context:r,timeoutMs:e.timeout?e.timeout*1e3:void 0,logger:this.logger})}catch(p){throw new x("ActionFailureError",p instanceof Error?p.message:`${p}`,{cause:p})}try{JSON.stringify(u)}catch(p){throw new x("ActionFailureError",`Return value is not serializable: ${p instanceof Error?p.message:`${p}`}`,{cause:p})}return{urlAfterCommand:await this.browser.url(),succeedImmediately:!1,data:u}}case"TYPE":{let u=await this.browser.url(),p,m,g=Ch(e.target);if(g){g.elementDescriptor=cr(e,g.elementDescriptor);let{elementInteractedDisplayString:f,cache:h,thoughts:b}=await this.wrapElementTargetingCommand({target:g,cache:e.cache?.target??g.a11yData,action:T=>this.browser.typeIntoTarget(e.value,T,{force:e.force,clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});m=b,p=f,h&&(e.cache={target:h})}else await this.browser.type(e.value,{force:e.force,clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially});e.pressEnter&&await this.browser.press("Enter");let w={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:p,thoughts:m};return Ct(u,w.urlAfterCommand)&&(w.succeedImmediately=!0,w.succeedImmediatelyReason="URL changed"),w}case"HOVER":{if(at(e.target)){await this.browser.hoverUsingVisualCoordinates(e.target.percentXYLocation);break}let{cache:u,thoughts:p,elementInteractedDisplayString:m}=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.hover(g,e.force),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});return u&&(e.cache={target:u}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:m,thoughts:p}}case"FOCUS":{if(!Ge(e.target))throw new Error("Focus with x/y is not supported yet");let{elementInteractedDisplayString:u,cache:p,thoughts:m}=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.focus(g),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});return p&&(e.cache={target:p}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:u,thoughts:m}}case"BLUR":{if(!Ge(e.target))throw new Error("Blur with x/y is not supported yet");let{cache:u,thoughts:p,elementInteractedDisplayString:m}=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.blur(g),options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});return u&&(e.cache={target:u}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:m,thoughts:p}}case"PRESS":let c=await this.browser.url();await this.browser.press(e.value);let l={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return Ct(c,l.urlAfterCommand)&&(l.succeedImmediately=!0,l.succeedImmediatelyReason="URL changed"),l;case"REQUEST":{let u=e.timeout??30,p=null,m=new AbortController,g=Object.fromEntries(Object.entries(e.headers||{}).filter(([v,_])=>v&&_)),w=new URLSearchParams;Object.entries(e.params||{}).filter(([v,_])=>v&&_).forEach(([v,_])=>{w.append(v,_)});let f;if(Vn(e.url)&&(f=e.url),qn(e.url,this.browser.baseURL)&&(f=new URL(e.url,this.browser.baseURL).toString()),!f)throw new x("ActionFailureError",`Invalid URL: ${e.url}`);let h=async()=>{try{p=await fetch(`${f}?${w.toString()}`,{headers:g,method:e.method,body:e.body,signal:m.signal})}catch(v){this.logger.error({err:v},"Failed to make HTTP request")}},b=async()=>{await new Promise(v=>setTimeout(v,u*1e3)),m.abort()};await Promise.race([b(),h()]);let T=p;if(!T)throw new x("ActionFailureError",`Fetch request timed out after ${u} seconds`);if(!T.ok)throw new x("ActionFailureError",`Fetch request failed with status ${T.status}`);let S={};T.headers.forEach((v,_)=>{S[_]=v});let C={status:T.status,headers:S};return T.headers.get("content-type")?.includes("json")?C.json=await T.json():T.headers.get("content-type")?.includes("text")&&(C.text=await T.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:C}}case"VISUAL_DIFF":{if(e.target&&!Ge(e.target))throw new Error("Visual Diff with x/y is not supported yet");if(!e.screenshot)throw new x("ActionFailureError","Cannot execute visual diff without saved screenshot");await this.getBrowserState({skipWait:!1});let u={clearHighlights:!0,scale:"css",hideCaret:!0},p;if(!e.target)p=await this.screenshotWithDimensions(u);else{let K=await this.wrapElementTargetingCommand({target:e.target,cache:e.cache?.target??e.target?.a11yData,action:async G=>{},options:{disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector,iframeUrl:e.iframeUrl}});if(!K.cache)throw new x("ActionFailureError",`Could not find target given description: ${e.target.elementDescriptor}`);p=await this.screenshotWithDimensions({target:K.cache,...u})}let m;if(e.screenshot.data.startsWith("https://")){let K=await fetch(e.screenshot.data);m=Buffer.from(await K.arrayBuffer())}else m=Buffer.from(e.screenshot.data,"base64");if(Math.abs(p.height-e.screenshot.height)>10||Math.abs(p.width-e.screenshot.width)>10){let K=`${p.width}x${p.height}`,G=`${e.screenshot.width}x${e.screenshot.height}`;return{fail:!0,thoughts:`Current screenshot (${K}) does not match saved screenshot dimensions (${G}) - did you change the size of the target or the viewport?`,beforeScreenshotOverride:m,afterScreenshotOverride:p.buffer,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}let g=is(p.buffer),w={width:p.width,height:p.height},f=is(m),h={width:e.screenshot.width,height:e.screenshot.height},b,T=w.width*w.height,S=h.width*h.height,C=Math.abs(w.height-h.height),v=Math.abs(w.width-h.width);T>S?(p.buffer=await g.resize(h.width,h.height,{fit:"cover"}).toBuffer(),b="current",p.width=h.width,p.height=h.height):S>T&&(m=await f.resize(w.width,w.height,{fit:"cover"}).toBuffer(),b="saved");let _={data:Buffer.alloc(p.width*p.height*4),width:p.width,height:p.height},L=Ah(ss.decode(m).data,ss.decode(p.buffer).data,_.data,p.width,p.height,{threshold:e.threshold,diffColorAlt:[0,255,0]})/(p.width*p.height)*100,A=L>e.threshold*100,P=`Visual diff of ${L.toFixed(2)}% detected, which is ${A?"over":"under"} the threshold of ${e.threshold*100}%.`;return b&&(P+=` The ${b} screenshot was cropped since it was taller by ${C} pixels and wider by ${v} pixels.`),{fail:A,thoughts:P,beforeScreenshotOverride:p.buffer,afterScreenshotOverride:ss.encode(_,75).data,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}case"FILE_UPLOAD":{let u=await jl({url:e.fileSource.url,logger:this.logger});await this.browser.setFileChooserHandler(u);break}default:return(u=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(e)}return{succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}async getReverseMappedTarget(e,r,o){return(await this.generator.getReverseMappedDescription({browserState:e.browserState,goal:`${r}`},o)).phrase}stopRecordMode(){this.recordAbortController?.abort()}async startRecordMode(e){this.recordAbortController=new AbortController;let r=new zt({signal:this.recordAbortController.signal,...e});await this.browser.startRecording(this.recordAbortController.signal,r)}};var _h=2;async function Yl({socket:t,logger:e,storage:r,devicePixelRatio:o,generator:n}){let s=t.id,i=t.handshake.query.testId,a=t.handshake.query.baseUrl,c=t.handshake.query.viewport?JSON.parse(decodeURIComponent(t.handshake.query.viewport)):void 0,l=t.handshake.query.testMetadata?J.parse(JSON.parse(decodeURIComponent(t.handshake.query.testMetadata))):void 0;if(!i||!a)throw new Error("Socket connection request is missing testId or baseUrl");let d=await r.getOrgId(i),u=t.handshake.query.environmentVariables?JSON.parse(decodeURIComponent(t.handshake.query.environmentVariables)):{},p=t.handshake.headers["x-forwarded-for"]?.split(",")[0],m=e.child({testId:i,baseUrl:a,orgId:d,sessionId:s});if(m.info({clientIp:p,event:"connect",args:t.handshake.query,sessionId:s},"Websocket event (connect)"),p&&O.getCurrentConnectionsByIp(p)>=_h)throw e.error({clientIp:p,sessions:O.getCurrentSessionsByIp(),...t.handshake.query},"Socket connection browser creation rate limit triggered"),new Error("You have exceeded the maximum number of connections allowed per client. Momentic limits the number of simultaneously open tabs to uphold browser reliability. Please close another tab and retry or contact Momentic Support if you believe this is in error.");O.reserveCapacityByIp(p);try{await Oh({socket:t,baseUrl:a,viewport:c,testMetadata:l,orgId:d,sessionId:s,logger:m,environmentVariables:u,clientIp:p,devicePixelRatio:o,storage:r,generator:n})}catch(g){throw m.debug({err:g},"Error setting up socket session"),O.releaseCapacityByIp(p),g}return{sessionId:s,testId:i,orgId:d}}async function Oh({socket:t,baseUrl:e,viewport:r,devicePixelRatio:o,testMetadata:n,orgId:s,sessionId:i,logger:a,storage:c,generator:l,environmentVariables:d,clientIp:u}){let p=C=>{t.emit("browserState",{...C,url:e})},m={};r&&(m.viewport=r),o&&(m.deviceScaleFactor=o);let g=ir.parse(n?.advanced??{}),[w,f]=await Promise.all([j.init({baseUrl:e,networkSettings:g,waitForLoad:!1,enricher:new So(a),timeout:g.pageLoadTimeoutMs,storage:c,logger:a,onBrowserUpdateDuringLoad:p,contextArgs:m}),Jt.init(s)]),h=new Qt({browser:w,generator:l,config:_r,logger:a,flagStore:f,orgId:s,storage:c}),b=Zi(t,i,a),T=async()=>{clearInterval(b.timer)},S=new Se({baseUrl:e,currentUrl:await h.browser.url(),variablesFromEnvironment:d});if(!t.connected)throw await w.cleanup(),new Error("Socket not connected anymore, not proceeding with session setup");t.emit("session",{url:e,userAgent:j.USER_AGENT,viewport:await h.browser.getViewport(),sessionId:i}),O.registerSession({controller:h,context:S,sessionId:i,cleanup:T,clientIp:u})}var Xl=[Ta,Nl,zl,Gl,Ul,$l,ea,Bl,Fl,pa,ha,ma,fa,ga,ya,wa,ra,ta,ba,oa];var Jl=t=>{let{logger:e}=t,r=new Ih(t.baseServer,{cors:{origin:"*",methods:["GET","POST"]},pingTimeout:3e4,pingInterval:6e4,maxHttpBufferSize:1e7});return r.on("connection",async o=>{let n;try{n=await Yl({...t,socket:o})}catch(s){e.error({event:"connection",type:"websocket",err:s},"Failed to setup connection"),o.emit("error",{message:s instanceof Error?s.message:`${s}`}),o.disconnect(!0);return}Xl.forEach(s=>xh(s,{socket:o,metadata:n,...t}))}),r};var xh=(t,e)=>{let r=t.createHandler(e),o=(...n)=>{["cloudMouseMove","cloudScroll"].includes(t.event)||e.logger.debug({...e.metadata,event:t.event,args:n},`Websocket event (${t.event})`);let s=i=>{e.logger.error({event:t.event,type:"websocket",args:n,err:i instanceof Error?i:new Error(`${i}`)},"Unhandled exception in socket handler"),e.socket.emit("error",{message:i instanceof Error?i.message:`${i}`})};try{let i=r.apply(void 0,n);i&&typeof i.catch=="function"&&i.catch(s)}catch(i){s(i)}};e.socket.on(t.event,o)};import{z as Lh}from"zod";import Ph from"fetch-retry";var kh=Ph(global.fetch),ne=class{static API_VERSION="v1";baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async sendRequest(e,r){let o=await kh(`${this.baseURL}${e}`,{retries:1,retryDelay:1e3,method:"POST",body:JSON.stringify(r),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!o.ok)throw new Error(`Request to ${e} failed with status ${o.status}: ${await o.text()}`);return o.json()}};var _t=class extends ne{constructor(e){super(e)}async getRecommendedChunks(e){let r=await this.sendRequest(`/${ne.API_VERSION}/web-agent/recommend-chunks`,e);return Ai.parse(r)}async getScreenshotFromS3(e){let r=await this.sendRequest(`/${ne.API_VERSION}/s3/visual-diff-screenshot`,{url:e});return Lh.string().parse(r)}async getElementLocation(e,r){let o=await this.sendRequest(`/${ne.API_VERSION}/web-agent/locate-element`,{...e,disableCache:r});return ui.parse(o)}async getAssertionResult(e,r){let o={...e,disableCache:r},n=await this.sendRequest(`/${ne.API_VERSION}/web-agent/assertion`,o);return li.parse(n)}async getProposedCommand(e,r){let o=await this.sendRequest(`/${ne.API_VERSION}/web-agent/next-command`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,screenshot:e.screenshot,disableCache:r});return ci.parse(o)}async getGranularGoals(e,r){let o=await this.sendRequest(`/${ne.API_VERSION}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:r});return di.parse(o)}async getReverseMappedDescription(e,r){let o=await this.sendRequest(`/${ne.API_VERSION}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:r});return pi.parse(o)}async getTextExtraction(e,r){let o={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:r},n=await this.sendRequest(`/${ne.API_VERSION}/web-agent/text-extraction`,o);return Vo.parse(n)}async getTestResultClassification(e){let r=await this.sendRequest(`/${ne.API_VERSION}/web-agent/result-classification`,e);return jo.parse(r)}};import{Router as Mh}from"express";function ge(t){return function(...e){let r=e[e.length-1],o=t(...e);Promise.resolve(o).catch(r)}}var Ql=Mh();Ql.get("/",ge((t,e)=>{let r=Ji();e.status(200).json(r)}));var Zl=Ql;import{Router as Uh}from"express";import eu,{multistream as Dh}from"pino";import Nh from"pino-pretty";var as=new Map,Fh=process.env.NODE_ENV==="production",cs=class t{consoleLogger;ddClientToken;hostname;bindingAttributes;excludeAttributesInConsoleLogs;excludeTimestamps;disableConsoleLogs;site="https://http-intake.logs.us5.datadoghq.com/api/v2/logs";constructor({bindings:e,clientToken:r,hostname:o,excludeAttributesInConsoleLogs:n,excludeTimestamps:s,disableConsoleLogs:i}){this.ddClientToken=r,this.hostname=o,this.excludeAttributesInConsoleLogs=n,this.excludeTimestamps=s,this.disableConsoleLogs=i,this.bindingAttributes={...e,env:process.env.NODE_ENV};let a={base:n?{}:this.bindingAttributes,errorKey:"err",level:"debug",timestamp:!s};this.consoleLogger=Fh?eu(a):eu(a,Dh([{stream:Nh({colorize:!0})}]))}child(e){return new t({clientToken:this.ddClientToken,bindings:{...this.bindingAttributes,...e},hostname:this.hostname,excludeAttributesInConsoleLogs:this.excludeAttributesInConsoleLogs,disableConsoleLogs:this.disableConsoleLogs})}flush(e){this.disableConsoleLogs||this.consoleLogger.flush(e)}log(e,r,o,...n){if(r&&o===void 0&&(o=`${r}`,r={}),this.disableConsoleLogs||this.consoleLogger[e](e==="error"?r:{testId:r.testId,runId:r.runId},o,...n),typeof r=="object"&&r&&"err"in r&&r.err instanceof Error){let i={};for(let a of Object.getOwnPropertyNames(r.err))i[a]=r.err[a];i.name=r.err.name,r.err=i}let s=Object.assign({},this.bindingAttributes,r&&typeof r=="object"?r:{});n.length>0&&(s.args=n),(async()=>{try{let i=await fetch(this.site,{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json","DD-API-KEY":this.ddClientToken},body:JSON.stringify([{ddsource:this.bindingAttributes.app,hostname:this.hostname??"vercel",service:"momentic",message:{message:o||"",...s,level:e}}])});if(!i.ok)throw new Error(`Failed to log to Datadog: ${i.statusText})}`)}catch(i){this.disableConsoleLogs||this.consoleLogger.warn({obj:r,msg:o,args:n,err:i},"Failed to log to Datadog")}})()}debug(e,r,...o){this.log("debug",e,r,...o)}info(e,r,...o){this.log("info",e,r,...o)}warn(e,r,...o){this.log("warn",e,r,...o)}error(e,r,...o){this.log("error",e,r,...o)}bindings(){return this.bindingAttributes}setMinLevel(e){this.consoleLogger.level=e}enableConsoleLogs(){this.disableConsoleLogs=!1}},Ao=({app:t,clientToken:e,hostname:r,excludeAttributesInConsoleLogs:o,excludeTimestamps:n,disableConsoleLogs:s})=>{if(!process.env.DD_CLIENT_TOKEN&&!process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN&&!e)throw new Error("Missing DD_CLIENT_TOKEN");return as.has(t)||as.set(t,new cs({bindings:{app:t},hostname:r,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN,excludeAttributesInConsoleLogs:o,excludeTimestamps:n,disableConsoleLogs:s})),as.get(t)};import{hostname as Gh}from"os";var Te=Ao({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:Gh(),excludeAttributesInConsoleLogs:!0,excludeTimestamps:!0,disableConsoleLogs:!0});var Or=Uh();Or.get("/",ge(async(t,e)=>{let r=await Hi(Te);e.status(200).json(r)}));Or.get("/metadata",ge((t,e)=>{let r=Bi();e.status(200).json(r)}));Or.post("/",ge(async(t,e)=>{let r;try{r=si.parse(t.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}try{ar(r.name)}catch(n){e.status(400).json({error:`Invalid module name: ${n}`});return}let o=$i(r.name,r.steps);e.status(201).json(o)}));Or.get("/:moduleId",ge(async(t,e)=>{if(!t.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let r=await Wi(t.params.moduleId,Te);e.json(r)}));var tu=Or;import{Router as zh}from"express";import{existsSync as $h}from"fs";import{join as Oo}from"path";import{v4 as Bh}from"uuid";var us="https://api.momentic.ai",ru=t=>{us=t},Ir=()=>us,Ro,ls,ou=async t=>{if(Ro)return;let e=new oe({baseURL:us,apiKey:t});try{Ro=await e.getOrgId(),ls=t}catch(r){throw new Error(`Error checking API key against server: ${r}`)}},_o=()=>{if(!Ro)throw new Error("Your organization ID appears invalid. Please contact Momentic Support.");return Ro},nu=()=>{if(!ls)throw new Error("Your API key appears invalid. Please contact Momentic Support.");return ls};var Ot=zh();Ot.get("/",ge((t,e)=>{let r=gr(Z,Te);e.status(200).json(r)}));Ot.post("/",ge((t,e)=>{let r;try{r=ni.parse(t.body)}catch(a){e.status(400).json({error:`Invalid request body: ${a}`});return}try{ar(r.name)}catch(a){e.status(400).json({error:a.message});return}let n={id:Bh(),name:r.name,baseUrl:r.baseUrl,schemaVersion:X,advanced:{disableAICaching:!1,viewport:r.viewport??ut},retries:0,steps:[]};r.environment&&(n.envs=[{name:r.environment,default:!0}]);let s=Ki(n,r.name),i={...n,testPath:s};e.status(201).json(i)}));Ot.get("/:testPath",ge(async(t,e)=>{if(!t.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let r=Oo(Z,t.params.testPath);if(!$h(r)){e.status(400).json({error:`Test not found at path: ${r}`});return}let o=await no(r,Tt,Te);e.status(200).json(o)}));Ot.patch("/:testPath/metadata",ge(async(t,e)=>{if(!t.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let r;try{r=oi.parse(t.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}let o=Oo(Z,t.params.testPath);mr(r,o),e.status(201).json({message:"ok"})}));Ot.patch("/:testPath",ge(async(t,e)=>{if(!t.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let r;try{r=ri.parse(t.body)}catch(l){e.status(400).json({error:`Invalid request body: ${l}`});return}let o=Oo(Z,t.params.testPath),n;try{n=Yi(o)}catch(l){e.status(400).json({error:`Existing test file on disk is invalid: ${l}`});return}let{stepsToSave:s,moduleUpdates:i,cachesToSave:a}=de({steps:r.steps,testId:n.id,orgId:_o()});i.forEach(l=>zi(l)),mr({steps:s},o),await new oe({apiKey:nu(),baseURL:Ir()}).updateStepCaches({entries:a,testId:n.id}),e.status(201).json({message:"ok"})}));Ot.patch("/:testPath/environments",ge(async(t,e)=>{if(!t.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let r;try{r=ii.parse(t.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}let o=Oo(Z,t.params.testPath);mr({envs:r.defaultEnv?[{name:r.defaultEnv,default:!0}]:[]},o),e.status(201).json({message:"ok"})}));var su=Ot;async function iu({momenticServerUrl:t,apiKey:e,serverPort:r,appPort:o,staticDir:n,devicePixelRatio:s}){if(!jh(Z)||!Vh(Z).isDirectory()){let m=Yh.isAbsolute(Z);throw new Error(`Root folder ${Z} does not exist${m?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}t&&ru(t),Te.info("Checking API key"),await ou(e);let a=`http://localhost:${r}`;o&&(a=`http://localhost:${o}`);let c=Xh(n);await new Promise(m=>{c.listen(r,()=>{Te.info(`Server is running at http://localhost:${r}`),m()})});let d=new _t({baseURL:Ir(),apiKey:e}),u=new oe({baseURL:Ir(),apiKey:e}),p=new Ut(u,_o());Jl({baseServer:c,generator:d,storage:p,logger:Te,devicePixelRatio:s}),await Kh(a)}function Xh(t){let e=ds();e.use(Wh()),e.use(Hh.json({limit:"50mb"}));let r=ds.Router();if(r.use("/tests",su),r.use("/modules",tu),r.use("/environments",Zl),e.use("/api",r),e.use((n,s,i)=>{n.path!=="/healthcheck"&&Te.debug({url:n.url,path:n.path,query:n.query,method:n.method,body:n.body,headers:n.rawHeaders,client:n.ip},"Received desktop-server request"),s.on("close",()=>{s.statusCode>=400&&Te.error({url:n.url,method:n.method,statusCode:s.statusCode},"Request completed in error")}),i()}),e.use((n,s,i,a)=>{n instanceof Error&&n.message.includes("BadRequestError: request aborted")||(Te.error({stack:n.stack,msg:n.message,url:s.url,method:s.method},"Unhandled exception leading to 500 on desktop-server"),i.status(500).send("Internal Server Error"),a(n))}),t){let n=ds.static(t);e.use("/",n),e.use("*",n)}return qh.createServer(e)}import{existsSync as qf}from"fs";import Fu,{dirname as Kf,join as Yf}from"path";import{chdir as Xf}from"process";import{fileURLToPath as Jf}from"url";var au="0.0.93";import xr from"chalk";var It=(...t)=>{Ie(xr.blue(...t))};var Pr=(...t)=>{Ie(xr.dim(...t))};var se=(...t)=>{Ie(xr.green(...t))},Ie=(...t)=>{console.log(...t)},le=(...t)=>{console.error(xr.yellow(...t))},F=(...t)=>{console.error(xr.red(...t))};import{hostname as Qh}from"os";var I=Ao({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:Qh(),excludeAttributesInConsoleLogs:!0,excludeTimestamps:!0,disableConsoleLogs:!0});import{execSync as Zh}from"child_process";import{platform as ef}from"os";function ps(){return cu()?(It("Setting device pixel ratio to 2 automatically since a Mac OS Retina screen was detected."),It("If you are using a low pixel-density monitor, you should manually set --pixel-ratio to 1 to avoid incorrect viewport calculations."),It("Confirm your device's pixel-ratio at https://www.mydevice.io."),2):(It("Setting device pixel ratio to 1."),It("If you are using Momentic on a high-pixel density (HiDPI) monitor, relaunch with the --pixel-ratio option to avoid incorrect viewport calculations"),It("Confirm your device's pixel-ratio at https://www.mydevice.io."),1)}function cu(){return ef()==="darwin"&&Zh("system_profiler SPDisplaysDataType").toString().includes("Retina")}function ms(t){cu()&&t===1&&(le("If you are using Momentic on a Retina screen, relaunch with the --pixel-ratio option to avoid incorrect viewport calculations."),le("Confirm your device's pixel-ratio at https://www.mydevice.io."))}import{createHash as pu}from"crypto";import{existsSync as mu,readFileSync as bs,writeFileSync as gu}from"fs";import{join as hu}from"path";import{parse as mf}from"yaml";import{existsSync as xo,mkdirSync as lu,readdirSync as sf,statSync as fs}from"fs";import{homedir as uu}from"os";import{join as We,resolve as af}from"path";import{registry as gs}from"playwright-core/lib/server";function tf(t){let e=[],r=[];for(let o of t){let n=gs.findExecutable(o);!n||n.installType==="none"?e.push(o):r.push(n)}return r}async function Io(){let t=tf(["chromium"]);await gs.installDeps(t,!1),await gs.install(t,!1)}import{statSync as rf}from"fs";import of from"readline/promises";var hs=!1,nf=(()=>{try{return rf("/.dockerenv"),!0}catch{return!1}})();async function Ae(t,e){if(process.env.CI||hs||!process.stdout.isTTY||nf)return!0;I.flush(),await new Promise(i=>setTimeout(i,500));let r=of.createInterface({input:process.stdin,output:process.stdout}),o=t.split("."),n;if(o.length===1)n=t;else{let i=`${o.slice(0,o.length-1).join(". ").trim()}.`;e?le(i):Ie(i),n=o[o.length-1].trim()}let s=await r.question(`${n} ('y' for yes / n for no / 'A' to accept all) `);return r.close(),s==="A"?(hs=!0,setTimeout(()=>{hs=!1},3e3),!0):s.toLowerCase()==="y"}var cf=[Be,Kr,Ft],je=pf(),xt=We(je,Be),ws=We(je,Kr),Ve=We(je,Ft),lf=!!process.env.CI||!process.stdout.isTTY,ys=We(uu(),"momentic",rn),du=[xt,ws,Ve];function kr(t){try{return xo(t)&&fs(t).isDirectory()}catch(e){return I.error({err:e},`Error reading path ${t} during directory exists check`),!1}}async function rt(t){let e=process.versions.node,r=parseInt(e.split(".")[0]);(isNaN(r)||r<20)&&(F(`Node.js version 20 or higher is required to run the Momentic CLI. Detected: ${process.versions.node}.`),process.exit(1));let o=await t.client.getOrgId();return await Io(),await df(t),o}async function uf(t){kr(je)||!t&&!await Ae(`The root '${Xe}' folder was not found in the current directory. You will need to invoke the Momentic CLI from this directory in the future. Proceed?`)&&(F("Setup cancelled."),process.exit(1)),kr(ys)||(!t&&!await Ae("The Chrome cache folder was not found in your home directory. Proceed with creation?")&&(F("Setup cancelled."),process.exit(1)),lu(ys,{recursive:!0}));for(let e of du)kr(e)||(!t&&!await Ae(`'${e}' folder was not found in the current directory. Create it now?`)&&(F("Setup cancelled."),process.exit(1)),lu(e,{recursive:!0}))}function Lr(t,e,r=new Set){for(let o of t){let n=af(o),s=!1;try{s=xo(n)&&fs(n).isDirectory()}catch(i){I.error({err:i},`Error reading path ${n} during collect paths`)}if(n&&s){let i=sf(n).map(a=>We(n,a));Lr(i,e,r);continue}if(n.endsWith(".yaml")){try{if(!xo(n)||!fs(n).isFile()){I.error(`File not found or unreadable: ${n}`);continue}}catch(i){I.error({err:i},`Error reading file ${n} during collect paths`);continue}if(!e(n))continue;r.add(n)}}return r}async function df({client:t,createFolders:e,skipPrompts:r,pullEnvs:o}){if(uu()||(F("Your home directory could not be found. Please ensure the HOME environment variable is set for POSIX systems, and the USERPROFILE environment variable is set for Windows systems."),process.exit(1)),e)await uf(r);else for(let n of[je,ys,...du])!kr(n)&&!lf&&(F(`The '${n}' folder is required to run the Momentic CLI but was not found in the current directory. Please run 'npx momentic@latest init' to create the required folders.`),F(`The current working directory is: ${process.cwd()}`),process.exit(1));o&&(!r&&!await Ae("Pull environments from Momentic Cloud? Environment definitions may be required to run tests locally.")&&(F("Setup cancelled."),process.exit(1)),await Po({client:t,all:!0,skipPrompts:r}))}function pf(){let t=o=>xo(We(o,Xe))&&cf.some(n=>kr(We(o,Xe,n))),e=process.cwd();for(;e!=="/"&&!t(e);)e=We(e,"..");let r;return t(e)?r=e:r=process.cwd(),r!==process.cwd()?le(`Current working directory does not contain a ${Xe} folder, treating ${r} as the Momentic root folder instead`):I.info(`Identified ${r} as the Momentic root folder`),We(r,Xe)}async function Po({envNames:t,client:e,all:r,skipPrompts:o}){let n=await e.getAllEnvironments();t&&!r&&(n=n.filter(a=>t?.includes(a.name))),mu(Ve)||(F(`${Ve} does not exist in the current directory. Please run 'npx momentic@latest init' first.`),process.exit(1));let s=[],i=[];for(let a of n){let c=hu(Ve,`${a.name}.yaml`);if(!mu(c))gu(c,ln(a)),s.push(c);else{let l=pu("sha256").update(bs(c)).digest("hex"),d=ln(a),u=pu("sha256").update(d).digest("hex");if(l!==u){if(!o&&!await Ae(`Environment ${a.name} already exists on disk but needs to be updated. Overwrite?`))return;gu(c,d),i.push(c)}}}se("Pulled environments successfully!"),s.length&&(se("Created:"),s.forEach(a=>se(" ",a))),i.length&&(se("Updated:"),i.forEach(a=>se(" ",a)))}function gf(t){return t.endsWith(".yaml")?bs(t,"utf8").includes("momentic/environment")?!0:(le(`Skipping YAML that is not a Momentic environment: ${t}`),!1):!1}async function fu({client:t,names:e,all:r,yes:o}){let n;r?n=[Ve]:n=e.map(a=>hu(Ve,`${a.toLowerCase()}.yaml`));let s=Lr(n,gf);Ie(`Found ${s.size} environments to push:`),s.forEach(a=>Ie(" ",a));let i=[];for(let a of s){let c=mf(bs(a,"utf-8"));try{let l=we.parse(c);i.push(l)}catch(l){F(`${a} failed to parse as a valid environment file.`),F(l),process.exit(1)}}!o&&!await Ae("Pushing environments can overwrite variables on cloud and instantly affect scheduled runs. Continue?",!0)||(await t.updateEnvironments(i),se("Pushed environments successfully!"))}import{Argument as ko,Option as Ne}from"commander";import{cpus as yu}from"os";import{z as hf}from"zod";var ot=new Ne("--api-key <key>","API key for authentication. If not supplied, attempts to read the MOMENTIC_API_KEY env var.").env("MOMENTIC_API_KEY").makeOptionMandatory(!0),nt=new Ne("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),st=new Ne("-y, --yes","Skip all confirmation prompts.").env("CI"),wu=new Ne("-w, --wait","Wait for tests to finish running before exiting. Only applicable when running tests remotely").implies({remote:!0}),Ts=new Ne("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").implies({local:!0}),Ss=new Ne("--pixel-ratio <number>","Device pixel ratio of your screen or monitor. Mac OS Retina displays and machines marketed as 'HiDPI' should set this to 2. Visit https://www.mydevice.io/ to find your device's pixel ratio.").argParser(t=>parseInt(t,10)),bu=new Ne("--env <env>","Name of the environment to use when running tests."),Tu=new Ne("--url-override <urlOverride>","Fully qualified url (e.g. https://www.google.com) to start all tests from. Overrides any default starting url set from the test or environment."),Es=new Ne("-a, --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments."),vs=new Ne("-a, --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments."),Su=new ko("<tests...>",`One or more test paths to pull from Momentic Cloud.
|
|
1347
1347
|
|
|
1348
1348
|
A test path is a lowercased version of your test name where spaces are replaced with underscores: 'npx momentic pull hello-world'.`).argOptional(),Eu=new ko("<tests...>",`One or more test identifiers.
|
|
1349
1349
|
|
|
1350
1350
|
To use tests stored on your local file system, pass '--local' and specify file paths to Momentic YAML files or folders that exist locally: 'npx momentic run --local momentic/hello-world.yaml'.
|
|
1351
1351
|
|
|
1352
|
-
To use tests stored remotely on Momentic Cloud, pass '--remote' and specify one or more test paths. A test path is a lowercased version of your test name where spaces are replaced with underscores: 'npx momentic run --remote hello-world'.`).argOptional(),vu=new ko("<paths...>","File paths pointing to one or more Momentic YAML files. Paths to directories are supported as well."),Cs=new ko("<envs...>","One or more environment names to push").argOptional(),Cu=t=>{for(let[e,r]of Object.entries(t))switch(e){case"urlOverride":if(r===void 0)break;hf.string().url().parse(
|
|
1352
|
+
To use tests stored remotely on Momentic Cloud, pass '--remote' and specify one or more test paths. A test path is a lowercased version of your test name where spaces are replaced with underscores: 'npx momentic run --remote hello-world'.`).argOptional(),vu=new ko("<paths...>","File paths pointing to one or more Momentic YAML files. Paths to directories are supported as well."),Cs=new ko("<envs...>","One or more environment names to push").argOptional(),Cu=t=>{for(let[e,r]of Object.entries(t))switch(e){case"urlOverride":if(r===void 0)break;hf.string().url().parse(r);break;case"parallel":if(typeof r!="number"||isNaN(r))throw new Error(`Expected a number for ${e}`);yu().length<r*2&&I.error(`You requested to run ${r} tests in parallel on a machine with ${yu().length} cores. This may cause performance issues as Chrome requires at least 2 cores per browser instance.`);break;default:break}};import{createHash as Au}from"crypto";import{existsSync as yf,writeFileSync as Ru}from"fs";import{join as _u}from"path";async function xu({testsToFetch:t,client:e,all:r,yes:o}){let{tests:n,modules:s}=await e.getTestYAMLExport({paths:t,all:r}),i=0;for(let[c,l]of Object.entries(n)){let d=_u(je,Iu(c));!o&&!await Ou(d)||(i+=1,Ru(d,l,"utf-8"),I.info({checksum:Au("md5").update(l).digest("hex")},`Wrote '${d}'`))}let a=0;for(let[c,l]of Object.entries(s)){let d=_u(xt,Iu(c));!o&&!await Ou(d)||(a+=1,Ru(d,l,"utf-8"),I.info({checksum:Au("md5").update(l).digest("hex")},`Wrote '${d}'`))}i===0?se("Pulled 0 tests."):se(`Pulled ${i} test${i>1?"s":""}${a?` and ${a} module${a>1?"s":""}`:""}!`)}async function Ou(t){return yf(t)?Ae(`File '${t.replace(/(\s+)/g,"\\$1")}' already exists. Overwrite existing content?`,!0):!0}function Iu(t){return`${ht(t)}.yaml`}import{readFileSync as wf}from"fs";async function Pu(t){let e=Lr(t.paths,bf);Ie(`Found ${e.size} tests to push:`),e.forEach(s=>Ie(" ",s));let r=Array.from(e).map(s=>mn(s,xt)),o=new Set(r.flatMap(s=>Object.keys(s.modules)));e.size===0&&o.size===0&&(F("Found no valid tests or modules to push."),process.exit(1));let n="Pushing tests overwrites tests on production and will instantly affect scheduled runs. Continue?";!t.yes&&!await Ae(n,!0)&&(F("Push cancelled."),process.exit(1)),await t.client.updateTestWithYAML(r),se("Pushed tests successfully!")}function bf(t){return t.endsWith(".yaml")?wf(t,"utf8").includes("momentic/test")?!0:(I.warn(`Skipping YAML that is not a Momentic test: ${t}`),!1):(I.warn(`Skipping file that does not have YAML extension: ${t}`),!1)}import Uf from"cli-progress";import{existsSync as Mu,statSync as zf}from"fs";import $f from"wait-on";import Tf from"@actions/exec";import Sf from"@actions/io";import Ef from"quote";import vf from"string-argv";async function ku(t,e=!0){let r=vf(t),o=await Sf.which(r[0],!0),n=r.slice(1),s=Tf.exec(Ef(o),n,{delay:100});if(e)return s}import{diff as If}from"deep-object-diff";import{readFileSync as xf}from"fs";import{cloneDeep as Pf}from"lodash-es";import{join as kf}from"path";import Lf from"semver";import{v4 as Mf}from"uuid";var Lo=class extends ne{async runTemplateMatching(e){let r=await this.sendRequest(`/${ne.API_VERSION}/web-agent/template-matching`,e);return Vr.parse(r)}};import{parse as Df}from"yaml";import{existsSync as Cf,readFileSync as Af}from"fs";import{join as Rf}from"path";import{parse as _f}from"yaml";function Of(t){let e=Rf(ws,`${t}.yaml`);if(!Cf(e))throw new Error(`Fixture '${t}' does not exist at expected path ${e}.`);let r;try{r=_f(Af(e,"utf-8").replace(/\r\n|\r/g,`
|
|
1353
1353
|
`))}catch(n){throw new Error(`Fixture at path ${e} does not parse as valid YAML.`,{cause:n})}let o;try{o=Zs.parse(r)}catch(n){throw new Error(`Fixture at path ${e} does not conform to expected schema`,{cause:n})}return o}async function As(t,e){let r=Of(t),o=e==="setup"?r.setup:r.teardown;if(!o){I.warn(`Fixture '${t}' does not have any steps in the ${e} phase, skipping...`);return}let n=o.timeout,s=async function(){for(let l=0;l<o.steps.length;l++){let d=o.steps[l],u=d.run,p=async m=>{try{await sl(`set -ex; ${u}`)}catch(g){if(m){let w=`Fixture '${t}' ${e} step ${l+1} errored in the background. The test will continue, but this may affect test execution and results: ${g}`;I.error(w)}else throw new Error(`Fixture '${t}' ${e} step ${l+1} errored: ${g}`)}};d.waitForCompletion===!1?p(!0):await p(!1)}};if(I.info(`Running ${e} phase of fixture '${t}'`),!n){await s();return}let i,a,c=async function(){return new Promise((l,d)=>{a=l,i=setTimeout(()=>{a=void 0,d(`Fixture '${t}' ${e} phase timed out after ${n} seconds`)},n*1e3)})};try{await Promise.race([s(),c()])}catch(l){throw l}finally{a&&a(),clearTimeout(i)}}async function Lu({path:t,apiClient:e,generator:r,orgId:o,retriesOverride:n,useLocalFiles:s,noReport:i,envName:a,urlOverride:c,devicePixelRatio:l}){let d=new Ut(e,o),u;if(s)I.info(`Reading ${t} from local filesystem`),u=await no(t,xt,I);else{I.info(`Fetching ${t} from Momentic Cloud server (${e.baseURL})`);let v=await e.getTest(t),_=J.parse(v),R=await bt({metadata:_,steps:v.steps,logger:I});u={..._,steps:pt.array().parse(R.steps)}}if(Lf.gt(u.schemaVersion,X)&&I.warn(`Test ${t} 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.`),!a){for(let v of u.envs??[])if(v.default){a=v.name;break}}let p,m=[],g={};a&&(p=await Gf(e,a,I.child({testId:u.id})),g=p.variables,m=u.envs?.find(_=>_.name===a)?.fixtures??[]);let w=u.baseUrl;if(c?w=c:w||(w=g[qe]),!w){let v=`Cannot run test with no base URL and no ${qe} variable defined in its environment`;throw new Error(v)}let f,h;if(i)h=Mf();else try{f=await e.createRun({testId:u.id,testName:u.name,trigger:"CLI"}),h=f.id}catch(v){throw I.error({err:v,testId:u.id},"Error creating run on Momentic Cloud"),new Error("Failed to create test run on server. Please ensure the Momentic Cloud Server is accessible from your environment or pass the --no-report flag.")}let b=I.child({testId:u.id,runId:h}),T=async v=>{if(!i)try{await e.updateRun(h,{...v,finishedAt:new Date})}catch(_){b.warn({err:_},`Failed to update final run status for test ${h}. You may see stale data on the Momentic Cloud Server.`)}},S,C=Math.max(n??u.retries,0);for(let v=0;v<=C;v++)try{let{controller:_,context:R}=await Nf({baseUrl:w,apiClient:e,devicePixelRatio:l,logger:b,storageClient:d,test:u,localFixtures:m,generator:r,orgId:o,variables:g});if(S=await Ff({logger:b,storageClient:d,apiClient:e,test:u,orgId:o,runId:h,context:R,controller:_,noReport:i}),S.status!=="FAILED")return await T({status:S.status}),{...S,runId:h};let L=S.failedStepResult,A=L?.message||"Unknown failure";if(v>=C){b.error({errResult:L},`Test '${u.name}' failed after all exhausting ${C+1} attempts: ${A}
|
|
1354
1354
|
`),await T({status:"FAILED",failureReason:L?.failureReason}),b.info(`Classifying failure for test ${u.name}`);let P;try{let K=await r.getTestResultClassification({results:S.results,errorMessage:A});P={classification:K,errorMessage:A},await T({failureDetails:P,failureReason:K.reason})}catch(K){b.debug({err:K},"Failed to classify test failure with AI, continuing...")}return{...S,runId:h,failureDetails:P}}else b.warn({errResult:L},`Test '${u.name}' failed attempt ${v+1} of ${C+1}, retrying...`)}catch(_){b.error({err:_},`Encountered fatal platform error while running test '${u.name}': ${_}`);let R={errorMessage:_.message,errStack:_.stack},L={status:sn.FAILED,failureDetails:R,failureReason:"InternalPlatformError"};return await T(L),{...L,runId:h}}finally{try{await Promise.all(m.map(_=>As(_,"teardown")))}catch(_){b.error(`Failed to run teardown fixtures, continuing...: ${_}`)}}throw new Error("This code should not be reachable")}async function Nf({baseUrl:t,devicePixelRatio:e,apiClient:r,test:o,localFixtures:n,storageClient:s,generator:i,orgId:a,variables:c,logger:l}){for(let g of n)await As(g,"setup");let d=ir.parse(o.advanced),u=await j.init({baseUrl:t,logger:l,networkSettings:d,storage:s,enricher:new Lo({baseURL:r.baseURL,apiKey:r.apiKey}),timeout:d.pageLoadTimeoutMs,contextArgs:{viewport:o.advanced.viewport??ut,deviceScaleFactor:e}}),p=new Qt({browser:u,generator:i,config:_r,logger:l,orgId:a,storage:s,flagStore:await Jt.init(a)}),m=new Se({baseUrl:t,currentUrl:await p.browser.url(),variablesFromEnvironment:c});return{controller:p,context:m}}async function Ff({storageClient:t,apiClient:e,test:r,orgId:o,runId:n,context:s,controller:i,noReport:a,logger:c}){c.info(`Running test '${r.name}' locally${a?"":` and reporting results to https://app.momentic.ai/runs/${n}`}`),await t.resolveStepCacheEntries({schemaVersion:r.schemaVersion,organizationId:o,steps:r.steps,testId:r.id,logger:c});let l=Pf(r.steps);return await kl({test:r,context:s,runId:n,controller:i,logger:c,takeScreenshots:!0,onSaveScreenshot:async u=>{if(a)return"";let{key:p}=await e.uploadScreenshot({screenshot:u.toString("base64")});return p},onUpdateRun:async u=>{if(!a)try{await e.updateRun(n,u)}catch(p){c.warn({err:p},"Failed to update run data. You may see stale data on the Momentic Cloud Server.")}},onTestSuccess:async u=>{let p=If(l,u.steps);if(Object.keys(p).length===0){c.debug("No changes to test steps after success");return}c.debug({changes:p},"Updating steps post-run success in worker");try{let{cachesToSave:m}=de({steps:u.steps,testId:u.id,orgId:o});await e.updateStepCaches({testId:u.id,entries:m})}catch(m){c.error({err:m},"Failed to save step caches after successful execution. This is not critical, but can impact future performance.")}}})}async function Gf(t,e,r){r.info(`Attempting to resolve environment '${e}' from local disk`);let o=kf(Ve,`${e}.yaml`);try{let s=Df(xf(o,"utf-8"));try{return we.parse(s)}catch(i){r.warn({err:i},`${o} failed to parse as a valid environment file, pulling environment from Momentic Cloud instead`)}}catch(s){r.warn({err:s},`Failed to read file '${o}', attempting to pull environment from Momentic Cloud instead`)}return await t.getEnvironment(e)}async function Du({tests:t,start:e,waitOn:r,waitOnTimeout:o,client:n,all:s,report:i,retriesOverride:a,urlOverride:c,envName:l,orgId:d,devicePixelRatio:u,parallel:p=1}){e&&(I.info(`Executing start command: ${e}`),await ku(e,!1)),r&&(I.info(`Waiting for url: ${r} with timeout: ${o} seconds.`),await $f({resources:[r],timeout:o*1e3,headers:{Accept:"*/*"},followRedirect:!0,verbose:!0,log:!0,strictSSL:!1}));let m=new _t({baseURL:n.baseURL,apiKey:n.apiKey}),g=!1;(t.some(R=>R.endsWith(".yaml"))||t.some(R=>Mu(R)))&&(g=!0);let w=new Set;if(g)I.info({tests:t},`Reading tests from the following local file paths:
|
|
1355
1355
|
${t.map(R=>` - ${R}`).join(`
|
|
1356
1356
|
`)}`),t.forEach(R=>{if(!Mu(R))throw new Error(`Path '${R}' does not exist.`);let L,A;try{L=zf(R),A=L.isDirectory()}catch(P){I.error({err:P},`Skipping path ${R} because it cannot be read`);return}if(A)gr(R,I).map(P=>P.fullFilePath).forEach(P=>w.add(P));else if(R.endsWith(".yaml"))w.add(R);else throw new Error(`Path '${R}' is not a directory or a .yaml file.`)});else if(I.warn("The paths you specified are not files or directories that exist locally."),I.warn(`Fetching tests from Momentic Cloud (${n.baseURL}) instead...`),s)for(let R of await n.getAllTestIds())w.add(R);else w=new Set(t);let f=Array.from(w);I.info({testsToRun:f},`Running ${f.length} tests using local machine as worker:
|
|
1357
1357
|
${f.map(R=>` - ${R}`).join(`
|
|
1358
|
-
`)}`),Pr(`Running ${f.length} tests with ${p} workers.`);let h=new Uf.SingleBar({format:"{testPath} | {value}/{total}",noTTYOutput:!0});h.start(f.length,0);let b=[],T=Date.now();for(let R=0;R<f.length;R+=p){let L=await Promise.all(f.slice(R,R+p).map(async A=>{h.update(b.length,{testPath:A});try{let P=await Lu({path:A,orgId:d,useLocalFiles:g,devicePixelRatio:u,apiClient:n,generator:m,retriesOverride:a,urlOverride:c,envName:l,noReport:!i});return{identifier:A,...P}}catch(P){return{identifier:A,status:"FAILED",failureDetails:{errorMessage:`A fatal error occurred while setting up the test '${A}': ${P.message}`,errorStack:P.stack}}}}));b.push(...L)}h.stop();let S=n.baseURL.replace(/\/\/api/,"//app"),C=b.filter(R=>R.status==="PASSED"),v=b.filter(R=>R.status==="FAILED"),_=b.filter(R=>R.status==="CANCELLED");v.length&&(F(`${v.length} failed:`),v.forEach(R=>{let L=` ${R.identifier}`;i&&(L+=`: ${S}/runs/${R.runId}`),F(L)})),_.length&&(le(`${v.length} cancelled:`),v.forEach(R=>{let L=` ${R.identifier}`;i&&(L+=`: ${S}/runs/${R.runId}`),le(L)})),se(`${C.length} passed. (${Math.floor(Date.now()-T)/1e3}s)`),v.length>0?process.exit(1):process.exit(0)}import Bf from"cli-progress";var Hf=30*6e4;async function Nu({tests:t,client:e,...r}){let{queuedTests:o,runIds:n}=await e.queueTests({testPaths:t,...r});if(Pr(`Queued ${o.length} tests.`),r.wait){Pr(`Waiting for ${o.length} tests to complete.`);let s=e.baseURL.replace(/\/\/api/,"//app"),i=Date.now(),a=await Wf(e,n),c=a.filter(u=>u.status==="PASSED"),l=a.filter(u=>u.status==="FAILED"),d=a.filter(u=>u.status==="CANCELLED");
|
|
1358
|
+
`)}`),Pr(`Running ${f.length} tests with ${p} workers.`);let h=new Uf.SingleBar({format:"{testPath} | {value}/{total}",noTTYOutput:!0});h.start(f.length,0);let b=[],T=Date.now();for(let R=0;R<f.length;R+=p){let L=await Promise.all(f.slice(R,R+p).map(async A=>{h.update(b.length,{testPath:A});try{let P=await Lu({path:A,orgId:d,useLocalFiles:g,devicePixelRatio:u,apiClient:n,generator:m,retriesOverride:a,urlOverride:c,envName:l,noReport:!i});return{identifier:A,...P}}catch(P){return{identifier:A,status:"FAILED",failureDetails:{errorMessage:`A fatal error occurred while setting up the test '${A}': ${P.message}`,errorStack:P.stack}}}}));b.push(...L)}h.stop();let S=n.baseURL.replace(/\/\/api/,"//app"),C=b.filter(R=>R.status==="PASSED"),v=b.filter(R=>R.status==="FAILED"),_=b.filter(R=>R.status==="CANCELLED");v.length&&(F(`${v.length} failed:`),v.forEach(R=>{let L=` ${R.identifier}`;i&&(L+=`: ${S}/runs/${R.runId}`),F(L)})),_.length&&(le(`${v.length} cancelled:`),v.forEach(R=>{let L=` ${R.identifier}`;i&&(L+=`: ${S}/runs/${R.runId}`),le(L)})),se(`${C.length} passed. (${Math.floor(Date.now()-T)/1e3}s)`),v.length>0?process.exit(1):process.exit(0)}import Bf from"cli-progress";var Hf=30*6e4;async function Nu({tests:t,client:e,...r}){let{queuedTests:o,runIds:n}=await e.queueTests({testPaths:t,...r});if(Pr(`Queued ${o.length} tests.`),r.wait){Pr(`Waiting for ${o.length} tests to complete.`);let s=e.baseURL.replace(/\/\/api/,"//app"),i=Date.now(),a=await Wf(e,n),c=a.filter(u=>u.status==="PASSED"),l=a.filter(u=>u.status==="FAILED"),d=a.filter(u=>u.status==="CANCELLED");se(`${c.length} passed. (${Math.floor(Date.now()-i)/1e3}s)`),d.length&&(le(`${d.length} cancelled:`),d.forEach(u=>{le(` ${u.testName||u.test?.name||"Unknown test"}: ${s}/runs/${u.id}`)})),l.length&&(F(`${l.length} failed:`),l.forEach(u=>{F(` ${u.testName||u.test?.name||"Unknown test"}: ${s}/runs/${u.id}`)}),process.exit(1))}process.exit(0)}async function Wf(t,e){if(!e.length)return[];let r=Date.now(),o=new Bf.SingleBar({format:"{testName} | {value}/{total}",noTTYOutput:!0});o.start(e.length,0);let n=0,s="";for(;Date.now()-r<Hf;){let i;n>3&&(F("Failed to fetch run status too many times."),process.exit(1));try{i=await t.bulkGetRunStatus(e),n=0}catch{n++,await new Promise(d=>setTimeout(d,5e3));continue}let a=i.filter(l=>l.status==="RUNNING");if(a.length&&(s=a[Math.floor(Math.random()*a.length)].testName||"Unknown test"),o.update(i.filter(l=>l.status!=="PENDING"&&l.status!=="RUNNING").length,{testName:s}),i.every(l=>l.status!=="PENDING"&&l.status!=="RUNNING"))return o.stop(),i;await new Promise(l=>setTimeout(l,5e3))}F("Timeout elapsed waiting for runs to complete."),process.exit(1)}var xe=new Vf;xe.name("momentic").description("Momentic CLI").version(au);xe.command("install-browsers").action(async()=>{await Io()});xe.addOption(new Fe("--log-level <level>").choices(["debug","info","warn","error"]).default("info")).on("option:log-level",t=>{I.setMinLevel(t.toLowerCase())});xe.addOption(new Fe("--verbose","Enable verbose logging.")).on("option:verbose",()=>{I.enableConsoleLogs()});xe.command("init").addOption(st).addOption(ot).addOption(nt).action(async t=>{let{yes:e,apiKey:r,server:o}=t,n=new oe({baseURL:o,apiKey:r,logger:I});await rt({client:n,skipPrompts:e,pullEnvs:!0,createFolders:!0})});xe.command("app").addOption(ot).addOption(nt).addOption(st).addOption(Ss).addOption(new Fe("--port <port>","Port to run the app on. Defaults to 58888.").default(58888)).action(async t=>{let{apiKey:e,port:r,yes:o,server:n,pixelRatio:s}=t,i=new oe({baseURL:n,apiKey:e,logger:I});await rt({client:i,skipPrompts:o,pullEnvs:!1,createFolders:!1}),Xf(Yf(je,".."));let a=Jf(import.meta.url),c=Kf(a),l=Fu.resolve(c,"..","static"),d=Fu.resolve(c,"..","assets"),u=s??ps();ms(u),await iu({momenticServerUrl:n,apiKey:e,serverPort:r,appPort:r,staticDir:l,assetsDir:d,devicePixelRatio:u})});xe.command("run").alias("run-tests").addOption(ot).addOption(nt).addOption(Es).addOption(st).addOption(bu).addOption(Tu).addOption(new Fe("-r, --remote","Run tests remotely. The production version of this test will be queued for execution.").default(!0).conflicts(["start","waitOn","waitOnTimeout","retries","parallel","no-report","pixel-ratio"])).addOption(wu).addOption(Ts).addOption(Ss).addOption(new Fe("-l, --local","Run tests locally. Useful for accessing apps on localhost. This option does not control where tests are read from (see <tests> argument documentation).").conflicts(["wait"])).addOption(new Fe("--start <start>","Arbitrary setup command that will run before Momentic steps begin.")).addOption(new Fe("--wait-on <waitOn>","URL to wait to become accessible before Momentic tests begin.")).addOption(new Fe("--wait-on-timeout <waitOnTimeout>","Max time to wait for the --wait-on URL to become accessible.").default(60,"one minute")).addOption(new Fe("--retries <retries>","Number of retries to attempt when running tests locally. Defaults to each test's own retry configuration.")).addOption(new Fe("-p, --parallel <parallel>","When running with the --local flag, the number of tests to run in parallel. Defaults to 1.").default(1).argParser(t=>parseInt(t,10))).addOption(Ts).addArgument(Eu).action(async(t,e)=>{Cu(e);let{apiKey:r,server:o,remote:n,local:s,all:i,env:a,urlOverride:c,yes:l,parallel:d,report:u,start:p,waitOn:m,waitOnTimeout:g,retries:w,wait:f,pixelRatio:h}=e;!i&&(!t||!t.length)&&(F("You must pass at least one test path or use the --all flag."),process.exit(1));let b=new oe({baseURL:o,apiKey:r,logger:I});if(s){let T=await rt({client:b,skipPrompts:l,pullEnvs:!1,createFolders:!1}),S=h??ps();ms(S);try{await Du({tests:t,client:b,all:i,start:p,waitOn:m,waitOnTimeout:g,retriesOverride:w,parallel:d,report:u,devicePixelRatio:S,envName:a,orgId:T,urlOverride:c})}catch(C){F("Failed to run tests locally. Please check the error message below or run with the --verbose flag."),F(C),process.exit(1)}return}if(n){for(let T of t)(T.endsWith(".yaml")||qf(T))&&(F(`Looks like you tried to run a local test remotely: ${T}`),F("Use the --local flag or specify the test path without its file extension to queue tests remotely (e.g. 'hello-world')."),process.exit(1));await Nu({tests:t,client:b,all:i,env:a,urlOverride:c,wait:f});return}F("One of --remote or --local flag must be specified."),process.exit(1)});xe.command("pull").description("Fetch one or more tests from Momentic Cloud and save it in to local disk in YAML format.").addOption(ot).addOption(nt).addOption(Es).addOption(st).addArgument(Su).action(async(t,e)=>{let{apiKey:r,server:o,all:n,yes:s}=e,i=new oe({baseURL:o,apiKey:r,logger:I});await rt({client:i,skipPrompts:s,pullEnvs:!1,createFolders:!1}),!n&&!t?.length&&(F("At least one test name or the --all flag must be provided."),process.exit(1)),await xu({testsToFetch:t??[],client:i,all:n,yes:s})});xe.command("push").description("Save one or more tests in YAML format to Momentic Cloud.").addOption(st).addOption(ot).addOption(nt).addArgument(vu).action(async(t,e)=>{let{apiKey:r,server:o,yes:n}=e,s=new oe({baseURL:o,apiKey:r,logger:I});await rt({client:s,skipPrompts:n,pullEnvs:!1,createFolders:!1}),await Pu({paths:t,client:s,yes:n}),process.exit(0)});var Gu=xe.command("env").description("Perform operations on Momentic environments");Gu.command("push").description("Push one or more environments and associated variables to Momentic cloud.").addOption(st).addOption(ot).addOption(nt).addOption(vs).addArgument(Cs).action(async(t,e)=>{let{apiKey:r,server:o,yes:n,all:s}=e;!t?.length&&!s&&(F("At least one environment name or the --all flag must be provided."),process.exit(1));let i=new oe({baseURL:o,apiKey:r,logger:I});await rt({client:i,skipPrompts:n,pullEnvs:!1,createFolders:!1}),await fu({names:t,client:i,yes:n,all:s})});Gu.command("pull").description("Pull one or more environments and associated variables from Momentic cloud.").addOption(st).addOption(ot).addOption(nt).addOption(vs).addArgument(Cs).action(async(t,e)=>{let{apiKey:r,server:o,yes:n,all:s}=e;!t?.length&&!s&&(F("At least one environment name or the --all flag must be provided."),process.exit(1));let i=new oe({baseURL:o,apiKey:r,logger:I});await rt({client:i,skipPrompts:n,pullEnvs:!1,createFolders:!1}),await Po({envNames:t,client:i,skipPrompts:n,all:s})});async function Qf(){try{await xe.parseAsync(process.argv)}catch(t){let e={};try{e.playwrightVersion=jf("npx playwright --version").toString()}catch(r){I.error({err:r},"Error fetching debug information")}I.error({err:t,debugInfo:e},"Uncaught error in CLI"),I.flush(),F("Failed to start Momentic CLI."),F(t),process.exit(1)}}Qf();
|