momentic 0.0.62 → 0.0.64
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
CHANGED
|
@@ -97,14 +97,14 @@ You have already executed the following commands successfully (most recent liste
|
|
|
97
97
|
`).forEach(l=>n.push(` ${l}`))):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(`
|
|
98
98
|
`)}getListHistory(){return Sl`Here are the commands that you have successfully executed:
|
|
99
99
|
${this.commandHistory.filter(e=>e.type==="AI_ACTION").map(e=>`- ${e.serializedCommand}`).join(`
|
|
100
|
-
`)}`}async executeCommand(e,t,o,n=!1){let s=this.commandHistory[this.commandHistory.length-1];if(!n&&(!s||s.state!=="PENDING"))throw new A("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 a=Date.now(),c=await this.executePresetStep(e,t,o),l=Date.now()-a;return this.logger.debug({result:c,duration:l},"Got execution result"),c.succeedImmediately&&!n&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(c.succeedImmediately=!1)),c.elementInteracted&&"target"in e&&e.target&&!e.target.elementDescriptor&&(e.target.elementDescriptor=c.elementInteracted.trim()),n||(s.generatedStep=e,s.serializedCommand=Le(e),s.state="DONE"),c}async executeAssertion(e){let t=await this.getBrowserState(e.filterByViewport),o=await this.browser.url(),n,s;if(e.useVision)await this.browser.removeAllHighlights(),s=await this.browser.screenshot({}),n={goal:e.assertion,url:o,screenshot:s,browserState:"",history:"",numPrevious:-1,lastCommand:null};else{let c=this.getSerializedHistory(o,t);n={goal:e.assertion,url:o,browserState:t,history:c,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let a=await this.generator.getAssertionResult(n,e.useVision,e.disableCache);if(a.relevantElements&&Promise.all(Array.from(new Set(a.relevantElements)).slice(0,5).map(c=>this.browser.highlight({id:c}))),!a.result)throw new A("AssertionFailureError",a.thoughts);return{succeedImmediately:!1,thoughts:a.thoughts,urlAfterCommand:o,beforeScreenshotOverride:s}}async wrapMultiElementTargetingCommand(e,t,o,n,s=1){let a=await Promise.all(e.map((c,l)=>this.wrapElementTargetingCommand({description:c,cache:t[l],action:async i=>i,options:n})));try{return{result:await o(...a.map(l=>l.result)),caches:a.map(l=>l.cache)}}catch(c){if(s>0)return this.logger.warn({err:c},"Failed to execute action with multiple cached targets, retrying with AI"),this.wrapMultiElementTargetingCommand(e,e.map(()=>{}),o,n,s-1);throw new A("ActionFailureError",c.message,{cause:c})}}async wrapElementTargetingCommand({description:e,cache:t,action:o,options:n,originalCache:s}){let{useVision:a,disableCache:c,filterByViewport:l,useSelector:i}=n,d=n.retriesWithAI??1;if((!t||c)&&!e)throw new A("ActionFailureError","Cannot target element with no cached data or element descriptor");if(i){let m={id:-1,selector:e};return{result:await o(m),cache:m}}c&&(this.logger.info("Cache explicitly disabled for this step"),t=void 0);let u=!!t&&_t(t);if(!t){this.logger.debug("No cached locator data for target, prompting AI for fresh location"),d--;let m=await this.locateElement({description:e,useVision:a,disableCache:c,filterByViewport:l});if(m.id<=0)throw new Error(`Failed to locate element with AI: ${m.thoughts}`);if(t={id:m.id},await this.browser.resolveTarget(t),s?.serializedHtml&&t.serializedHtml&&bl(s.serializedHtml,t.serializedHtml)/Math.min(s.serializedHtml.length,t.serializedHtml.length)>.2)throw this.logger.error({originalCache:s,cache:t},"Rejecting new target due to high change ratio"),new A("ActionFailureError",`Failed to find element with description: ${e}`)}try{let m=await o(t);return u?this.logger.info("Successfully used cached target to perform action"):this.logger.info("Successfully generated and used new target data"),this.logger.debug({cache:t},"Cache state after action"),{result:m,cache:t}}catch(m){if(d>0&&e)return this.logger.debug({cache:t},"Cache state after failed targeting attempt"),this.logger.warn({err:m},"Failed to execute action with cached target, retrying with AI"),this.wrapElementTargetingCommand({description:e,cache:void 0,action:o,options:{...n,retriesWithAI:d},originalCache:t});if(m instanceof A)throw m;let h=`Failed to find ${e?`${e}`:"element"}: ${m instanceof Error?m.message:m}`;throw this.logger.error({err:m,cache:t},h),new A("ActionFailureError",h,{cause:m})}}async screenshotWithDimensions(e){await this.browser.removeAllHighlights();let t=await this.browser.screenshot(e),o=yl(t);return{buffer:t,...o}}async executePresetStep(e,t,o){let n;try{n=await this.resolveCommandTemplateStrings(e,t)}catch(s){throw new A("ActionFailureError",s.message,{cause:s})}try{let s=this.browser.getOpenPages(),a=await this.browser.url(),c=await this.executePresetStepHelper(e,t,o),l=!0;e.type==="NAVIGATE"&&(l=!1);let i=this.browser.getOpenPages();if(l&&i.length!==s.length)for(let d=i.length-1;d>=0;d--){let u=i[d];if(u!==a){await this.browser.switchToPage(u,d);break}}return c}catch(s){throw this.logger.error({err:s},"Error thrown in web agent controller"),s}finally{this.restoreCommandTemplateReplacements(e,n)}}restoreCommandTemplateReplacements(e,t={}){for(let[o,n]of Object.entries(t))ao(e,o,n)}async resolveCommandTemplateStrings(e,t,o="",n={}){let s=["type","a11yData","thoughts"];for(let a in e){if(s.includes(a))continue;let c=e[a],l=o?`${o}.${a}`:a;if(typeof c=="string"&&c.includes("{{")){let i=await Wr({s:c,state:t.toObjectRef(),logger:this.logger});if(c===i)continue;n[l]=c,e[a]=i}else typeof c=="object"&&c!==null&&!Array.isArray(c)&&await this.resolveCommandTemplateStrings(c,t,l,n)}return n}async executePresetStepHelper(e,t,o){switch(o=o||"disableCache"in e&&e.disableCache,e.type){case"SUCCESS":return e.condition?.assertion.trim()?this.executeAssertion(e.condition):{succeedImmediately:!1,urlAfterCommand:await this.browser.url()};case"AI_ASSERTION":{if(!e.assertion.trim())throw new A("ActionFailureError","Missing assertion");return this.executeAssertion(e)}case"AI_WAIT":{if(!e.assertion.trim())throw new A("ActionFailureError","Missing assertion");let i=Date.now();if(e.timeout&&e.timeout>60)throw new A("AssertionFailureError",`AI wait timeout of ${e.timeout} exceeds the maximum allowed timeout of 60s`);let d=(e.timeout??10)*1e3,u,m,h=0;for(;Date.now()-i<d;)try{u=await this.executeAssertion({...e,type:"AI_ASSERTION"});break}catch(g){m=g instanceof Error?g:new Error(`${g}`),this.logger.warn({err:g},`AI_WAIT assert attempt ${h} failed, retrying...`),h++,await V(d/10)}if(!u){let g=`AI wait still failing after ${d}ms.`;throw m&&(g+=` Latest result: ${m.message}`),new A("AssertionFailureError",g)}return u}case"AI_EXTRACT":{if(!e.goal.trim())throw new A("ActionFailureError","Cannot perform AI extraction without goal");let i=await this.browser.getHTML(),d=await this.generator.getTextExtraction({goal:e.goal,browserState:i,returnSchema:e.schema},o);if(d.result==="NOT_FOUND")throw new A("ActionFailureError","No relevant data found for extraction goal on this page");return{data:d.result,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}case"NAVIGATE":if(!Yr(e.url)&&!Xr(e.url,this.browser.baseURL))throw new A("ActionFailureError",`Invalid URL provided to navigate command: ${e.url}`);await this.browser.navigate({url:e.url});break;case"DIALOG":this.browser.registerDialogHandler(e.action);break;case"CAPTCHA":let n=await this.browser.solveCaptcha();n&&(await this.wrapElementTargetingCommand({description:"the captcha image solution input",cache:void 0,action:i=>this.browser.click(i),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}}),await this.browser.type(n,{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":let s;if(e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)){let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.hover(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});s=i,d&&(e.cache={target:d})}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:s};case"WAIT":await this.browser.wait(e.delay*1e3);break;case"REFRESH":await this.browser.refresh();break;case"CLICK":{let i=await this.browser.url(),d={useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector},{result:u,cache:m}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.click(g,{doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force}),options:d});m&&(e.cache={target:m});let h={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:u};return Qe(i,h.urlAfterCommand)&&(h.succeedImmediately=!0,h.succeedImmediatelyReason="URL changed"),h}case"DRAG":{let{result:i,caches:d}=await this.wrapMultiElementTargetingCommand([e.fromTarget.elementDescriptor,e.toTarget.elementDescriptor],[e.cache?.fromTarget,e.cache?.toTarget],(u,m)=>this.browser.dragAndDrop(u,m,{force:e.force}),{useVision:e.useVision,filterByViewport:e.filterByViewport,useSelector:e.useSelector,disableCache:o});return d&&d.every(u=>u)&&(e.cache={fromTarget:d[0],toTarget:d[1]}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"MOUSE_DRAG":{let i;e.target?.elementDescriptor&&(i=(await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target,action:async g=>g,options:{useVision:e.useVision,filterByViewport:e.filterByViewport,useSelector:e.useSelector,disableCache:o}})).result);let d=parseInt(e.deltaX),u=parseInt(e.deltaY);if(isNaN(d)||isNaN(u))throw new A("ActionFailureError",`Invalid pixel values passed to mouse drag command: (${e.deltaX}, ${e.deltaY})`);let m=await this.browser.mouseDrag(d,u,e.steps,i,{force:e.force});return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:m}}case"SELECT_OPTION":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.selectOption(u,e.option),options:{useVision:!1,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"TAB":await this.browser.switchToPage(e.url);break;case"NEW_TAB":await this.browser.createNewTab(e.url);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 i;try{e.environment==="BROWSER"?i=await this.browser.executePageFunction(new Bt(e.fragment?`return ${e.code}`:e.code),void 0):i=await Xe({code:e.code,fragment:!!e.fragment,state:t.toObjectRef(),timeoutMs:e.timeout?e.timeout*1e3:void 0,logger:this.logger})}catch(d){throw new A("ActionFailureError",d instanceof Error?d.message:`${d}`,{cause:d})}try{JSON.stringify(i)}catch(d){throw new A("ActionFailureError",`Return value is not serializable: ${d instanceof Error?d.message:`${d}`}`,{cause:d})}return{urlAfterCommand:await this.browser.url(),succeedImmediately:!1,data:i}}case"TYPE":{let i=await this.browser.url(),d;if(e.target){let{result:m,cache:h}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.typeIntoTarget(e.value,g,{force:e.force,clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});d=m,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 u={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:d};return Qe(i,u.urlAfterCommand)&&(u.succeedImmediately=!0,u.succeedImmediatelyReason="URL changed"),u}case"HOVER":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.hover(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"FOCUS":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.focus(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"BLUR":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.blur(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"PRESS":let a=await this.browser.url();await this.browser.press(e.value);let c={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return Qe(a,c.urlAfterCommand)&&(c.succeedImmediately=!0,c.succeedImmediatelyReason="URL changed"),c;case"REQUEST":{let i=e.timeout??30,d=null,u=new AbortController,m=Object.fromEntries(Object.entries(e.headers||{}).filter(([v,T])=>v&&T)),h=new URLSearchParams;Object.entries(e.params||{}).filter(([v,T])=>v&&T).forEach(([v,T])=>{h.append(v,T)});let g;if(Yr(e.url)&&(g=e.url),Xr(e.url,this.browser.baseURL)&&(g=new URL(e.url,this.browser.baseURL).toString()),!g)throw new A("ActionFailureError",`Invalid URL: ${e.url}`);let p=async()=>{try{d=await fetch(`${g}?${h.toString()}`,{headers:m,method:e.method,body:e.body,signal:u.signal})}catch(v){this.logger.error({err:v},"Failed to make fetch request")}},S=async()=>{await new Promise(v=>setTimeout(v,i*1e3)),u.abort()};await Promise.race([S(),p()]);let y=d;if(!y)throw new A("ActionFailureError",`Fetch request timed out after ${i} seconds`);if(!y.ok)throw new A("ActionFailureError",`Fetch request failed with status ${y.status}`);let w={};y.headers.forEach((v,T)=>{w[T]=v});let b={status:y.status,headers:w};return y.headers.get("content-type")?.includes("json")?b.json=await y.json():y.headers.get("content-type")?.includes("text")&&(b.text=await y.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:b}}case"VISUAL_DIFF":{if(!e.screenshot)throw new A("ActionFailureError","Cannot execute visual diff without saved screenshot");await this.browser.removeAllHighlights();let i=await this.screenshotWithDimensions({target:e.target?.a11yData,hideCaret:!0});if(i.height!==e.screenshot.height||i.width!==e.screenshot.width)throw new A("ActionFailureError","Current screenshot does not match saved screenshot dimensions - did you change the size of the target or the viewport?");let d={data:Buffer.alloc(i.width*i.height*4),width:i.width,height:i.height},u;if(e.screenshot.data.startsWith("https://")){let p=await fetch(e.screenshot.data);u=Buffer.from(await p.arrayBuffer())}else u=Buffer.from(e.screenshot.data,"base64");let h=vl(co.decode(u).data,co.decode(i.buffer).data,d.data,i.width,i.height,{threshold:e.threshold,diffColorAlt:[0,255,0]})/(i.width*i.height)*100,g=h>e.threshold;return{fail:g,thoughts:g?`Visual diff of ${h.toFixed(2)}% detected`:void 0,beforeScreenshotOverride:i.buffer,afterScreenshotOverride:co.encode(d,75).data,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}default:return(i=>{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,t,o){return(await this.generator.getReverseMappedDescription({browserState:e.browserState,goal:`${t}`},o)).phrase}stopRecordMode(){this.recordAbortController?.abort()}async startRecordMode(e){this.recordAbortController=new AbortController;let t=new mt({signal:this.recordAbortController.signal,...e});await this.browser.startRecording(this.recordAbortController.signal,t)}};async function Ws({socket:r,generator:e,storage:t,logger:o,rootState:n,localApp:s,devicePixelRatio:a}){let c=o.child({package:"web-agent"}),l=!0;s==="iframe"&&(l=!1);let i=r.handshake.query.testId,d=r.handshake.query.baseUrl,u=r.handshake.query.viewport?JSON.parse(decodeURIComponent(r.handshake.query.viewport)):void 0;if(!i||!d)throw new Error("Socket connection request is missing testId or baseUrl");let m=await t.getOrgId(i),h=r.handshake.query.environmentName,g=h?await t.fetchEnvironment(m,h,o):void 0,p=El(),S=x=>{l&&r.emit("screenshot",{...x,url:d})},y=n?.controller;if(y)y.browser.setActiveFrame(Kt),y.browser.baseURL=d,await y.resetState({clearCookies:!0,clearStorage:!0,timeout:null}),y.setOpen(),n?.context.resetVariables(d,g?.variables);else{let x={};u&&(x.viewport=u),s==="web"&&a&&(x.deviceScaleFactor=a);let U=await _.init({baseUrl:d,logger:c,takeScreenshots:l,onScreenshot:S,contextArgs:x});y=new Re({browser:U,generator:e,config:tt,logger:c})}let w;l&&(w=Rn(r,p,o));let b=async()=>{clearInterval(w.timer)},v=new ce(d,g?.variables);r.emit("testContext",{state:v.toRedactedDisplayCopy()}),R.registerSession({controller:y,context:v,sessionId:p,cleanup:b});let T=_.USER_AGENT;return r.emit("session",{url:d,userAgent:T,viewport:await y.browser.getViewport(),localApp:s}),{sessionId:p,testId:i,orgId:m}}var js=[Un,Us,Bs,$s,Vs,In,Hs,Mn,Dn,Nn,_n,Pn,kn,Fn,On,xn,zn,Ln];var lo=r=>{let{logger:e}=r,t=new Tl(r.baseServer,{cors:{origin:"*",methods:["GET","POST"]},maxHttpBufferSize:1e7});return t.on("connection",async o=>{let n;try{n=await Ws({...r,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}`});return}js.forEach(s=>Al(s,{socket:o,metadata:n,...r}))}),t},Al=(r,e)=>{let t=r.createHandler(e),o=(...n)=>{r.event!=="cloudMouseMove"&&e.logger.debug({event:r.event,metadata:e.metadata,args:n},"Websocket event");let s=a=>{e.logger.error({event:r.event,type:"websocket",args:n,err:a instanceof Error?a:new Error(`${a}`)},"Unhandled exception in socket handler"),e.socket.emit("error",{message:a instanceof Error?a.message:`${a}`})};try{let a=t.apply(void 0,n);a&&typeof a.catch=="function"&&a.catch(s)}catch(a){s(a)}};e.socket.on(r.event,o)};import{z as xl}from"zod";import Rl from"fetch-retry";var Il=Rl(global.fetch),ie=class{static API_VERSION="v1";baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async sendRequest(e,t){let o=await Il(`${this.baseURL}${e}`,{retries:1,retryDelay:1e3,method:"POST",body:JSON.stringify(t),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 rt=class extends ie{constructor(e){super(e)}async getScreenshotFromS3(e){let t=await this.sendRequest(`/${ie.API_VERSION}/s3/visual-diff-screenshot`,{url:e});return xl.string().parse(t)}async getElementLocation(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:t});return Br.parse(o)}async getElementLocationWithVision(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:t});return Br.parse(o)}async getAssertionResult(e,t,o){if(t){let s=await this.sendRequest(`/${ie.API_VERSION}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:o,vision:!0});return $r.parse(s)}let n=await this.sendRequest(`/${ie.API_VERSION}/web-agent/assertion`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:o,vision:!1});return $r.parse(n)}async getProposedCommand(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/next-command`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:t});return mn.parse(o)}async getGranularGoals(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:t});return pn.parse(o)}async getReverseMappedDescription(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:t});return hn.parse(o)}async getTextExtraction(e,t){let o={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:t},n=await this.sendRequest(`/${ie.API_VERSION}/web-agent/text-extraction`,o);return Cr.parse(n)}};import{existsSync as Ol,readFileSync as Ll,writeFileSync as uo}from"fs";import mo from"path";import{parse as Ml,stringify as Nl}from"yaml";var X=process.env.MOMENTIC_DIR??Me;function Gs(r,e){let t=an(r);for(let[s,a]of Object.entries(t.modules)){let c=qe(s);uo(mo.join(X,Ne,`${c}.yaml`),a,"utf-8")}let o=qe(e),n=mo.join(X,`${o}.yaml`);return uo(n,t.test,"utf-8"),`${o}.yaml`}function Ks(r,e){if(!Ol(e))throw new Error(`Test not found at path: ${mo}`);let t=Ll(e,"utf-8"),n={...Ml(t),...r},s=Nl(n);uo(e,s,"utf-8")}import{readFileSync as cr,readdirSync as qs,writeFileSync as Ys}from"fs";import{join as po}from"path";import{v4 as Pl}from"uuid";import{parse as lr,stringify as Xs}from"yaml";var ot=po(X,Ne);function Js(r){let e=tr(ot,r.moduleId).path,t=cr(e,"utf-8"),n={...lr(t),...r},s=Xs(n);Ys(e,s,"utf-8")}function Qs(r,e){let t=qe(r),o=po(ot,`${t}.yaml`),n=Pl(),{stepsToSave:s}=ee({steps:e,moduleId:n,testId:"",orgId:""}),a={fileType:"momentic/module",schemaVersion:j,moduleId:n,name:r,steps:s},c=Xs(a);return Ys(o,c,"utf-8"),a}function Zs(){let r=[];for(let e in qs(ot)){if(!e.endsWith(".yaml"))continue;let t=cr(e,"utf-8"),o=lr(t),n={name:o.name,moduleId:o.moduleId,numSteps:o.steps.length};r.push(n)}return r}function ei(){let r=[];for(let e of qs(ot)){if(!e.endsWith(".yaml"))continue;let t=po(ot,e),o=cr(t,"utf-8"),n=lr(o);try{let s=Et.parse(n);r.push(s)}catch(s){B.warn({err:s},"Error parsing module, skipping...")}}return r}function ti(r){let e=tr(Ne,r).path,t=cr(e,"utf-8"),o=lr(t);return Et.parse(o)}import{Router as zl}from"express";import{existsSync as Dl,readFileSync as _l,readdirSync as kl}from"fs";import{join as ri}from"path";import{parse as Fl}from"yaml";var ho=ri(X,vt);function oi(){let r=[];if(!Dl(ho))return[];for(let e of kl(ho)){if(!e.endsWith(".yaml"))continue;let t=ri(ho,e),o=_l(t,"utf-8"),n=Fl(o);try{let s=ne.parse(n);r.push(s)}catch(s){B.warn({err:s},"Error parsing environment, skipping...")}}return r}function de(r){return function(...e){let t=e[e.length-1],o=r(...e);Promise.resolve(o).catch(t)}}var ni=zl();ni.get("/",de((r,e)=>{let t=oi();e.status(200).json(t)}));var si=ni;import{Router as Ul}from"express";var xt=Ul();xt.get("/",de((r,e)=>{let t=ei();e.status(200).json(t)}));xt.get("/metadata",de((r,e)=>{let t=Zs();e.status(200).json(t)}));xt.post("/",de(async(r,e)=>{let t;try{t=dn.parse(r.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}try{Ct(t.name)}catch(n){e.status(400).json({error:`Invalid module name: ${n}`});return}let o=Qs(t.name,t.steps);e.status(201).json(o)}));xt.get("/:moduleId",de((r,e)=>{if(!r.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let t=ti(r.params.moduleId);e.json(t)}));var ii=xt;import{Router as jl}from"express";import{existsSync as Gl}from"fs";import{join as mi}from"path";import{v4 as Kl}from"uuid";var fo="https://api.momentic.ai",ai=r=>{fo=r},Ot=()=>fo,Hl,ci=r=>{Hl=r};var dr,go,li=async r=>{if(dr)return;let e=new K({baseURL:fo,apiKey:r});try{dr=await e.getOrgId(),go=r}catch(t){throw new Error(`Error checking API key against server: ${t}`)}},ur=()=>{if(!dr)throw new Error("Your organization ID appears invalid. Please contact Momentic Support.");return dr},di=()=>{if(!go)throw new Error("Your API key appears invalid. Please contact Momentic Support.");return go};import ui,{multistream as $l}from"pino";import Bl from"pino-pretty";var yo=new Map,Vl=process.env.NODE_ENV==="production",So=class r{consoleLogger;ddClientToken;hostname;bindingAttributes;site="https://http-intake.logs.us5.datadoghq.com/api/v2/logs";constructor({bindings:e,clientToken:t,hostname:o}){this.ddClientToken=t,this.hostname=o,this.bindingAttributes={...e,env:process.env.NODE_ENV};let n={base:this.bindingAttributes,errorKey:"err",level:"debug"};this.consoleLogger=Vl?ui(n):ui(n,$l([{stream:Bl({colorize:!0})}]))}child(e){return new r({clientToken:this.ddClientToken,bindings:{...this.bindingAttributes,...e},hostname:this.hostname})}flush(e){this.consoleLogger.flush(e)}log(e,t,o,...n){t&&o===void 0&&(o=`${t}`,t={}),this.consoleLogger[e](t,o,...n);let s=Object.assign({},this.bindingAttributes,t&&typeof t=="object"?t:{});n.length>0&&(s.args=n),(async()=>{try{let a=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(!a.ok)throw new Error(`Failed to log to Datadog: ${a.statusText})}`)}catch(a){this.consoleLogger.warn({obj:t,msg:o,args:n,err:a},"Failed to log to Datadog")}})()}debug(e,t,...o){this.log("debug",e,t,...o)}info(e,t,...o){this.log("info",e,t,...o)}warn(e,t,...o){this.log("warn",e,t,...o)}error(e,t,...o){this.log("error",e,t,...o)}bindings(){return this.bindingAttributes}setMinLevel(e){this.consoleLogger.level=e}},mr=({app:r,clientToken:e,hostname:t})=>{if(!process.env.DD_CLIENT_TOKEN&&!process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN&&!e)throw new Error("Missing DD_CLIENT_TOKEN");return yo.has(r)||yo.set(r,new So({bindings:{app:r},hostname:t,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN})),yo.get(r)};import{hostname as Wl}from"os";var ue=mr({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:Wl()});var Lt=jl();Lt.get("/",de((r,e)=>{let t=At(X,ue);e.status(200).json(t)}));Lt.post("/",de((r,e)=>{let t;try{t=ln.parse(r.body)}catch(c){e.status(400).json({error:`Invalid request body: ${c}`});return}try{Ct(t.name)}catch(c){e.status(400).json({error:c.message});return}let n={id:Kl(),name:t.name,baseUrl:t.baseUrl,schemaVersion:j,advanced:{disableAICaching:!1,viewport:t.viewport??We},retries:0,steps:[]};t.environment&&(n.envs=[{name:t.environment,defaultOnLocal:!0}]);let s=Gs(n,t.name),a={...n,testPath:s};e.status(201).json(a)}));Lt.get("/:testPath",de(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t=mi(X,r.params.testPath);if(!Gl(t)){e.status(400).json({error:`Test not found at path: ${t}`});return}let o=await rr(t,ot);e.status(200).json(o)}));Lt.patch("/:testPath",de(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t;try{t=cn.parse(r.body)}catch(i){e.status(400).json({error:`Invalid request body: ${i}`});return}let o=mi(X,r.params.testPath),n;try{n=Jn(o)}catch(i){e.status(400).json({error:`Existing test file on disk is invalid: ${i}`});return}let{stepsToSave:s,moduleUpdates:a,cachesToSave:c}=ee({steps:t.steps,testId:n.id,orgId:ur()});a.forEach(i=>Js(i)),Ks({steps:s},o),await new K({apiKey:di(),baseURL:Ot()}).updateStepCaches({entries:c,testId:n.id}),e.status(201).json({message:"ok"})}));var pi=Lt;async function hi({momenticServerUrl:r,apiKey:e,mode:t,serverPort:o,appPort:n,staticDir:s,assetsDir:a,devicePixelRatio:c}){if(!Xl(X)||!Jl(X).isDirectory()){let p=ed.isAbsolute(X);throw new Error(`Root folder ${X} does not exist${p?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}r&&ai(r),B.info("Checking API key"),await li(e);let i=`http://localhost:${o}`;n&&(i=`http://localhost:${n}`);let d=rd(s);await new Promise(p=>{d.listen(o,()=>{B.info(`Server is running at http://localhost:${o}`),p()})});let m=new rt({baseURL:Ot(),apiKey:e}),h=new ut(new K({baseURL:Ot(),apiKey:e}),ur());if(t==="web"){lo({localApp:"web",baseServer:d,generator:m,storage:h,logger:ue,devicePixelRatio:c}),await Zl(i);return}let g=await od({appUrl:i,apiGenerator:m,extensionPath:td(a,"extension"),onClose:async()=>{B.info("Browser closed, closing app."),await g.browser.cleanup(),d.close(()=>{process.exit(0)})}});lo({localApp:"iframe",baseServer:d,generator:m,storage:h,logger:ue,rootState:{controller:g,context:new ce(""),executionCancelled:!1}}),process.on("uncaughtException",p=>{ue.error({err:p},"Uncaught exception in desktop-server")}),process.on("unhandledRejection",p=>{ue.error({err:p},"Unhandled rejection in desktop-server")})}function rd(r){let e=wo();e.use(Yl()),e.use(ql.json({limit:"50mb"}));let t=wo.Router();if(t.use("/tests",pi),t.use("/modules",ii),t.use("/environments",si),e.use("/api",t),e.use((n,s,a)=>{n.path!=="/healthcheck"&&ue.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&&ue.error({url:n.url,method:n.method,statusCode:s.statusCode},"Request completed in error")}),a()}),e.use((n,s,a,c)=>{n instanceof Error&&n.message.includes("BadRequestError: request aborted")||(ue.error({stack:n.stack,msg:n.message,url:s.url,method:s.method},"Unhandled exception leading to 500 on desktop-server"),a.status(500).send("Internal Server Error"),c(n))}),r){let n=wo.static(r);e.use("/",n),e.use("*",n)}return Ql.createServer(e)}async function od({appUrl:r,apiGenerator:e,extensionPath:t,onClose:o}){let n=await _.init({baseUrl:r,logger:ue,browserArgs:{headless:!1,handleSIGTERM:!0},contextArgs:{bypassCSP:!0,viewport:null},waitForLoad:!0,localMode:!0,localAppUrl:r,extensionPath:t,skipPageSetup:!0,timeout:null,onClose:o}),s=new Re({browser:n,config:tt,generator:e,logger:ue});return ci(s),s}import{existsSync as zd}from"fs";import{platform as Ud}from"os";import Vi,{dirname as Hd}from"path";import{fileURLToPath as $d}from"url";var gi="0.0.62";import{hostname as sd}from"os";var C=mr({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:sd()});import{createHash as wi}from"crypto";import{existsSync as bi,mkdirSync as md,readFileSync as To,writeFileSync as vi}from"fs";import{join as Ci}from"path";import{parse as pd}from"yaml";import{existsSync as bo,mkdirSync as cd,readdirSync as yi,statSync as vo}from"fs";import{homedir as Si}from"os";import{join as Mt,resolve as ld}from"path";import id from"chalk";import ad from"readline/promises";async function he(r,e){if(process.env.CI)return!0;C.flush(),await new Promise(s=>setTimeout(s,500));let t=ad.createInterface({input:process.stdin,output:process.stdout});r=`${r} (y/N) `;let o=e?id.bold.yellow(r):r,n=await t.question(o);return t.close(),n.toLowerCase()==="y"}var Co=Me,nt=Mt(Me,Ne),Eo=Mt(Me,Nr),ve=Mt(Me,vt),dd=Mt(Si(),"momentic",Pr),ud=[dd,nt,Eo,ve];function fi(r){try{return bo(r)&&vo(r).isDirectory()}catch(e){return C.error({err:e},`Error reading path ${r} during directory exists check`),!1}}async function ke(r,e=!1){let t=await r.getOrgId();Si()||(C.error("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)),fi(Co)||!e&&!await he(`A '${Me}' folder was not found in the current directory. Proceed with creation here? You will need to invoke the Momentic CLI from this directory in the future.`)&&(C.error("Setup cancelled"),process.exit(1));for(let o of ud)fi(o)||(!e&&!await he(`'${o}' folder was not found in the current directory. Create it now?`)&&(C.error("Setup cancelled"),process.exit(1)),cd(o,{recursive:!0}));return yi(ve).length===0&&(!e&&!await he("You do not appear to have any environments configured locally. Pull environments from Momentic Cloud?")&&(C.error("Setup cancelled"),process.exit(1)),await pr({client:r,all:!0,skipPrompts:e})),C.info("CLI initialization complete"),t}function Nt(r,e,t=new Set){for(let o of r){let n=ld(o),s=!1;try{s=bo(n)&&vo(n).isDirectory()}catch(a){C.error({err:a},`Error reading path ${n} during collect paths`)}if(n&&s){let a=yi(n).map(c=>Mt(n,c));Nt(a,e,t);continue}if(n.endsWith(".yaml")){try{if(!bo(n)||!vo(n).isFile()){C.error(`File not found or unreadable: ${n}`);continue}}catch(a){C.error({err:a},`Error reading file ${n} during collect paths`);continue}if(!e(n))continue;t.add(n)}}return t}async function pr({envNames:r,client:e,all:t,skipPrompts:o}){let n=await e.getAllEnvironments();r&&!t&&(n=n.filter(c=>r?.includes(c.name))),bi(ve)||md(ve);let s=0,a=0;for(let c of n){let l=Ci(ve,`${c.name}.yaml`);if(!bi(l))vi(l,kr(c)),s++;else{let i=wi("sha256").update(To(l)).digest("hex"),d=kr(c),u=wi("sha256").update(d).digest("hex");if(i!==u){if(!o&&!await he(`Environment ${c.name} already exists on disk but needs to be updated. Overwrite?`)){C.error("Pull cancelled");return}vi(l,d),a++}}}C.info(`Pulled ${s} new environments and updated ${a} existing environments`)}function hd(r){return r.endsWith(".yaml")?To(r,"utf8").includes("momentic/environment")?!0:(C.warn(`Skipping YAML that is not a Momentic environment: ${r}`),!1):!1}async function Ei({client:r,names:e,all:t,yes:o}){let n;t?n=[ve]:n=e.map(c=>Ci(ve,`${c.toLowerCase()}.yaml`));let s=Nt(n,hd);C.info(`Found ${s.size} environments(s) to push:`),s.forEach(c=>C.info(` - ${c}`)),C.info("Loading file contents");let a=[];for(let c of s){let l=pd(To(c,"utf-8"));try{let i=ne.parse(l);a.push(i)}catch(i){C.error({err:i},`${c} failed to parse as a valid environment file`),process.exit(1)}}if(!o&&!await he("Pushing environments can overwrite variables on cloud and instantly affect scheduled runs. Continue?",!0)){C.error("Push cancelled");return}C.info(`Pushing ${s.size} environment(s)`),await r.updateEnvironments(a),C.info("Push successful!")}import{registry as Ao}from"playwright-core/lib/server";function gd(r){let e=[],t=[];for(let o of r){let n=Ao.findExecutable(o);!n||n.installType==="none"?e.push(o):t.push(n)}return t}async function Ti(){let r=gd(["chromium"]);await Ao.installDeps(r,!1),await Ao.install(r,!1)}import{Argument as hr,Option as Fe}from"commander";var ze=new Fe("--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),Ue=new Fe("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),He=new Fe("-y, --yes","Skip confirmation prompts.").env("CI").default(!0),Ai=new Fe("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag."),Ri=new Fe("--pixel-ratio <number>","Device pixel ratio of your screen or monitor. Mac OS Retina displays and machines marketed as 'HiDPI' should usually set this to 2.").default(1).argParser(r=>parseInt(r,10)),Ii=new Fe("--env <env>","Name of the environment to use when running tests."),Ro=new Fe("-a, --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments."),Io=new Fe("-a, --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments."),xi=new hr("<tests...>",`One or more test paths to pull from Momentic Cloud.
|
|
100
|
+
`)}`}async executeCommand(e,t,o,n=!1){let s=this.commandHistory[this.commandHistory.length-1];if(!n&&(!s||s.state!=="PENDING"))throw new A("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 a=Date.now(),c=await this.executePresetStep(e,t,o),l=Date.now()-a;return this.logger.debug({result:c,duration:l},"Got execution result"),c.succeedImmediately&&!n&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(c.succeedImmediately=!1)),c.elementInteracted&&"target"in e&&e.target&&!e.target.elementDescriptor&&(e.target.elementDescriptor=c.elementInteracted.trim()),n||(s.generatedStep=e,s.serializedCommand=Le(e),s.state="DONE"),c}async executeAssertion(e){let t=await this.getBrowserState(e.filterByViewport),o=await this.browser.url(),n,s;if(e.useVision)await this.browser.removeAllHighlights(),s=await this.browser.screenshot({}),n={goal:e.assertion,url:o,screenshot:s,browserState:"",history:"",numPrevious:-1,lastCommand:null};else{let c=this.getSerializedHistory(o,t);n={goal:e.assertion,url:o,browserState:t,history:c,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let a=await this.generator.getAssertionResult(n,e.useVision,e.disableCache);if(a.relevantElements&&Promise.all(Array.from(new Set(a.relevantElements)).slice(0,5).map(c=>this.browser.highlight({id:c}))),!a.result)throw new A("AssertionFailureError",a.thoughts);return{succeedImmediately:!1,thoughts:a.thoughts,urlAfterCommand:o,beforeScreenshotOverride:s}}async wrapMultiElementTargetingCommand(e,t,o,n,s=1){let a=await Promise.all(e.map((c,l)=>this.wrapElementTargetingCommand({description:c,cache:t[l],action:async i=>i,options:n})));try{return{result:await o(...a.map(l=>l.result)),caches:a.map(l=>l.cache)}}catch(c){if(s>0)return this.logger.warn({err:c},"Failed to execute action with multiple cached targets, retrying with AI"),this.wrapMultiElementTargetingCommand(e,e.map(()=>{}),o,n,s-1);throw new A("ActionFailureError",c.message,{cause:c})}}async wrapElementTargetingCommand({description:e,cache:t,action:o,options:n,originalCache:s}){let{useVision:a,disableCache:c,filterByViewport:l,useSelector:i}=n,d=n.retriesWithAI??1;if((!t||c)&&!e)throw new A("ActionFailureError","Cannot target element with no cached data or element descriptor");if(i){let m={id:-1,selector:e};return{result:await o(m),cache:m}}c&&(this.logger.info("Cache explicitly disabled for this step"),t=void 0);let u=!!t&&_t(t);if(!t){this.logger.debug("No cached locator data for target, prompting AI for fresh location"),d--;let m=await this.locateElement({description:e,useVision:a,disableCache:c,filterByViewport:l});if(m.id<=0)throw new Error(`Failed to locate element with AI: ${m.thoughts}`);if(t={id:m.id},await this.browser.resolveTarget(t),s?.serializedHtml&&t.serializedHtml&&bl(s.serializedHtml,t.serializedHtml)/Math.min(s.serializedHtml.length,t.serializedHtml.length)>.2)throw this.logger.error({originalCache:s,cache:t},"Rejecting new target due to high change ratio"),new A("ActionFailureError",`Failed to find element with description: ${e}`)}try{let m=await o(t);return u?this.logger.info("Successfully used cached target to perform action"):this.logger.info("Successfully generated and used new target data"),this.logger.debug({cache:t},"Cache state after action"),{result:m,cache:t}}catch(m){if(d>0&&e)return this.logger.debug({cache:t},"Cache state after failed targeting attempt"),this.logger.warn({err:m},"Failed to execute action with cached target, retrying with AI"),this.wrapElementTargetingCommand({description:e,cache:void 0,action:o,options:{...n,retriesWithAI:d},originalCache:t});if(m instanceof A)throw m;let h=`Failed to find ${e?`${e}`:"element"}: ${m instanceof Error?m.message:m}`;throw this.logger.error({err:m,cache:t},h),new A("ActionFailureError",h,{cause:m})}}async screenshotWithDimensions(e){await this.browser.removeAllHighlights();let t=await this.browser.screenshot(e),o=yl(t);return{buffer:t,...o}}async executePresetStep(e,t,o){let n;try{n=await this.resolveCommandTemplateStrings(e,t)}catch(s){throw new A("ActionFailureError",s.message,{cause:s})}try{let s=this.browser.getOpenPages(),a=await this.browser.url(),c=await this.executePresetStepHelper(e,t,o),l=!0;e.type==="NAVIGATE"&&(l=!1);let i=this.browser.getOpenPages();if(l&&i.length!==s.length)for(let d=i.length-1;d>=0;d--){let u=i[d];if(u!==a){await this.browser.switchToPage(u,d);break}}return c}catch(s){throw this.logger.error({err:s},"Error thrown in web agent controller"),s}finally{this.restoreCommandTemplateReplacements(e,n)}}restoreCommandTemplateReplacements(e,t={}){for(let[o,n]of Object.entries(t))ao(e,o,n)}async resolveCommandTemplateStrings(e,t,o="",n={}){let s=["type","a11yData","thoughts"];for(let a in e){if(s.includes(a))continue;let c=e[a],l=o?`${o}.${a}`:a;if(typeof c=="string"&&c.includes("{{")){let i=await Wr({s:c,state:t.toObjectRef(),logger:this.logger});if(c===i)continue;n[l]=c,e[a]=i}else typeof c=="object"&&c!==null&&!Array.isArray(c)&&await this.resolveCommandTemplateStrings(c,t,l,n)}return n}async executePresetStepHelper(e,t,o){switch(o=o||"disableCache"in e&&e.disableCache,e.type){case"SUCCESS":return e.condition?.assertion.trim()?this.executeAssertion(e.condition):{succeedImmediately:!1,urlAfterCommand:await this.browser.url()};case"AI_ASSERTION":{if(!e.assertion.trim())throw new A("ActionFailureError","Missing assertion");return this.executeAssertion(e)}case"AI_WAIT":{if(!e.assertion.trim())throw new A("ActionFailureError","Missing assertion");let i=Date.now();if(e.timeout&&e.timeout>60)throw new A("AssertionFailureError",`AI wait timeout of ${e.timeout} exceeds the maximum allowed timeout of 60s`);let d=(e.timeout??10)*1e3,u,m,h=0;for(;Date.now()-i<d;)try{u=await this.executeAssertion({...e,type:"AI_ASSERTION"});break}catch(g){m=g instanceof Error?g:new Error(`${g}`),this.logger.warn({err:g},`AI_WAIT assert attempt ${h} failed, retrying...`),h++,await V(d/10)}if(!u){let g=`AI wait still failing after ${d}ms.`;throw m&&(g+=` Latest result: ${m.message}`),new A("AssertionFailureError",g)}return u}case"AI_EXTRACT":{if(!e.goal.trim())throw new A("ActionFailureError","Cannot perform AI extraction without goal");let i=await this.browser.getHTML(),d=await this.generator.getTextExtraction({goal:e.goal,browserState:i,returnSchema:e.schema},o);if(d.result==="NOT_FOUND")throw new A("ActionFailureError","No relevant data found for extraction goal on this page");return{data:d.result,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}case"NAVIGATE":if(!Yr(e.url)&&!Xr(e.url,this.browser.baseURL))throw new A("ActionFailureError",`Invalid URL provided to navigate command: ${e.url}`);await this.browser.navigate({url:e.url});break;case"DIALOG":this.browser.registerDialogHandler(e.action);break;case"CAPTCHA":let n=await this.browser.solveCaptcha();n&&(await this.wrapElementTargetingCommand({description:"the captcha image solution input",cache:void 0,action:i=>this.browser.click(i),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}}),await this.browser.type(n,{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":let s;if(e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)){let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.hover(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});s=i,d&&(e.cache={target:d})}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:s};case"WAIT":await this.browser.wait(e.delay*1e3);break;case"REFRESH":await this.browser.refresh();break;case"CLICK":{let i=await this.browser.url(),d={useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector},{result:u,cache:m}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.click(g,{doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force}),options:d});m&&(e.cache={target:m});let h={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:u};return Qe(i,h.urlAfterCommand)&&(h.succeedImmediately=!0,h.succeedImmediatelyReason="URL changed"),h}case"DRAG":{let{result:i,caches:d}=await this.wrapMultiElementTargetingCommand([e.fromTarget.elementDescriptor,e.toTarget.elementDescriptor],[e.cache?.fromTarget,e.cache?.toTarget],(u,m)=>this.browser.dragAndDrop(u,m,{force:e.force}),{useVision:e.useVision,filterByViewport:e.filterByViewport,useSelector:e.useSelector,disableCache:o});return d&&d.every(u=>u)&&(e.cache={fromTarget:d[0],toTarget:d[1]}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"MOUSE_DRAG":{let i;e.target?.elementDescriptor&&(i=(await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target,action:async g=>g,options:{useVision:e.useVision,filterByViewport:e.filterByViewport,useSelector:e.useSelector,disableCache:o}})).result);let d=parseInt(e.deltaX),u=parseInt(e.deltaY);if(isNaN(d)||isNaN(u))throw new A("ActionFailureError",`Invalid pixel values passed to mouse drag command: (${e.deltaX}, ${e.deltaY})`);let m=await this.browser.mouseDrag(d,u,e.steps,i,{force:e.force});return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:m}}case"SELECT_OPTION":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.selectOption(u,e.option),options:{useVision:!1,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"TAB":await this.browser.switchToPage(e.url);break;case"NEW_TAB":await this.browser.createNewTab(e.url);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 i;try{e.environment==="BROWSER"?i=await this.browser.executePageFunction(new Bt(e.fragment?`return ${e.code}`:e.code),void 0):i=await Xe({code:e.code,fragment:!!e.fragment,state:t.toObjectRef(),timeoutMs:e.timeout?e.timeout*1e3:void 0,logger:this.logger})}catch(d){throw new A("ActionFailureError",d instanceof Error?d.message:`${d}`,{cause:d})}try{JSON.stringify(i)}catch(d){throw new A("ActionFailureError",`Return value is not serializable: ${d instanceof Error?d.message:`${d}`}`,{cause:d})}return{urlAfterCommand:await this.browser.url(),succeedImmediately:!1,data:i}}case"TYPE":{let i=await this.browser.url(),d;if(e.target){let{result:m,cache:h}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:g=>this.browser.typeIntoTarget(e.value,g,{force:e.force,clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});d=m,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 u={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:d};return Qe(i,u.urlAfterCommand)&&(u.succeedImmediately=!0,u.succeedImmediatelyReason="URL changed"),u}case"HOVER":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.hover(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"FOCUS":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.focus(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"BLUR":{let{result:i,cache:d}=await this.wrapElementTargetingCommand({description:e.target.elementDescriptor,cache:e.cache?.target??e.target.a11yData,action:u=>this.browser.blur(u),options:{useVision:e.useVision,disableCache:o,filterByViewport:e.filterByViewport,useSelector:e.useSelector}});return d&&(e.cache={target:d}),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:i}}case"PRESS":let a=await this.browser.url();await this.browser.press(e.value);let c={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return Qe(a,c.urlAfterCommand)&&(c.succeedImmediately=!0,c.succeedImmediatelyReason="URL changed"),c;case"REQUEST":{let i=e.timeout??30,d=null,u=new AbortController,m=Object.fromEntries(Object.entries(e.headers||{}).filter(([v,T])=>v&&T)),h=new URLSearchParams;Object.entries(e.params||{}).filter(([v,T])=>v&&T).forEach(([v,T])=>{h.append(v,T)});let g;if(Yr(e.url)&&(g=e.url),Xr(e.url,this.browser.baseURL)&&(g=new URL(e.url,this.browser.baseURL).toString()),!g)throw new A("ActionFailureError",`Invalid URL: ${e.url}`);let p=async()=>{try{d=await fetch(`${g}?${h.toString()}`,{headers:m,method:e.method,body:e.body,signal:u.signal})}catch(v){this.logger.error({err:v},"Failed to make fetch request")}},S=async()=>{await new Promise(v=>setTimeout(v,i*1e3)),u.abort()};await Promise.race([S(),p()]);let y=d;if(!y)throw new A("ActionFailureError",`Fetch request timed out after ${i} seconds`);if(!y.ok)throw new A("ActionFailureError",`Fetch request failed with status ${y.status}`);let w={};y.headers.forEach((v,T)=>{w[T]=v});let b={status:y.status,headers:w};return y.headers.get("content-type")?.includes("json")?b.json=await y.json():y.headers.get("content-type")?.includes("text")&&(b.text=await y.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:b}}case"VISUAL_DIFF":{if(!e.screenshot)throw new A("ActionFailureError","Cannot execute visual diff without saved screenshot");await this.browser.removeAllHighlights();let i=await this.screenshotWithDimensions({target:e.target?.a11yData,hideCaret:!0});if(i.height!==e.screenshot.height||i.width!==e.screenshot.width)throw new A("ActionFailureError","Current screenshot does not match saved screenshot dimensions - did you change the size of the target or the viewport?");let d={data:Buffer.alloc(i.width*i.height*4),width:i.width,height:i.height},u;if(e.screenshot.data.startsWith("https://")){let p=await fetch(e.screenshot.data);u=Buffer.from(await p.arrayBuffer())}else u=Buffer.from(e.screenshot.data,"base64");let h=vl(co.decode(u).data,co.decode(i.buffer).data,d.data,i.width,i.height,{threshold:e.threshold,diffColorAlt:[0,255,0]})/(i.width*i.height)*100,g=h>e.threshold;return{fail:g,thoughts:g?`Visual diff of ${h.toFixed(2)}% detected`:void 0,beforeScreenshotOverride:i.buffer,afterScreenshotOverride:co.encode(d,75).data,succeedImmediately:!1,urlAfterCommand:await this.browser.url()}}default:return(i=>{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,t,o){return(await this.generator.getReverseMappedDescription({browserState:e.browserState,goal:`${t}`},o)).phrase}stopRecordMode(){this.recordAbortController?.abort()}async startRecordMode(e){this.recordAbortController=new AbortController;let t=new mt({signal:this.recordAbortController.signal,...e});await this.browser.startRecording(this.recordAbortController.signal,t)}};async function Ws({socket:r,generator:e,storage:t,logger:o,rootState:n,localApp:s,devicePixelRatio:a}){let c=o.child({package:"web-agent"}),l=!0;s==="iframe"&&(l=!1);let i=r.handshake.query.testId,d=r.handshake.query.baseUrl,u=r.handshake.query.viewport?JSON.parse(decodeURIComponent(r.handshake.query.viewport)):void 0;if(!i||!d)throw new Error("Socket connection request is missing testId or baseUrl");let m=await t.getOrgId(i),h=r.handshake.query.environmentName,g=h?await t.fetchEnvironment(m,h,o):void 0,p=El(),S=x=>{l&&r.emit("screenshot",{...x,url:d})},y=n?.controller;if(y)y.browser.setActiveFrame(Kt),y.browser.baseURL=d,await y.resetState({clearCookies:!0,clearStorage:!0,timeout:null}),y.setOpen(),n?.context.resetVariables(d,g?.variables);else{let x={};u&&(x.viewport=u),s==="web"&&a&&(x.deviceScaleFactor=a);let U=await _.init({baseUrl:d,logger:c,takeScreenshots:l,onScreenshot:S,contextArgs:x});y=new Re({browser:U,generator:e,config:tt,logger:c})}let w;l&&(w=Rn(r,p,o));let b=async()=>{clearInterval(w.timer)},v=new ce(d,g?.variables);r.emit("testContext",{state:v.toRedactedDisplayCopy()}),R.registerSession({controller:y,context:v,sessionId:p,cleanup:b});let T=_.USER_AGENT;return r.emit("session",{url:d,userAgent:T,viewport:await y.browser.getViewport(),localApp:s}),{sessionId:p,testId:i,orgId:m}}var js=[Un,Us,Bs,$s,Vs,In,Hs,Mn,Dn,Nn,_n,Pn,kn,Fn,On,xn,zn,Ln];var lo=r=>{let{logger:e}=r,t=new Tl(r.baseServer,{cors:{origin:"*",methods:["GET","POST"]},maxHttpBufferSize:1e7});return t.on("connection",async o=>{let n;try{n=await Ws({...r,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}`});return}js.forEach(s=>Al(s,{socket:o,metadata:n,...r}))}),t},Al=(r,e)=>{let t=r.createHandler(e),o=(...n)=>{r.event!=="cloudMouseMove"&&e.logger.debug({event:r.event,metadata:e.metadata,args:n},"Websocket event");let s=a=>{e.logger.error({event:r.event,type:"websocket",args:n,err:a instanceof Error?a:new Error(`${a}`)},"Unhandled exception in socket handler"),e.socket.emit("error",{message:a instanceof Error?a.message:`${a}`})};try{let a=t.apply(void 0,n);a&&typeof a.catch=="function"&&a.catch(s)}catch(a){s(a)}};e.socket.on(r.event,o)};import{z as xl}from"zod";import Rl from"fetch-retry";var Il=Rl(global.fetch),ie=class{static API_VERSION="v1";baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async sendRequest(e,t){let o=await Il(`${this.baseURL}${e}`,{retries:1,retryDelay:1e3,method:"POST",body:JSON.stringify(t),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 rt=class extends ie{constructor(e){super(e)}async getScreenshotFromS3(e){let t=await this.sendRequest(`/${ie.API_VERSION}/s3/visual-diff-screenshot`,{url:e});return xl.string().parse(t)}async getElementLocation(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:t});return Br.parse(o)}async getElementLocationWithVision(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:t});return Br.parse(o)}async getAssertionResult(e,t,o){if(t){let s=await this.sendRequest(`/${ie.API_VERSION}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:o,vision:!0});return $r.parse(s)}let n=await this.sendRequest(`/${ie.API_VERSION}/web-agent/assertion`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:o,vision:!1});return $r.parse(n)}async getProposedCommand(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/next-command`,{url:e.url,browserState:e.browserState,goal:e.goal,history:e.history,numPrevious:e.numPrevious,lastCommand:e.lastCommand,disableCache:t});return mn.parse(o)}async getGranularGoals(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:t});return pn.parse(o)}async getReverseMappedDescription(e,t){let o=await this.sendRequest(`/${ie.API_VERSION}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:t});return hn.parse(o)}async getTextExtraction(e,t){let o={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:t},n=await this.sendRequest(`/${ie.API_VERSION}/web-agent/text-extraction`,o);return Cr.parse(n)}};import{existsSync as Ol,readFileSync as Ll,writeFileSync as uo}from"fs";import mo from"path";import{parse as Ml,stringify as Nl}from"yaml";var X=process.env.MOMENTIC_DIR??Me;function Gs(r,e){let t=an(r);for(let[s,a]of Object.entries(t.modules)){let c=qe(s);uo(mo.join(X,Ne,`${c}.yaml`),a,"utf-8")}let o=qe(e),n=mo.join(X,`${o}.yaml`);return uo(n,t.test,"utf-8"),`${o}.yaml`}function Ks(r,e){if(!Ol(e))throw new Error(`Test not found at path: ${mo}`);let t=Ll(e,"utf-8"),n={...Ml(t),...r},s=Nl(n);uo(e,s,"utf-8")}import{readFileSync as cr,readdirSync as qs,writeFileSync as Ys}from"fs";import{join as po}from"path";import{v4 as Pl}from"uuid";import{parse as lr,stringify as Xs}from"yaml";var ot=po(X,Ne);function Js(r){let e=tr(ot,r.moduleId).path,t=cr(e,"utf-8"),n={...lr(t),...r},s=Xs(n);Ys(e,s,"utf-8")}function Qs(r,e){let t=qe(r),o=po(ot,`${t}.yaml`),n=Pl(),{stepsToSave:s}=ee({steps:e,moduleId:n,testId:"",orgId:""}),a={fileType:"momentic/module",schemaVersion:j,moduleId:n,name:r,steps:s},c=Xs(a);return Ys(o,c,"utf-8"),a}function Zs(){let r=[];for(let e in qs(ot)){if(!e.endsWith(".yaml"))continue;let t=cr(e,"utf-8"),o=lr(t),n={name:o.name,moduleId:o.moduleId,numSteps:o.steps.length};r.push(n)}return r}function ei(){let r=[];for(let e of qs(ot)){if(!e.endsWith(".yaml"))continue;let t=po(ot,e),o=cr(t,"utf-8"),n=lr(o);try{let s=Et.parse(n);r.push(s)}catch(s){B.warn({err:s},"Error parsing module, skipping...")}}return r}function ti(r){let e=tr(Ne,r).path,t=cr(e,"utf-8"),o=lr(t);return Et.parse(o)}import{Router as zl}from"express";import{existsSync as Dl,readFileSync as _l,readdirSync as kl}from"fs";import{join as ri}from"path";import{parse as Fl}from"yaml";var ho=ri(X,vt);function oi(){let r=[];if(!Dl(ho))return[];for(let e of kl(ho)){if(!e.endsWith(".yaml"))continue;let t=ri(ho,e),o=_l(t,"utf-8"),n=Fl(o);try{let s=ne.parse(n);r.push(s)}catch(s){B.warn({err:s},"Error parsing environment, skipping...")}}return r}function de(r){return function(...e){let t=e[e.length-1],o=r(...e);Promise.resolve(o).catch(t)}}var ni=zl();ni.get("/",de((r,e)=>{let t=oi();e.status(200).json(t)}));var si=ni;import{Router as Ul}from"express";var xt=Ul();xt.get("/",de((r,e)=>{let t=ei();e.status(200).json(t)}));xt.get("/metadata",de((r,e)=>{let t=Zs();e.status(200).json(t)}));xt.post("/",de(async(r,e)=>{let t;try{t=dn.parse(r.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}try{Ct(t.name)}catch(n){e.status(400).json({error:`Invalid module name: ${n}`});return}let o=Qs(t.name,t.steps);e.status(201).json(o)}));xt.get("/:moduleId",de((r,e)=>{if(!r.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let t=ti(r.params.moduleId);e.json(t)}));var ii=xt;import{Router as jl}from"express";import{existsSync as Gl}from"fs";import{join as mi}from"path";import{v4 as Kl}from"uuid";var fo="https://api.momentic.ai",ai=r=>{fo=r},Ot=()=>fo,Hl,ci=r=>{Hl=r};var dr,go,li=async r=>{if(dr)return;let e=new K({baseURL:fo,apiKey:r});try{dr=await e.getOrgId(),go=r}catch(t){throw new Error(`Error checking API key against server: ${t}`)}},ur=()=>{if(!dr)throw new Error("Your organization ID appears invalid. Please contact Momentic Support.");return dr},di=()=>{if(!go)throw new Error("Your API key appears invalid. Please contact Momentic Support.");return go};import ui,{multistream as $l}from"pino";import Bl from"pino-pretty";var yo=new Map,Vl=process.env.NODE_ENV==="production",So=class r{consoleLogger;ddClientToken;hostname;bindingAttributes;site="https://http-intake.logs.us5.datadoghq.com/api/v2/logs";constructor({bindings:e,clientToken:t,hostname:o}){this.ddClientToken=t,this.hostname=o,this.bindingAttributes={...e,env:process.env.NODE_ENV};let n={base:this.bindingAttributes,errorKey:"err",level:"debug"};this.consoleLogger=Vl?ui(n):ui(n,$l([{stream:Bl({colorize:!0})}]))}child(e){return new r({clientToken:this.ddClientToken,bindings:{...this.bindingAttributes,...e},hostname:this.hostname})}flush(e){this.consoleLogger.flush(e)}log(e,t,o,...n){t&&o===void 0&&(o=`${t}`,t={}),this.consoleLogger[e](t,o,...n);let s=Object.assign({},this.bindingAttributes,t&&typeof t=="object"?t:{});n.length>0&&(s.args=n),(async()=>{try{let a=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(!a.ok)throw new Error(`Failed to log to Datadog: ${a.statusText})}`)}catch(a){this.consoleLogger.warn({obj:t,msg:o,args:n,err:a},"Failed to log to Datadog")}})()}debug(e,t,...o){this.log("debug",e,t,...o)}info(e,t,...o){this.log("info",e,t,...o)}warn(e,t,...o){this.log("warn",e,t,...o)}error(e,t,...o){this.log("error",e,t,...o)}bindings(){return this.bindingAttributes}setMinLevel(e){this.consoleLogger.level=e}},mr=({app:r,clientToken:e,hostname:t})=>{if(!process.env.DD_CLIENT_TOKEN&&!process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN&&!e)throw new Error("Missing DD_CLIENT_TOKEN");return yo.has(r)||yo.set(r,new So({bindings:{app:r},hostname:t,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN})),yo.get(r)};import{hostname as Wl}from"os";var ue=mr({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:Wl()});var Lt=jl();Lt.get("/",de((r,e)=>{let t=At(X,ue);e.status(200).json(t)}));Lt.post("/",de((r,e)=>{let t;try{t=ln.parse(r.body)}catch(c){e.status(400).json({error:`Invalid request body: ${c}`});return}try{Ct(t.name)}catch(c){e.status(400).json({error:c.message});return}let n={id:Kl(),name:t.name,baseUrl:t.baseUrl,schemaVersion:j,advanced:{disableAICaching:!1,viewport:t.viewport??We},retries:0,steps:[]};t.environment&&(n.envs=[{name:t.environment,defaultOnLocal:!0}]);let s=Gs(n,t.name),a={...n,testPath:s};e.status(201).json(a)}));Lt.get("/:testPath",de(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t=mi(X,r.params.testPath);if(!Gl(t)){e.status(400).json({error:`Test not found at path: ${t}`});return}let o=await rr(t,ot);e.status(200).json(o)}));Lt.patch("/:testPath",de(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t;try{t=cn.parse(r.body)}catch(i){e.status(400).json({error:`Invalid request body: ${i}`});return}let o=mi(X,r.params.testPath),n;try{n=Jn(o)}catch(i){e.status(400).json({error:`Existing test file on disk is invalid: ${i}`});return}let{stepsToSave:s,moduleUpdates:a,cachesToSave:c}=ee({steps:t.steps,testId:n.id,orgId:ur()});a.forEach(i=>Js(i)),Ks({steps:s},o),await new K({apiKey:di(),baseURL:Ot()}).updateStepCaches({entries:c,testId:n.id}),e.status(201).json({message:"ok"})}));var pi=Lt;async function hi({momenticServerUrl:r,apiKey:e,mode:t,serverPort:o,appPort:n,staticDir:s,assetsDir:a,devicePixelRatio:c}){if(!Xl(X)||!Jl(X).isDirectory()){let p=ed.isAbsolute(X);throw new Error(`Root folder ${X} does not exist${p?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}r&&ai(r),B.info("Checking API key"),await li(e);let i=`http://localhost:${o}`;n&&(i=`http://localhost:${n}`);let d=rd(s);await new Promise(p=>{d.listen(o,()=>{B.info(`Server is running at http://localhost:${o}`),p()})});let m=new rt({baseURL:Ot(),apiKey:e}),h=new ut(new K({baseURL:Ot(),apiKey:e}),ur());if(t==="web"){lo({localApp:"web",baseServer:d,generator:m,storage:h,logger:ue,devicePixelRatio:c}),await Zl(i);return}let g=await od({appUrl:i,apiGenerator:m,extensionPath:td(a,"extension"),onClose:async()=>{B.info("Browser closed, closing app."),await g.browser.cleanup(),d.close(()=>{process.exit(0)})}});lo({localApp:"iframe",baseServer:d,generator:m,storage:h,logger:ue,rootState:{controller:g,context:new ce(""),executionCancelled:!1}}),process.on("uncaughtException",p=>{ue.error({err:p},"Uncaught exception in desktop-server")}),process.on("unhandledRejection",p=>{ue.error({err:p},"Unhandled rejection in desktop-server")})}function rd(r){let e=wo();e.use(Yl()),e.use(ql.json({limit:"50mb"}));let t=wo.Router();if(t.use("/tests",pi),t.use("/modules",ii),t.use("/environments",si),e.use("/api",t),e.use((n,s,a)=>{n.path!=="/healthcheck"&&ue.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&&ue.error({url:n.url,method:n.method,statusCode:s.statusCode},"Request completed in error")}),a()}),e.use((n,s,a,c)=>{n instanceof Error&&n.message.includes("BadRequestError: request aborted")||(ue.error({stack:n.stack,msg:n.message,url:s.url,method:s.method},"Unhandled exception leading to 500 on desktop-server"),a.status(500).send("Internal Server Error"),c(n))}),r){let n=wo.static(r);e.use("/",n),e.use("*",n)}return Ql.createServer(e)}async function od({appUrl:r,apiGenerator:e,extensionPath:t,onClose:o}){let n=await _.init({baseUrl:r,logger:ue,browserArgs:{headless:!1,handleSIGTERM:!0},contextArgs:{bypassCSP:!0,viewport:null},waitForLoad:!0,localMode:!0,localAppUrl:r,extensionPath:t,skipPageSetup:!0,timeout:null,onClose:o}),s=new Re({browser:n,config:tt,generator:e,logger:ue});return ci(s),s}import{existsSync as zd}from"fs";import{platform as Ud}from"os";import Vi,{dirname as Hd}from"path";import{fileURLToPath as $d}from"url";var gi="0.0.64";import{hostname as sd}from"os";var C=mr({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:sd()});import{createHash as wi}from"crypto";import{existsSync as bi,mkdirSync as md,readFileSync as To,writeFileSync as vi}from"fs";import{join as Ci}from"path";import{parse as pd}from"yaml";import{existsSync as bo,mkdirSync as cd,readdirSync as yi,statSync as vo}from"fs";import{homedir as Si}from"os";import{join as Mt,resolve as ld}from"path";import id from"chalk";import ad from"readline/promises";async function he(r,e){if(process.env.CI)return!0;C.flush(),await new Promise(s=>setTimeout(s,500));let t=ad.createInterface({input:process.stdin,output:process.stdout});r=`${r} (y/N) `;let o=e?id.bold.yellow(r):r,n=await t.question(o);return t.close(),n.toLowerCase()==="y"}var Co=Me,nt=Mt(Me,Ne),Eo=Mt(Me,Nr),ve=Mt(Me,vt),dd=Mt(Si(),"momentic",Pr),ud=[dd,nt,Eo,ve];function fi(r){try{return bo(r)&&vo(r).isDirectory()}catch(e){return C.error({err:e},`Error reading path ${r} during directory exists check`),!1}}async function ke(r,e=!1){let t=await r.getOrgId();Si()||(C.error("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)),fi(Co)||!e&&!await he(`A '${Me}' folder was not found in the current directory. Proceed with creation here? You will need to invoke the Momentic CLI from this directory in the future.`)&&(C.error("Setup cancelled"),process.exit(1));for(let o of ud)fi(o)||(!e&&!await he(`'${o}' folder was not found in the current directory. Create it now?`)&&(C.error("Setup cancelled"),process.exit(1)),cd(o,{recursive:!0}));return yi(ve).length===0&&(!e&&!await he("You do not appear to have any environments configured locally. Pull environments from Momentic Cloud?")&&(C.error("Setup cancelled"),process.exit(1)),await pr({client:r,all:!0,skipPrompts:e})),C.info("CLI initialization complete"),t}function Nt(r,e,t=new Set){for(let o of r){let n=ld(o),s=!1;try{s=bo(n)&&vo(n).isDirectory()}catch(a){C.error({err:a},`Error reading path ${n} during collect paths`)}if(n&&s){let a=yi(n).map(c=>Mt(n,c));Nt(a,e,t);continue}if(n.endsWith(".yaml")){try{if(!bo(n)||!vo(n).isFile()){C.error(`File not found or unreadable: ${n}`);continue}}catch(a){C.error({err:a},`Error reading file ${n} during collect paths`);continue}if(!e(n))continue;t.add(n)}}return t}async function pr({envNames:r,client:e,all:t,skipPrompts:o}){let n=await e.getAllEnvironments();r&&!t&&(n=n.filter(c=>r?.includes(c.name))),bi(ve)||md(ve);let s=0,a=0;for(let c of n){let l=Ci(ve,`${c.name}.yaml`);if(!bi(l))vi(l,kr(c)),s++;else{let i=wi("sha256").update(To(l)).digest("hex"),d=kr(c),u=wi("sha256").update(d).digest("hex");if(i!==u){if(!o&&!await he(`Environment ${c.name} already exists on disk but needs to be updated. Overwrite?`)){C.error("Pull cancelled");return}vi(l,d),a++}}}C.info(`Pulled ${s} new environments and updated ${a} existing environments`)}function hd(r){return r.endsWith(".yaml")?To(r,"utf8").includes("momentic/environment")?!0:(C.warn(`Skipping YAML that is not a Momentic environment: ${r}`),!1):!1}async function Ei({client:r,names:e,all:t,yes:o}){let n;t?n=[ve]:n=e.map(c=>Ci(ve,`${c.toLowerCase()}.yaml`));let s=Nt(n,hd);C.info(`Found ${s.size} environments(s) to push:`),s.forEach(c=>C.info(` - ${c}`)),C.info("Loading file contents");let a=[];for(let c of s){let l=pd(To(c,"utf-8"));try{let i=ne.parse(l);a.push(i)}catch(i){C.error({err:i},`${c} failed to parse as a valid environment file`),process.exit(1)}}if(!o&&!await he("Pushing environments can overwrite variables on cloud and instantly affect scheduled runs. Continue?",!0)){C.error("Push cancelled");return}C.info(`Pushing ${s.size} environment(s)`),await r.updateEnvironments(a),C.info("Push successful!")}import{registry as Ao}from"playwright-core/lib/server";function gd(r){let e=[],t=[];for(let o of r){let n=Ao.findExecutable(o);!n||n.installType==="none"?e.push(o):t.push(n)}return t}async function Ti(){let r=gd(["chromium"]);await Ao.installDeps(r,!1),await Ao.install(r,!1)}import{Argument as hr,Option as Fe}from"commander";var ze=new Fe("--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),Ue=new Fe("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),He=new Fe("-y, --yes","Skip confirmation prompts.").env("CI").default(!0),Ai=new Fe("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag."),Ri=new Fe("--pixel-ratio <number>","Device pixel ratio of your screen or monitor. Mac OS Retina displays and machines marketed as 'HiDPI' should usually set this to 2.").default(1).argParser(r=>parseInt(r,10)),Ii=new Fe("--env <env>","Name of the environment to use when running tests."),Ro=new Fe("-a, --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments."),Io=new Fe("-a, --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments."),xi=new hr("<tests...>",`One or more test paths to pull from Momentic Cloud.
|
|
101
101
|
|
|
102
102
|
A test path is a lowercased version of your test name where spaces are replaced with underscores: 'npx momentic pull hello-world'.`).argOptional(),Oi=new hr("<tests...>",`One or more test identifiers.
|
|
103
103
|
|
|
104
104
|
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'.
|
|
105
105
|
|
|
106
106
|
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(),Li=new hr("<paths...>","File paths pointing to one or more Momentic YAML files. Paths to directories are supported as well."),xo=new hr("<envs...>","One or more environment names to push").argOptional();import{createHash as Mi}from"crypto";import{existsSync as yd,writeFileSync as Ni}from"fs";import{join as Pi}from"path";async function ki({testsToFetch:r,client:e,all:t}){t&&C.info({url:e.baseURL},"Fetching all tests from the server. This may take a while...");let{tests:o,modules:n}=await e.getTestYAMLExport({paths:r,all:t}),s=Object.keys(o).length;for(let[c,l]of Object.entries(o)){let i=Pi(Co,_i(c));if(!await Di(i)){C.error("Pull cancelled");return}Ni(i,l,"utf-8"),C.info({checksum:Mi("md5").update(l).digest("hex")},`Wrote '${i}'`)}let a=Object.keys(n).length;for(let[c,l]of Object.entries(n)){let i=Pi(nt,_i(c));if(!await Di(i)){C.error("Pull cancelled");return}Ni(i,l,"utf-8"),C.info({checksum:Mi("md5").update(l).digest("hex")},`Wrote '${i}'`)}s===0?C.info("Pulled zero tests! For onprem deployments, please ensure the --server parameter is set correctly."):C.info(`Pulled ${s} test${s>1?"s":""}${a?` and ${a} module${a>1?"s":""}`:""}!`)}async function Di(r){return yd(r)?he(`File '${r.replace(/(\s+)/g,"\\$1")}' already exists. Overwrite existing content?`,!0):!0}function _i(r){return`${qe(r)}.yaml`}import{existsSync as VC,readFileSync as Sd}from"fs";async function Fi(r){let e=Nt(r.paths,wd);C.info(`Found ${e.size} test(s) to push:`),e.forEach(n=>C.info(` - ${n}`)),C.info("Loading file contents and resolving dependent modules");let t=Array.from(e).map(n=>qr(n,nt)),o=new Set(t.flatMap(n=>Object.keys(n.modules)));if(C.info(`Resolved ${e.size} test(s) and ${o.size} module(s)`),!r.yes&&!await he("Pushing tests overwrites tests on production and will instantly affect scheduled runs. Continue?",!0)){C.error("Push cancelled");return}await r.client.updateTestWithYAML(t),C.info("Update successful!")}function wd(r){return r.endsWith(".yaml")?Sd(r,"utf8").includes("momentic/test")?!0:(C.warn(`Skipping YAML that is not a Momentic test: ${r}`),!1):(C.warn(`Skipping file that does not have YAML extension: ${r}`),!1)}import{existsSync as Hi,readFileSync as Nd,statSync as Pd}from"fs";import{join as Dd}from"path";import _d from"wait-on";import{parse as kd}from"yaml";import bd from"@actions/exec";import vd from"@actions/io";import Cd from"quote";import Ed from"string-argv";async function zi(r,e=!0){let t=Ed(r),o=await vd.which(t[0],!0),n=t.slice(1),s=bd.exec(Cd(o),n,{delay:100});if(e)return s}import{diff as Od}from"deep-object-diff";import{cloneDeep as Ld}from"lodash-es";import{v4 as Md}from"uuid";import{existsSync as Td,readFileSync as Ad}from"fs";import{join as Rd}from"path";import{parse as Id}from"yaml";function xd(r){let e=Rd(Eo,`${r}.yaml`);if(!Td(e))throw new Error(`Fixture '${r}' does not exist at expected path ${e}`);let t;try{t=Id(Ad(e,"utf-8").replace(/\r\n|\r/g,`
|
|
107
|
-
`))}catch(n){throw new Error(`Fixture at path ${e} does not parse as valid YAML: ${n}`)}let o;try{o=tn.parse(t)}catch(n){throw new Error(`Fixture at path ${e} does not conform to expected schema: ${n}`)}return o}async function Oo(r,e){let t=xd(r),o=e==="setup"?t.setup:t.teardown;if(!o){C.warn(`Fixture '${r}' does not have any steps in the ${e} phase, skipping...`);return}let n=o.timeout,s=async function(){for(let i=0;i<o.steps.length;i++){let d=o.steps[i],u=d.run,m=async h=>{try{await Qn(`set -ex; ${u}`)}catch(g){if(h){let p=`Fixture '${r}' ${e} step ${i+1} errored in the background. The test will continue, but this may affect test execution and results: ${g}`;C.error(p)}else throw new Error(`Fixture '${r}' ${e} step ${i+1} errored: ${g}`)}};d.waitForCompletion===!1?m(!0):await m(!1)}};if(C.info(`Running ${e} phase of fixture '${r}'`),!n){await s();return}let a,c,l=async function(){return new Promise((i,d)=>{c=i,a=setTimeout(()=>{c=void 0,d(`Fixture '${r}' ${e} phase timed out after ${n} seconds`)},n*1e3)})};try{await Promise.race([s(),l()])}catch(i){throw i}finally{c&&c(),clearTimeout(a)}}async function Ui({path:r,apiClient:e,generator:t,orgId:o,useLocalFiles:n,noReport:s,environment:a,newBaseUrl:c}){let l=new ut(e,o),i;n?(C.info(`Reading ${r} from local filesystem`),i=await rr(r,nt)):(C.info(`Fetching ${r} from Momentic Cloud server (${e.baseURL})`),i=await e.getTest(r)),i.schemaVersion>j&&C.warn(`Test ${r} has schema version ${i.schemaVersion}, which is greater than what is currently supported by this SDK. Please update your momentic package version to avoid unexpected behavior.`);let d=[],u={};a&&(d=i.envs?.find(T=>T.name===a.name)?.fixtures??[],u=a.variables);for(let v of d)await Oo(v,"setup");let m;if(i.baseUrl){if(m=i.baseUrl,c){let v=new URL(i.baseUrl),T=new URL(c);v.hostname=T.hostname,v.protocol=T.protocol,v.port=T.port,m=v.toString()}}else m=u[Oe],m||(C.error(`Test ${i.name} has no baseURL and the environment has no ${Oe} variable either`),process.exit(1));let h=await _.init({baseUrl:m,logger:C,waitForLoad:!0,contextArgs:{viewport:i.advanced.viewport??We}}),g=new Re({browser:h,generator:t,config:tt,logger:C}),p=new ce(m,u),S,y;if(s)y=Md();else try{S=await e.createRun({testId:i.id,testName:i.name,trigger:"CLI"}),y=S.id}catch(v){throw C.error(v),new Error("Failed to create run object on server. Please ensure the Momentic Cloud Server is accessible from your environment or pass the --no-report flag.")}await l.resolveStepCacheEntries({schemaVersion:i.schemaVersion,organizationId:o,steps:i.steps,testId:i.id,logger:C});let w="FAILED",b=Ld(i.steps);try{w=await Ds({test:i,context:p,runId:y,controller:g,logger:C,takeScreenshots:!0,onSaveScreenshot:async v=>{if(s)return"";let{key:T}=await e.uploadScreenshot({screenshot:v.toString("base64")});return T},onUpdateRun:async v=>{if(!s)try{await e.updateRun(y,v)}catch(T){C.warn({err:T,runId:y},"Failed to update run data. You may see stale data on the Momentic Cloud Server.")}},onTestSuccess:async v=>{let T=Od(b,v.steps);if(Object.keys(T).length===0){C.debug("No changes to test steps after success");return}C.debug({changes:T},"Updating steps post-run success in worker");try{let{cachesToSave:x}=ee({steps:v.steps,testId:v.id,orgId:o});await e.updateStepCaches({testId:v.id,entries:x})}catch(x){C.error({err:x,testId:v.id},"Failed to save step caches after successful execution. This is not critical, but can impact future performance.")}}})}catch(v){throw s||await e.updateRun(y,{status:"FAILED",finishedAt:new Date}),s||C.error(`Test '${i.name}' failed. You can view a playback of this run at https://app.momentic.ai/runs/${y}`),v}finally{try{for(let v of d)await Oo(v,"teardown")}catch(v){C.error(`Failed to run teardown fixtures: ${v}`)}}return w}async function $i({tests:r,start:e,waitOn:t,waitOnTimeout:o,client:n,all:s,report:a,env:c,orgId:l,parallel:i=1}){e&&(C.info(`Running start command: ${e}`),await zi(e,!1)),t&&(C.info(`Waiting for ${t} to be accessible (timeout: ${o}s)`),await _d({resources:[t],timeout:o*1e3}));let d;if(c){let b=Dd(ve,`${c}.yaml`),v=kd(Nd(b,"utf-8"));try{d=ne.parse(v),C.info(`Running in the ${c} environment with the following variables:`),C.info(JSON.stringify(d.variables))}catch(T){C.error({err:T},`${b} failed to parse as a valid environment file`),process.exit(1)}}let u=new rt({baseURL:n.baseURL,apiKey:n.apiKey}),m=!1;(r.some(b=>b.endsWith(".yaml"))||r.some(b=>Hi(b)))&&(m=!0);let h=new Set;if(m)C.info(r,"Reading tests from the following local file paths:"),r.forEach(b=>{if(!Hi(b))throw new Error(`Path '${b}' does not exist.`);let v,T;try{v=Pd(b),T=v.isDirectory()}catch(x){C.error({err:x},`Skipping path ${b} because it cannot be read`);return}if(T)At(b,C).map(x=>x.fullFilePath).forEach(x=>h.add(x));else if(b.endsWith(".yaml"))h.add(b);else throw new Error(`Path '${b}' is not a directory or a .yaml file.`)});else if(C.warn("The paths you specified are not files or directories that exist locally."),C.warn(`Fetching tests from Momentic Cloud (${n.baseURL}) instead...`),s)for(let b of await n.getAllTestIds())h.add(b);else h=new Set(r);let g=Array.from(h);C.info(g,`Running ${g.length} tests using local machine as worker:`);let p=[];for(let b=0;b<g.length;b+=i){let v=await Promise.all(g.slice(b,b+i).map(async T=>{let x="FAILED";try{x=await Ui({useLocalFiles:m,path:T,orgId:l,apiClient:n,generator:u,newBaseUrl:t,environment:d,noReport:!a})}catch(U){let ge=U instanceof Error?U.message:`${U}`;C.error(`Test ${T} failed with error: ${ge}`)}return{runStatus:x,path:T}}));p=p.concat(v)}let S=p.filter(b=>b.runStatus==="PASSED");S.length>0&&(C.info(`Passed ${S.length} out of ${p.length} tests:`),S.forEach(b=>{C.info(`- ${b.path}`)}));let y=p.filter(b=>b.runStatus==="CANCELLED");y.length>0&&(C.warn(`Cancelled ${y.length} out of ${p.length} tests:`),y.forEach(b=>{C.warn(`- ${b.path}`)}));let w=p.filter(b=>b.runStatus==="FAILED");w.length>0&&(C.error(`Failed ${w.length} out of ${p.length} tests:`),w.forEach(b=>{C.error(`- ${b.path}`)}),process.exit(1)),process.exit(0)}async function Bi({tests:r,client:e,env:t,all:o}){let{queuedTests:n}=await e.queueTests({testPaths:r,env:t,all:o});C.info(`Successfully queued ${n.length} tests!`)}var Ce=new Fd;Ce.name("momentic").description("Momentic CLI").version(gi);Ce.command("install-browsers").action(async()=>{await Ti()});Ce.addOption(new Ie("--log-level <level>").choices(["debug","info","warn","error"]).default("info")).on("option:log-level",r=>{C.setMinLevel(r.toLowerCase())});Ce.command("init").addOption(He).addOption(ze).addOption(Ue).action(async r=>{let{yes:e,apiKey:t,server:o}=r,n=new K({baseURL:o,apiKey:t});await ke(n,e)});Ce.command("app").addOption(ze).addOption(Ue).addOption(He).addOption(Ri).addOption(new Ie("--port <port>","Port to run the app on. Defaults to 58888.").default(58888)).addOption(new Ie("--mode <mode>","Mode to run the app in. Defaults to
|
|
107
|
+
`))}catch(n){throw new Error(`Fixture at path ${e} does not parse as valid YAML: ${n}`)}let o;try{o=tn.parse(t)}catch(n){throw new Error(`Fixture at path ${e} does not conform to expected schema: ${n}`)}return o}async function Oo(r,e){let t=xd(r),o=e==="setup"?t.setup:t.teardown;if(!o){C.warn(`Fixture '${r}' does not have any steps in the ${e} phase, skipping...`);return}let n=o.timeout,s=async function(){for(let i=0;i<o.steps.length;i++){let d=o.steps[i],u=d.run,m=async h=>{try{await Qn(`set -ex; ${u}`)}catch(g){if(h){let p=`Fixture '${r}' ${e} step ${i+1} errored in the background. The test will continue, but this may affect test execution and results: ${g}`;C.error(p)}else throw new Error(`Fixture '${r}' ${e} step ${i+1} errored: ${g}`)}};d.waitForCompletion===!1?m(!0):await m(!1)}};if(C.info(`Running ${e} phase of fixture '${r}'`),!n){await s();return}let a,c,l=async function(){return new Promise((i,d)=>{c=i,a=setTimeout(()=>{c=void 0,d(`Fixture '${r}' ${e} phase timed out after ${n} seconds`)},n*1e3)})};try{await Promise.race([s(),l()])}catch(i){throw i}finally{c&&c(),clearTimeout(a)}}async function Ui({path:r,apiClient:e,generator:t,orgId:o,useLocalFiles:n,noReport:s,environment:a,newBaseUrl:c}){let l=new ut(e,o),i;n?(C.info(`Reading ${r} from local filesystem`),i=await rr(r,nt)):(C.info(`Fetching ${r} from Momentic Cloud server (${e.baseURL})`),i=await e.getTest(r)),i.schemaVersion>j&&C.warn(`Test ${r} has schema version ${i.schemaVersion}, which is greater than what is currently supported by this SDK. Please update your momentic package version to avoid unexpected behavior.`);let d=[],u={};a&&(d=i.envs?.find(T=>T.name===a.name)?.fixtures??[],u=a.variables);for(let v of d)await Oo(v,"setup");let m;if(i.baseUrl){if(m=i.baseUrl,c){let v=new URL(i.baseUrl),T=new URL(c);v.hostname=T.hostname,v.protocol=T.protocol,v.port=T.port,m=v.toString()}}else m=u[Oe],m||(C.error(`Test ${i.name} has no baseURL and the environment has no ${Oe} variable either`),process.exit(1));let h=await _.init({baseUrl:m,logger:C,waitForLoad:!0,contextArgs:{viewport:i.advanced.viewport??We}}),g=new Re({browser:h,generator:t,config:tt,logger:C}),p=new ce(m,u),S,y;if(s)y=Md();else try{S=await e.createRun({testId:i.id,testName:i.name,trigger:"CLI"}),y=S.id}catch(v){throw C.error(v),new Error("Failed to create run object on server. Please ensure the Momentic Cloud Server is accessible from your environment or pass the --no-report flag.")}await l.resolveStepCacheEntries({schemaVersion:i.schemaVersion,organizationId:o,steps:i.steps,testId:i.id,logger:C});let w="FAILED",b=Ld(i.steps);try{w=await Ds({test:i,context:p,runId:y,controller:g,logger:C,takeScreenshots:!0,onSaveScreenshot:async v=>{if(s)return"";let{key:T}=await e.uploadScreenshot({screenshot:v.toString("base64")});return T},onUpdateRun:async v=>{if(!s)try{await e.updateRun(y,v)}catch(T){C.warn({err:T,runId:y},"Failed to update run data. You may see stale data on the Momentic Cloud Server.")}},onTestSuccess:async v=>{let T=Od(b,v.steps);if(Object.keys(T).length===0){C.debug("No changes to test steps after success");return}C.debug({changes:T},"Updating steps post-run success in worker");try{let{cachesToSave:x}=ee({steps:v.steps,testId:v.id,orgId:o});await e.updateStepCaches({testId:v.id,entries:x})}catch(x){C.error({err:x,testId:v.id},"Failed to save step caches after successful execution. This is not critical, but can impact future performance.")}}})}catch(v){throw s||await e.updateRun(y,{status:"FAILED",finishedAt:new Date}),s||C.error(`Test '${i.name}' failed. You can view a playback of this run at https://app.momentic.ai/runs/${y}`),v}finally{try{for(let v of d)await Oo(v,"teardown")}catch(v){C.error(`Failed to run teardown fixtures: ${v}`)}}return w}async function $i({tests:r,start:e,waitOn:t,waitOnTimeout:o,client:n,all:s,report:a,env:c,orgId:l,parallel:i=1}){e&&(C.info(`Running start command: ${e}`),await zi(e,!1)),t&&(C.info(`Waiting for ${t} to be accessible (timeout: ${o}s)`),await _d({resources:[t],timeout:o*1e3}));let d;if(c){let b=Dd(ve,`${c}.yaml`),v=kd(Nd(b,"utf-8"));try{d=ne.parse(v),C.info(`Running in the ${c} environment with the following variables:`),C.info(JSON.stringify(d.variables))}catch(T){C.error({err:T},`${b} failed to parse as a valid environment file`),process.exit(1)}}let u=new rt({baseURL:n.baseURL,apiKey:n.apiKey}),m=!1;(r.some(b=>b.endsWith(".yaml"))||r.some(b=>Hi(b)))&&(m=!0);let h=new Set;if(m)C.info(r,"Reading tests from the following local file paths:"),r.forEach(b=>{if(!Hi(b))throw new Error(`Path '${b}' does not exist.`);let v,T;try{v=Pd(b),T=v.isDirectory()}catch(x){C.error({err:x},`Skipping path ${b} because it cannot be read`);return}if(T)At(b,C).map(x=>x.fullFilePath).forEach(x=>h.add(x));else if(b.endsWith(".yaml"))h.add(b);else throw new Error(`Path '${b}' is not a directory or a .yaml file.`)});else if(C.warn("The paths you specified are not files or directories that exist locally."),C.warn(`Fetching tests from Momentic Cloud (${n.baseURL}) instead...`),s)for(let b of await n.getAllTestIds())h.add(b);else h=new Set(r);let g=Array.from(h);C.info(g,`Running ${g.length} tests using local machine as worker:`);let p=[];for(let b=0;b<g.length;b+=i){let v=await Promise.all(g.slice(b,b+i).map(async T=>{let x="FAILED";try{x=await Ui({useLocalFiles:m,path:T,orgId:l,apiClient:n,generator:u,newBaseUrl:t,environment:d,noReport:!a})}catch(U){let ge=U instanceof Error?U.message:`${U}`;C.error(`Test ${T} failed with error: ${ge}`)}return{runStatus:x,path:T}}));p=p.concat(v)}let S=p.filter(b=>b.runStatus==="PASSED");S.length>0&&(C.info(`Passed ${S.length} out of ${p.length} tests:`),S.forEach(b=>{C.info(`- ${b.path}`)}));let y=p.filter(b=>b.runStatus==="CANCELLED");y.length>0&&(C.warn(`Cancelled ${y.length} out of ${p.length} tests:`),y.forEach(b=>{C.warn(`- ${b.path}`)}));let w=p.filter(b=>b.runStatus==="FAILED");w.length>0&&(C.error(`Failed ${w.length} out of ${p.length} tests:`),w.forEach(b=>{C.error(`- ${b.path}`)}),process.exit(1)),process.exit(0)}async function Bi({tests:r,client:e,env:t,all:o}){let{queuedTests:n}=await e.queueTests({testPaths:r,env:t,all:o});C.info(`Successfully queued ${n.length} tests!`)}var Ce=new Fd;Ce.name("momentic").description("Momentic CLI").version(gi);Ce.command("install-browsers").action(async()=>{await Ti()});Ce.addOption(new Ie("--log-level <level>").choices(["debug","info","warn","error"]).default("info")).on("option:log-level",r=>{C.setMinLevel(r.toLowerCase())});Ce.command("init").addOption(He).addOption(ze).addOption(Ue).action(async r=>{let{yes:e,apiKey:t,server:o}=r,n=new K({baseURL:o,apiKey:t});await ke(n,e)});Ce.command("app").addOption(ze).addOption(Ue).addOption(He).addOption(Ri).addOption(new Ie("--port <port>","Port to run the app on. Defaults to 58888.").default(58888)).addOption(new Ie("--mode <mode>","Mode to run the app in. Defaults to web.").choices(["iframe","web"]).default("web")).action(async r=>{let{apiKey:e,port:t,yes:o,server:n,mode:s,pixelRatio:a}=r,c=new K({baseURL:n,apiKey:e});await ke(c,o);let l=$d(import.meta.url),i=Hd(l),d=Vi.resolve(i,"..","static"),u=Vi.resolve(i,"..","assets");if(s==="web"){if(isNaN(a))throw new Error(`Invalid option passed to --pixel-ratio: ${a}`);B.info(`Using a device pixel ratio of ${a}.`),a===1&&(Ud()==="darwin"&&Wi("system_profiler SPDisplaysDataType").toString().includes("Retina")?B.warn(Lo`If you are using Momentic on a Retina screen,
|
|
108
108
|
relaunch with the --pixel-ratio option for a better experience.
|
|
109
109
|
If you are using a non-Retina monitor, you can ignore this warning.`):B.info(Lo`If you are using Momentic on a high-pixel density (HiDPI) monitor,
|
|
110
110
|
relaunch with the --pixel-ratio option for a better experience.`),B.info("Please visit https://www.mydevice.io/ to confirm your device's pixel-ratio."))}await hi({momenticServerUrl:n,mode:s,apiKey:e,serverPort:t,appPort:t,staticDir:d,assetsDir:u,devicePixelRatio:a})});Ce.command("run").alias("run-tests").addOption(ze).addOption(Ue).addOption(Ro).addOption(He).addOption(new Ie("-r, --remote","Run tests remotely. The production version of this test will be queued for execution.").default(!0).conflicts(["start, waitOn, waitOnTimeout, local"]).implies({local:!1})).addOption(Ai).addOption(new Ie("-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).").implies({remote:!1,noReport:!0})).addOption(new Ie("--start <command>","Arbitrary setup command that will run before Momentic steps begin.")).addOption(new Ie("--wait-on <url>","URL to wait to become accessible before Momentic tests begin.")).addOption(Ii).addOption(new Ie("--wait-on-timeout <timeout>","Max time to wait for the --wait-on URL to become accessible.").default(60,"one minute")).addOption(new Ie("-p, --parallel <parallelization>","When running with the --local flag, the number of tests to run in parallel. Defaults to 1.").default(1)).addArgument(Oi).action(async(r,e)=>{let{apiKey:t,server:o,remote:n,local:s,all:a,env:c,yes:l,parallel:i,report:d,start:u,waitOn:m,waitOnTimeout:h}=e;!a&&(!r||!r.length)&&(C.error("You must pass at least one test path or the --all flag"),process.exit(1));let g=new K({baseURL:o,apiKey:t}),p=await ke(g,l);if(s){try{await $i({tests:r,client:g,all:a,start:u,waitOn:m,waitOnTimeout:h,parallel:i,report:d,env:c,orgId:p})}catch(S){C.error(S),process.exit(1)}return}if(n){for(let S of r)(S.endsWith(".yaml")||zd(S))&&(C.error(Lo`'${S}' looks like a local file or directory, but the --local flag was not supplied.
|