momentic 0.0.39 → 0.0.40

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
@@ -21,17 +21,17 @@ import{Command as gl,Option as ue}from"commander";import yl from"dedent";import
21
21
  ${h}${a}</${i}>
22
22
  `}if(e.neighbors!==void 0&&e.neighbors>0&&this.parent){let h=this.parent.children.findIndex(S=>S.id===this.id),m=h>0?this.parent.children[h-1]?.serialize({...e,neighbors:0}):"",g=h<this.parent.children.length-1?this.parent.children[h+1]?.serialize({...e,neighbors:0}):"";return`${m||""}
23
23
  ${p}
24
- ${g||""}`}return p}},ar=class{constructor(e,t,o){this.root=e;this.a11yIdNodeMap=t;this.dataMomenticIdMap=o}serialize(){return this.root?this.root.serialize():""}};function ui(r){return r.name?.value?`"${r.name.value}"`:r.role?.value&&r.role.value!=="none"&&r.role.value!=="generic"?`"${r.role.value}"`:`"${r.nodeId}"`}function Oo(r,e,t){if(!e&&r.parentId)throw new Error(`Got no parent for accessibility node ${r.nodeId}: ${JSON.stringify(r)}`);let o=new ir({id:parseInt(r.nodeId),role:r.role?.value||"",name:r.name?.value?typeof r.name.value=="string"?r.name.value:`${r.name.value}`:"",content:r.value?.value?typeof r.value.value=="string"?r.value.value:`${r.value.value}`:"",properties:r.properties,children:[],pathFromRoot:(e?`${e.pathFromRoot} `:"")+ui(r),backendNodeID:r.backendDOMNodeId,ignoredByCDP:r.ignored}),n=r.childIds??[];for(let i of n){if(!i)continue;let l=t.get(parseInt(i));if(!l)continue;let c=Oo(l,o,t);c.length&&(o.children=o.children.concat(c))}if(o.role==="StaticText"&&(o.children=[]),o.children.length===1&&o.children[0].role==="StaticText"){let i=o.name,l=o.children[0]?.name;(i===l||!l)&&(o.children=[])}let s=[];for(let i=o.children.length-1;i>=0;i--){let l=o.children[i];if(l.role!=="StaticText"){s.push(l);continue}if(i===0||o.children[i-1].role!=="StaticText"){s.push(l);continue}o.children[i-1].name+=` ${l.name}`}if(o.children=s.reverse(),o.role==="generic"&&o.children.length===1){let i=o.children[0];if(o.name&&!lr.includes(i.role)&&o.name===i.name)return o.children}if(!o.isInteresting()&&r.parentId)return o.children;for(let i of o.children)i.parent=o;return[o]}function Mo(r,e,t,o,n=1){r.id=n,n+=1,e.set(r.id,r),r.dataMomenticId?t.set(r.dataMomenticId,r):lr.includes(r.role)||o.debug({node:r.serialize({neighbors:1,maxLevel:1})},"Node has no data-momentic-id");for(let s of r.children)n=Mo(s,e,t,o,n);return n}function No(r,e){if(!r.root)throw new Error("a11y tree has null root");r.allNodes=r.allNodes.filter(a=>a.ignored?!a.ignoredReasons?.find(l=>ci.includes(l.name)):!0);let t=new Map;for(let a of r.allNodes)t.set(parseInt(a.nodeId),a);let o=Oo(r.root,null,t);if(o.length>1)throw new Error(`Something went horribly wrong processing the a11y tree, we got: ${JSON.stringify(o)}`);if(o.length===0)throw new ct;let n=new Map,s=new Map;return Mo(o[0],n,s,e),new ar(o[0],n,s)}var Ze=(r,e)=>{e.id=r.id,e.content=r.content,e.name=r.name,e.role=r.role,e.numChildren=r.children.length,e.serializedForm=r.serialize({noID:!0,maxLevel:1,neighbors:1}),e.nodeOnlySerializedForm=r.serialize({noID:!0,noChildren:!0,noProperties:!0})},cr=(r,e)=>{let t=1;r.role===e.role&&t++;let o=["name","content"];for(let n of o){let s=r[n];if(typeof s!="string"||!s.trim())continue;let a=sr(s,e[n])/Math.min(s.length,e[n].length);a===0?t+=2:a<=.1&&t++}if(e.numChildren!==void 0&&(r.children.length===e.numChildren&&e.numChildren>0?t++:(e.numChildren>0&&r.children.length===0||Math.abs(r.children.length-e.numChildren)>2)&&t--),e.nodeOnlySerializedForm){let n=r.serialize({noID:!0,noChildren:!0,noProperties:!0});sr(n,e.nodeOnlySerializedForm)/Math.min(n.length,e.nodeOnlySerializedForm.length)<=.1&&t++}if(e.serializedForm){let n=r.serialize({noID:!0,maxLevel:1,neighbors:1}),s=sr(n,e.serializedForm)/Math.min(n.length,e.serializedForm.length);s===0?t+=2:s<=.1&&t++}return t};var Oe={r:147,g:196,b:125,a:.55},Do={showInfo:!1,showRulers:!1,showStyles:!1,showAccessibilityInfo:!1,showExtensionLines:!1,contrastAlgorithm:"aa",contentColor:Oe,paddingColor:Oe,borderColor:Oe,marginColor:Oe,eventTargetColor:Oe,shapeColor:Oe,shapeMarginColor:Oe};var B=(r=1e3)=>new Promise(e=>setTimeout(()=>e(),r));function Po(){window.cursor||(window.cursor=document.createElement("img"),window.cursor.setAttribute("src","data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjMyIiB2aWV3Qm94PSIwIDAgMzIgMzIiIHdpZHRoPSIzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwIDcpIj48cGF0aCBkPSJtNi4xNDggMTguNDczIDEuODYzLTEuMDAzIDEuNjE1LS44MzktMi41NjgtNC44MTZoNC4zMzJsLTExLjM3OS0xMS40MDh2MTYuMDE1bDMuMzE2LTMuMjIxeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Im02LjQzMSAxNyAxLjc2NS0uOTQxLTIuNzc1LTUuMjAyaDMuNjA0bC04LjAyNS04LjA0M3YxMS4xODhsMi41My0yLjQ0MnoiIGZpbGw9IiMwMDAiLz48L2c+PC9zdmc+"),window.cursor.setAttribute("id","momentic_cursor"),window.cursor.setAttribute("style","position: absolute; z-index: 99999999999; pointer-events: none; left:0; top:0; height:4%;"),window.cursor.style.filter="invert(0%) sepia(6%) saturate(24%) hue-rotate(315deg) brightness(89%) contrast(110%)",document.body.appendChild(cursor),document.onmousemove=function(r){r=r||window.event,document.getElementById("momentic_cursor").style.left=r.pageX+"px",document.getElementById("momentic_cursor").style.top=r.pageY+"px"})}function _o(){window.globalHintManager||(window.globalHintManager=new window.HintManager),window.globalHintManager.capture()}function ko(){window.globalHintManager&&window.globalHintManager.reset()}function Fo(){let r=document.body.getElementsByTagName("*"),e=1;for(let t=0;t<r.length;t++){let o=e.toString();for(;[6].some(s=>o.includes(s.toString()));)e++,o=e.toString();let n=r[t];n?.setAttribute("data-momentic-id",`${e}`),n?.setAttribute("aria-keyshortcuts",`${e}`),e++}}var pi=new Set(["document","script","XMLHttpRequest","fetch","xhr"]),hi=new Set(["script","document"]),fi=["intercom.io","googletagmanager.com","google-analytics.com","www.gstatic.com","gstatic.com","apis.google.com","sentry.io","newrelic.com","p.retool.com","m.stripe.com","m.stripe.network","js.stripe.com","assets.trybento.co","udon.trybento.co","cdn.lr-in-prod.com","r.lr-in-prod.com","content.product-usage.assembledhq.com","data.product-usage.assembledhq.com","static.zdassets.com","o.clarity.ms/collect"],gi=["api.stripe.com","supabase.co"],yi=[];async function Uo(r){for(let e of yi)await r.route(e,t=>t.abort())}function dr(r){return`${r.resourceType()} ${r.method()} ${r.url()}`}function zo(r){return r=r.replace(/^www\./,""),r}function $o(r){return gi.some(e=>r.includes(e))}function Ho(r,e){if(!pi.has(r.resourceType()))return!1;let t=new URL(e),o=new URL(r.url());return fi.some(n=>o.hostname.includes(n))?!1:hi.has(r.resourceType())||r.method()!=="GET"?!0:zo(o.hostname).includes(zo(t.hostname))}var Bo=()=>{window.clickListener&&document.removeEventListener("click",window.clickListener,{capture:!0}),window.clickListener=async r=>{console.log("[Momentic] Window click listener fired",r.target),await window.captureClick(r.target)},document.addEventListener("click",window.clickListener,{capture:!0})};var jo=()=>{window.pressListener&&document.removeEventListener("keydown",window.pressListener,{capture:!0}),window.pressListener=async r=>{console.log("[Momentic] Window press listener fired"),await window.captureKeystroke({key:r.key})},document.addEventListener("keydown",window.pressListener,{capture:!0})};var Ct=Di(Ni);Ct.use(_i());Ct.use(Pi({provider:{id:"2captcha",token:process.env.TWO_CAPTCHA_KEY},visualFeedback:!0}));var M=class r{browser;context;page;a11yIdToNodeMap=new Map;dataMomenticIdToNodeMap=new Map;cdpClient;logger;localMode;activeFrame;transformer;mostRecentBrowserState;baseURL;constructor({browser:e,context:t,page:o,baseUrl:n,logger:s,localMode:a,cdpClient:i}){this.browser=e,this.context=t,this.cdpClient=i,this.page=o,this.baseURL=n,this.logger=s,this.localMode=!!a}static USER_AGENT=Ko["Desktop Chrome"].userAgent;static VIEWPORT={width:1920,height:1080};static async init({baseUrl:e,logger:t,browserArgs:o,contextArgs:n,takeScreenshots:s,onScreenshot:a,onClose:i,waitForLoad:l,localMode:c,localAppUrl:d,extensionPath:p,skipPageSetup:f,timeout:h=8e3}){let m={headless:!0,handleSIGTERM:!1,chromiumSandbox:!1,...o??{}},g={viewport:r.VIEWPORT,deviceScaleFactor:process.platform==="darwin"?2:1,userAgent:Ko["Desktop Chrome"].userAgent,geolocation:{latitude:37.7749,longitude:-122.4194},locale:"en-US",timezoneId:"America/Los_Angeles",...n??{}},S=null,v,y;if(c){let F=Mi(Oi(),"momentic","chromium");v=await Ct.launchPersistentContext(F,{...m,...g,ignoreDefaultArgs:["--enable-automation","--enable-strict-mixed-content-checking"],ignoreHTTPSErrors:!0,bypassCSP:!0,args:["--allow-insecure-localhost","--disable-site-isolation-for-policy","--disable-site-isolation-trials",`--unsafely-treat-insecure-origin-as-secure=${d}`,`--load-extension=${p}`,"--test-type=browser"],baseURL:e}),y=v.pages()[0],i&&y.on("close",()=>{i()})}else S=await Ct.launch(m),v=await S.newContext({...g,baseURL:e}),y=await v.newPage();await Uo(v);let R=new r({browser:S,context:v,page:y,baseUrl:e,logger:t,localMode:c,cdpClient:await v.newCDPSession(y)}),E=!1,A=async()=>{try{await R.navigate({url:e,wrapPossibleNavigation:!1,initialNavigation:!f,timeout:h})}catch(F){if(t.error({err:F},"Failed to initialize chrome browser"),l)throw F}finally{E=!0}};if(l?await A():A(),a){let F=async()=>{if(s)try{a({viewport:await R.viewport(),buffer:await R.screenshot()})}catch(x){t.error({err:x},"Failed to take screenshot")}};F();let ze=setInterval(()=>{F()},400),Q=Date.now();for(;!E&&Date.now()-Q<(h??1/0);)await B(250);clearInterval(ze)}return R}async getUserPageOrFrame(){if(this.localMode&&this.activeFrame){let e=null,t=0;for(;!e&&t<5;)e=this.page.frame(this.activeFrame),await B(100),t++;if(!e)throw new T("InternalWebAgentError",`Failed to find frame ${this.activeFrame} on page ${this.page.url()}`);return e}return this.page}async initCDPSession(e=3e3){let t=!1,o=e===null?1/0:2,n=async()=>{try{this.cdpClient=await this.context.newCDPSession(this.page),await this.cdpClient.send("Accessibility.enable"),await this.cdpClient.send("DOM.enable"),await this.cdpClient.send("Overlay.enable"),t=!0}catch(s){if(o>0)return this.logger.warn({err:s},"Failed to initialize CDP session, re-creating CDP client"),await B(250),o--,n();throw s}};return e===null?await n():await Promise.race([n(),B(e)]),t}setLogger(e){this.logger=e}ping(){if(this.closed)throw new Error("Page has been closed");if(this.browser&&!this.browser.isConnected())throw new Error("Browser is not connected")}setActiveFrame(e){this.activeFrame=e}async reset(e){this.a11yIdToNodeMap.clear(),this.dataMomenticIdToNodeMap.clear();let t=this.context.pages();this.page=t[0];for(let o=1;o<t.length;o++)await t[o].close();if(e.clearCookies&&await this.context.clearCookies(),!this.page.isClosed()){if(e.clearStorage)try{await(await this.getUserPageOrFrame()).evaluate(async()=>{window.localStorage.clear(),window.sessionStorage.clear(),await indexedDB.databases().then(o=>{o.forEach(n=>{n.name&&indexedDB.deleteDatabase(n.name)})})})}catch(o){this.logger.warn({err:o},"Failed clearing site data, continuing...")}await this.navigate({url:e.url??this.baseURL,wrapPossibleNavigation:!1,initialNavigation:!0,timeout:e.timeout})}}async pageSetup(){try{this.localMode||await this.page.evaluate(Po)}catch{}}async wait(e){await B(e)}async toggleHints(e){let t=await this.getUserPageOrFrame();e.state==="on"?(await t.addStyleTag({content:At.css}),await t.addScriptTag({content:At.js}),await t.evaluate(_o)):await t.evaluate(ko)}async showHints(){await this.toggleHints({state:"on"});let e=async()=>{try{await this.toggleHints({state:"off"})}catch(t){this.logger.debug({err:t},"Failed to remove vision hints")}};setTimeout(()=>{e()},3e3)}async cleanup(){await this.page.close(),await this.context.close(),this.browser&&await this.browser.close()}get closed(){return this.page.isClosed()||!!this.browser&&!this.browser.isConnected()}async html(){return(await this.getUserPageOrFrame()).content()}async url(){return(await this.getUserPageOrFrame()).url()}async screenshotWithHints(e=100,t="device",o="/tmp/screenshots/test.jpg"){let n=o?.split("."),s=n?.slice(0,-1).join("."),a=n?.slice(-1)[0],i=Buffer.from("");await this.showHints();let l=await this.screenshot(e,t,o?`${s}-after-hint.${a}`:void 0);return{before:i,after:l}}async screenshot(e=100,t="device",o){let n={fullPage:!1,quality:e,scale:t,type:"jpeg",caret:"initial",path:o};return!this.localMode||!this.activeFrame?this.page.screenshot(n):this.page.locator(`iframe[name="${this.activeFrame}"]`).screenshot(n)}async viewport(){if(this.localMode&&this.activeFrame){let t=await this.page.locator(`iframe[name="${this.activeFrame}"]`).boundingBox();if(!t)throw new Error(`Failed to get bounding box for frame: ${this.activeFrame}`);return t}let e=this.page.viewportSize();if(!e)throw new Error("failed to get viewport");return e}async navigate({url:e,wrapPossibleNavigation:t=!0,initialNavigation:o=!1,timeout:n=8e3}){this.logger.debug(`Navigating to ${e}`);let s=Date.now(),a=async()=>{try{await(await this.getUserPageOrFrame()).goto(e,{waitUntil:"load",timeout:n??void 0}),this.logger.debug({url:e},`Got load event in ${Math.floor(Date.now()-s)}ms`)}catch(l){if(l instanceof Error&&l.message.includes("ERR_ABORTED"))return;this.logger.warn({url:e,err:l},"Timeout elapsed waiting for page to fire load event. Are you sure this page is accessible?")}};t?await this.wrapPossibleNavigation(a):await a();let i=await this.url();if(Ro.has(i)&&process.env.NODE_ENV==="production")throw new T("ActionFailureError",`${e} took too long to load \u{1F61E}. Please ensure the site and your internet are working.`,{},!0);if(o){if(!await this.initCDPSession(n))throw new T("ActionFailureError",`Initializing libraries timed out on ${e} \u{1F61E}. Please ensure the site and your internet are working.`,{},!0);if(await this.pageSetup(),await this.attachPageLoadListeners(),this.localMode)try{await this.exposeRecordingBindings()}catch(c){c instanceof Error&&c.message.includes("already registered")||this.logger.error({err:c},"Failed to expose recording bindings during navigation")}}this.logger.info({url:e},"Navigation complete")}async type(e,t={}){let{clearContent:o=!0,pressKeysSequentially:n=!1}=t;o&&(process.platform==="darwin"?await this.page.keyboard.press("Meta+A"):await this.page.keyboard.press("Control+A"),await this.page.keyboard.press("Backspace")),n?await this.page.keyboard.type(e):await this.page.keyboard.insertText(e)}async clickByA11yID(e,t={}){let o=this.a11yIdToNodeMap.get(e);if(!o)throw new Error(`Could not find DOM node during click: ${e}`);let n=await this.clickUsingCDP(o,t);return await this.highlightNode(n),o.serialize({noChildren:!0,noID:!0})}async selectOptionByA11yID(e,t){let o=this.a11yIdToNodeMap.get(e);if(!o)throw new Error(`Could not find DOM node while selecting option: ${e}`);if(!o.backendNodeID)throw new Error(`Select target missing backend node id: ${o.getLogForm()}`);return await(await this.getLocatorFromBackendID(o.backendNodeID)).selectOption(t,{timeout:8e3}),await this.highlightNode(o),o.serialize({noChildren:!0,noID:!0})}async scrollIntoView(e){let t=await this.resolveCachedTargetToID(e),o=this.a11yIdToNodeMap.get(t);if(!o)throw new Error(`Could not find node in DOM with a11y id: ${t}`);if(!o.backendNodeID)throw new Error(`Focus target missing backend node id: ${o.getLogForm()}`);await(await this.getLocatorFromBackendID(o.backendNodeID)).scrollIntoViewIfNeeded({timeout:8e3})}async highlight(e){try{let t=await this.resolveCachedTargetToID(e),o=this.a11yIdToNodeMap.get(t);if(!o)throw new Error(`Could not find DOM node during highlight: ${t}`);if(!o.backendNodeID)throw new Error(`Highlight target missing backend node id: ${o.getLogForm()}`);await this.highlightNode(o)}catch(t){this.logger.warn({err:t,target:e},"Failed to highlight target")}}async highlightNode(e){try{await this.cdpClient.send("Overlay.highlightNode",{highlightConfig:Do,backendNodeId:e.backendNodeID})}catch{this.logger.warn("Failed to add node highlight, a page navigation likely occurred. This is non-fatal for tests.")}let t=async()=>{try{await this.cdpClient.send("Overlay.hideHighlight",{backendNodeId:e.backendNodeID})}catch(o){this.logger.debug({err:o},"Failed to remove node highlight")}};setTimeout(()=>{t()},3e3)}async wrapPossibleNavigation(e,t=8e3,o=!0){let n=Date.now(),s=await this.url(),a=Date.now(),i=new Map,l=new Map,c=E=>{let A=dr(E.request());l.set(A,(l.get(A)??0)+1);let F=E.status();F>=500&&this.logger.warn({request:A,status:F},"Received 500 level response")},d=E=>{if(!Ho(E,s))return;let A=dr(E);i.set(A,(i.get(A)??0)+1),a=Date.now()};this.page.on("response",c),this.page.on("request",d);let p=[];o&&(p=this.context.pages().map(E=>E.url()));let f=!1,h=e().catch(E=>(f=!0,E instanceof Error?E:new Error(`${E}`)));await B(250);let m=async E=>{let A=await E;if(A instanceof Error)throw A;return A},g=new Set,S=!1,y=await(async()=>{for(;!f&&!(!S&&Date.now()-n>t);){if(await B(250),S=!1,g=new Set,Date.now()-a<=1250)continue;let E=!1;for(let A of i.keys())i.get(A)!==l.get(A)&&($o(A)&&(S=!0),E=!0,g.add(A));if(!E)return this.logger.debug({url:await this.url(),requests:JSON.stringify(Array.from(i.entries()))},`Network idle in ${Math.floor(Date.now()-n)}ms`),!0}return!f&&g.size>0&&this.logger.warn({url:await this.url(),unfinishedRequests:JSON.stringify(Array.from(g.entries()))},"Timeout elapsed waiting for network idle, continuing anyways..."),!1})();if(this.page.off("response",c),this.page.off("request",d),!y)return m(h);let R=await this.url();if(!f&&Le(R,s)){this.logger.debug({startUrl:s,newUrl:R},"Detected url change in wrapPossibleNavigation, waiting for load state");let E=Math.max(t-(Date.now()-n),0);if(E>0)try{await(await this.getUserPageOrFrame()).waitForLoadState("load",{timeout:E})}catch{this.logger.warn({url:await this.url()},"Timeout elapsed waiting for load state, continuing anyways...")}}if(o){let E=this.context.pages().map(A=>A.url());if(E.length>p.length)for(let A of E)A!==R&&await this.switchToPage(A)}return m(h)}async resolveCachedTargetToID(e){if(!at(e)){let i=this.a11yIdToNodeMap.get(e.id);if(!i)throw new Error(`Resolving target failed, fresh value did not exist in node map: ${e.id}`);return Ze(i,e),e.id}let t=(await this.getA11yTree()).serialize();this.logger.debug({tree:t},"Refreshed a11y tree before resolving target");let o=this.a11yIdToNodeMap.get(e.id);if(o){let i=cr(o,e);if(i>=5)return this.logger.debug({target:e,proposedNode:o.getLogForm(),comparisonScore:i},"Resolved cached a11y target to node with exact same id"),Ze(o,e),e.id}let n=1/0,s=1/0,a;for(let i of this.a11yIdToNodeMap.values()){let l=cr(i,e);if(l>=5)return this.logger.debug({newNode:i.getLogForm(),target:e,comparisonScore:l},"Resolved cached a11y target to new node with field comparison"),Ze(i,e),i.id;if(!e.serializedForm)continue;let c=i.serialize({noID:!0,maxLevel:1,neighbors:1});if(Math.abs(c.length-e.serializedForm.length)>15)continue;let d=Ii(e.serializedForm,c),p=d/Math.min(e.serializedForm.length,c.length);d<n&&p<.2&&(n=d,s=p,a=i)}if(a&&n<15)return this.logger.debug({newNode:a.getLogForm(),target:e,distance:n,ratio:s},"Resolved cached a11y target to new node with pure levenshtein distance"),Ze(a,e),a.id;throw new Error(`Could not find any relevant node given cached target: ${JSON.stringify({target:e,closestLevenshteinDistance:n,closestNode:a})}`)}async click(e,t={}){let o=await this.resolveCachedTargetToID(e);return await this.wrapPossibleNavigation(()=>this.clickByA11yID(o,t))}async dragAndDrop(e,t,o={}){let n=await this.resolveCachedTargetToID(e),s=this.a11yIdToNodeMap.get(n);if(!s||!s.backendNodeID)throw new Error(`Could not find DOM node for drag and drop 'from' target: ${n}`);let a=await this.getLocatorFromBackendID(s.backendNodeID),i=await this.resolveCachedTargetToID(t),l=this.a11yIdToNodeMap.get(i);if(!l||!l.backendNodeID)throw new Error(`Could not find DOM node for drag and drop 'to' target: ${i}`);let c=await this.getLocatorFromBackendID(l.backendNodeID);return await a.hover(o),await this.page.mouse.down(),await c.hover(o),await B(250),await this.page.mouse.up(),s.serialize({noChildren:!0,noID:!0})}async hover(e){let t=await this.resolveCachedTargetToID(e),o=this.a11yIdToNodeMap.get(t);if(!o)throw new Error(`Could not find DOM node for hover: ${t}`);if(!o.backendNodeID)throw new Error(`Hover target missing backend node id: ${o.getLogForm()}`);return await(await this.getLocatorFromBackendID(o.backendNodeID)).hover({timeout:8e3}),await this.highlightNode(o),o.serialize({noChildren:!0,noID:!0})}async selectOption(e,t){let o=await this.resolveCachedTargetToID(e);return this.selectOptionByA11yID(o,t)}async press(e){await this.wrapPossibleNavigation(()=>this.page.keyboard.press(e))}async refresh(){if(this.localMode&&this.activeFrame){let t=(await this.getUserPageOrFrame()).url();await this.navigate({url:t,wrapPossibleNavigation:!0})}else try{await this.page.reload({timeout:3e3})}catch{this.logger.warn({url:await this.url()},"Timeout elapsed waiting for page on reload, continuing anyways...")}}async getA11yTree(e=!1){let t=null,o=async()=>{let n=0,s=await this.url();for(;!t&&n<3;)try{let a=await this.getRawA11yTree(e);if(!a.root||a.allNodes.length<=1)throw new Error("No a11y tree found on page");t=No(a,this.logger)}catch(a){if(this.logger.error({err:a,url:s},"Error fetching a11y tree"),n===0)await B(1e3),n++;else throw new Error(`Max retries exceeded fetching a11y tree: ${a}`)}if(!t||!t.root)throw new Error("Accessibility tree appears empty");this.a11yIdToNodeMap=t.a11yIdNodeMap,this.dataMomenticIdToNodeMap=t.dataMomenticIdMap};if(await Promise.race([o(),B(8e3)]),t)return t;throw new T("ActionFailureError",`Getting accessibility tree timed out after ${8e3}ms`)}getA11yIdFromDataMomenticId(e){return this.dataMomenticIdToNodeMap.get(e)?.id}async getRawA11yTree(e=!1){let t=await this.url(),o=Date.now(),n=()=>{o=Date.now()};this.cdpClient.addListener("Accessibility.nodesUpdated",n);let s=!1,a=()=>{this.logger.info({url:t},"Load event fired on page"),s=!0,o=Date.now()};this.cdpClient.addListener("Accessibility.loadComplete",a);let i=Date.now(),l=!e;for(;!e&&Date.now()-i<3e3;){if(await B(250),!s&&Date.now()-i<1e3){process.env.NODE_ENV!=="production"&&this.logger.debug({url:t},"A11y tree not loaded yet, waiting...");continue}if(Date.now()-o>=1250){l=!1;break}this.logger.debug({url:t},"A11y tree not stable yet, waiting...")}this.logger.debug({duration:Date.now()-i,eventReceived:s,timeoutTriggered:l,skipWait:e},"A11y wait phase completed"),await(await this.getUserPageOrFrame()).evaluate(Fo);let c;if(this.localMode&&this.activeFrame){let{result:{objectId:p}}=await this.cdpClient.send("Runtime.evaluate",{expression:`document.querySelector("#${this.activeFrame}")`}),f=await this.cdpClient.send("DOM.describeNode",{objectId:p});c=(await this.cdpClient.send("Accessibility.getRootAXNode",{frameId:f.node.frameId})).node.backendDOMNodeId}else{let{node:p}=await this.cdpClient.send("Accessibility.getRootAXNode");c=p.backendDOMNodeId}let{nodes:d}=await this.cdpClient.send("Accessibility.queryAXTree",{backendNodeId:c});return this.cdpClient.removeListener("Accessibility.loadComplete",a),this.cdpClient.removeListener("Accessibility.nodesUpdated",n),{root:d[0],allNodes:d}}async clickUsingVisualCoordinates(e){let t=await this.getElementLocation(e);if(!t)throw new Error(`Could not find element location with backend node id: ${e}`);this.logger.debug({location:t},"Executing mouse click"),await this.page.mouse.click(t.centerX,t.centerY)}async getIDAttributeUsingCDP(e){await this.cdpClient.send("DOM.getDocument",{depth:0});let t=await this.cdpClient.send("DOM.requestNode",{objectId:e}),n=(await this.cdpClient.send("DOM.getAttributes",{nodeId:t.nodeId})).attributes,s=n.findIndex(a=>a===Ge);if(s===-1||!n[s+1])throw new Error(`Could not find ${Ge} for object ${e}`);return n[s+1]}async getLocatorFromBackendID(e){let t=await this.cdpClient.send("DOM.resolveNode",{backendNodeId:e});if(!t||!t.object.objectId)throw new Error(`Could not resolve backend node ${e}`);let o;try{o=await this.getIDAttributeUsingCDP(t.object.objectId)}catch(n){throw this.logger.error({err:n,object:JSON.stringify(t.object)},"Failed to get ID attribute"),n}return(await this.getUserPageOrFrame()).locator(`[${Ge}="${o}"]`)}async clickUsingCDP(e,t={}){let o=0,n=e,s;for(;o<xo;){if(!n||n.role==="RootWebArea")throw new T("ActionFailureError",s??`Attempted to click node with no clickable surrounding elements: ${e.getLogForm()}`);if(n.role==="StaticText"){n=n.parent;continue}let a=n.backendNodeID;if(!a){this.logger.warn({node:n.getLogForm()},"Click candidate had no backend node ID"),n=n.parent;continue}try{let i=await this.getLocatorFromBackendID(a);return t.doubleClick?await i.dblclick({timeout:8e3}):await i.click({timeout:8e3,button:t.rightClick?"right":"left",force:t.force}),n.id!==e.id&&this.logger.info({oldNode:e.getLogForm(),newNode:n.getLogForm()},"Redirected click successfully to new element"),n}catch(i){let l=`Failed to click '${n.getLogForm()}' - are you sure the element is currently visible and not obscured on the page?`;this.logger.error({err:i},l),s||(s=l),o++,n=n.parent}}throw new Error(`Max click redirection attempts exhausted on original element: ${e.getLogForm()}`)}async getElementLocation(e){let t=await this.cdpClient.send("DOMSnapshot.captureSnapshot",{computedStyles:[],includeDOMRects:!0,includePaintOrder:!0}),o=await this.page.evaluate(()=>window.devicePixelRatio);process.platform==="darwin"&&o===1&&(o=2);let n=t.documents[0],s=n.layout,a=n.nodes,i=a.nodeName||[],l=a.backendNodeId||[],c=s.nodeIndex,d=s.bounds,p=-1;for(let y=0;y<i.length;y++)if(l[y]===e){p=c.indexOf(y);break}if(p===-1)throw new Error(`Could not find any backend node with ID ${e}`);let[f=0,h=0,m=0,g=0]=d[p];f/=o,h/=o,m/=o,g/=o;let S=f+m/2,v=h+g/2;return{centerX:S,centerY:v}}async scroll(e,t,o,n){let s=t==="left"?-1:1,a=n==="up"?-1:1;this.activeFrame?await(await this.getUserPageOrFrame()).evaluate(([l,c,d,p])=>window.scrollTo(window.scrollX+(l??window.innerWidth)*d,window.scrollY+(c??window.innerHeight)*p),[e,o,s,a]):await this.page.mouse.wheel((e??r.VIEWPORT.width)*s,(o??r.VIEWPORT.height)*a)}async scrollUp(e){await this.scroll(0,null,e??null,"up")}async scrollDown(e){await this.scroll(0,null,e??null,"down")}async scrollLeft(e){await this.scroll(e??null,"left",0,null)}async scrollRight(e){await this.scroll(e??null,"right",0,null)}async goForward(){await this.wrapPossibleNavigation(async()=>this.localMode&&this.activeFrame?(await this.getUserPageOrFrame()).evaluate(e=>{let t=e().contentWindow;t?t.history.forward():console.error("Failed to get content window for frame")},()=>document.querySelector(`iframe[name="${this.activeFrame}"]`)):this.page.goForward({timeout:8e3}))}async goBack(){await this.wrapPossibleNavigation(async()=>this.localMode&&this.activeFrame?(await this.getUserPageOrFrame()).evaluate(e=>{let t=e().contentWindow;t?t.history.back():console.error("Failed to get content window for frame")},()=>document.querySelector(`iframe[name="${this.activeFrame}"]`)):this.page.goBack({timeout:8e3}))}async switchToPage(e){let t=this.context.pages();for(let o=0;o<t.length;o++){let n=t[o];if(n.url().includes(e)){this.logger.info(`Switching to tab ${o} with url ${n.url()}`),this.page=n,await this.pageSetup(),await this.attachPageLoadListeners();try{await n.waitForLoadState("load",{timeout:3e3})}catch{this.logger.warn({url:await this.url()},"Timeout elapsed waiting for load state during tab switch, continuing anyways...")}if(!await this.initCDPSession())throw new T("ActionFailureError",`Initializing libraries timed out on ${n.url()} \u{1F61E}. Please ensure the site and your internet are working.`);return}}throw new Error(`Could not find page with url containing ${e}`)}async attachPageLoadListeners(){if(this.localMode)return;let e=async t=>{t.parentFrame()||await this.pageSetup()};this.page.off("framenavigated",e),this.page.on("framenavigated",e)}async setCookie(e){let t=Gr(e);await this.context.addCookies([t])}async setLocalStorage(e,t){await(await this.getUserPageOrFrame()).evaluate(([n,s])=>{n&&localStorage.setItem(n,s||"")},[e,t])}async solveCaptcha(){await this.getA11yTree();let e;for(let i of this.a11yIdToNodeMap.values())if(i.role==="image"&&i.name.toLowerCase().includes("captcha")){if(!i.backendNodeID)continue;e=await this.getLocatorFromBackendID(i.backendNodeID);break}if(!e){let i=await(await this.getUserPageOrFrame()).solveRecaptchas();if(!i.captchas||!i.captchas.length)throw new Error("No captchas found on the page");return}let t=await e.screenshot({type:"jpeg",animations:"allow",quality:100}),o=await fetch("https://api.2captcha.com/createTask",{method:"POST",body:JSON.stringify({clientKey:process.env.TWO_CAPTCHA_KEY,task:{type:"ImageToTextTask",body:t.toString("base64"),case:!0},languagePool:"en"})});if(!o.ok){let i=`Captcha solver API returned error response: ${o.statusText}`;throw this.logger.error({text:await o.text()},i),new Error(i)}let{taskId:n}=await o.json(),s=Date.now(),a="";for(;Date.now()-s<6e4;){await B(2500);let i=await fetch("https://api.2captcha.com/getTaskResult",{method:"POST",body:JSON.stringify({clientKey:process.env.TWO_CAPTCHA_KEY,taskId:n})});if(!i.ok){let c=`Captcha solution API returned error response: ${i.statusText}`;throw this.logger.error({text:await i.text()},c),new Error(c)}let l=await i.json();if(l.errorId){let c=`Captcha solution API returned error ID ${l.errorId}`;throw this.logger.error(c),new Error(c)}if(l.status==="ready"){a=l.solution.text;break}}if(!a)throw new Error("Captcha solution timed out");return a}getActiveFrame(){return this.activeFrame}async exposeRecordingBindings(){await this.context.exposeBinding("captureClick",async(e,t)=>{if(!this.transformer){this.logger.warn("Click captured without transformer");return}let o,n;try{o=await t.getAttribute("id"),n=await t.getAttribute(Ge)}catch(l){throw this.logger.error({err:l},"Failed to get element attributes for click processing"),l}if(this.logger.info({id:o,dataMomenticId:n},"Click captured on element"),!n){this.logger.error({url:await this.url(),element:await t.evaluate(l=>l.outerHTML)},`Could not find ${Ge} for recorded click`);return}let s=this.getA11yIdFromDataMomenticId(parseInt(n));if(!s){this.logger.warn({url:await this.url(),element:await t.evaluate(l=>l.outerHTML)},"Could not find a11y id for recorded click");return}let a={id:s};await this.resolveCachedTargetToID(a),(async()=>{try{let l=this.mostRecentBrowserState??(await this.getA11yTree(!0)).serialize();await this.transformer?.recordClick(a,l)}catch(l){this.logger.error({err:l},"Failed transforming captured click")}})()},{handle:!0}),await this.context.exposeBinding("captureKeystroke",async({},{key:e})=>{this.transformer&&(this.logger.info({key:e},"Captured keypress"),this.transformer.recordKeystroke(e))})}async startRecording(e,t){this.transformer=t;let o=await this.getA11yTree(!0);this.mostRecentBrowserState=o.serialize();let n=null,s=[async()=>{this.transformer=void 0}],a=async l=>{n&&clearTimeout(n),n=setInterval(()=>{if(!this.transformer||e.aborted)return;(async()=>{let d=0;for(;d<2;)try{let p=await this.getA11yTree(!0);this.mostRecentBrowserState=p.serialize();break}catch(p){this.logger.error({err:p},"Failed to get a11y tree in frame navigation listener"),d++}})()},250),s.push(async()=>{n&&(clearInterval(n),n=null)}),await l.evaluate(Bo),s.push(async()=>{await l.evaluate(()=>{document.removeEventListener("click",window.clickListener,{capture:!0})})}),await l.evaluate(jo),s.push(async()=>{await l.evaluate(()=>{document.removeEventListener("keydown",window.pressListener,{capture:!0})})})};await a(await this.getUserPageOrFrame());let i=async l=>{l.name().startsWith(jt)&&(e.aborted||!this.transformer||(this.logger.info("Re-adding window recording event listeners on navigation"),await a(l)))};this.page.on("framenavigated",i),s.push(async()=>{this.page.off("framenavigated",i)}),e.addEventListener("abort",async()=>{for(let l of s)try{await l()}catch(c){this.logger.warn({err:c},"Recording cleanup function failed, continuing...")}})}async getSelectOptions(e){let t=this.a11yIdToNodeMap.get(e);if(!t)throw new Error(`Could not find node with a11y id ${e}`);if(!t.backendNodeID)throw new Error(`Node with a11y id ${e} has no backend node ID`);return await(await this.getLocatorFromBackendID(t.backendNodeID)).evaluate(s=>Array.from(s.querySelectorAll("option")).map(i=>i.value))}async getHTML(){let e=await this.getUserPageOrFrame();await e.addScriptTag({content:At.htmlJs});let t=await e.evaluate(()=>window.getHTMLBodyString());if(!t)throw new T("InternalWebAgentError","Empty HTML tree");return Li.html(t,{indent_size:1,indent_with_tabs:!1,preserve_newlines:!1,wrap_line_length:80})}};var Ve=async({controller:r,context:e,step:t,logger:o,advanced:n,takeScreenshots:s,...a})=>{a.onStarted?.(),r.resetHistory();let i={...t,startedAt:new Date,userAgent:M.USER_AGENT,finishedAt:new Date,results:[],status:"SUCCESS"};try{let l=0;t.commands=t.commands||[];let c=t.commands&&t.commands.length>0;for(;;){if(l>12)throw new Error(`Exceeded max number of commands per step (${12})`);if(r.isClosed())throw new Error("Cancelling remaining steps in AI action because the controller is now closed");let d,p=new Date,f;if(s){let m=await r.browser.screenshot();f=await a.onSaveScreenshot(m)}if(c){if(d=t.commands[l],!d)throw new Error(`Saved command at index ${l} is undefined.`)}else{o.info(`Generating new sub-command ${l} within AI step`);let m;try{m=await r.promptToCommand(t.type,t.text,n.disableAICaching),d=m.command}catch(S){d={type:"FAILURE",thoughts:S instanceof Error?S.message:`${S}`}}finally{if(d.type==="FAILURE")if(l===0){i.finishedAt=new Date,i.status="FAILED",i.message=d.thoughts;break}else{let S="Stopping command generation prematurely since no progress can be made";o.warn({command:d},S),d={type:"SUCCESS",thoughts:S}}}(async()=>{let S=l,v=t,y=d;if("target"in y&&y.target){await new Promise(R=>setTimeout(()=>R(),250));try{let R=await ki(r,m.context,y.target);y.target.elementDescriptor=R,v.commands[l]=y,a.onCommandExecuted?.({commandIndex:S,command:y})}catch(R){o.error({err:R,currentIndex:S,currentStep:v},"Failed to generate element description, continuing...")}}})()}a.onCommandGenerated?.({commandIndex:l,message:Ut[d.type]||`Unknown command (${d.type})`});let h={beforeScreenshot:f,beforeUrl:await r.browser.url(),startedAt:p,viewport:await r.browser.viewport(),finishedAt:new Date,status:"SUCCESS"};o.info(`Executing sub-command ${l} within AI step: ${Be(d)}`);try{let m=await r.executeCommand(d,e,n.disableAICaching,c);o.info(`AI sub-command ${l} completed successfully`),h.elementInteracted=m.elementInteracted;let g;if(s){let v=await r.browser.screenshot();g=await a.onSaveScreenshot(v)}h.afterScreenshot=g,h.afterUrl=await r.browser.url(),h.finishedAt=new Date;let S={status:"SUCCESS",startedAt:h.startedAt,finishedAt:h.finishedAt,type:"PRESET_ACTION",data:m.data,command:d,results:[h]};if(i.results.push(S),t.commands[l]=d,a.onCommandExecuted?.({commandIndex:l,output:{...S,message:d.thoughts??m.thoughts??"Successfully executed preset action."},command:d}),d.type==="SUCCESS"){i.finishedAt=new Date,i.status="SUCCESS",i.message=m.thoughts??"All commands completed.";break}if(m.succeedImmediately&&!c){i.finishedAt=new Date,i.status="SUCCESS",i.message=m.succeedImmediatelyReason,d={type:"SUCCESS",thoughts:m.succeedImmediatelyReason},t.commands.push(d),a.onCommandExecuted?.({commandIndex:l+1,output:{...S,message:i.message},command:d}),i.results.push({...S,command:d});break}}catch(m){if(c){c=!1,l=0,i.results=[];continue}let g=m instanceof Error?m.message:`${m}`;h.status="FAILED",h.message=g,h.finishedAt=new Date,h.afterUrl=await r.browser.url();let S;try{S=await r.browser.screenshot()}catch(v){o.warn({err:v},"Failed to take screenshot after error, skipping")}h.afterScreenshot=S,i.results.push({status:"FAILED",startedAt:h.startedAt,finishedAt:h.finishedAt,type:"PRESET_ACTION",command:d,results:[h],message:g}),i.status="FAILED",i.finishedAt=new Date,i.message=g;break}l++}}catch(l){i.message=l instanceof Error?l.message:`${l}`,i.finishedAt=new Date,i.status="FAILED"}return i.status==="SUCCESS"?(i.data=i.results[i.results.length-1]?.data,a.onSuccess?.({message:i.message||"AI step succeeded.",startedAt:i.startedAt.getTime(),durationMs:i.finishedAt.getTime()-i.startedAt.getTime(),output:i})):a.onFailure?.({message:i.message||"AI step errored.",startedAt:i.startedAt.getTime(),durationMs:i.finishedAt.getTime()-i.startedAt.getTime(),output:i}),i};async function ki(r,e,t){let o=t.a11yData?.id;if(!o)throw new Error("Attempted to get reverse mapping for command with no a11y id target");return r.getReverseMappedTarget(e,o,!0)}var We=async({controller:r,context:e,step:t,advanced:o,takeScreenshots:n,...s})=>{s.onStarted?.();let a=new Date,i=await r.browser.url(),l;if(n){let c=await r.browser.screenshot();l=await s.onSaveScreenshot(c)}try{let c=await r.executePresetStep(t.command,e,o.disableAICaching),d;if(n){let g=await r.browser.screenshot();d=await s.onSaveScreenshot(g)}let p=new Date,f={...t,startedAt:a,finishedAt:p,status:"SUCCESS",data:c.data,results:[]},h="Successfully executed preset action.";"assertion"in t.command&&(h=c.thoughts||"Assertion passed.");let m={beforeUrl:i,beforeScreenshot:l,afterUrl:await r.browser.url(),afterScreenshot:d,startedAt:a,finishedAt:p,viewport:await r.browser.viewport(),status:"SUCCESS"};return f.status="SUCCESS",f.results=[m],f.message=h,s.onSuccess?.({message:h,startedAt:a.getTime(),durationMs:p.getTime()-a.getTime(),command:t.command,output:f}),f}catch(c){s.logger.error({err:c},`Failed executing preset step ${t.command.type}`);let d=new Date,p=c instanceof Error?c.message:`${c}`,f=await r.browser.screenshot();if("cancelOnFailure"in t.command&&t.command.cancelOnFailure){let m={...t,startedAt:a,finishedAt:d,status:"CANCELLED",message:p,results:[{beforeUrl:i,beforeScreenshot:l,afterUrl:await r.browser.url(),afterScreenshot:f,startedAt:a,finishedAt:d,viewport:await r.browser.viewport(),status:"CANCELLED",message:p}]};return s.onCancelled?.({message:p,startedAt:a.getTime(),durationMs:d.getTime()-a.getTime(),output:m}),m}let h={...t,startedAt:a,finishedAt:d,status:"FAILED",message:p,results:[{beforeUrl:i,beforeScreenshot:l,afterUrl:await r.browser.url(),afterScreenshot:f,startedAt:a,finishedAt:d,viewport:await r.browser.viewport(),status:"FAILED",message:p}]};return s.onFailure?.({message:p,startedAt:a.getTime(),durationMs:d.getTime()-a.getTime(),output:h}),h}};var Rt=async({controller:r,context:e,step:t,advanced:o,logger:n,takeScreenshots:s,...a})=>{a.onStarted?.();let i={type:"MODULE",moduleId:t.moduleId,startedAt:new Date,userAgent:M.USER_AGENT,results:[],finishedAt:new Date,status:"SUCCESS"};for(let l=0;l<t.steps.length;l++){let c=t.steps[l];if(r.isClosed())throw i.status="CANCELLED",new Error("Cancelling remaining steps in module because the controller is now closed");n.debug({i:l,moduleStep:c},"Starting module step"),n.info(`Starting module sub-step ${l+1}/${t.steps.length}: ${yt(c)}`);let d;switch(c.type){case"PRESET_ACTION":d=await We({controller:r,context:e,step:c,advanced:o,logger:n,takeScreenshots:s,onSaveScreenshot:a.onSaveScreenshot,onStarted(){a.onStepStarted?.({index:l})},onSuccess({message:f,startedAt:h,durationMs:m,output:g}){a.onStepSuccess?.({index:l,message:f,startedAt:h,durationMs:m,output:g})},onFailure({message:f,startedAt:h,durationMs:m,output:g}){a.onStepFailure?.({index:l,message:f,startedAt:h,durationMs:m,output:g})},onCancelled({message:f,startedAt:h,durationMs:m,output:g}){a.onStepCancelled?.({index:l,message:f,startedAt:h,durationMs:m,output:g})}});break;case"AI_ACTION":d=await Ve({controller:r,context:e,step:c,advanced:o,logger:n,takeScreenshots:s,onSaveScreenshot:a.onSaveScreenshot,onStarted(){a.onStepStarted?.({index:l})},onSuccess({message:f,startedAt:h,durationMs:m,output:g}){a.onStepSuccess?.({index:l,message:f,startedAt:h,durationMs:m,output:g})},onFailure({message:f,startedAt:h,durationMs:m,output:g}){a.onStepFailure?.({index:l,message:f,startedAt:h,durationMs:m,output:g})},onCommandGenerated({commandIndex:f,message:h}){a.onCommandGenerated?.({index:l,commandIndex:f,message:h})},onCommandExecuted({commandIndex:f,command:h,output:m}){a.onCommandExecuted?.({index:l,commandIndex:f,command:h,output:m})}});break;default:return(f=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(c)}if(i.results.push(d),d.status==="FAILED"||d.status==="CANCELLED"){i.status=d.status,i.finishedAt=new Date,i.message=d.message;for(let p=l+1;p<t.steps.length;p++){let h={...t.steps[p],status:"CANCELLED",startedAt:new Date,finishedAt:new Date,userAgent:M.USER_AGENT,results:[],message:"Cancelled due to previous failure."};i.results.push(h)}break}}return i.status==="SUCCESS"?(i.data=i.results[i.results.length-1]?.data,a.onSuccess?.({message:"Executed module step.",startedAt:i.startedAt.getTime(),durationMs:i.finishedAt.getTime()-i.startedAt.getTime(),output:i})):i.status==="FAILED"?a.onFailure?.({message:"Failed to execute module step.",startedAt:i.startedAt.getTime(),durationMs:i.finishedAt.getTime()-i.startedAt.getTime(),output:i}):i.status==="CANCELLED"&&a.onCancelled?.({message:"Module step cancelled.",startedAt:i.startedAt.getTime(),durationMs:i.finishedAt.getTime()-i.startedAt.getTime(),output:i}),i};import{v4 as jp}from"uuid";var Gp=process.env.AWS_ACCESS_KEY_ID,Vp=process.env.AWS_SECRET_ACCESS_KEY,Wp=process.env.S3_REGION,Kp=process.env.S3_BUCKET;var Yo=async({test:r,runId:e,controller:t,context:o,logger:n,takeScreenshots:s,onUpdateRun:a,onSaveScreenshot:i,onTestSuccess:l})=>{try{let c=await Fi({test:r,runId:e,controller:t,context:o,logger:n,takeScreenshots:s,onUpdateRun:a,onSaveScreenshot:i});if(c==="PASSED"||c==="CANCELLED")return await l?.(r),c;throw new T("InternalPlatformError",c.message||"An unknown platform error occurred")}catch(c){throw c instanceof T||(c=new T("InternalPlatformError",c instanceof Error?c.message:`${c}`,{cause:c})),c}finally{await t.browser.cleanup()}},Fi=async({test:r,runId:e,controller:t,context:o,logger:n,takeScreenshots:s,onUpdateRun:a,onSaveScreenshot:i})=>{let l=pe.parse(r.advanced),c=n.child({runId:e,testId:r.id});c.info("Starting test run"),await a({status:"RUNNING",startedAt:new Date});let d,p="PASSED",f=[];for(let h=0;h<r.steps.length;h++){let m=r.steps[h];c.info(`Starting step ${h+1}/${r.steps.length}: ${yt(m)}`);let g=o.toObjectCopy(),S;switch(m.type){case"PRESET_ACTION":S=await We({controller:t,context:o,step:m,advanced:l,logger:c,takeScreenshots:s,onSaveScreenshot:i});break;case"AI_ACTION":S=await Ve({controller:t,context:o,step:m,advanced:l,logger:c,takeScreenshots:s,onSaveScreenshot:i});break;case"RESOLVED_MODULE":S=await Rt({controller:t,context:new V(await t.browser.url(),m.steps),step:m,advanced:l,logger:c,takeScreenshots:s,onSaveScreenshot:i});break;default:return(y=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(m)}if(f.push({...S,beforeTestContext:g,afterTestContext:o.toObjectCopy()}),o.setResult(h,S.data),await a({results:f}),S.status==="SUCCESS"){c.info(`Step ${h+1}/${r.steps.length} succeeded`);continue}if(S.status!=="FAILED"&&S.status!=="CANCELLED")throw new T("InternalPlatformError",`Received unexpected non-terminal status from step: ${S.status}`);c.info({message:f[f.length-1]?.message},`Step ${h+1}/${r.steps.length} ended with status: ${S.status}`),d=S,p=S.status;for(let v=h+1;v<r.steps.length;v++){let y=r.steps[v];if(y.type==="RESOLVED_MODULE"){let R={type:"MODULE",moduleId:y.moduleId,startedAt:new Date,userAgent:M.USER_AGENT,results:y.steps.map(E=>({...E,status:"CANCELLED",startedAt:new Date,finishedAt:new Date,userAgent:M.USER_AGENT,results:[]})),finishedAt:new Date,status:"CANCELLED"};f.push(R)}else{let R={...y,status:"CANCELLED",startedAt:new Date,finishedAt:new Date,userAgent:M.USER_AGENT,results:[]};f.push(R)}}break}return await a({status:p,finishedAt:new Date,results:f}),p==="FAILED"?d:p};var Xo={currentlyExecutingRequests:0},zi=r=>async e=>{let t=!1;try{Xo.currentlyExecutingRequests++,t=await Ui({...r,...e})}finally{r.logger.info({success:t,sessionId:r.metadata.sessionId},"Test execution complete"),Xo.currentlyExecutingRequests--}},Ui=async({socket:r,steps:e,baseUrl:t,testMetadata:o,reInitialize:n,indicesFilter:s,metadata:a,logger:i,rootState:l,localApp:c})=>{let{testId:d,sessionId:p,orgId:f}=a,h={testId:d,orgId:f,baseUrl:t},m=i.child(h),g=i.child({package:"web-agent",...h}),S=!0,v=!0;c==="iframe"&&(v=!1);let y=pe.parse(o.advanced??{}),R=$.getSession(p);if(!R)throw new Error("No active session found");let{controller:E,context:A}=R;E.setOpen();let F=M.USER_AGENT;m.info({reInitialize:n,context:A,steps:e,indicesFilter:s},"Starting execution");let ze={url:t,clearCookies:!0,clearStorage:!0};if(n&&(l?(E.browser.baseURL=t,E.browser.setActiveFrame(ft),await E.resetState(ze),E.setLogger(g),E.browser.setLogger(g)):await E.resetState(ze),E.setOpen(),A.reset(t)),m.info(`Session restarted for test ${d} at ${t}`),r.emit("session",{url:t,userAgent:F,viewport:await E.browser.viewport(),localApp:c}),A.setSteps(e),A.clearResults(),r.emit("testContext",{state:A.toObjectRef()}),s?.length){let Q=Hi(e,s);return m.info({step:Q},"Starting individual step"),r.emit("started",{indices:s}),S=await Jo({indices:s,step:Q,controller:E,context:A,advanced:y,socket:r,logger:m,takeScreenshots:v}),r.emit("testContext",{state:A.toObjectRef()}),r.emit("finished"),S}for(let Q=0;Q<e.length&&!(E.browser.closed||E.isClosed()||r.disconnected);Q++){let x=e[Q];m.info({index:Q,step:x},"Starting step");let is=await Jo({indices:[Q],step:x,controller:E,context:A,advanced:y,socket:r,logger:m,takeScreenshots:v});if(r.emit("testContext",{state:A.toObjectRef()}),!is){S=!1;break}}return r.emit("finished"),S},$i=(r,e)=>{switch(r.type){case"RESOLVED_MODULE":return r.steps[e];case"AI_ACTION":let t=r.commands[e];return{type:"PRESET_ACTION",command:t};case"PRESET_ACTION":default:throw new Error(`Cannot get a child step from step type ${r.type}`)}},Hi=(r,e)=>{let t=r[e[0]];for(let o=1;o<e.length;o++)t=$i(t,e[o]);return t},ur=async r=>r,Jo=async r=>{try{return await Bi(r)}catch(e){throw e instanceof T&&e.emitToUser&&r.socket.emit("error",{message:e.message}),e}},Bi=async({indices:r,step:e,controller:t,context:o,advanced:n,socket:s,logger:a,takeScreenshots:i})=>{let l=()=>{s.emit("started",{indices:r})},c=({message:h,startedAt:m,durationMs:g,command:S,output:v})=>{s.emit("success",{indices:r,message:h,startedAt:m,durationMs:g,command:S,output:ie.parse(v)})},d=({message:h,startedAt:m,durationMs:g,output:S})=>{s.emit("failure",{indices:r,message:h,startedAt:m,durationMs:g,output:ie.parse(S)})},p=({message:h,startedAt:m,durationMs:g,output:S})=>{s.emit("cancelled",{indices:r,message:h,startedAt:m,durationMs:g,output:ie.parse(S)})},f;switch(e.type){case"PRESET_ACTION":{f=await We({controller:t,context:o,step:e,advanced:n,logger:a,takeScreenshots:i,onSaveScreenshot:ur,onStarted:l,onSuccess:c,onFailure:d,onCancelled:p});break}case"AI_ACTION":{f=await Ve({controller:t,context:o,step:e,advanced:n,logger:a,takeScreenshots:i,onSaveScreenshot:ur,onStarted:l,onSuccess:c,onFailure:d,onCommandGenerated({commandIndex:m,message:g}){s.emit("commandGenerated",{indices:[...r,m],message:g})},onCommandExecuted({commandIndex:m,command:g,output:S}){s.emit("commandExecuted",{indices:[...r,m],command:g,output:ie.parse(S)})}});break}case"RESOLVED_MODULE":{f=await Rt({controller:t,context:new V(t.browser.baseURL,e.steps),step:e,advanced:n,logger:a,takeScreenshots:i,onSaveScreenshot:ur,onStarted:l,onSuccess:c,onFailure:d,onCancelled:p,onStepStarted({index:m}){s.emit("started",{indices:[...r,m]})},onStepSuccess({index:m,message:g,startedAt:S,durationMs:v,output:y}){s.emit("success",{indices:[...r,m],message:g,startedAt:S,durationMs:v,output:ie.parse(y)})},onStepFailure({index:m,message:g,startedAt:S,durationMs:v,output:y}){s.emit("failure",{indices:[...r,m],message:g,startedAt:S,durationMs:v,output:ie.parse(y)})},onStepCancelled({index:m,message:g,startedAt:S,durationMs:v,output:y}){s.emit("cancelled",{indices:[...r,m],message:g,startedAt:S,durationMs:v,output:ie.parse(y)})},onCommandGenerated({commandIndex:m,index:g,message:S}){s.emit("commandGenerated",{indices:[...r,g,m],message:S})},onCommandExecuted({index:m,commandIndex:g,command:S,output:v}){s.emit("commandExecuted",{indices:[...r,m,g],command:S,output:ie.parse(v)})}});break}default:return(m=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(e)}return o.setResult(r[0],f.data),f.status==="SUCCESS"};var Qo={event:"execute",createHandler:zi};var ji=({metadata:r,logger:e,socket:t})=>{let{sessionId:o}=r;return async({description:n,useVision:s,testMetadata:a,isSelect:i},l)=>{let c=$.getSession(o);if(!c)throw new Error("No active session found");let{controller:d,context:p}=c,f=pe.parse(a.advanced??{}),h,m;try{if(h=await d.locateElement(n,s,f.disableAICaching,p),m={id:h.id},await d.browser.resolveCachedTargetToID(m),i)try{h.options=await d.browser.getSelectOptions(h.id)}catch(g){e.warn({err:g},"Failed getting select options"),t.emit("error",{message:`Failed getting select options: ${g}`})}}catch(g){e.error({err:g},"Error locating element"),t.emit("error",{message:g instanceof Error?g.message:`${g}`}),l({found:!1});return}e.info({locator:h,a11yData:m},"Located element result");try{await d.browser.scrollIntoView({id:h.id}),await d.browser.highlight({id:h.id})}catch(g){e.error({err:g,locator:h},"Error highlighting element"),l({found:!1});return}l({found:!0,locator:h,a11yData:m})}},Zo={event:"locate",createHandler:ji};var Gi=({socket:r,metadata:e,logger:t})=>{let{sessionId:o}=e;return async({action:n,indices:s},a)=>{try{let i=$.getSession(o);if(!i)throw new Error("No active session found");let{controller:l}=i;n==="START"&&t.info({sessionId:o},"Starting record mode"),await l.toggleRecordMode({action:n,indices:s,onStepRecord:(c,d)=>{r.emit("stepRecorded",{step:c,indices:d})}}),a()}catch(i){t.error({err:i},"Error in record socket handler"),a(i instanceof Error?i.message:`${i}`)}}},en={event:"record",createHandler:Gi};var Vi=({socket:r,metadata:e,rootState:t,logger:o,localApp:n})=>async({baseUrl:s})=>{let{testId:a,sessionId:i}=e;o.info({testId:a,sessionId:i,baseUrl:s},"Reset event received");let l;if(t?.controller){let{controller:d,context:p}=t;await d.resetState({clearCookies:!0,clearStorage:!0,url:s}),l=d.browser.baseURL,p.reset(l)}else{let d=$.getSession(i);if(!d){r.emit("error",{message:"No session to reset"});return}let{controller:p,context:f}=d;await p.browser.reset({clearCookies:!0,clearStorage:!0,url:s}),l=p.browser.baseURL,f.reset(l)}let c=M.USER_AGENT;o.info(`Session reset for test ${a} at ${l}`),r.emit("session",{url:l,userAgent:c,viewport:M.VIEWPORT,localApp:n})},tn={event:"reset",createHandler:Vi};import{v4 as ia}from"uuid";var Wi={type:"a11y",version:"1.0.0",useHistory:"diff",useGoalSplitter:!0},De=Wi;import{existsSync as ea}from"fs";import{faker as Ki}from"@faker-js/faker";import qi from"assert";import Yi from"axios";import Xi from"moment";import Ji from"p-timeout";var Qi=Object.getPrototypeOf(async function(){}).constructor;async function rn({code:r,fragment:e,state:t,timeoutMs:o=1e4}){let n=r;e&&(n=`return ${r}`);let s=JSON.parse(JSON.stringify(t)),a=(c,d)=>{s.env[c]=d,t.env[c]=d},i;return await Ji((async()=>{i=await Promise.resolve(new Qi("axios","moment","faker","assert","env","steps","results","setVariable",n)(Yi,Xi,Ki,qi,s.env,s.steps,s.results,a))})(),{milliseconds:o,message:`Timeout of ${o}ms exceeded for code execution`}),i}import{z as Pe}from"zod";var on=process.env.GCP_JS_EVAL_FUNCTION_ENDPOINT,Zi=Pe.object({result:Pe.unknown(),variableUpdates:Pe.record(Pe.string(),Pe.unknown()).optional(),error:Pe.string().optional(),success:Pe.boolean()});async function nn({code:r,fragment:e,state:t,logger:o,timeoutMs:n=1e4}){let s=new AbortController;if(!on)throw new Error("GCP_JS_EVAL_FUNCTION_ENDPOINT environment variable not set");let a=setTimeout(()=>{s.abort()},n),i=await fetch(on,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({code:r,fragment:e,state:t}),signal:s.signal});if(!i.ok)throw new Error(`Code evaluation server returned error code: ${i.status}`);clearTimeout(a);let l;try{l=Zi.parse(await i.json()),o.debug({response:l},"Response from code evaluation server")}catch(c){throw new Error(`Code evaluation server returned invalid response: ${c}`)}if(l.error)throw new Error(`Code evaluation server returned error: ${l.error}`);if(l.variableUpdates)for(let[c,d]of Object.entries(l.variableUpdates))t.env[c]=d;return l.result}var ta=ra();function ra(){if(process.env.NODE_ENV!=="production")return!1;if(process.env.NAMESPACE)return!0;try{if(ea("/var/run/secrets/kubernetes.io/serviceaccount/token"))return!0}catch{}return!1}async function pr(r){let e;return ta?(r.logger.info({code:r.code,fragment:r.fragment},"Running code remotely"),e=nn):(r.logger.info({code:r.code,fragment:r.fragment},"Running code locally"),e=rn),e(r)}async function hr({s:r,state:e,logger:t,timeoutMs:o=1e3}){let n=/{{(.*?)}}/g,s=r.matchAll(n),a=r;for(let i of s){if(i.length<2)continue;let l=i[1].trim(),c=await pr({code:l,fragment:!0,state:e,timeoutMs:o,logger:t}),d=typeof c=="string"?c:`${c}`;a=a.replace(i[0],d)}return a}import oa from"dedent";import na from"diff-lines";var xt=class r{static ACTION_DEBOUNCE_DELAY=1e3;onStepRecord;signal;generator;indices;keyBuffer=[];timer=null;keyBufferMode="normal";lastStep=null;constructor({signal:e,onStepRecord:t,generator:o,initialIndices:n}){this.signal=e,this.onStepRecord=(s,a)=>{this.lastStep={step:s,indices:a},t(s,a)},this.generator=o,this.indices=n}async recordClick(e,t){if(this.signal.aborted)return;let o=lt("CLICK"),n={type:"PRESET_ACTION",command:{...o,target:{elementDescriptor:`Generating description for: ${e.nodeOnlySerializedForm?.trim()??`element with id ${e.id}`}`,a11yData:e}}},s=Array.from(this.indices);this.indices[this.indices.length-1]++,this.onStepRecord(n,s);let a=await this.generator.getReverseMappedDescription({goal:`${e.id}`,browserState:t},!1);n.command.target.elementDescriptor=a.phrase,this.onStepRecord(n,s)}flushKeyBufferToCommand(){let e;if(this.keyBufferMode==="normal"){let n=lt("TYPE");e={type:"PRESET_ACTION",command:{...n,target:void 0,value:this.keyBuffer.join("")}}}else{let n=lt("PRESS");e={type:"PRESET_ACTION",command:{...n,value:this.keyBuffer.join("+")}}}let t,o=this.lastStep?.step.command;if(o?.type===e.command.type){let n=o.value,s=e.command.value;e={type:"PRESET_ACTION",command:{...o,value:e.command.type==="PRESS"?`${n}+${s}`:`${n}${s}`}},t=this.lastStep.indices}else o?.type==="CLICK"&&e.command.type==="TYPE"?(this.lastStep.step.command={...e.command,target:o.target},e=this.lastStep.step,t=this.lastStep.indices):(t=Array.from(this.indices),this.indices[this.indices.length-1]++);this.onStepRecord(e,t),this.keyBuffer=[],this.timer=null}recordKeystroke(e){if(e==="Shift"||e==="CapsLock"||this.signal.aborted)return;this.timer!==null&&clearTimeout(this.timer);let t="normal";e.length>1&&(t="special"),this.keyBuffer.length!==0&&t!==this.keyBufferMode&&this.flushKeyBufferToCommand(),this.keyBufferMode=t,this.keyBuffer.push(e),this.timer=setTimeout(()=>{this.flushKeyBufferToCommand()},r.ACTION_DEBOUNCE_DELAY)}};var fr=(r,e,t)=>{let[o,...n]=e.split("."),s=o;return n.length>=1?fr(r[s],n.join("."),t):r[s]=t,r};var sa=1e4,me=class{browser;pendingInstructions;generator;commandHistory;config;closed=!1;logger;recordAbortController=null;constructor({browser:e,config:t,generator:o,logger:n}){this.browser=e,this.generator=o,this.config=t,this.logger=n,this.pendingInstructions=[],this.commandHistory=[]}get history(){return this.commandHistory.filter(e=>e.state==="DONE")}get lastExecutedCommand(){let e=this.history;return e.length===0?null:e[e.length-1]}setOpen(){this.closed=!1}isClosed(){return this.closed}setLogger(e){this.logger=e}resetHistory(){this.commandHistory=[],this.pendingInstructions=[]}async resetState(e){this.resetHistory(),this.closed=!0,await this.browser.reset(e)}async getBrowserState(){let t=await(await this.browser.getA11yTree()).serialize();return this.logger.debug({tree:t,activeFrame:this.browser.getActiveFrame()},"Got a11y tree"),t}getSerializedHistory(e,t){let o;return this.config.useHistory==="diff"?o=this.getDiffHistory(e,t):o=this.getListHistory(),o}async splitUserGoal(e,t,o){if(e==="AI_ACTION"&&t.match(/[,!;.]|(?:and)|(?:then)/)&&this.config.useGoalSplitter){let n=await this.generator.getGranularGoals({goal:t,url:await this.browser.url()},o);this.pendingInstructions=n.reverse()}else this.pendingInstructions=[t]}async promptToCommand(e,t,o){try{return await this.promptToCommandHelper(e,t,o)}catch(n){throw n instanceof T?n:new T("InternalWebAgentError",n instanceof Error?n.message:`${n}`,{cause:n})}}async promptToCommandHelper(e,t,o){if(this.pendingInstructions.length===0){if(!t.trim())throw new Error("Cannot generate commands for empty goal");await this.splitUserGoal(e,t,o)}let n=this.pendingInstructions[this.pendingInstructions.length-1];this.logger.info({goal:n},"Starting prompt translation");let s=Date.now(),a=await this.browser.url(),i=await this.getBrowserState();this.logger.info({duration:Date.now()-s,url:a},"Got browser state");let l=this.commandHistory.length;this.commandHistory.push({state:"PENDING",browserStateBeforeCommand:i,urlBeforeCommand:a,type:e});let c=this.getSerializedHistory(a,i),d={url:a,numPrevious:l,browserState:i,history:c,goal:n,lastCommand:this.lastExecutedCommand},p=await this.generator.getProposedCommand(d,o);if(this.logger.info({type:p.type,thoughts:p.thoughts},"Got proposed command"),p.type==="SUCCESS"){let f=this.pendingInstructions.pop();if(this.logger.info({finishedInstruction:f,remainingInstructions:this.pendingInstructions},"Removing pending instruction due to SUCCESS"),this.pendingInstructions.length!==0)return this.commandHistory=[],this.promptToCommand(e,"",o)}else p.type==="FAILURE"&&(this.logger.info({remainingInstructions:this.pendingInstructions},"Removing pending instructions due to FAILURE"),this.pendingInstructions=[]);return{context:d,command:p}}async locateElement(e,t,o,n){if(!e)throw new T("ActionFailureError","Cannot locate element with empty description");n&&(e=await hr({s:e,state:n.toObjectRef(),logger:this.logger}));let s=await this.getBrowserState(),a;if(t){let{before:i,after:l}=await this.browser.screenshotWithHints();if(a=await this.generator.getElementLocationWithVision({goal:e,screenshot:i,hintActivatedScreenshot:l},o),a.id>0){let c=this.browser.getA11yIdFromDataMomenticId(a.id);if(!c)throw new T("InternalWebAgentError",`Unable to find corresponding DOM node for id ${a.id}`);a.id=c}}else a=await this.generator.getElementLocation({browserState:s,goal:e},o);if(a.id<0)throw new T("ActionFailureError",`Unable to locate element: ${a.thoughts?a.thoughts:"please ensure the element is visible and conforms to Accessibility guidelines"}`);return a}getDiffHistory(e,t){let o=this.history.filter(s=>s.type==="AI_ACTION");if(o.length===0)return"<NONE/>";let n=[`
24
+ ${g||""}`}return p}},ar=class{constructor(e,t,o){this.root=e;this.a11yIdNodeMap=t;this.dataMomenticIdMap=o}serialize(){return this.root?this.root.serialize():""}};function ui(r){return r.name?.value?`"${r.name.value}"`:r.role?.value&&r.role.value!=="none"&&r.role.value!=="generic"?`"${r.role.value}"`:`"${r.nodeId}"`}function Oo(r,e,t){if(!e&&r.parentId)throw new Error(`Got no parent for accessibility node ${r.nodeId}: ${JSON.stringify(r)}`);let o=new ir({id:parseInt(r.nodeId),role:r.role?.value||"",name:r.name?.value?typeof r.name.value=="string"?r.name.value:`${r.name.value}`:"",content:r.value?.value?typeof r.value.value=="string"?r.value.value:`${r.value.value}`:"",properties:r.properties,children:[],pathFromRoot:(e?`${e.pathFromRoot} `:"")+ui(r),backendNodeID:r.backendDOMNodeId,ignoredByCDP:r.ignored}),n=r.childIds??[];for(let i of n){if(!i)continue;let l=t.get(parseInt(i));if(!l)continue;let c=Oo(l,o,t);c.length&&(o.children=o.children.concat(c))}if(o.role==="StaticText"&&(o.children=[]),o.children.length===1&&o.children[0].role==="StaticText"){let i=o.name,l=o.children[0]?.name;(i===l||!l)&&(o.children=[])}let s=[];for(let i=o.children.length-1;i>=0;i--){let l=o.children[i];if(l.role!=="StaticText"){s.push(l);continue}if(i===0||o.children[i-1].role!=="StaticText"){s.push(l);continue}o.children[i-1].name+=` ${l.name}`}if(o.children=s.reverse(),o.role==="generic"&&o.children.length===1){let i=o.children[0];if(o.name&&!lr.includes(i.role)&&o.name===i.name)return o.children}if(!o.isInteresting()&&r.parentId)return o.children;for(let i of o.children)i.parent=o;return[o]}function Mo(r,e,t,o,n=1){r.id=n,n+=1,e.set(r.id,r),r.dataMomenticId?t.set(r.dataMomenticId,r):lr.includes(r.role)||o.debug({node:r.serialize({neighbors:1,maxLevel:1})},"Node has no data-momentic-id");for(let s of r.children)n=Mo(s,e,t,o,n);return n}function No(r,e){if(!r.root)throw new Error("a11y tree has null root");r.allNodes=r.allNodes.filter(a=>a.ignored?!a.ignoredReasons?.find(l=>ci.includes(l.name)):!0);let t=new Map;for(let a of r.allNodes)t.set(parseInt(a.nodeId),a);let o=Oo(r.root,null,t);if(o.length>1)throw new Error(`Something went horribly wrong processing the a11y tree, we got: ${JSON.stringify(o)}`);if(o.length===0)throw new ct;let n=new Map,s=new Map;return Mo(o[0],n,s,e),new ar(o[0],n,s)}var Ze=(r,e)=>{e.id=r.id,e.content=r.content,e.name=r.name,e.role=r.role,e.numChildren=r.children.length,e.serializedForm=r.serialize({noID:!0,maxLevel:1,neighbors:1}),e.nodeOnlySerializedForm=r.serialize({noID:!0,noChildren:!0,noProperties:!0})},cr=(r,e)=>{let t=1;r.role===e.role&&t++;let o=["name","content"];for(let n of o){let s=r[n];if(typeof s!="string"||!s.trim())continue;let a=sr(s,e[n])/Math.min(s.length,e[n].length);a===0?t+=2:a<=.1&&t++}if(e.numChildren!==void 0&&(r.children.length===e.numChildren&&e.numChildren>0?t++:(e.numChildren>0&&r.children.length===0||Math.abs(r.children.length-e.numChildren)>2)&&t--),e.nodeOnlySerializedForm){let n=r.serialize({noID:!0,noChildren:!0,noProperties:!0});sr(n,e.nodeOnlySerializedForm)/Math.min(n.length,e.nodeOnlySerializedForm.length)<=.1&&t++}if(e.serializedForm){let n=r.serialize({noID:!0,maxLevel:1,neighbors:1}),s=sr(n,e.serializedForm)/Math.min(n.length,e.serializedForm.length);s===0?t+=2:s<=.1&&t++}return t};var Oe={r:147,g:196,b:125,a:.55},Do={showInfo:!1,showRulers:!1,showStyles:!1,showAccessibilityInfo:!1,showExtensionLines:!1,contrastAlgorithm:"aa",contentColor:Oe,paddingColor:Oe,borderColor:Oe,marginColor:Oe,eventTargetColor:Oe,shapeColor:Oe,shapeMarginColor:Oe};var B=(r=1e3)=>new Promise(e=>setTimeout(()=>e(),r));function Po(){window.cursor||(window.cursor=document.createElement("img"),window.cursor.setAttribute("src","data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjMyIiB2aWV3Qm94PSIwIDAgMzIgMzIiIHdpZHRoPSIzMiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwIDcpIj48cGF0aCBkPSJtNi4xNDggMTguNDczIDEuODYzLTEuMDAzIDEuNjE1LS44MzktMi41NjgtNC44MTZoNC4zMzJsLTExLjM3OS0xMS40MDh2MTYuMDE1bDMuMzE2LTMuMjIxeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Im02LjQzMSAxNyAxLjc2NS0uOTQxLTIuNzc1LTUuMjAyaDMuNjA0bC04LjAyNS04LjA0M3YxMS4xODhsMi41My0yLjQ0MnoiIGZpbGw9IiMwMDAiLz48L2c+PC9zdmc+"),window.cursor.setAttribute("id","momentic_cursor"),window.cursor.setAttribute("style","position: absolute; z-index: 99999999999; pointer-events: none; left:0; top:0; height:4%;"),window.cursor.style.filter="invert(0%) sepia(6%) saturate(24%) hue-rotate(315deg) brightness(89%) contrast(110%)",document.body.appendChild(cursor),document.onmousemove=function(r){r=r||window.event,document.getElementById("momentic_cursor").style.left=r.pageX+"px",document.getElementById("momentic_cursor").style.top=r.pageY+"px"})}function _o(){window.globalHintManager||(window.globalHintManager=new window.HintManager),window.globalHintManager.capture()}function ko(){window.globalHintManager&&window.globalHintManager.reset()}function Fo(){let r=document.body.getElementsByTagName("*"),e=1;for(let t=0;t<r.length;t++){let o=e.toString();for(;[6].some(s=>o.includes(s.toString()));)e++,o=e.toString();let n=r[t];n?.setAttribute("data-momentic-id",`${e}`),n?.setAttribute("aria-keyshortcuts",`${e}`),e++}}var pi=new Set(["document","script","XMLHttpRequest","fetch","xhr"]),hi=new Set(["script","document"]),fi=["intercom.io","googletagmanager.com","google-analytics.com","www.gstatic.com","gstatic.com","apis.google.com","sentry.io","newrelic.com","p.retool.com","m.stripe.com","m.stripe.network","js.stripe.com","assets.trybento.co","udon.trybento.co","cdn.lr-in-prod.com","r.lr-in-prod.com","content.product-usage.assembledhq.com","data.product-usage.assembledhq.com","static.zdassets.com","o.clarity.ms/collect"],gi=["api.stripe.com","supabase.co"],yi=[];async function Uo(r){for(let e of yi)await r.route(e,t=>t.abort())}function dr(r){return`${r.resourceType()} ${r.method()} ${r.url()}`}function zo(r){return r=r.replace(/^www\./,""),r}function $o(r){return gi.some(e=>r.includes(e))}function Ho(r,e){if(!pi.has(r.resourceType()))return!1;let t=new URL(e),o=new URL(r.url());return fi.some(n=>o.hostname.includes(n))?!1:hi.has(r.resourceType())||r.method()!=="GET"?!0:zo(o.hostname).includes(zo(t.hostname))}var Bo=()=>{window.clickListener&&document.removeEventListener("click",window.clickListener,{capture:!0}),window.clickListener=async r=>{console.log("[Momentic] Window click listener fired",r.target),await window.captureClick(r.target)},document.addEventListener("click",window.clickListener,{capture:!0})};var jo=()=>{window.pressListener&&document.removeEventListener("keydown",window.pressListener,{capture:!0}),window.pressListener=async r=>{console.log("[Momentic] Window press listener fired"),await window.captureKeystroke({key:r.key})},document.addEventListener("keydown",window.pressListener,{capture:!0})};var Ct=Di(Ni);Ct.use(_i());Ct.use(Pi({provider:{id:"2captcha",token:process.env.TWO_CAPTCHA_KEY},visualFeedback:!0}));var M=class r{browser;context;page;a11yIdToNodeMap=new Map;dataMomenticIdToNodeMap=new Map;cdpClient;logger;localMode;activeFrame;transformer;mostRecentBrowserState;baseURL;constructor({browser:e,context:t,page:o,baseUrl:n,logger:s,localMode:a,cdpClient:i}){this.browser=e,this.context=t,this.cdpClient=i,this.page=o,this.baseURL=n,this.logger=s,this.localMode=!!a}static USER_AGENT=Ko["Desktop Chrome"].userAgent;static VIEWPORT={width:1920,height:1080};static async init({baseUrl:e,logger:t,browserArgs:o,contextArgs:n,takeScreenshots:s,onScreenshot:a,onClose:i,waitForLoad:l,localMode:c,localAppUrl:d,extensionPath:p,skipPageSetup:f,timeout:h=8e3}){let m={headless:!0,handleSIGTERM:!1,chromiumSandbox:!1,...o??{}},g={viewport:r.VIEWPORT,deviceScaleFactor:process.platform==="darwin"?2:1,userAgent:Ko["Desktop Chrome"].userAgent,geolocation:{latitude:37.7749,longitude:-122.4194},locale:"en-US",timezoneId:"America/Los_Angeles",...n??{}},S=null,v,y;if(c){let F=Mi(Oi(),"momentic","chromium");v=await Ct.launchPersistentContext(F,{...m,...g,ignoreDefaultArgs:["--enable-automation","--enable-strict-mixed-content-checking"],ignoreHTTPSErrors:!0,bypassCSP:!0,args:["--allow-insecure-localhost","--disable-site-isolation-for-policy","--disable-site-isolation-trials",`--unsafely-treat-insecure-origin-as-secure=${d}`,`--load-extension=${p}`,"--test-type=browser"],baseURL:e}),y=v.pages()[0],i&&y.on("close",()=>{i()})}else S=await Ct.launch(m),v=await S.newContext({...g,baseURL:e}),y=await v.newPage();await Uo(v);let R=new r({browser:S,context:v,page:y,baseUrl:e,logger:t,localMode:c,cdpClient:await v.newCDPSession(y)}),E=!1,A=async()=>{try{await R.navigate({url:e,wrapPossibleNavigation:!1,initialNavigation:!f,timeout:h})}catch(F){if(t.error({err:F},"Failed to initialize chrome browser"),l)throw F}finally{E=!0}};if(l?await A():A(),a){let F=async()=>{if(s)try{a({viewport:await R.viewport(),buffer:await R.screenshot()})}catch(x){t.error({err:x},"Failed to take screenshot")}};F();let ze=setInterval(()=>{F()},400),Q=Date.now();for(;!E&&Date.now()-Q<(h??1/0);)await B(250);clearInterval(ze)}return R}async getUserPageOrFrame(){if(this.localMode&&this.activeFrame){let e=null,t=0;for(;!e&&t<5;)e=this.page.frame(this.activeFrame),await B(100),t++;if(!e)throw new T("InternalWebAgentError",`Failed to find frame ${this.activeFrame} on page ${this.page.url()}`);return e}return this.page}async initCDPSession(e=3e3){let t=!1,o=e===null?1/0:2,n=async()=>{try{this.cdpClient=await this.context.newCDPSession(this.page),await this.cdpClient.send("Accessibility.enable"),await this.cdpClient.send("DOM.enable"),await this.cdpClient.send("Overlay.enable"),t=!0}catch(s){if(o>0)return this.logger.warn({err:s},"Failed to initialize CDP session, re-creating CDP client"),await B(250),o--,n();throw s}};return e===null?await n():await Promise.race([n(),B(e)]),t}setLogger(e){this.logger=e}ping(){if(this.closed)throw new Error("Page has been closed");if(this.browser&&!this.browser.isConnected())throw new Error("Browser is not connected")}setActiveFrame(e){this.activeFrame=e}async reset(e){this.a11yIdToNodeMap.clear(),this.dataMomenticIdToNodeMap.clear();let t=this.context.pages();this.page=t[0];for(let o=1;o<t.length;o++)await t[o].close();if(e.clearCookies&&await this.context.clearCookies(),!this.page.isClosed()){if(e.clearStorage)try{await(await this.getUserPageOrFrame()).evaluate(async()=>{window.localStorage.clear(),window.sessionStorage.clear(),await indexedDB.databases().then(o=>{o.forEach(n=>{n.name&&indexedDB.deleteDatabase(n.name)})})})}catch(o){this.logger.warn({err:o},"Failed clearing site data, continuing...")}await this.navigate({url:e.url??this.baseURL,wrapPossibleNavigation:!1,initialNavigation:!0,timeout:e.timeout})}}async pageSetup(){try{this.localMode||await this.page.evaluate(Po)}catch{}}async wait(e){await B(e)}async toggleHints(e){let t=await this.getUserPageOrFrame();e.state==="on"?(await t.addStyleTag({content:At.css}),await t.addScriptTag({content:At.js}),await t.evaluate(_o)):await t.evaluate(ko)}async showHints(){await this.toggleHints({state:"on"});let e=async()=>{try{await this.toggleHints({state:"off"})}catch(t){this.logger.debug({err:t},"Failed to remove vision hints")}};setTimeout(()=>{e()},3e3)}async cleanup(){await this.page.close(),await this.context.close(),this.browser&&await this.browser.close()}get closed(){return this.page.isClosed()||!!this.browser&&!this.browser.isConnected()}async html(){return(await this.getUserPageOrFrame()).content()}async url(){return(await this.getUserPageOrFrame()).url()}async screenshotWithHints(e=100,t="device",o="/tmp/screenshots/test.jpg"){let n=o?.split("."),s=n?.slice(0,-1).join("."),a=n?.slice(-1)[0],i=Buffer.from("");await this.showHints();let l=await this.screenshot(e,t,o?`${s}-after-hint.${a}`:void 0);return{before:i,after:l}}async screenshot(e=100,t="device",o){let n={fullPage:!1,quality:e,scale:t,type:"jpeg",caret:"initial",path:o};return!this.localMode||!this.activeFrame?this.page.screenshot(n):this.page.locator(`iframe[name="${this.activeFrame}"]`).screenshot(n)}async viewport(){if(this.localMode&&this.activeFrame){let t=await this.page.locator(`iframe[name="${this.activeFrame}"]`).boundingBox();if(!t)throw new Error(`Failed to get bounding box for frame: ${this.activeFrame}`);return t}let e=this.page.viewportSize();if(!e)throw new Error("failed to get viewport");return e}async navigate({url:e,wrapPossibleNavigation:t=!0,initialNavigation:o=!1,timeout:n=8e3}){this.logger.debug(`Navigating to ${e}`);let s=Date.now(),a=async()=>{try{await(await this.getUserPageOrFrame()).goto(e,{waitUntil:"load",timeout:n??void 0}),this.logger.debug({url:e},`Got load event in ${Math.floor(Date.now()-s)}ms`)}catch(l){if(l instanceof Error&&l.message.includes("ERR_ABORTED"))return;this.logger.warn({url:e,err:l},"Timeout elapsed waiting for page to fire load event. Are you sure this page is accessible?")}};t?await this.wrapPossibleNavigation(a):await a();let i=await this.url();if(Ro.has(i)&&process.env.NODE_ENV==="production")throw new T("ActionFailureError",`${e} took too long to load \u{1F61E}. Please ensure the site and your internet are working.`,{},!0);if(o){if(!await this.initCDPSession(n))throw new T("ActionFailureError",`Initializing libraries timed out on ${e} \u{1F61E}. Please ensure the site and your internet are working.`,{},!0);if(await this.pageSetup(),await this.attachPageLoadListeners(),this.localMode)try{await this.exposeRecordingBindings()}catch(c){c instanceof Error&&c.message.includes("already registered")||this.logger.error({err:c},"Failed to expose recording bindings during navigation")}}this.logger.info({url:e},"Navigation complete")}async type(e,t={}){let{clearContent:o=!0,pressKeysSequentially:n=!1}=t;o&&(process.platform==="darwin"?await this.page.keyboard.press("Meta+A"):await this.page.keyboard.press("Control+A"),await this.page.keyboard.press("Backspace")),n?await this.page.keyboard.type(e):await this.page.keyboard.insertText(e)}async clickByA11yID(e,t={}){let o=this.a11yIdToNodeMap.get(e);if(!o)throw new Error(`Could not find DOM node during click: ${e}`);let n=await this.clickUsingCDP(o,t);return await this.highlightNode(n),o.serialize({noChildren:!0,noID:!0})}async selectOptionByA11yID(e,t){let o=this.a11yIdToNodeMap.get(e);if(!o)throw new Error(`Could not find DOM node while selecting option: ${e}`);if(!o.backendNodeID)throw new Error(`Select target missing backend node id: ${o.getLogForm()}`);return await(await this.getLocatorFromBackendID(o.backendNodeID)).selectOption(t,{timeout:8e3}),await this.highlightNode(o),o.serialize({noChildren:!0,noID:!0})}async scrollIntoView(e){let t=await this.resolveCachedTargetToID(e),o=this.a11yIdToNodeMap.get(t);if(!o)throw new Error(`Could not find node in DOM with a11y id: ${t}`);if(!o.backendNodeID)throw new Error(`Focus target missing backend node id: ${o.getLogForm()}`);await(await this.getLocatorFromBackendID(o.backendNodeID)).scrollIntoViewIfNeeded({timeout:8e3})}async highlight(e){try{let t=await this.resolveCachedTargetToID(e),o=this.a11yIdToNodeMap.get(t);if(!o)throw new Error(`Could not find DOM node during highlight: ${t}`);if(!o.backendNodeID)throw new Error(`Highlight target missing backend node id: ${o.getLogForm()}`);await this.highlightNode(o)}catch(t){this.logger.warn({err:t,target:e},"Failed to highlight target")}}async highlightNode(e){try{await this.cdpClient.send("Overlay.highlightNode",{highlightConfig:Do,backendNodeId:e.backendNodeID})}catch{this.logger.warn("Failed to add node highlight, a page navigation likely occurred. This is non-fatal for tests.")}let t=async()=>{try{await this.cdpClient.send("Overlay.hideHighlight",{backendNodeId:e.backendNodeID})}catch(o){this.logger.debug({err:o},"Failed to remove node highlight")}};setTimeout(()=>{t()},3e3)}async wrapPossibleNavigation(e,t=8e3,o=!0){let n=Date.now(),s=await this.url(),a=Date.now(),i=new Map,l=new Map,c=E=>{let A=dr(E.request());l.set(A,(l.get(A)??0)+1);let F=E.status();F>=500&&this.logger.warn({request:A,status:F},"Received 500 level response")},d=E=>{if(!Ho(E,s))return;let A=dr(E);i.set(A,(i.get(A)??0)+1),a=Date.now()};this.page.on("response",c),this.page.on("request",d);let p=[];o&&(p=this.context.pages().map(E=>E.url()));let f=!1,h=e().catch(E=>(f=!0,E instanceof Error?E:new Error(`${E}`)));await B(250);let m=async E=>{let A=await E;if(A instanceof Error)throw A;return A},g=new Set,S=!1,y=await(async()=>{for(;!f&&!(!S&&Date.now()-n>t);){if(await B(250),S=!1,g=new Set,Date.now()-a<=1250)continue;let E=!1;for(let A of i.keys())i.get(A)!==l.get(A)&&($o(A)&&(S=!0),E=!0,g.add(A));if(!E)return this.logger.debug({url:await this.url(),requests:JSON.stringify(Array.from(i.entries()))},`Network idle in ${Math.floor(Date.now()-n)}ms`),!0}return!f&&g.size>0&&this.logger.warn({url:await this.url(),unfinishedRequests:JSON.stringify(Array.from(g.entries()))},"Timeout elapsed waiting for network idle, continuing anyways..."),!1})();if(this.page.off("response",c),this.page.off("request",d),!y)return m(h);let R=await this.url();if(!f&&Le(R,s)){this.logger.debug({startUrl:s,newUrl:R},"Detected url change in wrapPossibleNavigation, waiting for load state");let E=Math.max(t-(Date.now()-n),0);if(E>0)try{await(await this.getUserPageOrFrame()).waitForLoadState("load",{timeout:E})}catch{this.logger.warn({url:await this.url()},"Timeout elapsed waiting for load state, continuing anyways...")}}if(o){let E=this.context.pages().map(A=>A.url());if(E.length>p.length)for(let A of E)A!==R&&await this.switchToPage(A)}return m(h)}async resolveCachedTargetToID(e){if(!at(e)){let i=this.a11yIdToNodeMap.get(e.id);if(!i)throw new Error(`Resolving target failed, fresh value did not exist in node map: ${e.id}`);return Ze(i,e),e.id}let t=(await this.getA11yTree()).serialize();this.logger.debug({tree:t},"Refreshed a11y tree before resolving target");let o=this.a11yIdToNodeMap.get(e.id);if(o){let i=cr(o,e);if(i>=5)return this.logger.debug({target:e,proposedNode:o.getLogForm(),comparisonScore:i},"Resolved cached a11y target to node with exact same id"),Ze(o,e),e.id}let n=1/0,s=1/0,a;for(let i of this.a11yIdToNodeMap.values()){let l=cr(i,e);if(l>=5)return this.logger.debug({newNode:i.getLogForm(),target:e,comparisonScore:l},"Resolved cached a11y target to new node with field comparison"),Ze(i,e),i.id;if(!e.serializedForm)continue;let c=i.serialize({noID:!0,maxLevel:1,neighbors:1});if(Math.abs(c.length-e.serializedForm.length)>15)continue;let d=Ii(e.serializedForm,c),p=d/Math.min(e.serializedForm.length,c.length);d<n&&p<.2&&(n=d,s=p,a=i)}if(a&&n<15)return this.logger.debug({newNode:a.getLogForm(),target:e,distance:n,ratio:s},"Resolved cached a11y target to new node with pure levenshtein distance"),Ze(a,e),a.id;throw new Error(`Could not find any relevant node given cached target: ${JSON.stringify({target:e,closestLevenshteinDistance:n,closestNode:a})}`)}async click(e,t={}){let o=await this.resolveCachedTargetToID(e);return await this.wrapPossibleNavigation(()=>this.clickByA11yID(o,t))}async dragAndDrop(e,t,o={}){let n=await this.resolveCachedTargetToID(e),s=this.a11yIdToNodeMap.get(n);if(!s||!s.backendNodeID)throw new Error(`Could not find DOM node for drag and drop 'from' target: ${n}`);let a=await this.getLocatorFromBackendID(s.backendNodeID),i=await this.resolveCachedTargetToID(t),l=this.a11yIdToNodeMap.get(i);if(!l||!l.backendNodeID)throw new Error(`Could not find DOM node for drag and drop 'to' target: ${i}`);let c=await this.getLocatorFromBackendID(l.backendNodeID);return await a.hover(o),await this.page.mouse.down(),await c.hover(o),await B(250),await this.page.mouse.up(),s.serialize({noChildren:!0,noID:!0})}async hover(e){let t=await this.resolveCachedTargetToID(e),o=this.a11yIdToNodeMap.get(t);if(!o)throw new Error(`Could not find DOM node for hover: ${t}`);if(!o.backendNodeID)throw new Error(`Hover target missing backend node id: ${o.getLogForm()}`);return await(await this.getLocatorFromBackendID(o.backendNodeID)).hover({timeout:8e3}),await this.highlightNode(o),o.serialize({noChildren:!0,noID:!0})}async selectOption(e,t){let o=await this.resolveCachedTargetToID(e);return this.selectOptionByA11yID(o,t)}async press(e){await this.wrapPossibleNavigation(()=>this.page.keyboard.press(e))}async refresh(){if(this.localMode&&this.activeFrame){let t=(await this.getUserPageOrFrame()).url();await this.navigate({url:t,wrapPossibleNavigation:!0})}else try{await this.page.reload({timeout:3e3})}catch{this.logger.warn({url:await this.url()},"Timeout elapsed waiting for page on reload, continuing anyways...")}}async getA11yTree(e=!1){let t=null,o=async()=>{let n=0,s=await this.url();for(;!t&&n<3;)try{let a=await this.getRawA11yTree(e);if(!a.root||a.allNodes.length<=1)throw new Error("No a11y tree found on page");t=No(a,this.logger)}catch(a){if(this.logger.error({err:a,url:s},"Error fetching a11y tree"),n===0)await B(1e3),n++;else throw new Error(`Max retries exceeded fetching a11y tree: ${a}`)}if(!t||!t.root)throw new Error("Accessibility tree appears empty");this.a11yIdToNodeMap=t.a11yIdNodeMap,this.dataMomenticIdToNodeMap=t.dataMomenticIdMap};if(await Promise.race([o(),B(8e3)]),t)return t;throw new T("ActionFailureError",`Getting accessibility tree timed out after ${8e3}ms`)}getA11yIdFromDataMomenticId(e){return this.dataMomenticIdToNodeMap.get(e)?.id}async getRawA11yTree(e=!1){let t=await this.url(),o=Date.now(),n=()=>{o=Date.now()};this.cdpClient.addListener("Accessibility.nodesUpdated",n);let s=!1,a=()=>{this.logger.info({url:t},"Load event fired on page"),s=!0,o=Date.now()};this.cdpClient.addListener("Accessibility.loadComplete",a);let i=Date.now(),l=!e;for(;!e&&Date.now()-i<3e3;){if(await B(250),!s&&Date.now()-i<1e3){process.env.NODE_ENV!=="production"&&this.logger.debug({url:t},"A11y tree not loaded yet, waiting...");continue}if(Date.now()-o>=1250){l=!1;break}this.logger.debug({url:t},"A11y tree not stable yet, waiting...")}this.logger.debug({duration:Date.now()-i,eventReceived:s,timeoutTriggered:l,skipWait:e},"A11y wait phase completed"),await(await this.getUserPageOrFrame()).evaluate(Fo);let c;if(this.localMode&&this.activeFrame){let{result:{objectId:p}}=await this.cdpClient.send("Runtime.evaluate",{expression:`document.querySelector("#${this.activeFrame}")`}),f=await this.cdpClient.send("DOM.describeNode",{objectId:p});c=(await this.cdpClient.send("Accessibility.getRootAXNode",{frameId:f.node.frameId})).node.backendDOMNodeId}else{let{node:p}=await this.cdpClient.send("Accessibility.getRootAXNode");c=p.backendDOMNodeId}let{nodes:d}=await this.cdpClient.send("Accessibility.queryAXTree",{backendNodeId:c});return this.cdpClient.removeListener("Accessibility.loadComplete",a),this.cdpClient.removeListener("Accessibility.nodesUpdated",n),{root:d[0],allNodes:d}}async clickUsingVisualCoordinates(e){let t=await this.getElementLocation(e);if(!t)throw new Error(`Could not find element location with backend node id: ${e}`);this.logger.debug({location:t},"Executing mouse click"),await this.page.mouse.click(t.centerX,t.centerY)}async getIDAttributeUsingCDP(e){await this.cdpClient.send("DOM.getDocument",{depth:0});let t=await this.cdpClient.send("DOM.requestNode",{objectId:e}),n=(await this.cdpClient.send("DOM.getAttributes",{nodeId:t.nodeId})).attributes,s=n.findIndex(a=>a===Ge);if(s===-1||!n[s+1])throw new Error(`Could not find ${Ge} for object ${e}`);return n[s+1]}async getLocatorFromBackendID(e){let t=await this.cdpClient.send("DOM.resolveNode",{backendNodeId:e});if(!t||!t.object.objectId)throw new Error(`Could not resolve backend node ${e}`);let o;try{o=await this.getIDAttributeUsingCDP(t.object.objectId)}catch(n){throw this.logger.error({err:n,object:JSON.stringify(t.object)},"Failed to get ID attribute"),n}return(await this.getUserPageOrFrame()).locator(`[${Ge}="${o}"]`)}async clickUsingCDP(e,t={}){let o=0,n=e,s;for(;o<xo;){if(!n||n.role==="RootWebArea")throw new T("ActionFailureError",s??`Attempted to click node with no clickable surrounding elements: ${e.getLogForm()}`);if(n.role==="StaticText"){n=n.parent;continue}let a=n.backendNodeID;if(!a){this.logger.warn({node:n.getLogForm()},"Click candidate had no backend node ID"),n=n.parent;continue}try{let i=await this.getLocatorFromBackendID(a);return t.doubleClick?await i.dblclick({timeout:8e3}):await i.click({timeout:8e3,button:t.rightClick?"right":"left",force:t.force}),n.id!==e.id&&this.logger.info({oldNode:e.getLogForm(),newNode:n.getLogForm()},"Redirected click successfully to new element"),n}catch(i){let l=`Failed to click '${n.getLogForm()}' - are you sure the element is currently visible and not obscured on the page?`;this.logger.error({err:i},l),s||(s=l),o++,n=n.parent}}throw new Error(`Max click redirection attempts exhausted on original element: ${e.getLogForm()}`)}async getElementLocation(e){let t=await this.cdpClient.send("DOMSnapshot.captureSnapshot",{computedStyles:[],includeDOMRects:!0,includePaintOrder:!0}),o=await this.page.evaluate(()=>window.devicePixelRatio);process.platform==="darwin"&&o===1&&(o=2);let n=t.documents[0],s=n.layout,a=n.nodes,i=a.nodeName||[],l=a.backendNodeId||[],c=s.nodeIndex,d=s.bounds,p=-1;for(let y=0;y<i.length;y++)if(l[y]===e){p=c.indexOf(y);break}if(p===-1)throw new Error(`Could not find any backend node with ID ${e}`);let[f=0,h=0,m=0,g=0]=d[p];f/=o,h/=o,m/=o,g/=o;let S=f+m/2,v=h+g/2;return{centerX:S,centerY:v}}async scroll(e,t,o,n){let s=t==="left"?-1:1,a=n==="up"?-1:1;this.activeFrame?await(await this.getUserPageOrFrame()).evaluate(([l,c,d,p])=>window.scrollTo(window.scrollX+(l??window.innerWidth)*d,window.scrollY+(c??window.innerHeight)*p),[e,o,s,a]):await this.page.mouse.wheel((e??r.VIEWPORT.width)*s,(o??r.VIEWPORT.height)*a)}async scrollUp(e){await this.scroll(0,null,e??null,"up")}async scrollDown(e){await this.scroll(0,null,e??null,"down")}async scrollLeft(e){await this.scroll(e??null,"left",0,null)}async scrollRight(e){await this.scroll(e??null,"right",0,null)}async goForward(){await this.wrapPossibleNavigation(async()=>this.localMode&&this.activeFrame?(await this.getUserPageOrFrame()).evaluate(e=>{let t=e().contentWindow;t?t.history.forward():console.error("Failed to get content window for frame")},()=>document.querySelector(`iframe[name="${this.activeFrame}"]`)):this.page.goForward({timeout:8e3}))}async goBack(){await this.wrapPossibleNavigation(async()=>this.localMode&&this.activeFrame?(await this.getUserPageOrFrame()).evaluate(e=>{let t=e().contentWindow;t?t.history.back():console.error("Failed to get content window for frame")},()=>document.querySelector(`iframe[name="${this.activeFrame}"]`)):this.page.goBack({timeout:8e3}))}async switchToPage(e){let t=this.context.pages();for(let o=0;o<t.length;o++){let n=t[o];if(n.url().includes(e)){this.logger.info(`Switching to tab ${o} with url ${n.url()}`),this.page=n,await this.pageSetup(),await this.attachPageLoadListeners();try{await n.waitForLoadState("load",{timeout:3e3})}catch{this.logger.warn({url:await this.url()},"Timeout elapsed waiting for load state during tab switch, continuing anyways...")}if(!await this.initCDPSession())throw new T("ActionFailureError",`Initializing libraries timed out on ${n.url()} \u{1F61E}. Please ensure the site and your internet are working.`);return}}throw new Error(`Could not find page with url containing ${e}`)}async attachPageLoadListeners(){if(this.localMode)return;let e=async t=>{t.parentFrame()||await this.pageSetup()};this.page.off("framenavigated",e),this.page.on("framenavigated",e)}async setCookie(e){let t=Gr(e);await this.context.addCookies([t])}async setLocalStorage(e,t){await(await this.getUserPageOrFrame()).evaluate(([n,s])=>{n&&localStorage.setItem(n,s||"")},[e,t])}async solveCaptcha(){await this.getA11yTree();let e;for(let i of this.a11yIdToNodeMap.values())if(i.role==="image"&&i.name.toLowerCase().includes("captcha")){if(!i.backendNodeID)continue;e=await this.getLocatorFromBackendID(i.backendNodeID);break}if(!e){let i=await(await this.getUserPageOrFrame()).solveRecaptchas();if(!i.captchas||!i.captchas.length)throw new Error("No captchas found on the page");return}let t=await e.screenshot({type:"jpeg",animations:"allow",quality:100}),o=await fetch("https://api.2captcha.com/createTask",{method:"POST",body:JSON.stringify({clientKey:process.env.TWO_CAPTCHA_KEY,task:{type:"ImageToTextTask",body:t.toString("base64"),case:!0},languagePool:"en"})});if(!o.ok){let i=`Captcha solver API returned error response: ${o.statusText}`;throw this.logger.error({text:await o.text()},i),new Error(i)}let{taskId:n}=await o.json(),s=Date.now(),a="";for(;Date.now()-s<6e4;){await B(2500);let i=await fetch("https://api.2captcha.com/getTaskResult",{method:"POST",body:JSON.stringify({clientKey:process.env.TWO_CAPTCHA_KEY,taskId:n})});if(!i.ok){let c=`Captcha solution API returned error response: ${i.statusText}`;throw this.logger.error({text:await i.text()},c),new Error(c)}let l=await i.json();if(l.errorId){let c=`Captcha solution API returned error ID ${l.errorId}`;throw this.logger.error(c),new Error(c)}if(l.status==="ready"){a=l.solution.text;break}}if(!a)throw new Error("Captcha solution timed out");return a}getActiveFrame(){return this.activeFrame}async exposeRecordingBindings(){await this.context.exposeBinding("captureClick",async(e,t)=>{if(!this.transformer){this.logger.warn("Click captured without transformer");return}let o,n;try{o=await t.getAttribute("id"),n=await t.getAttribute(Ge)}catch(l){throw this.logger.error({err:l},"Failed to get element attributes for click processing"),l}if(this.logger.info({id:o,dataMomenticId:n},"Click captured on element"),!n){this.logger.error({url:await this.url(),element:await t.evaluate(l=>l.outerHTML)},`Could not find ${Ge} for recorded click`);return}let s=this.getA11yIdFromDataMomenticId(parseInt(n));if(!s){this.logger.warn({url:await this.url(),element:await t.evaluate(l=>l.outerHTML)},"Could not find a11y id for recorded click");return}let a={id:s};await this.resolveCachedTargetToID(a),(async()=>{try{let l=this.mostRecentBrowserState??(await this.getA11yTree(!0)).serialize();await this.transformer?.recordClick(a,l)}catch(l){this.logger.error({err:l},"Failed transforming captured click")}})()},{handle:!0}),await this.context.exposeBinding("captureKeystroke",async({},{key:e})=>{this.transformer&&(this.logger.info({key:e},"Captured keypress"),this.transformer.recordKeystroke(e))})}async startRecording(e,t){this.transformer=t;let o=await this.getA11yTree(!0);this.mostRecentBrowserState=o.serialize();let n=null,s=[async()=>{this.transformer=void 0}],a=async l=>{n&&clearTimeout(n),n=setInterval(()=>{if(!this.transformer||e.aborted)return;(async()=>{let d=0;for(;d<2;)try{let p=await this.getA11yTree(!0);this.mostRecentBrowserState=p.serialize();break}catch(p){this.logger.error({err:p},"Failed to get a11y tree in frame navigation listener"),d++}})()},250),s.push(async()=>{n&&(clearInterval(n),n=null)}),await l.evaluate(Bo),s.push(async()=>{await l.evaluate(()=>{document.removeEventListener("click",window.clickListener,{capture:!0})})}),await l.evaluate(jo),s.push(async()=>{await l.evaluate(()=>{document.removeEventListener("keydown",window.pressListener,{capture:!0})})})};await a(await this.getUserPageOrFrame());let i=async l=>{l.name().startsWith(jt)&&(e.aborted||!this.transformer||(this.logger.info("Re-adding window recording event listeners on navigation"),await a(l)))};this.page.on("framenavigated",i),s.push(async()=>{this.page.off("framenavigated",i)}),e.addEventListener("abort",async()=>{for(let l of s)try{await l()}catch(c){this.logger.warn({err:c},"Recording cleanup function failed, continuing...")}})}async getSelectOptions(e){let t=this.a11yIdToNodeMap.get(e);if(!t)throw new Error(`Could not find node with a11y id ${e}`);if(!t.backendNodeID)throw new Error(`Node with a11y id ${e} has no backend node ID`);return await(await this.getLocatorFromBackendID(t.backendNodeID)).evaluate(s=>Array.from(s.querySelectorAll("option")).map(i=>i.value))}async getHTML(){let e=await this.getUserPageOrFrame();await e.addScriptTag({content:At.htmlJs});let t=await e.evaluate(()=>window.getHTMLBodyString());if(!t)throw new T("InternalWebAgentError","Empty HTML tree");return Li.html(t,{indent_size:1,indent_with_tabs:!1,preserve_newlines:!1,wrap_line_length:80})}};var Ve=async({controller:r,context:e,step:t,logger:o,advanced:n,takeScreenshots:s,...a})=>{a.onStarted?.(),r.resetHistory();let i={...t,startedAt:new Date,userAgent:M.USER_AGENT,finishedAt:new Date,results:[],status:"SUCCESS"};try{let l=0;t.commands=t.commands||[];let c=t.commands&&t.commands.length>0;for(;;){if(l>12)throw new Error(`Exceeded max number of commands per step (${12})`);if(r.isClosed())throw new Error("Cancelling remaining steps in AI action because the controller is now closed");let d,p=new Date,f;if(s){let m=await r.browser.screenshot();f=await a.onSaveScreenshot(m)}if(c){if(d=t.commands[l],!d)throw new Error(`Saved command at index ${l} is undefined.`)}else{o.info(`Generating new sub-command ${l} within AI step`);let m;try{m=await r.promptToCommand(t.type,t.text,n.disableAICaching),d=m.command}catch(S){d={type:"FAILURE",thoughts:S instanceof Error?S.message:`${S}`}}finally{if(d.type==="FAILURE")if(l===0){i.finishedAt=new Date,i.status="FAILED",i.message=d.thoughts;break}else{let S="Stopping command generation prematurely since no progress can be made";o.warn({command:d},S),d={type:"SUCCESS",thoughts:S}}}(async()=>{let S=l,v=t,y=d;if("target"in y&&y.target){await new Promise(R=>setTimeout(()=>R(),250));try{let R=await ki(r,m.context,y.target);y.target.elementDescriptor=R,v.commands[l]=y,a.onCommandExecuted?.({commandIndex:S,command:y})}catch(R){o.error({err:R,currentIndex:S,currentStep:v},"Failed to generate element description, continuing...")}}})()}a.onCommandGenerated?.({commandIndex:l,message:Ut[d.type]||`Unknown command (${d.type})`});let h={beforeScreenshot:f,beforeUrl:await r.browser.url(),startedAt:p,viewport:await r.browser.viewport(),finishedAt:new Date,status:"SUCCESS"};o.info(`Executing sub-command ${l} within AI step: ${Be(d)}`);try{let m=await r.executeCommand(d,e,n.disableAICaching,c);o.info(`AI sub-command ${l} completed successfully`),h.elementInteracted=m.elementInteracted;let g;if(s){let v=await r.browser.screenshot();g=await a.onSaveScreenshot(v)}h.afterScreenshot=g,h.afterUrl=await r.browser.url(),h.finishedAt=new Date;let S={status:"SUCCESS",startedAt:h.startedAt,finishedAt:h.finishedAt,type:"PRESET_ACTION",data:m.data,command:d,results:[h]};if(i.results.push(S),t.commands[l]=d,a.onCommandExecuted?.({commandIndex:l,output:{...S,message:d.thoughts??m.thoughts??"Successfully executed preset action."},command:d}),d.type==="SUCCESS"){i.finishedAt=new Date,i.status="SUCCESS",i.message=m.thoughts??"All commands completed.";break}if(m.succeedImmediately&&!c){i.finishedAt=new Date,i.status="SUCCESS",i.message=m.succeedImmediatelyReason,d={type:"SUCCESS",thoughts:m.succeedImmediatelyReason},t.commands.push(d),a.onCommandExecuted?.({commandIndex:l+1,output:{...S,message:i.message},command:d}),i.results.push({...S,command:d});break}}catch(m){if(c){c=!1,l=0,i.results=[];continue}let g=m instanceof Error?m.message:`${m}`;h.status="FAILED",h.message=g,h.finishedAt=new Date,h.afterUrl=await r.browser.url();let S;try{S=await r.browser.screenshot()}catch(v){o.warn({err:v},"Failed to take screenshot after error, skipping")}h.afterScreenshot=S,i.results.push({status:"FAILED",startedAt:h.startedAt,finishedAt:h.finishedAt,type:"PRESET_ACTION",command:d,results:[h],message:g}),i.status="FAILED",i.finishedAt=new Date,i.message=g;break}l++}}catch(l){i.message=l instanceof Error?l.message:`${l}`,i.finishedAt=new Date,i.status="FAILED"}return i.status==="SUCCESS"?(i.data=i.results[i.results.length-1]?.data,a.onSuccess?.({message:i.message||"AI step succeeded.",startedAt:i.startedAt.getTime(),durationMs:i.finishedAt.getTime()-i.startedAt.getTime(),output:i})):a.onFailure?.({message:i.message||"AI step errored.",startedAt:i.startedAt.getTime(),durationMs:i.finishedAt.getTime()-i.startedAt.getTime(),output:i}),i};async function ki(r,e,t){let o=t.a11yData?.id;if(!o)throw new Error("Attempted to get reverse mapping for command with no a11y id target");return r.getReverseMappedTarget(e,o,!0)}var We=async({controller:r,context:e,step:t,advanced:o,takeScreenshots:n,...s})=>{s.onStarted?.();let a=new Date,i=await r.browser.url(),l;if(n){let c=await r.browser.screenshot();l=await s.onSaveScreenshot(c)}try{let c=await r.executePresetStep(t.command,e,o.disableAICaching),d;if(n){let g=await r.browser.screenshot();d=await s.onSaveScreenshot(g)}let p=new Date,f={...t,startedAt:a,finishedAt:p,status:"SUCCESS",data:c.data,results:[]},h="Successfully executed preset action.";"assertion"in t.command&&(h=c.thoughts||"Assertion passed.");let m={beforeUrl:i,beforeScreenshot:l,afterUrl:await r.browser.url(),afterScreenshot:d,startedAt:a,finishedAt:p,viewport:await r.browser.viewport(),status:"SUCCESS"};return f.status="SUCCESS",f.results=[m],f.message=h,s.onSuccess?.({message:h,startedAt:a.getTime(),durationMs:p.getTime()-a.getTime(),command:t.command,output:f}),f}catch(c){s.logger.error({err:c},`Failed executing preset step ${t.command.type}`);let d=new Date,p=c instanceof Error?c.message:`${c}`,f=await r.browser.screenshot();if("cancelOnFailure"in t.command&&t.command.cancelOnFailure){let m={...t,startedAt:a,finishedAt:d,status:"CANCELLED",message:p,results:[{beforeUrl:i,beforeScreenshot:l,afterUrl:await r.browser.url(),afterScreenshot:f,startedAt:a,finishedAt:d,viewport:await r.browser.viewport(),status:"CANCELLED",message:p}]};return s.onCancelled?.({message:p,startedAt:a.getTime(),durationMs:d.getTime()-a.getTime(),output:m}),m}let h={...t,startedAt:a,finishedAt:d,status:"FAILED",message:p,results:[{beforeUrl:i,beforeScreenshot:l,afterUrl:await r.browser.url(),afterScreenshot:f,startedAt:a,finishedAt:d,viewport:await r.browser.viewport(),status:"FAILED",message:p}]};return s.onFailure?.({message:p,startedAt:a.getTime(),durationMs:d.getTime()-a.getTime(),output:h}),h}};var Rt=async({controller:r,context:e,step:t,advanced:o,logger:n,takeScreenshots:s,...a})=>{a.onStarted?.();let i={type:"MODULE",moduleId:t.moduleId,startedAt:new Date,userAgent:M.USER_AGENT,results:[],finishedAt:new Date,status:"SUCCESS"};for(let l=0;l<t.steps.length;l++){let c=t.steps[l];if(r.isClosed())throw i.status="CANCELLED",new Error("Cancelling remaining steps in module because the controller is now closed");n.debug({i:l,moduleStep:c},"Starting module step"),n.info(`Starting module sub-step ${l+1}/${t.steps.length}: ${yt(c)}`);let d;switch(c.type){case"PRESET_ACTION":d=await We({controller:r,context:e,step:c,advanced:o,logger:n,takeScreenshots:s,onSaveScreenshot:a.onSaveScreenshot,onStarted(){a.onStepStarted?.({index:l})},onSuccess({message:f,startedAt:h,durationMs:m,output:g}){a.onStepSuccess?.({index:l,message:f,startedAt:h,durationMs:m,output:g})},onFailure({message:f,startedAt:h,durationMs:m,output:g}){a.onStepFailure?.({index:l,message:f,startedAt:h,durationMs:m,output:g})},onCancelled({message:f,startedAt:h,durationMs:m,output:g}){a.onStepCancelled?.({index:l,message:f,startedAt:h,durationMs:m,output:g})}});break;case"AI_ACTION":d=await Ve({controller:r,context:e,step:c,advanced:o,logger:n,takeScreenshots:s,onSaveScreenshot:a.onSaveScreenshot,onStarted(){a.onStepStarted?.({index:l})},onSuccess({message:f,startedAt:h,durationMs:m,output:g}){a.onStepSuccess?.({index:l,message:f,startedAt:h,durationMs:m,output:g})},onFailure({message:f,startedAt:h,durationMs:m,output:g}){a.onStepFailure?.({index:l,message:f,startedAt:h,durationMs:m,output:g})},onCommandGenerated({commandIndex:f,message:h}){a.onCommandGenerated?.({index:l,commandIndex:f,message:h})},onCommandExecuted({commandIndex:f,command:h,output:m}){a.onCommandExecuted?.({index:l,commandIndex:f,command:h,output:m})}});break;default:return(f=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(c)}if(i.results.push(d),d.status==="FAILED"||d.status==="CANCELLED"){i.status=d.status,i.finishedAt=new Date,i.message=d.message;for(let p=l+1;p<t.steps.length;p++){let h={...t.steps[p],status:"CANCELLED",startedAt:new Date,finishedAt:new Date,userAgent:M.USER_AGENT,results:[],message:"Cancelled due to previous failure."};i.results.push(h)}break}}return i.status==="SUCCESS"?(i.data=i.results[i.results.length-1]?.data,a.onSuccess?.({message:"Executed module step.",startedAt:i.startedAt.getTime(),durationMs:i.finishedAt.getTime()-i.startedAt.getTime(),output:i})):i.status==="FAILED"?a.onFailure?.({message:"Failed to execute module step.",startedAt:i.startedAt.getTime(),durationMs:i.finishedAt.getTime()-i.startedAt.getTime(),output:i}):i.status==="CANCELLED"&&a.onCancelled?.({message:"Module step cancelled.",startedAt:i.startedAt.getTime(),durationMs:i.finishedAt.getTime()-i.startedAt.getTime(),output:i}),i};import{v4 as jp}from"uuid";var Gp=process.env.AWS_ACCESS_KEY_ID,Vp=process.env.AWS_SECRET_ACCESS_KEY,Wp=process.env.S3_REGION,Kp=process.env.S3_BUCKET;var Yo=async({test:r,runId:e,controller:t,context:o,logger:n,takeScreenshots:s,onUpdateRun:a,onSaveScreenshot:i,onTestSuccess:l})=>{try{let c=await Fi({test:r,runId:e,controller:t,context:o,logger:n,takeScreenshots:s,onUpdateRun:a,onSaveScreenshot:i});if(c==="PASSED"||c==="CANCELLED")return await l?.(r),c;throw new T("ActionFailureError",c.message||"An unknown error occurred")}catch(c){throw c instanceof T||(c=new T("InternalPlatformError",c instanceof Error?c.message:`${c}`,{cause:c})),c}finally{await t.browser.cleanup()}},Fi=async({test:r,runId:e,controller:t,context:o,logger:n,takeScreenshots:s,onUpdateRun:a,onSaveScreenshot:i})=>{let l=pe.parse(r.advanced),c=n.child({runId:e,testId:r.id});c.info("Starting test run"),await a({status:"RUNNING",startedAt:new Date});let d,p="PASSED",f=[];for(let h=0;h<r.steps.length;h++){let m=r.steps[h];c.info(`Starting step ${h+1}/${r.steps.length}: ${yt(m)}`);let g=o.toObjectCopy(),S;switch(m.type){case"PRESET_ACTION":S=await We({controller:t,context:o,step:m,advanced:l,logger:c,takeScreenshots:s,onSaveScreenshot:i});break;case"AI_ACTION":S=await Ve({controller:t,context:o,step:m,advanced:l,logger:c,takeScreenshots:s,onSaveScreenshot:i});break;case"RESOLVED_MODULE":S=await Rt({controller:t,context:new V(await t.browser.url(),m.steps),step:m,advanced:l,logger:c,takeScreenshots:s,onSaveScreenshot:i});break;default:return(y=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(m)}if(f.push({...S,beforeTestContext:g,afterTestContext:o.toObjectCopy()}),o.setResult(h,S.data),await a({results:f}),S.status==="SUCCESS"){c.info(`Step ${h+1}/${r.steps.length} succeeded`);continue}if(S.status!=="FAILED"&&S.status!=="CANCELLED")throw new T("InternalPlatformError",`Received unexpected non-terminal status from step: ${S.status}`);c.info({message:f[f.length-1]?.message},`Step ${h+1}/${r.steps.length} ended with status: ${S.status}`),d=S,p=S.status;for(let v=h+1;v<r.steps.length;v++){let y=r.steps[v];if(y.type==="RESOLVED_MODULE"){let R={type:"MODULE",moduleId:y.moduleId,startedAt:new Date,userAgent:M.USER_AGENT,results:y.steps.map(E=>({...E,status:"CANCELLED",startedAt:new Date,finishedAt:new Date,userAgent:M.USER_AGENT,results:[]})),finishedAt:new Date,status:"CANCELLED"};f.push(R)}else{let R={...y,status:"CANCELLED",startedAt:new Date,finishedAt:new Date,userAgent:M.USER_AGENT,results:[]};f.push(R)}}break}return await a({status:p,finishedAt:new Date,results:f}),p==="FAILED"?d:p};var Xo={currentlyExecutingRequests:0},zi=r=>async e=>{let t=!1;try{Xo.currentlyExecutingRequests++,t=await Ui({...r,...e})}finally{r.logger.info({success:t,sessionId:r.metadata.sessionId},"Test execution complete"),Xo.currentlyExecutingRequests--}},Ui=async({socket:r,steps:e,baseUrl:t,testMetadata:o,reInitialize:n,indicesFilter:s,metadata:a,logger:i,rootState:l,localApp:c})=>{let{testId:d,sessionId:p,orgId:f}=a,h={testId:d,orgId:f,baseUrl:t},m=i.child(h),g=i.child({package:"web-agent",...h}),S=!0,v=!0;c==="iframe"&&(v=!1);let y=pe.parse(o.advanced??{}),R=$.getSession(p);if(!R)throw new Error("No active session found");let{controller:E,context:A}=R;E.setOpen();let F=M.USER_AGENT;m.info({reInitialize:n,context:A,steps:e,indicesFilter:s},"Starting execution");let ze={url:t,clearCookies:!0,clearStorage:!0};if(n&&(l?(E.browser.baseURL=t,E.browser.setActiveFrame(ft),await E.resetState(ze),E.setLogger(g),E.browser.setLogger(g)):await E.resetState(ze),E.setOpen(),A.reset(t)),m.info(`Session restarted for test ${d} at ${t}`),r.emit("session",{url:t,userAgent:F,viewport:await E.browser.viewport(),localApp:c}),A.setSteps(e),s?.length){let Q=Hi(e,s);return m.info({step:Q},"Starting individual step"),r.emit("started",{indices:s}),S=await Jo({indices:s,step:Q,controller:E,context:A,advanced:y,socket:r,logger:m,takeScreenshots:v}),r.emit("testContext",{state:A.toObjectRef()}),r.emit("finished"),S}else A.clearResults(),r.emit("testContext",{state:A.toObjectRef()});for(let Q=0;Q<e.length&&!(E.browser.closed||E.isClosed()||r.disconnected);Q++){let x=e[Q];m.info({index:Q,step:x},"Starting step");let is=await Jo({indices:[Q],step:x,controller:E,context:A,advanced:y,socket:r,logger:m,takeScreenshots:v});if(r.emit("testContext",{state:A.toObjectRef()}),!is){S=!1;break}}return r.emit("finished"),S},$i=(r,e)=>{switch(r.type){case"RESOLVED_MODULE":return r.steps[e];case"AI_ACTION":let t=r.commands[e];return{type:"PRESET_ACTION",command:t};case"PRESET_ACTION":default:throw new Error(`Cannot get a child step from step type ${r.type}`)}},Hi=(r,e)=>{let t=r[e[0]];for(let o=1;o<e.length;o++)t=$i(t,e[o]);return t},ur=async r=>r,Jo=async r=>{try{return await Bi(r)}catch(e){throw e instanceof T&&e.emitToUser&&r.socket.emit("error",{message:e.message}),e}},Bi=async({indices:r,step:e,controller:t,context:o,advanced:n,socket:s,logger:a,takeScreenshots:i})=>{let l=()=>{s.emit("started",{indices:r})},c=({message:h,startedAt:m,durationMs:g,command:S,output:v})=>{s.emit("success",{indices:r,message:h,startedAt:m,durationMs:g,command:S,output:ie.parse(v)})},d=({message:h,startedAt:m,durationMs:g,output:S})=>{s.emit("failure",{indices:r,message:h,startedAt:m,durationMs:g,output:ie.parse(S)})},p=({message:h,startedAt:m,durationMs:g,output:S})=>{s.emit("cancelled",{indices:r,message:h,startedAt:m,durationMs:g,output:ie.parse(S)})},f;switch(e.type){case"PRESET_ACTION":{f=await We({controller:t,context:o,step:e,advanced:n,logger:a,takeScreenshots:i,onSaveScreenshot:ur,onStarted:l,onSuccess:c,onFailure:d,onCancelled:p});break}case"AI_ACTION":{f=await Ve({controller:t,context:o,step:e,advanced:n,logger:a,takeScreenshots:i,onSaveScreenshot:ur,onStarted:l,onSuccess:c,onFailure:d,onCommandGenerated({commandIndex:m,message:g}){s.emit("commandGenerated",{indices:[...r,m],message:g})},onCommandExecuted({commandIndex:m,command:g,output:S}){s.emit("commandExecuted",{indices:[...r,m],command:g,output:ie.parse(S)})}});break}case"RESOLVED_MODULE":{f=await Rt({controller:t,context:new V(t.browser.baseURL,e.steps),step:e,advanced:n,logger:a,takeScreenshots:i,onSaveScreenshot:ur,onStarted:l,onSuccess:c,onFailure:d,onCancelled:p,onStepStarted({index:m}){s.emit("started",{indices:[...r,m]})},onStepSuccess({index:m,message:g,startedAt:S,durationMs:v,output:y}){s.emit("success",{indices:[...r,m],message:g,startedAt:S,durationMs:v,output:ie.parse(y)})},onStepFailure({index:m,message:g,startedAt:S,durationMs:v,output:y}){s.emit("failure",{indices:[...r,m],message:g,startedAt:S,durationMs:v,output:ie.parse(y)})},onStepCancelled({index:m,message:g,startedAt:S,durationMs:v,output:y}){s.emit("cancelled",{indices:[...r,m],message:g,startedAt:S,durationMs:v,output:ie.parse(y)})},onCommandGenerated({commandIndex:m,index:g,message:S}){s.emit("commandGenerated",{indices:[...r,g,m],message:S})},onCommandExecuted({index:m,commandIndex:g,command:S,output:v}){s.emit("commandExecuted",{indices:[...r,m,g],command:S,output:ie.parse(v)})}});break}default:return(m=>{throw"If Typescript complains about the line below, you missed a case or break in the switch above"})(e)}return o.setResult(r[0],f.data),f.status==="SUCCESS"};var Qo={event:"execute",createHandler:zi};var ji=({metadata:r,logger:e,socket:t})=>{let{sessionId:o}=r;return async({description:n,useVision:s,testMetadata:a,isSelect:i},l)=>{let c=$.getSession(o);if(!c)throw new Error("No active session found");let{controller:d,context:p}=c,f=pe.parse(a.advanced??{}),h,m;try{if(h=await d.locateElement(n,s,f.disableAICaching,p),m={id:h.id},await d.browser.resolveCachedTargetToID(m),i)try{h.options=await d.browser.getSelectOptions(h.id)}catch(g){e.warn({err:g},"Failed getting select options"),t.emit("error",{message:`Failed getting select options: ${g}`})}}catch(g){e.error({err:g},"Error locating element"),t.emit("error",{message:g instanceof Error?g.message:`${g}`}),l({found:!1});return}e.info({locator:h,a11yData:m},"Located element result");try{await d.browser.scrollIntoView({id:h.id}),await d.browser.highlight({id:h.id})}catch(g){e.error({err:g,locator:h},"Error highlighting element"),l({found:!1});return}l({found:!0,locator:h,a11yData:m})}},Zo={event:"locate",createHandler:ji};var Gi=({socket:r,metadata:e,logger:t})=>{let{sessionId:o}=e;return async({action:n,indices:s},a)=>{try{let i=$.getSession(o);if(!i)throw new Error("No active session found");let{controller:l}=i;n==="START"&&t.info({sessionId:o},"Starting record mode"),await l.toggleRecordMode({action:n,indices:s,onStepRecord:(c,d)=>{r.emit("stepRecorded",{step:c,indices:d})}}),a()}catch(i){t.error({err:i},"Error in record socket handler"),a(i instanceof Error?i.message:`${i}`)}}},en={event:"record",createHandler:Gi};var Vi=({socket:r,metadata:e,rootState:t,logger:o,localApp:n})=>async({baseUrl:s})=>{let{testId:a,sessionId:i}=e;o.info({testId:a,sessionId:i,baseUrl:s},"Reset event received");let l;if(t?.controller){let{controller:d,context:p}=t;await d.resetState({clearCookies:!0,clearStorage:!0,url:s}),l=d.browser.baseURL,p.reset(l)}else{let d=$.getSession(i);if(!d){r.emit("error",{message:"No session to reset"});return}let{controller:p,context:f}=d;await p.browser.reset({clearCookies:!0,clearStorage:!0,url:s}),l=p.browser.baseURL,f.reset(l)}let c=M.USER_AGENT;o.info(`Session reset for test ${a} at ${l}`),r.emit("session",{url:l,userAgent:c,viewport:M.VIEWPORT,localApp:n})},tn={event:"reset",createHandler:Vi};import{v4 as ia}from"uuid";var Wi={type:"a11y",version:"1.0.0",useHistory:"diff",useGoalSplitter:!0},De=Wi;import{existsSync as ea}from"fs";import{faker as Ki}from"@faker-js/faker";import qi from"assert";import Yi from"axios";import Xi from"moment";import Ji from"p-timeout";var Qi=Object.getPrototypeOf(async function(){}).constructor;async function rn({code:r,fragment:e,state:t,timeoutMs:o=1e4}){let n=r;e&&(n=`return ${r}`);let s=JSON.parse(JSON.stringify(t)),a=(c,d)=>{s.env[c]=d,t.env[c]=d},i;return await Ji((async()=>{i=await Promise.resolve(new Qi("axios","moment","faker","assert","env","steps","results","setVariable",n)(Yi,Xi,Ki,qi,s.env,s.steps,s.results,a))})(),{milliseconds:o,message:`Timeout of ${o}ms exceeded for code execution`}),i}import{z as Pe}from"zod";var on=process.env.GCP_JS_EVAL_FUNCTION_ENDPOINT,Zi=Pe.object({result:Pe.unknown(),variableUpdates:Pe.record(Pe.string(),Pe.unknown()).optional(),error:Pe.string().optional(),success:Pe.boolean()});async function nn({code:r,fragment:e,state:t,logger:o,timeoutMs:n=1e4}){let s=new AbortController;if(!on)throw new Error("GCP_JS_EVAL_FUNCTION_ENDPOINT environment variable not set");let a=setTimeout(()=>{s.abort()},n),i=await fetch(on,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({code:r,fragment:e,state:t}),signal:s.signal});if(!i.ok)throw new Error(`Code evaluation server returned error code: ${i.status}`);clearTimeout(a);let l;try{l=Zi.parse(await i.json()),o.debug({response:l},"Response from code evaluation server")}catch(c){throw new Error(`Code evaluation server returned invalid response: ${c}`)}if(l.error)throw new Error(`Code evaluation server returned error: ${l.error}`);if(l.variableUpdates)for(let[c,d]of Object.entries(l.variableUpdates))t.env[c]=d;return l.result}var ta=ra();function ra(){if(process.env.NODE_ENV!=="production")return!1;if(process.env.NAMESPACE)return!0;try{if(ea("/var/run/secrets/kubernetes.io/serviceaccount/token"))return!0}catch{}return!1}async function pr(r){let e;return ta?(r.logger.info({code:r.code,fragment:r.fragment,state:r.state},"Running code remotely"),e=nn):(r.logger.info({code:r.code,fragment:r.fragment,state:r.state},"Running code locally"),e=rn),e(r)}async function hr({s:r,state:e,logger:t,timeoutMs:o=1e3}){let n=/{{(.*?)}}/g,s=r.matchAll(n),a=r;for(let i of s){if(i.length<2)continue;let l=i[1].trim(),c=await pr({code:l,fragment:!0,state:e,timeoutMs:o,logger:t}),d=typeof c=="string"?c:`${c}`;a=a.replace(i[0],d)}return a}import oa from"dedent";import na from"diff-lines";var xt=class r{static ACTION_DEBOUNCE_DELAY=1e3;onStepRecord;signal;generator;indices;keyBuffer=[];timer=null;keyBufferMode="normal";lastStep=null;constructor({signal:e,onStepRecord:t,generator:o,initialIndices:n}){this.signal=e,this.onStepRecord=(s,a)=>{this.lastStep={step:s,indices:a},t(s,a)},this.generator=o,this.indices=n}async recordClick(e,t){if(this.signal.aborted)return;let o=lt("CLICK"),n={type:"PRESET_ACTION",command:{...o,target:{elementDescriptor:`Generating description for: ${e.nodeOnlySerializedForm?.trim()??`element with id ${e.id}`}`,a11yData:e}}},s=Array.from(this.indices);this.indices[this.indices.length-1]++,this.onStepRecord(n,s);let a=await this.generator.getReverseMappedDescription({goal:`${e.id}`,browserState:t},!1);n.command.target.elementDescriptor=a.phrase,this.onStepRecord(n,s)}flushKeyBufferToCommand(){let e;if(this.keyBufferMode==="normal"){let n=lt("TYPE");e={type:"PRESET_ACTION",command:{...n,target:void 0,value:this.keyBuffer.join("")}}}else{let n=lt("PRESS");e={type:"PRESET_ACTION",command:{...n,value:this.keyBuffer.join("+")}}}let t,o=this.lastStep?.step.command;if(o?.type===e.command.type){let n=o.value,s=e.command.value;e={type:"PRESET_ACTION",command:{...o,value:e.command.type==="PRESS"?`${n}+${s}`:`${n}${s}`}},t=this.lastStep.indices}else o?.type==="CLICK"&&e.command.type==="TYPE"?(this.lastStep.step.command={...e.command,target:o.target},e=this.lastStep.step,t=this.lastStep.indices):(t=Array.from(this.indices),this.indices[this.indices.length-1]++);this.onStepRecord(e,t),this.keyBuffer=[],this.timer=null}recordKeystroke(e){if(e==="Shift"||e==="CapsLock"||this.signal.aborted)return;this.timer!==null&&clearTimeout(this.timer);let t="normal";e.length>1&&(t="special"),this.keyBuffer.length!==0&&t!==this.keyBufferMode&&this.flushKeyBufferToCommand(),this.keyBufferMode=t,this.keyBuffer.push(e),this.timer=setTimeout(()=>{this.flushKeyBufferToCommand()},r.ACTION_DEBOUNCE_DELAY)}};var fr=(r,e,t)=>{let[o,...n]=e.split("."),s=o;return n.length>=1?fr(r[s],n.join("."),t):r[s]=t,r};var sa=1e4,me=class{browser;pendingInstructions;generator;commandHistory;config;closed=!1;logger;recordAbortController=null;constructor({browser:e,config:t,generator:o,logger:n}){this.browser=e,this.generator=o,this.config=t,this.logger=n,this.pendingInstructions=[],this.commandHistory=[]}get history(){return this.commandHistory.filter(e=>e.state==="DONE")}get lastExecutedCommand(){let e=this.history;return e.length===0?null:e[e.length-1]}setOpen(){this.closed=!1}isClosed(){return this.closed}setLogger(e){this.logger=e}resetHistory(){this.commandHistory=[],this.pendingInstructions=[]}async resetState(e){this.resetHistory(),this.closed=!0,await this.browser.reset(e)}async getBrowserState(){let t=await(await this.browser.getA11yTree()).serialize();return this.logger.debug({tree:t,activeFrame:this.browser.getActiveFrame()},"Got a11y tree"),t}getSerializedHistory(e,t){let o;return this.config.useHistory==="diff"?o=this.getDiffHistory(e,t):o=this.getListHistory(),o}async splitUserGoal(e,t,o){if(e==="AI_ACTION"&&t.match(/[,!;.]|(?:and)|(?:then)/)&&this.config.useGoalSplitter){let n=await this.generator.getGranularGoals({goal:t,url:await this.browser.url()},o);this.pendingInstructions=n.reverse()}else this.pendingInstructions=[t]}async promptToCommand(e,t,o){try{return await this.promptToCommandHelper(e,t,o)}catch(n){throw n instanceof T?n:new T("InternalWebAgentError",n instanceof Error?n.message:`${n}`,{cause:n})}}async promptToCommandHelper(e,t,o){if(this.pendingInstructions.length===0){if(!t.trim())throw new Error("Cannot generate commands for empty goal");await this.splitUserGoal(e,t,o)}let n=this.pendingInstructions[this.pendingInstructions.length-1];this.logger.info({goal:n},"Starting prompt translation");let s=Date.now(),a=await this.browser.url(),i=await this.getBrowserState();this.logger.info({duration:Date.now()-s,url:a},"Got browser state");let l=this.commandHistory.length;this.commandHistory.push({state:"PENDING",browserStateBeforeCommand:i,urlBeforeCommand:a,type:e});let c=this.getSerializedHistory(a,i),d={url:a,numPrevious:l,browserState:i,history:c,goal:n,lastCommand:this.lastExecutedCommand},p=await this.generator.getProposedCommand(d,o);if(this.logger.info({type:p.type,thoughts:p.thoughts},"Got proposed command"),p.type==="SUCCESS"){let f=this.pendingInstructions.pop();if(this.logger.info({finishedInstruction:f,remainingInstructions:this.pendingInstructions},"Removing pending instruction due to SUCCESS"),this.pendingInstructions.length!==0)return this.commandHistory=[],this.promptToCommand(e,"",o)}else p.type==="FAILURE"&&(this.logger.info({remainingInstructions:this.pendingInstructions},"Removing pending instructions due to FAILURE"),this.pendingInstructions=[]);return{context:d,command:p}}async locateElement(e,t,o,n){if(!e)throw new T("ActionFailureError","Cannot locate element with empty description");n&&(e=await hr({s:e,state:n.toObjectRef(),logger:this.logger}));let s=await this.getBrowserState(),a;if(t){let{before:i,after:l}=await this.browser.screenshotWithHints();if(a=await this.generator.getElementLocationWithVision({goal:e,screenshot:i,hintActivatedScreenshot:l},o),a.id>0){let c=this.browser.getA11yIdFromDataMomenticId(a.id);if(!c)throw new T("InternalWebAgentError",`Unable to find corresponding DOM node for id ${a.id}`);a.id=c}}else a=await this.generator.getElementLocation({browserState:s,goal:e},o);if(a.id<0)throw new T("ActionFailureError",`Unable to locate element: ${a.thoughts?a.thoughts:"please ensure the element is visible and conforms to Accessibility guidelines"}`);return a}getDiffHistory(e,t){let o=this.history.filter(s=>s.type==="AI_ACTION");if(o.length===0)return"<NONE/>";let n=[`
25
25
  You have already executed the following commands successfully (most recent listed first)`,"-".repeat(10)];return o.reverse().forEach((s,a)=>{if(n.push(`COMMAND ${o.length-a}${a===0?" (command just executed)":""}: ${s.serializedCommand}`),a===0)if(Le(s.urlBeforeCommand,e))n.push(` URL CHANGE: '${s.urlBeforeCommand}' -> '${e}'`);else{let i=na(s.browserStateBeforeCommand,t,{n_surrounding:1});i?i.length<sa?(n.push("PAGE CONTENT CHANGE:"),i.split(`
26
26
  `).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(`
27
27
  `)}getListHistory(){return oa`Here are the commands that you have successfully executed:
28
28
  ${this.commandHistory.filter(e=>e.type==="AI_ACTION").map(e=>`- ${e.serializedCommand}`).join(`
29
- `)}`}async executeCommand(e,t,o,n=!1){let s=this.commandHistory[this.commandHistory.length-1];if(!n&&(!s||s.state!=="PENDING"))throw new T("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(),i=await this.executePresetStep(e,t,o),l=Date.now()-a;return this.logger.debug({result:i,duration:l},"Got execution result"),i.succeedImmediately&&!n&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(i.succeedImmediately=!1)),i.elementInteracted&&"target"in e&&e.target&&!e.target.elementDescriptor&&(e.target.elementDescriptor=i.elementInteracted.trim()),n||(s.generatedStep=e,s.serializedCommand=Be(e),s.state="DONE"),i}async executeAssertion(e){let t=await this.getBrowserState(),o=await this.browser.url(),n;if(e.useVision)n={goal:e.assertion,url:o,screenshot:await this.browser.screenshot(),browserState:"",history:"",numPrevious:-1,lastCommand:null};else{let a=this.getSerializedHistory(o,t);n={goal:e.assertion,url:o,browserState:t,history:a,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let s=await this.generator.getAssertionResult(n,e.useVision,e.disableCache);if(s.relevantElements&&Promise.all(s.relevantElements.map(a=>this.browser.highlight({id:a}))),!s.result)throw new T("AssertionFailureError",s.thoughts);return{succeedImmediately:!1,thoughts:s.thoughts,urlAfterCommand:o}}async wrapMultiElementTargetingCommand(e,t,o,...n){let s=await Promise.all(n.map(a=>this.wrapElementTargetingCommand(a,t.useVision,t.disableCache,async i=>i)));try{return await e(...s)}catch(a){if(o>0)return this.logger.warn({err:a},"Failed to execute action with multiple cached targets, retrying with AI"),n.forEach(i=>i.a11yData=void 0),this.wrapMultiElementTargetingCommand(e,t,o-1,...n);throw new T("ActionFailureError","",{cause:a})}}async wrapElementTargetingCommand(e,t,o,n,s=1){if(!e.a11yData&&!e.elementDescriptor)throw new T("ActionFailureError","Cannot target element with no target data or element descriptor");let a=e.a11yData&&at(e.a11yData);e.a11yData||(this.logger.info("No cached locator data for target, prompting AI for fresh location"),s--,e.a11yData=it.parse(await this.locateElement(e.elementDescriptor,t,o)));try{let i=await n(e.a11yData);return a?this.logger.info({target:e},"Successfully used cached target to perform action"):this.logger.debug({target:e},"Successfully generated and used new a11y target information"),i}catch(i){if(s>0&&e.elementDescriptor)return this.logger.warn({err:i,target:e},"Failed to execute action with cached target, retrying with AI"),e.a11yData=void 0,this.wrapElementTargetingCommand(e,t,o,n,s);if(i instanceof T)throw i;let l=`Failed to find ${e.elementDescriptor?`${e.elementDescriptor}`:"element"}: ${i instanceof Error?i.message:i}`;throw this.logger.error({err:i,target:e},l),new T("ActionFailureError",l,{cause:i})}}async executePresetStep(e,t,o){let n;try{n=await this.resolveCommandTemplateStrings(e,t)}catch(s){throw new T("ActionFailureError",s.message,{cause:s})}try{return await this.executePresetStepHelper(e,t,o)}catch(s){throw s instanceof T?s:new T("InternalWebAgentError",s instanceof Error?s.message:`${s}`,{cause:s})}finally{this.restoreCommandTemplateReplacements(e,n)}}restoreCommandTemplateReplacements(e,t={}){for(let[o,n]of Object.entries(t))fr(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 i=e[a],l=o?`${o}.${a}`:a;if(typeof i=="string"&&i.includes("{{")){let c=await hr({s:i,state:t.toObjectRef(),logger:this.logger});if(i===c)continue;n[l]=i,e[a]=c}else typeof i=="object"&&i!==null&&!Array.isArray(i)&&await this.resolveCommandTemplateStrings(i,t,l,n)}return n}async executePresetStepHelper(e,t,o){switch(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 T("ActionFailureError","Missing assertion");return this.executeAssertion(e)}case"AI_WAIT":{if(!e.assertion.trim())throw new T("ActionFailureError","Missing assertion");let c=Date.now();if(e.timeout&&e.timeout>30)throw new T("AssertionFailureError",`AI wait timeout of ${e.timeout} exceeds the maximum allowed timeout of 30s`);let d=(e.timeout??10)*1e3,p,f,h=0;for(;Date.now()-c<d;)try{p=await this.executeAssertion({...e,type:"AI_ASSERTION"});break}catch(m){f=m instanceof Error?m:new Error(`${m}`),this.logger.warn({err:m},`AI_WAIT assert attempt ${h} failed, retrying...`),h++,await B(d/10)}if(!p){let m=`AI wait still failing after ${d}ms.`;throw f&&(m+=` Latest result: ${f.message}`),new T("AssertionFailureError",m)}return p}case"AI_EXTRACT":{if(!e.goal.trim())throw new T("ActionFailureError","Cannot perform AI extraction without goal");let c=await this.browser.getHTML(),d=await this.generator.getTextExtraction({goal:e.goal,browserState:c,returnSchema:e.schema},e.disableCache);if(d.result==="NOT_FOUND")throw new T("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(!rr(e.url)&&!or(e.url,this.browser.baseURL))throw new T("ActionFailureError",`Invalid URL: ${e.url}`);await this.browser.navigate({url:e.url});break;case"CAPTCHA":let n=await this.browser.solveCaptcha();n&&(await this.wrapElementTargetingCommand({elementDescriptor:"the captcha image solution input"},e.useVision,o,c=>this.browser.click(c)),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;switch(e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)&&(s=await this.wrapElementTargetingCommand(e.target,e.useVision,o,c=>this.browser.hover(c))),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 c=await this.browser.url(),d=await this.wrapElementTargetingCommand(e.target,e.useVision,o,f=>this.browser.click(f,{doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force})),p={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:d};return Le(c,p.urlAfterCommand)&&(p.succeedImmediately=!0,p.succeedImmediatelyReason="URL changed"),p}case"DRAG":{let c=await this.wrapMultiElementTargetingCommand((d,p)=>this.browser.dragAndDrop(d,p,{force:e.force}),{useVision:e.useVision,disableCache:o},1,e.fromTarget,e.toTarget);return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:c}}case"SELECT_OPTION":{let c=await this.wrapElementTargetingCommand(e.target,!1,o,d=>this.browser.selectOption(d,e.option));return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:c}}case"TAB":await this.browser.switchToPage(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 c;try{c=await pr({code:e.code,fragment:!!e.fragment,state:t.toObjectRef(),timeoutMs:e.timeout?e.timeout*1e3:void 0,logger:this.logger})}catch(d){throw new T("ActionFailureError",d instanceof Error?d.message:`${d}`,{cause:d})}try{c=c===void 0?void 0:JSON.parse(JSON.stringify(c))}catch(d){throw new T("ActionFailureError",`Return value is not serializable: ${d instanceof Error?d.message:`${d}`}`,{cause:d})}return{urlAfterCommand:await this.browser.url(),succeedImmediately:!1,data:c}}case"TYPE":{let c=await this.browser.url(),d;e.target&&(d=await this.wrapElementTargetingCommand(e.target,e.useVision,o,f=>this.browser.click(f,{force:e.force}))),await this.browser.type(e.value,{clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),e.pressEnter&&await this.browser.press("Enter");let p={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:d};return Le(c,p.urlAfterCommand)&&(p.succeedImmediately=!0,p.succeedImmediatelyReason="URL changed"),p}case"HOVER":{let c=await this.wrapElementTargetingCommand(e.target,e.useVision,o,d=>this.browser.hover(d));return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:c}}case"PRESS":let a=await this.browser.url();await this.browser.press(e.value);let i={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return Le(a,i.urlAfterCommand)&&(i.succeedImmediately=!0,i.succeedImmediatelyReason="URL changed"),i;case"REQUEST":{let c=e.timeout??30,d=null,p=new AbortController,f=Object.fromEntries(Object.entries(e.headers||{}).filter(([E,A])=>E&&A)),h=new URLSearchParams;Object.entries(e.params||{}).filter(([E,A])=>E&&A).forEach(([E,A])=>{h.append(E,A)});let m;if(rr(e.url)&&(m=e.url),or(e.url,this.browser.baseURL)&&(m=new URL(e.url,this.browser.baseURL).toString()),!m)throw new T("ActionFailureError",`Invalid URL: ${e.url}`);let g=async()=>{try{d=await fetch(`${m}?${h.toString()}`,{headers:f,method:e.method,body:e.body,signal:p.signal})}catch(E){this.logger.error({err:E},"Failed to make fetch request")}},S=async()=>{await new Promise(E=>setTimeout(E,c*1e3)),p.abort()};await Promise.race([S(),g()]);let v=d;if(!v)throw new T("ActionFailureError",`Fetch request timed out after ${c} seconds`);if(!v.ok)throw new T("ActionFailureError",`Fetch request failed with status ${v.status}`);let y={};v.headers.forEach((E,A)=>{y[A]=E});let R={status:v.status,headers:y};return v.headers.get("content-type")?.includes("json")?R.json=await v.json():v.headers.get("content-type")?.includes("text")&&(R.text=await v.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:R}}default:return(c=>{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}async toggleRecordMode({action:e,indices:t,onStepRecord:o}){if(e==="STOP"){this.recordAbortController?.abort();return}if(!t||!o)throw new Error("Indices or step recording callback not provided to toggleRecordMode");this.recordAbortController=new AbortController;let n=new xt({signal:this.recordAbortController.signal,initialIndices:t,generator:this.generator,onStepRecord:o});await this.browser.startRecording(this.recordAbortController.signal,n)}};async function sn({socket:r,generator:e,logger:t,rootState:o,localApp:n}){let s=t.child({package:"web-agent"}),a=!0;n==="iframe"&&(a=!1);let i=r.handshake.query.testId,l=r.handshake.query.baseUrl;if(!i)throw new Error("Socket connection is missing testID");let c=r.handshake.query.orgId,d=ia(),p=g=>{r.emit("screenshot",{...g,url:l})},f=o?.controller;if(f)f.browser.setActiveFrame(ft),f.browser.baseURL=l,await f.resetState({clearCookies:!0,clearStorage:!0,timeout:null}),f.setOpen(),o?.context.reset(l);else{let g=await M.init({baseUrl:l,logger:s,takeScreenshots:a,onScreenshot:p});f=new me({browser:g,generator:e,config:De,logger:s})}a&&Pr(r,d,t);let h=new V(l,[]);r.emit("testContext",{state:h.toObjectRef()}),$.registerSession(f,h,d);let m=M.USER_AGENT;return r.emit("session",{url:l,userAgent:m,viewport:await f.browser.viewport(),localApp:n}),{sessionId:d,testId:i,orgId:c}}var an=[Qo,_r,tn,Zo,en];var ln=r=>{let{logger:e}=r,t=new aa(r.baseServer,{cors:{origin:"*",methods:["GET","POST"]}});return t.on("connection",async o=>{let n;try{n=await sn({...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}an.forEach(s=>la(s,{socket:o,metadata:n,...r}))}),t},la=(r,e)=>{let t=r.createHandler(e),o=(...n)=>{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 ca from"fetch-retry";var da=ca(global.fetch),ge="v1",_e=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getElementLocation(e,t){let o=await this.sendRequest(`/${ge}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:t});return Yt.parse(o)}async getElementLocationWithVision(e,t){let o=await this.sendRequest(`/${ge}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:t});return Yt.parse(o)}async getAssertionResult(e,t,o){if(t){let s=await this.sendRequest(`/${ge}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:o,vision:!0});return qt.parse(s)}let n=await this.sendRequest(`/${ge}/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 qt.parse(n)}async getProposedCommand(e,t){let o=await this.sendRequest(`/${ge}/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 to.parse(o)}async getGranularGoals(e,t){let o=await this.sendRequest(`/${ge}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:t});return ro.parse(o)}async getReverseMappedDescription(e,t){let o=await this.sendRequest(`/${ge}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:t});return oo.parse(o)}async getTextExtraction(e,t){let o={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:t},n=await this.sendRequest(`/${ge}/web-agent/text-extraction`,o);return Ft.parse(n)}async sendRequest(e,t){let o=await da(`${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()}};import{existsSync as ma,readFileSync as ua,writeFileSync as gr}from"fs";import yr from"path";import{parse as pa,stringify as ha}from"yaml";var U=process.env.MOMENTIC_DIR??he;function cn(r,e){let t=ho(r);for(let[s,a]of Object.entries(t.modules)){let i=xe(s);gr(yr.join(U,le,`${i}.yaml`),a,"utf-8")}let o=xe(e),n=yr.join(U,`${o}.yaml`);return gr(n,t.test,"utf-8"),`${o}.yaml`}function dn(r,e){if(!ma(e))throw new Error(`Test not found at path: ${yr}`);let t=ua(e,"utf-8"),n={...pa(t),...r},s=ha(n);gr(e,s,"utf-8")}import{readFileSync as It,readdirSync as mn,writeFileSync as un}from"fs";import{join as Sr}from"path";import{v4 as fa}from"uuid";import{parse as Lt,stringify as pn}from"yaml";var et=Sr(U,le);function hn(r,e){let t=vt(et,e).path,o=It(t,"utf-8"),s={...Lt(o),...r},a=pn(s);un(t,a,"utf-8")}function fn(r,e){let t=xe(r),o=Sr(et,`${t}.yaml`),n={fileType:"momentic/module",schemaVersion:j,moduleId:fa(),name:r,steps:e},s=pn(n);return un(o,s,"utf-8"),{type:"RESOLVED_MODULE",moduleId:n.moduleId,name:n.name,steps:n.steps}}function gn(){let r=[];for(let e in mn(et)){if(!e.endsWith(".yaml"))continue;let t=It(e,"utf-8"),o=Lt(t),n={name:o.name,moduleId:o.moduleId,numSteps:o.steps.length};r.push(n)}return r}function yn(){let r=[];for(let e of mn(et)){if(!e.endsWith(".yaml"))continue;let t=Sr(et,e),o=It(t,"utf-8"),n=Lt(o);try{let s=Jt.parse(n);r.push(s)}catch(s){H.warn({err:s},"Error parsing module, skipping...")}}return r}function Sn(r){let e=vt(le,r).path,t=It(e,"utf-8"),o=Lt(t);return Jt.parse(o)}import{Router as ba}from"express";import{existsSync as ga,readFileSync as ya,readdirSync as Sa}from"fs";import{join as wn}from"path";import{parse as wa}from"yaml";var wr=wn(U,ht);function bn(){let r=[];if(!ga(wr))return[];for(let e of Sa(wr)){if(!e.endsWith(".yaml"))continue;let t=wn(wr,e),o=ya(t,"utf-8"),n=wa(o);try{let s=Y.parse(n);r.push(s)}catch(s){H.warn({err:s},"Error parsing environment, skipping...")}}return r}function q(r){return function(...e){let t=e[e.length-1],o=r(...e);Promise.resolve(o).catch(t)}}var vn=ba();vn.get("/",q((r,e)=>{let t=bn();e.status(200).json(t)}));var En=vn;import{Router as va}from"express";var tt=va();tt.get("/",q((r,e)=>{let o=yn().map(n=>({...n,type:"RESOLVED_MODULE"}));e.status(200).json(o)}));tt.get("/metadata",q((r,e)=>{let t=gn();e.status(200).json(t)}));tt.post("/",q((r,e)=>{let t;try{t=Zr.parse(r.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}try{Je(t.name)}catch(n){e.status(400).json({error:`Invalid module name: ${n}`});return}let o=fn(t.name,t.steps);e.status(201).json(o)}));tt.get("/:moduleId",q((r,e)=>{if(!r.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let t=Sn(r.params.moduleId);e.json(t)}));var An=tt;import{Router as Ea}from"express";import{existsSync as Aa}from"fs";import{join as br}from"path";import{v4 as Ta}from"uuid";var rt=Ea();rt.get("/",q((r,e)=>{let t=Qe(U);e.status(200).json(t)}));rt.post("/",q((r,e)=>{let t;try{t=Qr.parse(r.body)}catch(i){e.status(400).json({error:`Invalid request body: ${i}`});return}try{Je(t.name)}catch(i){e.status(400).json({error:i.message});return}let n={id:Ta(),name:t.name,baseUrl:t.baseUrl,schemaVersion:j,advanced:{disableAICaching:!1,availableAsModule:!0},retries:0,steps:[]};t.environment&&(n.envs=[{name:t.environment,defaultOnLocal:!0}]);let s=cn(n,t.name),a={...n,testPath:s};e.status(201).json(a)}));rt.get("/:testPath",q(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t=br(U,r.params.testPath);if(!Aa(t)){e.status(400).json({error:`Test not found at path: ${t}`});return}let o=await Et(t,br(U,le));e.status(200).json(o)}));rt.patch("/:testPath",q((r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t;try{t=Jr.parse(r.body)}catch(s){e.status(400).json({error:`Invalid request body: ${s}`});return}let o=[],n={};for(let s of t.steps){if(s.type!=="RESOLVED_MODULE"){o.push(s);continue}o.push({type:"MODULE",moduleId:s.moduleId}),n[s.moduleId]=s.steps}for(let[s,a]of Object.entries(n))hn({steps:a},s);dn({steps:o},br(U,r.params.testPath)),e.status(201).json({message:"ok"})}));var Tn=rt;var vr="https://api.momentic.ai",Rn=r=>{vr=r},xn=()=>vr,Ca,In=r=>{Ca=r};var Cn,Ln=async r=>{if(Cn)return;let e=await fetch(`${vr}/v1/auth/check`,{headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`}});if(!e.ok)throw new Error(`Error checking API key with server (code ${e.status}): ${await e.text()}`);let t=await e.json(),{orgId:o}=uo.parse(t);Cn=o};import On,{multistream as Ra}from"pino";import xa from"pino-pretty";var Er=new Map,Ia=process.env.NODE_ENV==="production",Ar=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=Ia?On(n):On(n,Ra([{stream:xa({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}}])});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}},Ot=({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 Er.has(r)||Er.set(r,new Ar({bindings:{app:r},hostname:t,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN})),Er.get(r)};import{hostname as La}from"os";var te=Ot({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:La()});async function Nn({momenticServerUrl:r,apiKey:e,mode:t,serverPort:o,appPort:n,staticDir:s,assetsDir:a}){if(!Na(U)||!Da(U).isDirectory()){let h=ka.isAbsolute(U);throw new Error(`Root folder ${U} does not exist${h?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}r&&Rn(r),H.info("Checking API key"),await Ln(e);let l=`http://localhost:${o}`;n&&(l=`http://localhost:${n}`);let c=za(s);await new Promise(h=>{c.listen(o,()=>{H.info(`Server is running at http://localhost:${o}`),h()})});let p=new _e({baseURL:xn(),apiKey:e});if(t==="web"){await Mn("web",c,p,te),await _a(l);return}let f=await Ua({appUrl:l,apiGenerator:p,extensionPath:Fa(a,"extension"),onClose:async()=>{H.info("Browser closed, closing app."),await f.browser.cleanup(),c.close(()=>{process.exit(0)})}});await Mn("iframe",c,p,te,{controller:f,context:new V("",[])}),process.on("uncaughtException",h=>{te.error({err:h},"Uncaught exception in desktop-server")}),process.on("unhandledRejection",h=>{te.error({err:h},"Unhandled rejection in desktop-server")})}function za(r){let e=Tr();e.use(Ma()),e.use(Oa.json({limit:"50mb"}));let t=Tr.Router();if(t.use("/tests",Tn),t.use("/modules",An),t.use("/environments",En),e.use("/api",t),e.use((n,s,a)=>{n.path!=="/healthcheck"&&te.debug({url:n.url,path:n.path,query:n.query,method:n.method,body:n.body,headers:n.rawHeaders,client:n.ip},"Received desktop-server request"),s.on("close",()=>{s.statusCode>=400&&te.error({url:n.url,method:n.method,statusCode:s.statusCode},"Request completed in error")}),a()}),e.use((n,s,a,i)=>{te.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"),i(n)}),r){let n=Tr.static(r);e.use("/",n),e.use("*",n)}return Pa.createServer(e)}async function Ua({appUrl:r,apiGenerator:e,extensionPath:t,onClose:o}){let n=await M.init({baseUrl:r,logger:te,browserArgs:{headless:!1,handleSIGTERM:!0},contextArgs:{bypassCSP:!0,viewport:null,deviceScaleFactor:void 0},waitForLoad:!0,localMode:!0,localAppUrl:r,extensionPath:t,skipPageSetup:!0,timeout:null,onClose:o}),s=new me({browser:n,config:De,generator:e,logger:te});return In(s),s}async function Mn(r,e,t,o,n){ln({baseServer:e,generator:t,logger:o,localApp:r,rootState:n})}import{existsSync as Sl}from"fs";import ns,{dirname as wl}from"path";import{fileURLToPath as bl}from"url";var Dn="0.0.39";var X="v1",re=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getRun(e){let t=await this.sendRequest(`/${X}/runs/${e}`,{method:"GET"});return co.parse(t)}async createRun(e){let t=await this.sendRequest(`/${X}/runs`,{method:"POST",body:e});return lo.parse(t)}async updateRun(e,t){await this.sendRequest(`/${X}/runs/${e}`,{method:"PATCH",body:t})}async getTest(e){let t=await this.sendRequest(`/${X}/tests/${e}`,{method:"GET"});return so.parse(t)}async getAllTestIds(){let e=await this.sendRequest(`/${X}/tests`,{method:"GET"});return io.parse(e)}async getTestYAMLExport(e){let t=await this.sendRequest(`/${X}/tests/export`,{method:"POST",body:e});return ao.parse(t)}async updateTestWithYAML(e){await this.sendRequest(`/${X}/tests/update`,{method:"POST",body:e})}async queueTests(e){let t=await this.sendRequest(`/${X}/tests/queue`,{method:"POST",body:e});return no.parse(t)}async uploadScreenshot(e){let t=await this.sendRequest(`/${X}/screenshots`,{method:"POST",body:e});return mo.parse(t)}async getAllEnvironments(){let e=await this.sendRequest(`/${X}/environments`,{method:"GET"});return po.parse(e)}async updateEnvironments(e){await this.sendRequest(`/${X}/environments`,{method:"POST",body:e})}async sendRequest(e,t){let o=await fetch(`${this.baseURL}${e}`,{method:t.method,body:t.body?JSON.stringify(t.body):void 0,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.status===204?o.text():o.json()}};import{hostname as Ha}from"os";var w=Ot({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:Ha()});import{createHash as kn}from"crypto";import{existsSync as Fn,mkdirSync as Ka,readFileSync as Ir,writeFileSync as zn}from"fs";import{join as Un}from"path";import{parse as qa}from"yaml";import{existsSync as Cr,mkdirSync as ot,readdirSync as _n,statSync as Rr}from"fs";import{homedir as Ga}from"os";import{join as nt,resolve as Va}from"path";import Ba from"chalk";import ja from"readline/promises";async function oe(r,e){if(process.env.CI)return!0;let t=ja.createInterface({input:process.stdin,output:process.stdout});r=`${r} (y/N) `;let o=e?Ba.bold.yellow(r):r,n=await t.question(o);return t.close(),n.toLowerCase()==="y"}var Mt=he,ke=nt(he,le),xr=nt(he,Xr),J=nt(he,ht),Wa=nt(Ga(),"momentic","chromium");function Pn(r){return Cr(r)&&Rr(r).isDirectory()}async function ye(r,e=!1){Pn(Mt)||(!e&&!await oe(`A '${he}' folder was not found in the current directory. Setup the required Momentic folder structure?`)&&(w.error("Setup cancelled"),process.exit(1)),ot(Mt),ot(ke),ot(xr),ot(J),ot(Wa,{recursive:!0})),(!Pn(J)||_n(J).length===0)&&(!e&&!await oe("You do not appear to have any environments configured locally. Pull environments from Momentic Cloud?")&&(w.error("Setup cancelled"),process.exit(1)),await Nt({client:r,all:!0,skipPrompts:e})),w.info("CLI initialization complete")}function st(r,e,t=new Set){for(let o of r){let n=Va(o);if(n&&Cr(n)&&Rr(n).isDirectory()){let s=_n(n).map(a=>nt(n,a));st(s,e,t);continue}if(n.endsWith(".yaml")){if(!Cr(n)||!Rr(n).isFile())throw new Error(`File not found or unreadable: ${n}`);if(!e(n))continue;t.add(n)}}return t}async function Nt({envNames:r,client:e,all:t,skipPrompts:o}){let n=await e.getAllEnvironments();r&&!t&&(n=n.filter(i=>r?.includes(i.name))),Fn(J)||Ka(J);let s=0,a=0;for(let i of n){let l=Un(J,`${i.name}.yaml`);if(!Fn(l))zn(l,Qt(i)),s++;else{let c=kn("sha256").update(Ir(l)).digest("hex"),d=Qt(i),p=kn("sha256").update(d).digest("hex");if(c!==p){if(!o&&!await oe(`Environment ${i.name} already exists on disk but needs to be updated. Overwrite?`)){w.error("Pull cancelled");return}zn(l,d),a++}}}w.info(`Pulled ${s} new environments and updated ${a} existing environments`)}function Ya(r){return r.endsWith(".yaml")?Ir(r,"utf8").includes("momentic/environment")?!0:(w.warn(`Skipping YAML that is not a Momentic environment: ${r}`),!1):!1}async function $n({client:r,names:e,all:t,yes:o}){let n;t?n=[J]:n=e.map(i=>Un(J,`${i.toLowerCase()}.yaml`));let s=st(n,Ya);w.info(`Found ${s.size} environments(s) to push:`),s.forEach(i=>w.info(` - ${i}`)),w.info("Loading file contents");let a=[];for(let i of s){let l=qa(Ir(i,"utf-8"));try{let c=Y.parse(l);a.push(c)}catch(c){w.error({err:c},`${i} failed to parse as a valid environment file`),process.exit(1)}}if(!o&&!await oe("Pushing environments can overwrite variables on cloud and instantly affect scheduled runs. Continue?",!0)){w.error("Push cancelled");return}w.info(`Pushing ${s.size} environment(s)`),await r.updateEnvironments(a),w.info("Push successful!")}import{registry as Lr}from"playwright-core/lib/server";function Xa(r){let e=[],t=[];for(let o of r){let n=Lr.findExecutable(o);!n||n.installType==="none"?e.push(o):t.push(n)}return t}async function Hn(){let r=Xa(["chromium"]);await Lr.installDeps(r,!1),await Lr.install(r,!1)}import{Argument as Dt,Option as Fe}from"commander";var Se=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),we=new Fe("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),be=new Fe("-y, --yes","Skip confirmation prompts.").env("CI").default(!1),Bn=new Fe("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").default(!0),jn=new Fe("--env <env>","Name of the environment to use when running tests."),Or=new Fe("-a, --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments."),Mr=new Fe("-a, --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments."),Gn=new Dt("<tests...>",`One or more test paths to pull from Momentic Cloud.
29
+ `)}`}async executeCommand(e,t,o,n=!1){let s=this.commandHistory[this.commandHistory.length-1];if(!n&&(!s||s.state!=="PENDING"))throw new T("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(),i=await this.executePresetStep(e,t,o),l=Date.now()-a;return this.logger.debug({result:i,duration:l},"Got execution result"),i.succeedImmediately&&!n&&(this.pendingInstructions.pop(),this.pendingInstructions.length>0&&(i.succeedImmediately=!1)),i.elementInteracted&&"target"in e&&e.target&&!e.target.elementDescriptor&&(e.target.elementDescriptor=i.elementInteracted.trim()),n||(s.generatedStep=e,s.serializedCommand=Be(e),s.state="DONE"),i}async executeAssertion(e){let t=await this.getBrowserState(),o=await this.browser.url(),n;if(e.useVision)n={goal:e.assertion,url:o,screenshot:await this.browser.screenshot(),browserState:"",history:"",numPrevious:-1,lastCommand:null};else{let a=this.getSerializedHistory(o,t);n={goal:e.assertion,url:o,browserState:t,history:a,lastCommand:this.lastExecutedCommand,numPrevious:this.commandHistory.length}}let s=await this.generator.getAssertionResult(n,e.useVision,e.disableCache);if(s.relevantElements&&Promise.all(s.relevantElements.map(a=>this.browser.highlight({id:a}))),!s.result)throw new T("AssertionFailureError",s.thoughts);return{succeedImmediately:!1,thoughts:s.thoughts,urlAfterCommand:o}}async wrapMultiElementTargetingCommand(e,t,o,...n){let s=await Promise.all(n.map(a=>this.wrapElementTargetingCommand(a,t.useVision,t.disableCache,async i=>i)));try{return await e(...s)}catch(a){if(o>0)return this.logger.warn({err:a},"Failed to execute action with multiple cached targets, retrying with AI"),n.forEach(i=>i.a11yData=void 0),this.wrapMultiElementTargetingCommand(e,t,o-1,...n);throw new T("ActionFailureError","",{cause:a})}}async wrapElementTargetingCommand(e,t,o,n,s=1){if(!e.a11yData&&!e.elementDescriptor)throw new T("ActionFailureError","Cannot target element with no target data or element descriptor");let a=e.a11yData&&at(e.a11yData);e.a11yData||(this.logger.info("No cached locator data for target, prompting AI for fresh location"),s--,e.a11yData=it.parse(await this.locateElement(e.elementDescriptor,t,o)));try{let i=await n(e.a11yData);return a?this.logger.info({target:e},"Successfully used cached target to perform action"):this.logger.debug({target:e},"Successfully generated and used new a11y target information"),i}catch(i){if(s>0&&e.elementDescriptor)return this.logger.warn({err:i,target:e},"Failed to execute action with cached target, retrying with AI"),e.a11yData=void 0,this.wrapElementTargetingCommand(e,t,o,n,s);if(i instanceof T)throw i;let l=`Failed to find ${e.elementDescriptor?`${e.elementDescriptor}`:"element"}: ${i instanceof Error?i.message:i}`;throw this.logger.error({err:i,target:e},l),new T("ActionFailureError",l,{cause:i})}}async executePresetStep(e,t,o){let n;try{n=await this.resolveCommandTemplateStrings(e,t)}catch(s){throw new T("ActionFailureError",s.message,{cause:s})}try{return await this.executePresetStepHelper(e,t,o)}catch(s){throw s instanceof T?s:new T("InternalWebAgentError",s instanceof Error?s.message:`${s}`,{cause:s})}finally{this.restoreCommandTemplateReplacements(e,n)}}restoreCommandTemplateReplacements(e,t={}){for(let[o,n]of Object.entries(t))fr(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 i=e[a],l=o?`${o}.${a}`:a;if(typeof i=="string"&&i.includes("{{")){let c=await hr({s:i,state:t.toObjectRef(),logger:this.logger});if(i===c)continue;n[l]=i,e[a]=c}else typeof i=="object"&&i!==null&&!Array.isArray(i)&&await this.resolveCommandTemplateStrings(i,t,l,n)}return n}async executePresetStepHelper(e,t,o){switch(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 T("ActionFailureError","Missing assertion");return this.executeAssertion(e)}case"AI_WAIT":{if(!e.assertion.trim())throw new T("ActionFailureError","Missing assertion");let c=Date.now();if(e.timeout&&e.timeout>30)throw new T("AssertionFailureError",`AI wait timeout of ${e.timeout} exceeds the maximum allowed timeout of 30s`);let d=(e.timeout??10)*1e3,p,f,h=0;for(;Date.now()-c<d;)try{p=await this.executeAssertion({...e,type:"AI_ASSERTION"});break}catch(m){f=m instanceof Error?m:new Error(`${m}`),this.logger.warn({err:m},`AI_WAIT assert attempt ${h} failed, retrying...`),h++,await B(d/10)}if(!p){let m=`AI wait still failing after ${d}ms.`;throw f&&(m+=` Latest result: ${f.message}`),new T("AssertionFailureError",m)}return p}case"AI_EXTRACT":{if(!e.goal.trim())throw new T("ActionFailureError","Cannot perform AI extraction without goal");let c=await this.browser.getHTML(),d=await this.generator.getTextExtraction({goal:e.goal,browserState:c,returnSchema:e.schema},e.disableCache);if(d.result==="NOT_FOUND")throw new T("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(!rr(e.url)&&!or(e.url,this.browser.baseURL))throw new T("ActionFailureError",`Invalid URL: ${e.url}`);await this.browser.navigate({url:e.url});break;case"CAPTCHA":let n=await this.browser.solveCaptcha();n&&(await this.wrapElementTargetingCommand({elementDescriptor:"the captcha image solution input"},e.useVision,o,c=>this.browser.click(c)),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;switch(e.target&&(e.target.elementDescriptor.trim()||e.target.a11yData)&&(s=await this.wrapElementTargetingCommand(e.target,e.useVision,o,c=>this.browser.hover(c))),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 c=await this.browser.url(),d=await this.wrapElementTargetingCommand(e.target,e.useVision,o,f=>this.browser.click(f,{doubleClick:e.doubleClick,rightClick:e.rightClick,force:e.force})),p={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:d};return Le(c,p.urlAfterCommand)&&(p.succeedImmediately=!0,p.succeedImmediatelyReason="URL changed"),p}case"DRAG":{let c=await this.wrapMultiElementTargetingCommand((d,p)=>this.browser.dragAndDrop(d,p,{force:e.force}),{useVision:e.useVision,disableCache:o},1,e.fromTarget,e.toTarget);return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:c}}case"SELECT_OPTION":{let c=await this.wrapElementTargetingCommand(e.target,!1,o,d=>this.browser.selectOption(d,e.option));return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:c}}case"TAB":await this.browser.switchToPage(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 c;try{c=await pr({code:e.code,fragment:!!e.fragment,state:t.toObjectRef(),timeoutMs:e.timeout?e.timeout*1e3:void 0,logger:this.logger})}catch(d){throw new T("ActionFailureError",d instanceof Error?d.message:`${d}`,{cause:d})}try{c=c===void 0?void 0:JSON.parse(JSON.stringify(c))}catch(d){throw new T("ActionFailureError",`Return value is not serializable: ${d instanceof Error?d.message:`${d}`}`,{cause:d})}return{urlAfterCommand:await this.browser.url(),succeedImmediately:!1,data:c}}case"TYPE":{let c=await this.browser.url(),d;e.target&&(d=await this.wrapElementTargetingCommand(e.target,e.useVision,o,f=>this.browser.click(f,{force:e.force}))),await this.browser.type(e.value,{clearContent:e.clearContent,pressKeysSequentially:e.pressKeysSequentially}),e.pressEnter&&await this.browser.press("Enter");let p={urlAfterCommand:await this.browser.url(),succeedImmediately:!1,elementInteracted:d};return Le(c,p.urlAfterCommand)&&(p.succeedImmediately=!0,p.succeedImmediatelyReason="URL changed"),p}case"HOVER":{let c=await this.wrapElementTargetingCommand(e.target,e.useVision,o,d=>this.browser.hover(d));return{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),elementInteracted:c}}case"PRESS":let a=await this.browser.url();await this.browser.press(e.value);let i={urlAfterCommand:await this.browser.url(),succeedImmediately:!1};return Le(a,i.urlAfterCommand)&&(i.succeedImmediately=!0,i.succeedImmediatelyReason="URL changed"),i;case"REQUEST":{let c=e.timeout??30,d=null,p=new AbortController,f=Object.fromEntries(Object.entries(e.headers||{}).filter(([E,A])=>E&&A)),h=new URLSearchParams;Object.entries(e.params||{}).filter(([E,A])=>E&&A).forEach(([E,A])=>{h.append(E,A)});let m;if(rr(e.url)&&(m=e.url),or(e.url,this.browser.baseURL)&&(m=new URL(e.url,this.browser.baseURL).toString()),!m)throw new T("ActionFailureError",`Invalid URL: ${e.url}`);let g=async()=>{try{d=await fetch(`${m}?${h.toString()}`,{headers:f,method:e.method,body:e.body,signal:p.signal})}catch(E){this.logger.error({err:E},"Failed to make fetch request")}},S=async()=>{await new Promise(E=>setTimeout(E,c*1e3)),p.abort()};await Promise.race([S(),g()]);let v=d;if(!v)throw new T("ActionFailureError",`Fetch request timed out after ${c} seconds`);if(!v.ok)throw new T("ActionFailureError",`Fetch request failed with status ${v.status}`);let y={};v.headers.forEach((E,A)=>{y[A]=E});let R={status:v.status,headers:y};return v.headers.get("content-type")?.includes("json")?R.json=await v.json():v.headers.get("content-type")?.includes("text")&&(R.text=await v.text()),{succeedImmediately:!1,urlAfterCommand:await this.browser.url(),data:R}}default:return(c=>{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}async toggleRecordMode({action:e,indices:t,onStepRecord:o}){if(e==="STOP"){this.recordAbortController?.abort();return}if(!t||!o)throw new Error("Indices or step recording callback not provided to toggleRecordMode");this.recordAbortController=new AbortController;let n=new xt({signal:this.recordAbortController.signal,initialIndices:t,generator:this.generator,onStepRecord:o});await this.browser.startRecording(this.recordAbortController.signal,n)}};async function sn({socket:r,generator:e,logger:t,rootState:o,localApp:n}){let s=t.child({package:"web-agent"}),a=!0;n==="iframe"&&(a=!1);let i=r.handshake.query.testId,l=r.handshake.query.baseUrl;if(!i)throw new Error("Socket connection is missing testID");let c=r.handshake.query.orgId,d=ia(),p=g=>{r.emit("screenshot",{...g,url:l})},f=o?.controller;if(f)f.browser.setActiveFrame(ft),f.browser.baseURL=l,await f.resetState({clearCookies:!0,clearStorage:!0,timeout:null}),f.setOpen(),o?.context.reset(l);else{let g=await M.init({baseUrl:l,logger:s,takeScreenshots:a,onScreenshot:p});f=new me({browser:g,generator:e,config:De,logger:s})}a&&Pr(r,d,t);let h=new V(l,[]);r.emit("testContext",{state:h.toObjectRef()}),$.registerSession(f,h,d);let m=M.USER_AGENT;return r.emit("session",{url:l,userAgent:m,viewport:await f.browser.viewport(),localApp:n}),{sessionId:d,testId:i,orgId:c}}var an=[Qo,_r,tn,Zo,en];var ln=r=>{let{logger:e}=r,t=new aa(r.baseServer,{cors:{origin:"*",methods:["GET","POST"]}});return t.on("connection",async o=>{let n;try{n=await sn({...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}an.forEach(s=>la(s,{socket:o,metadata:n,...r}))}),t},la=(r,e)=>{let t=r.createHandler(e),o=(...n)=>{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 ca from"fetch-retry";var da=ca(global.fetch),ge="v1",_e=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getElementLocation(e,t){let o=await this.sendRequest(`/${ge}/web-agent/locate-element`,{browserState:e.browserState,goal:e.goal,disableCache:t});return Yt.parse(o)}async getElementLocationWithVision(e,t){let o=await this.sendRequest(`/${ge}/web-agent/visual-locate`,{goal:e.goal,screenshot:e.screenshot?.toString("base64"),hintActivatedScreenshot:e.hintActivatedScreenshot?.toString("base64"),disableCache:t});return Yt.parse(o)}async getAssertionResult(e,t,o){if(t){let s=await this.sendRequest(`/${ge}/web-agent/assertion`,{url:e.url,goal:e.goal,screenshot:e.screenshot?.toString("base64"),disableCache:o,vision:!0});return qt.parse(s)}let n=await this.sendRequest(`/${ge}/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 qt.parse(n)}async getProposedCommand(e,t){let o=await this.sendRequest(`/${ge}/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 to.parse(o)}async getGranularGoals(e,t){let o=await this.sendRequest(`/${ge}/web-agent/split-goal`,{url:e.url,goal:e.goal,disableCache:t});return ro.parse(o)}async getReverseMappedDescription(e,t){let o=await this.sendRequest(`/${ge}/web-agent/reverse-mapped-description`,{goal:e.goal,browserState:e.browserState,disableCache:t});return oo.parse(o)}async getTextExtraction(e,t){let o={goal:e.goal,browserState:e.browserState,returnSchema:e.returnSchema,disableCache:t},n=await this.sendRequest(`/${ge}/web-agent/text-extraction`,o);return Ft.parse(n)}async sendRequest(e,t){let o=await da(`${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()}};import{existsSync as ma,readFileSync as ua,writeFileSync as gr}from"fs";import yr from"path";import{parse as pa,stringify as ha}from"yaml";var U=process.env.MOMENTIC_DIR??he;function cn(r,e){let t=ho(r);for(let[s,a]of Object.entries(t.modules)){let i=xe(s);gr(yr.join(U,le,`${i}.yaml`),a,"utf-8")}let o=xe(e),n=yr.join(U,`${o}.yaml`);return gr(n,t.test,"utf-8"),`${o}.yaml`}function dn(r,e){if(!ma(e))throw new Error(`Test not found at path: ${yr}`);let t=ua(e,"utf-8"),n={...pa(t),...r},s=ha(n);gr(e,s,"utf-8")}import{readFileSync as It,readdirSync as mn,writeFileSync as un}from"fs";import{join as Sr}from"path";import{v4 as fa}from"uuid";import{parse as Lt,stringify as pn}from"yaml";var et=Sr(U,le);function hn(r,e){let t=vt(et,e).path,o=It(t,"utf-8"),s={...Lt(o),...r},a=pn(s);un(t,a,"utf-8")}function fn(r,e){let t=xe(r),o=Sr(et,`${t}.yaml`),n={fileType:"momentic/module",schemaVersion:j,moduleId:fa(),name:r,steps:e},s=pn(n);return un(o,s,"utf-8"),{type:"RESOLVED_MODULE",moduleId:n.moduleId,name:n.name,steps:n.steps}}function gn(){let r=[];for(let e in mn(et)){if(!e.endsWith(".yaml"))continue;let t=It(e,"utf-8"),o=Lt(t),n={name:o.name,moduleId:o.moduleId,numSteps:o.steps.length};r.push(n)}return r}function yn(){let r=[];for(let e of mn(et)){if(!e.endsWith(".yaml"))continue;let t=Sr(et,e),o=It(t,"utf-8"),n=Lt(o);try{let s=Jt.parse(n);r.push(s)}catch(s){H.warn({err:s},"Error parsing module, skipping...")}}return r}function Sn(r){let e=vt(le,r).path,t=It(e,"utf-8"),o=Lt(t);return Jt.parse(o)}import{Router as ba}from"express";import{existsSync as ga,readFileSync as ya,readdirSync as Sa}from"fs";import{join as wn}from"path";import{parse as wa}from"yaml";var wr=wn(U,ht);function bn(){let r=[];if(!ga(wr))return[];for(let e of Sa(wr)){if(!e.endsWith(".yaml"))continue;let t=wn(wr,e),o=ya(t,"utf-8"),n=wa(o);try{let s=Y.parse(n);r.push(s)}catch(s){H.warn({err:s},"Error parsing environment, skipping...")}}return r}function q(r){return function(...e){let t=e[e.length-1],o=r(...e);Promise.resolve(o).catch(t)}}var vn=ba();vn.get("/",q((r,e)=>{let t=bn();e.status(200).json(t)}));var En=vn;import{Router as va}from"express";var tt=va();tt.get("/",q((r,e)=>{let o=yn().map(n=>({...n,type:"RESOLVED_MODULE"}));e.status(200).json(o)}));tt.get("/metadata",q((r,e)=>{let t=gn();e.status(200).json(t)}));tt.post("/",q((r,e)=>{let t;try{t=Zr.parse(r.body)}catch(n){e.status(400).json({error:`Invalid request body: ${n}`});return}try{Je(t.name)}catch(n){e.status(400).json({error:`Invalid module name: ${n}`});return}let o=fn(t.name,t.steps);e.status(201).json(o)}));tt.get("/:moduleId",q((r,e)=>{if(!r.params.moduleId){e.status(400).json({error:"Missing moduleId in url path."});return}let t=Sn(r.params.moduleId);e.json(t)}));var An=tt;import{Router as Ea}from"express";import{existsSync as Aa}from"fs";import{join as br}from"path";import{v4 as Ta}from"uuid";var rt=Ea();rt.get("/",q((r,e)=>{let t=Qe(U);e.status(200).json(t)}));rt.post("/",q((r,e)=>{let t;try{t=Qr.parse(r.body)}catch(i){e.status(400).json({error:`Invalid request body: ${i}`});return}try{Je(t.name)}catch(i){e.status(400).json({error:i.message});return}let n={id:Ta(),name:t.name,baseUrl:t.baseUrl,schemaVersion:j,advanced:{disableAICaching:!1,availableAsModule:!0},retries:0,steps:[]};t.environment&&(n.envs=[{name:t.environment,defaultOnLocal:!0}]);let s=cn(n,t.name),a={...n,testPath:s};e.status(201).json(a)}));rt.get("/:testPath",q(async(r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t=br(U,r.params.testPath);if(!Aa(t)){e.status(400).json({error:`Test not found at path: ${t}`});return}let o=await Et(t,br(U,le));e.status(200).json(o)}));rt.patch("/:testPath",q((r,e)=>{if(!r.params.testPath){e.status(400).json({error:"Missing testPath in url path."});return}let t;try{t=Jr.parse(r.body)}catch(s){e.status(400).json({error:`Invalid request body: ${s}`});return}let o=[],n={};for(let s of t.steps){if(s.type!=="RESOLVED_MODULE"){o.push(s);continue}o.push({type:"MODULE",moduleId:s.moduleId}),n[s.moduleId]=s.steps}for(let[s,a]of Object.entries(n))hn({steps:a},s);dn({steps:o},br(U,r.params.testPath)),e.status(201).json({message:"ok"})}));var Tn=rt;var vr="https://api.momentic.ai",Rn=r=>{vr=r},xn=()=>vr,Ca,In=r=>{Ca=r};var Cn,Ln=async r=>{if(Cn)return;let e=await fetch(`${vr}/v1/auth/check`,{headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`}});if(!e.ok)throw new Error(`Error checking API key with server (code ${e.status}): ${await e.text()}`);let t=await e.json(),{orgId:o}=uo.parse(t);Cn=o};import On,{multistream as Ra}from"pino";import xa from"pino-pretty";var Er=new Map,Ia=process.env.NODE_ENV==="production",Ar=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=Ia?On(n):On(n,Ra([{stream:xa({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}}])});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}},Ot=({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 Er.has(r)||Er.set(r,new Ar({bindings:{app:r},hostname:t,clientToken:e||process.env.NEXT_PUBLIC_DD_CLIENT_TOKEN||process.env.DD_CLIENT_TOKEN})),Er.get(r)};import{hostname as La}from"os";var te=Ot({app:"desktop-server",clientToken:"pubcfd7516a5c0ba852b42675cd97bee027",hostname:La()});async function Nn({momenticServerUrl:r,apiKey:e,mode:t,serverPort:o,appPort:n,staticDir:s,assetsDir:a}){if(!Na(U)||!Da(U).isDirectory()){let h=ka.isAbsolute(U);throw new Error(`Root folder ${U} does not exist${h?"":` in the current directory ("${process.cwd()}")`}. Please run \`npx momentic init\` if you wish to setup Momentic here for the first time.`)}r&&Rn(r),H.info("Checking API key"),await Ln(e);let l=`http://localhost:${o}`;n&&(l=`http://localhost:${n}`);let c=za(s);await new Promise(h=>{c.listen(o,()=>{H.info(`Server is running at http://localhost:${o}`),h()})});let p=new _e({baseURL:xn(),apiKey:e});if(t==="web"){await Mn("web",c,p,te),await _a(l);return}let f=await Ua({appUrl:l,apiGenerator:p,extensionPath:Fa(a,"extension"),onClose:async()=>{H.info("Browser closed, closing app."),await f.browser.cleanup(),c.close(()=>{process.exit(0)})}});await Mn("iframe",c,p,te,{controller:f,context:new V("",[])}),process.on("uncaughtException",h=>{te.error({err:h},"Uncaught exception in desktop-server")}),process.on("unhandledRejection",h=>{te.error({err:h},"Unhandled rejection in desktop-server")})}function za(r){let e=Tr();e.use(Ma()),e.use(Oa.json({limit:"50mb"}));let t=Tr.Router();if(t.use("/tests",Tn),t.use("/modules",An),t.use("/environments",En),e.use("/api",t),e.use((n,s,a)=>{n.path!=="/healthcheck"&&te.debug({url:n.url,path:n.path,query:n.query,method:n.method,body:n.body,headers:n.rawHeaders,client:n.ip},"Received desktop-server request"),s.on("close",()=>{s.statusCode>=400&&te.error({url:n.url,method:n.method,statusCode:s.statusCode},"Request completed in error")}),a()}),e.use((n,s,a,i)=>{te.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"),i(n)}),r){let n=Tr.static(r);e.use("/",n),e.use("*",n)}return Pa.createServer(e)}async function Ua({appUrl:r,apiGenerator:e,extensionPath:t,onClose:o}){let n=await M.init({baseUrl:r,logger:te,browserArgs:{headless:!1,handleSIGTERM:!0},contextArgs:{bypassCSP:!0,viewport:null,deviceScaleFactor:void 0},waitForLoad:!0,localMode:!0,localAppUrl:r,extensionPath:t,skipPageSetup:!0,timeout:null,onClose:o}),s=new me({browser:n,config:De,generator:e,logger:te});return In(s),s}async function Mn(r,e,t,o,n){ln({baseServer:e,generator:t,logger:o,localApp:r,rootState:n})}import{existsSync as Sl}from"fs";import ns,{dirname as wl}from"path";import{fileURLToPath as bl}from"url";var Dn="0.0.40";var X="v1",re=class{baseURL;apiKey;constructor(e){this.baseURL=e.baseURL,this.apiKey=e.apiKey}async getRun(e){let t=await this.sendRequest(`/${X}/runs/${e}`,{method:"GET"});return co.parse(t)}async createRun(e){let t=await this.sendRequest(`/${X}/runs`,{method:"POST",body:e});return lo.parse(t)}async updateRun(e,t){await this.sendRequest(`/${X}/runs/${e}`,{method:"PATCH",body:t})}async getTest(e){let t=await this.sendRequest(`/${X}/tests/${e}`,{method:"GET"});return so.parse(t)}async getAllTestIds(){let e=await this.sendRequest(`/${X}/tests`,{method:"GET"});return io.parse(e)}async getTestYAMLExport(e){let t=await this.sendRequest(`/${X}/tests/export`,{method:"POST",body:e});return ao.parse(t)}async updateTestWithYAML(e){await this.sendRequest(`/${X}/tests/update`,{method:"POST",body:e})}async queueTests(e){let t=await this.sendRequest(`/${X}/tests/queue`,{method:"POST",body:e});return no.parse(t)}async uploadScreenshot(e){let t=await this.sendRequest(`/${X}/screenshots`,{method:"POST",body:e});return mo.parse(t)}async getAllEnvironments(){let e=await this.sendRequest(`/${X}/environments`,{method:"GET"});return po.parse(e)}async updateEnvironments(e){await this.sendRequest(`/${X}/environments`,{method:"POST",body:e})}async sendRequest(e,t){let o=await fetch(`${this.baseURL}${e}`,{method:t.method,body:t.body?JSON.stringify(t.body):void 0,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.status===204?o.text():o.json()}};import{hostname as Ha}from"os";var w=Ot({app:"cli",clientToken:"pub7eb923f18fb3f1d42ac5eba8c5ea13a5",hostname:Ha()});import{createHash as kn}from"crypto";import{existsSync as Fn,mkdirSync as Ka,readFileSync as Ir,writeFileSync as zn}from"fs";import{join as Un}from"path";import{parse as qa}from"yaml";import{existsSync as Cr,mkdirSync as ot,readdirSync as _n,statSync as Rr}from"fs";import{homedir as Ga}from"os";import{join as nt,resolve as Va}from"path";import Ba from"chalk";import ja from"readline/promises";async function oe(r,e){if(process.env.CI)return!0;let t=ja.createInterface({input:process.stdin,output:process.stdout});r=`${r} (y/N) `;let o=e?Ba.bold.yellow(r):r,n=await t.question(o);return t.close(),n.toLowerCase()==="y"}var Mt=he,ke=nt(he,le),xr=nt(he,Xr),J=nt(he,ht),Wa=nt(Ga(),"momentic","chromium");function Pn(r){return Cr(r)&&Rr(r).isDirectory()}async function ye(r,e=!1){Pn(Mt)||(!e&&!await oe(`A '${he}' folder was not found in the current directory. Setup the required Momentic folder structure?`)&&(w.error("Setup cancelled"),process.exit(1)),ot(Mt),ot(ke),ot(xr),ot(J),ot(Wa,{recursive:!0})),(!Pn(J)||_n(J).length===0)&&(!e&&!await oe("You do not appear to have any environments configured locally. Pull environments from Momentic Cloud?")&&(w.error("Setup cancelled"),process.exit(1)),await Nt({client:r,all:!0,skipPrompts:e})),w.info("CLI initialization complete")}function st(r,e,t=new Set){for(let o of r){let n=Va(o);if(n&&Cr(n)&&Rr(n).isDirectory()){let s=_n(n).map(a=>nt(n,a));st(s,e,t);continue}if(n.endsWith(".yaml")){if(!Cr(n)||!Rr(n).isFile())throw new Error(`File not found or unreadable: ${n}`);if(!e(n))continue;t.add(n)}}return t}async function Nt({envNames:r,client:e,all:t,skipPrompts:o}){let n=await e.getAllEnvironments();r&&!t&&(n=n.filter(i=>r?.includes(i.name))),Fn(J)||Ka(J);let s=0,a=0;for(let i of n){let l=Un(J,`${i.name}.yaml`);if(!Fn(l))zn(l,Qt(i)),s++;else{let c=kn("sha256").update(Ir(l)).digest("hex"),d=Qt(i),p=kn("sha256").update(d).digest("hex");if(c!==p){if(!o&&!await oe(`Environment ${i.name} already exists on disk but needs to be updated. Overwrite?`)){w.error("Pull cancelled");return}zn(l,d),a++}}}w.info(`Pulled ${s} new environments and updated ${a} existing environments`)}function Ya(r){return r.endsWith(".yaml")?Ir(r,"utf8").includes("momentic/environment")?!0:(w.warn(`Skipping YAML that is not a Momentic environment: ${r}`),!1):!1}async function $n({client:r,names:e,all:t,yes:o}){let n;t?n=[J]:n=e.map(i=>Un(J,`${i.toLowerCase()}.yaml`));let s=st(n,Ya);w.info(`Found ${s.size} environments(s) to push:`),s.forEach(i=>w.info(` - ${i}`)),w.info("Loading file contents");let a=[];for(let i of s){let l=qa(Ir(i,"utf-8"));try{let c=Y.parse(l);a.push(c)}catch(c){w.error({err:c},`${i} failed to parse as a valid environment file`),process.exit(1)}}if(!o&&!await oe("Pushing environments can overwrite variables on cloud and instantly affect scheduled runs. Continue?",!0)){w.error("Push cancelled");return}w.info(`Pushing ${s.size} environment(s)`),await r.updateEnvironments(a),w.info("Push successful!")}import{registry as Lr}from"playwright-core/lib/server";function Xa(r){let e=[],t=[];for(let o of r){let n=Lr.findExecutable(o);!n||n.installType==="none"?e.push(o):t.push(n)}return t}async function Hn(){let r=Xa(["chromium"]);await Lr.installDeps(r,!1),await Lr.install(r,!1)}import{Argument as Dt,Option as Fe}from"commander";var Se=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),we=new Fe("--server <server>","Momentic server to use. Leave unchanged unless using Momentic on-premise.").default("https://api.momentic.ai"),be=new Fe("-y, --yes","Skip confirmation prompts.").env("CI").default(!1),Bn=new Fe("--no-report","Skip reporting test results to Momentic Cloud when running with the --local flag.").default(!0),jn=new Fe("--env <env>","Name of the environment to use when running tests."),Or=new Fe("-a, --all","Select all tests in your organization from Momentic Cloud. Cannot be used together with <tests> arguments."),Mr=new Fe("-a, --all","Select all environments in your organization from Momentic Cloud. Cannot be used together with <paths> arguments."),Gn=new Dt("<tests...>",`One or more test paths to pull from Momentic Cloud.
30
30
 
31
31
  A test path is a lowercased version of your test name where spaces are replaced with underscores: 'npx momentic pull hello-world'.`).argOptional(),Vn=new Dt("<tests...>",`One or more test identifiers.
32
32
 
33
33
  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'.
34
34
 
35
- 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'.`).argRequired(),Wn=new Dt("<paths...>","File paths pointing to one or more Momentic YAML files. Paths to directories are supported as well."),Nr=new Dt("<envs...>","One or more environment names to push").argOptional();import{existsSync as Qa,writeFileSync as Kn}from"fs";import{join as qn}from"path";async function Jn({testsToFetch:r,client:e,all:t}){t&&w.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[i,l]of Object.entries(o)){let c=qn(Mt,Xn(i));if(!await Yn(c)){w.error("Pull cancelled");return}Kn(c,l,"utf-8"),w.info(`Wrote '${c}'`)}let a=Object.keys(n).length;for(let[i,l]of Object.entries(n)){let c=qn(ke,Xn(i));if(!await Yn(c)){w.error("Pull cancelled");return}Kn(c,l,"utf-8"),w.info(`Wrote '${c}'`)}s===0?w.info("Pulled zero tests! For onprem deployments, please ensure the --server parameter is set correctly."):w.info(`Pulled ${s} test${s>1?"s":""}${a?` and ${a} module${a>1?"s":""}`:""}!`)}async function Yn(r){return Qa(r)?oe(`File '${r.replace(/(\s+)/g,"\\$1")}' already exists. Overwrite existing content?`,!0):!0}function Xn(r){return`${xe(r)}.yaml`}import{existsSync as Xy,readFileSync as Za}from"fs";async function Qn(r){let e=st(r.paths,el);w.info(`Found ${e.size} test(s) to push:`),e.forEach(n=>w.info(` - ${n}`)),w.info("Loading file contents and resolving dependent modules");let t=Array.from(e).map(n=>tr(n,ke)),o=new Set(t.flatMap(n=>Object.keys(n.modules)));if(w.info(`Resolved ${e.size} test(s) and ${o.size} module(s)`),!r.yes&&!await oe("Pushing tests overwrites tests on production and will instantly affect scheduled runs. Continue?",!0)){w.error("Push cancelled");return}await r.client.updateTestWithYAML(t),w.info("Update successful!")}function el(r){return r.endsWith(".yaml")?Za(r,"utf8").includes("momentic/test")?!0:(w.warn(`Skipping YAML that is not a Momentic test: ${r}`),!1):!1}import{existsSync as ts,readFileSync as ml,statSync as ul}from"fs";import{join as pl}from"path";import hl from"wait-on";import{parse as fl}from"yaml";import tl from"@actions/exec";import rl from"@actions/io";import ol from"quote";import nl from"string-argv";async function Zn(r,e=!0){let t=nl(r),o=await rl.which(t[0],!0),n=t.slice(1),s=tl.exec(ol(o),n,{delay:100});if(e)return s}import{v4 as dl}from"uuid";import{existsSync as sl,readFileSync as il}from"fs";import{join as al}from"path";import{parse as ll}from"yaml";function cl(r){let e=al(xr,`${r}.yaml`);if(!sl(e))throw new Error(`Fixture '${r}' does not exist at expected path ${e}`);let t;try{t=ll(il(e,"utf-8").replace(/\r\n|\r/g,`
36
- `))}catch(n){throw new Error(`Fixture at path ${e} does not parse as valid YAML: ${n}`)}let o;try{o=Wr.parse(t)}catch(n){throw new Error(`Fixture at path ${e} does not conform to expected schema: ${n}`)}return o}async function Dr(r,e){let t=cl(r),o=e==="setup"?t.setup:t.teardown;if(!o){w.warn(`Fixture '${r}' does not have any steps in the ${e} phase, skipping...`);return}let n=o.timeout,s=async function(){for(let c=0;c<o.steps.length;c++){let d=o.steps[c],p=d.run,f=async h=>{try{await Co(`set -ex; ${p}`)}catch(m){if(h){let g=`Fixture '${r}' ${e} step ${c+1} errored in the background. The test will continue, but this may affect test execution and results: ${m}`;w.error(g)}else throw new Error(`Fixture '${r}' ${e} step ${c+1} errored: ${m}`)}};d.waitForCompletion===!1?f(!0):await f(!1)}};if(w.info(`Running ${e} phase of fixture '${r}'`),!n){await s();return}let a,i,l=async function(){return new Promise((c,d)=>{i=c,a=setTimeout(()=>{i=void 0,d(`Fixture '${r}' ${e} phase timed out after ${n} seconds`)},n*1e3)})};try{await Promise.race([s(),l()])}catch(c){throw c}finally{i&&i(),clearTimeout(a)}}async function es({path:r,apiClient:e,generator:t,useLocalFiles:o,noReport:n,environment:s,newBaseUrl:a}){let i;o?(w.info(`Reading ${r} from local filesystem`),i=await Et(r,ke)):(w.info(`Fetching ${r} from Momentic Cloud server (${e.baseURL})`),i=await e.getTest(r)),i.schemaVersion>j&&w.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 l=[],c={};s&&(l=i.envs?.find(y=>y.name===s.name)?.fixtures??[],c=s.variables);for(let v of l)await Dr(v,"setup");let d;if(i.baseUrl){if(d=i.baseUrl,a){let v=new URL(i.baseUrl),y=new URL(a);v.hostname=y.hostname,v.protocol=y.protocol,v.port=y.port,d=v.toString()}}else d=c[Te],d||(w.error(`Test ${i.name} has no baseURL and the environment has no ${Te} variable either`),process.exit(1));let p=await M.init({baseUrl:d,logger:w,waitForLoad:!0}),f=new me({browser:p,generator:t,config:De,logger:w}),h=new V(d,i.steps),m,g;if(n)g=dl();else try{m=await e.createRun({testId:i.id,trigger:"CLI"}),g=m.id}catch(v){throw w.error(v),new Error(`Are you sure test ${i.name} exists on the server?`)}let S="FAILED";try{S=await Yo({test:i,context:h,runId:g,controller:f,logger:w,takeScreenshots:!0,onSaveScreenshot:async v=>{if(n)return"";let{key:y}=await e.uploadScreenshot({screenshot:v.toString("base64")});return y},onUpdateRun:async v=>{n||await e.updateRun(g,v)}})}catch(v){throw n||await e.updateRun(g,{status:"FAILED",finishedAt:new Date}),n||w.error(`Test '${i.name}' failed. You can view a playback of this run at ${e.baseURL}/runs/${g}`),v}finally{try{for(let v of l)await Dr(v,"teardown")}catch(v){w.error(`Failed to run teardown fixtures: ${v}`)}}return S}async function rs({tests:r,start:e,waitOn:t,waitOnTimeout:o,client:n,all:s,noReport:a,env:i,parallelization:l=1}){e&&(w.info(`Running start command: ${e}`),await Zn(e,!1)),t&&(w.info(`Waiting for ${t} to be accessible (timeout: ${o}s)`),await hl({resources:[t],timeout:o*1e3}));let c;if(i){let y=pl(J,`${i}.yaml`),R=fl(ml(y,"utf-8"));try{c=Y.parse(R),w.info(`Running in the ${i} environment with the following variables:`),w.info(c.variables)}catch(E){w.error({err:E},`${y} failed to parse as a valid environment file`),process.exit(1)}}let d=new _e({baseURL:n.baseURL,apiKey:n.apiKey}),p=!1;(r.some(y=>y.endsWith(".yaml"))||r.some(y=>ts(y)))&&(p=!0);let f=new Set;if(p)w.info(r,"Reading tests from the following local file paths:"),r.forEach(y=>{if(!ts(y))throw new Error(`Path '${y}' does not exist.`);if(ul(y).isDirectory())Qe(y).map(E=>E.fullFilePath).forEach(E=>f.add(E));else if(y.endsWith(".yaml"))f.add(y);else throw new Error(`Path '${y}' is not a directory or a .yaml file.`)});else if(w.warn("The paths you specified are not files or directories that exist locally."),w.warn(`Fetching tests from Momentic Cloud (${n.baseURL}) instead...`),s)for(let y of await n.getAllTestIds())f.add(y);else f=new Set(r);let h=Array.from(f);w.info(h,`Identified ${h.length} tests to run locally:`);let m=[];for(let y=0;y<h.length;y+=l){let R=await Promise.all(h.slice(y,y+l).map(async E=>{let A="FAILED";try{A=await es({useLocalFiles:p,path:E,apiClient:n,generator:d,newBaseUrl:t,environment:c,noReport:a})}catch(F){let ze=F instanceof Error?F.message:`${F}`;w.error(`Test ${E} failed with error: ${ze}`)}return{runStatus:A,path:E}}));m=m.concat(R)}let g=m.filter(y=>y.runStatus==="PASSED");g.length>0&&(w.info(`Passed ${g.length} out of ${m.length} tests:`),g.forEach(y=>{w.info(`- ${y.path}`)}));let S=m.filter(y=>y.runStatus==="CANCELLED");S.length>0&&(w.warn(`Cancelled ${S.length} out of ${m.length} tests:`),S.forEach(y=>{w.warn(`- ${y.path}`)}));let v=m.filter(y=>y.runStatus==="FAILED");v.length>0&&(w.error(`Failed ${v.length} out of ${m.length} tests:`),v.forEach(y=>{w.error(`- ${y.path}`)}),process.exit(1)),process.exit(0)}async function os({tests:r,client:e,env:t,all:o}){let{queuedTests:n}=await e.queueTests({testPaths:r,env:t,all:o});w.info(`Successfully queued ${n.length} tests!`)}var ne=new gl;ne.name("momentic").description("Momentic CLI").version(Dn);ne.command("install-browsers").action(async()=>{await Hn()});ne.addOption(new ue("--log-level <level>").choices(["debug","info","warn","error"]).default("info")).on("option:log-level",r=>{w.setMinLevel(r.toLowerCase())});ne.command("init").addOption(be).addOption(Se).addOption(we).action(async r=>{let{yes:e,apiKey:t,server:o}=r,n=new re({baseURL:o,apiKey:t});await ye(n,e)});ne.command("app").addOption(Se).addOption(we).addOption(be).addOption(new ue("--port <port>","Port to run the app on. Defaults to 58888.").default(58888)).addOption(new ue("--mode <mode>","Mode to run the app in. Defaults to iframe.").choices(["iframe","web"]).default("iframe")).action(async r=>{let{apiKey:e,port:t,yes:o,server:n,mode:s}=r,a=new re({baseURL:n,apiKey:e});await ye(a,o);let i=bl(import.meta.url),l=wl(i),c=ns.resolve(l,"..","static"),d=ns.resolve(l,"..","assets");await Nn({momenticServerUrl:n,mode:s,apiKey:e,serverPort:t,appPort:t,staticDir:c,assetsDir:d})});ne.command("run").alias("run-tests").addOption(Se).addOption(we).addOption(Or).addOption(be).addOption(new ue("-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(Bn).addOption(new ue("-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 ue("--start <command>","Arbitrary setup command that will run before Momentic steps begin.")).addOption(new ue("--wait-on <url>","URL to wait to become accessible before Momentic tests begin.")).addOption(jn).addOption(new ue("--wait-on-timeout <timeout>","Max time to wait for the --wait-on URL to become accessible.").default(60,"one minute")).addOption(new ue("-p, --parallel <parallelization>","When running with the --local flag, the number of tests to run in parallel. Defaults to 1.").default(1)).addArgument(Vn).action(async(r,e)=>{let{apiKey:t,server:o,remote:n,local:s,all:a,env:i,yes:l}=e,c=new re({baseURL:o,apiKey:t});if(await ye(c,l),s){try{await rs({tests:r,client:c,...e})}catch(d){w.error(d),process.exit(1)}return}if(n){for(let d of r)(d.endsWith(".yaml")||Sl(d))&&(w.error(yl`'${d}' looks like a local file or directory, but the --local flag was not supplied.
35
+ 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(),Wn=new Dt("<paths...>","File paths pointing to one or more Momentic YAML files. Paths to directories are supported as well."),Nr=new Dt("<envs...>","One or more environment names to push").argOptional();import{existsSync as Qa,writeFileSync as Kn}from"fs";import{join as qn}from"path";async function Jn({testsToFetch:r,client:e,all:t}){t&&w.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[i,l]of Object.entries(o)){let c=qn(Mt,Xn(i));if(!await Yn(c)){w.error("Pull cancelled");return}Kn(c,l,"utf-8"),w.info(`Wrote '${c}'`)}let a=Object.keys(n).length;for(let[i,l]of Object.entries(n)){let c=qn(ke,Xn(i));if(!await Yn(c)){w.error("Pull cancelled");return}Kn(c,l,"utf-8"),w.info(`Wrote '${c}'`)}s===0?w.info("Pulled zero tests! For onprem deployments, please ensure the --server parameter is set correctly."):w.info(`Pulled ${s} test${s>1?"s":""}${a?` and ${a} module${a>1?"s":""}`:""}!`)}async function Yn(r){return Qa(r)?oe(`File '${r.replace(/(\s+)/g,"\\$1")}' already exists. Overwrite existing content?`,!0):!0}function Xn(r){return`${xe(r)}.yaml`}import{existsSync as Xy,readFileSync as Za}from"fs";async function Qn(r){let e=st(r.paths,el);w.info(`Found ${e.size} test(s) to push:`),e.forEach(n=>w.info(` - ${n}`)),w.info("Loading file contents and resolving dependent modules");let t=Array.from(e).map(n=>tr(n,ke)),o=new Set(t.flatMap(n=>Object.keys(n.modules)));if(w.info(`Resolved ${e.size} test(s) and ${o.size} module(s)`),!r.yes&&!await oe("Pushing tests overwrites tests on production and will instantly affect scheduled runs. Continue?",!0)){w.error("Push cancelled");return}await r.client.updateTestWithYAML(t),w.info("Update successful!")}function el(r){return r.endsWith(".yaml")?Za(r,"utf8").includes("momentic/test")?!0:(w.warn(`Skipping YAML that is not a Momentic test: ${r}`),!1):!1}import{existsSync as ts,readFileSync as ml,statSync as ul}from"fs";import{join as pl}from"path";import hl from"wait-on";import{parse as fl}from"yaml";import tl from"@actions/exec";import rl from"@actions/io";import ol from"quote";import nl from"string-argv";async function Zn(r,e=!0){let t=nl(r),o=await rl.which(t[0],!0),n=t.slice(1),s=tl.exec(ol(o),n,{delay:100});if(e)return s}import{v4 as dl}from"uuid";import{existsSync as sl,readFileSync as il}from"fs";import{join as al}from"path";import{parse as ll}from"yaml";function cl(r){let e=al(xr,`${r}.yaml`);if(!sl(e))throw new Error(`Fixture '${r}' does not exist at expected path ${e}`);let t;try{t=ll(il(e,"utf-8").replace(/\r\n|\r/g,`
36
+ `))}catch(n){throw new Error(`Fixture at path ${e} does not parse as valid YAML: ${n}`)}let o;try{o=Wr.parse(t)}catch(n){throw new Error(`Fixture at path ${e} does not conform to expected schema: ${n}`)}return o}async function Dr(r,e){let t=cl(r),o=e==="setup"?t.setup:t.teardown;if(!o){w.warn(`Fixture '${r}' does not have any steps in the ${e} phase, skipping...`);return}let n=o.timeout,s=async function(){for(let c=0;c<o.steps.length;c++){let d=o.steps[c],p=d.run,f=async h=>{try{await Co(`set -ex; ${p}`)}catch(m){if(h){let g=`Fixture '${r}' ${e} step ${c+1} errored in the background. The test will continue, but this may affect test execution and results: ${m}`;w.error(g)}else throw new Error(`Fixture '${r}' ${e} step ${c+1} errored: ${m}`)}};d.waitForCompletion===!1?f(!0):await f(!1)}};if(w.info(`Running ${e} phase of fixture '${r}'`),!n){await s();return}let a,i,l=async function(){return new Promise((c,d)=>{i=c,a=setTimeout(()=>{i=void 0,d(`Fixture '${r}' ${e} phase timed out after ${n} seconds`)},n*1e3)})};try{await Promise.race([s(),l()])}catch(c){throw c}finally{i&&i(),clearTimeout(a)}}async function es({path:r,apiClient:e,generator:t,useLocalFiles:o,noReport:n,environment:s,newBaseUrl:a}){let i;o?(w.info(`Reading ${r} from local filesystem`),i=await Et(r,ke)):(w.info(`Fetching ${r} from Momentic Cloud server (${e.baseURL})`),i=await e.getTest(r)),i.schemaVersion>j&&w.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 l=[],c={};s&&(l=i.envs?.find(y=>y.name===s.name)?.fixtures??[],c=s.variables);for(let v of l)await Dr(v,"setup");let d;if(i.baseUrl){if(d=i.baseUrl,a){let v=new URL(i.baseUrl),y=new URL(a);v.hostname=y.hostname,v.protocol=y.protocol,v.port=y.port,d=v.toString()}}else d=c[Te],d||(w.error(`Test ${i.name} has no baseURL and the environment has no ${Te} variable either`),process.exit(1));let p=await M.init({baseUrl:d,logger:w,waitForLoad:!0}),f=new me({browser:p,generator:t,config:De,logger:w}),h=new V(d,i.steps),m,g;if(n)g=dl();else try{m=await e.createRun({testId:i.id,trigger:"CLI"}),g=m.id}catch(v){throw w.error(v),new Error(`Are you sure test ${i.name} exists on the server?`)}let S="FAILED";try{S=await Yo({test:i,context:h,runId:g,controller:f,logger:w,takeScreenshots:!0,onSaveScreenshot:async v=>{if(n)return"";let{key:y}=await e.uploadScreenshot({screenshot:v.toString("base64")});return y},onUpdateRun:async v=>{n||await e.updateRun(g,v)}})}catch(v){throw n||await e.updateRun(g,{status:"FAILED",finishedAt:new Date}),n||w.error(`Test '${i.name}' failed. You can view a playback of this run at ${e.baseURL}/runs/${g}`),v}finally{try{for(let v of l)await Dr(v,"teardown")}catch(v){w.error(`Failed to run teardown fixtures: ${v}`)}}return S}async function rs({tests:r,start:e,waitOn:t,waitOnTimeout:o,client:n,all:s,noReport:a,env:i,parallelization:l=1}){e&&(w.info(`Running start command: ${e}`),await Zn(e,!1)),t&&(w.info(`Waiting for ${t} to be accessible (timeout: ${o}s)`),await hl({resources:[t],timeout:o*1e3}));let c;if(i){let y=pl(J,`${i}.yaml`),R=fl(ml(y,"utf-8"));try{c=Y.parse(R),w.info(`Running in the ${i} environment with the following variables:`),w.info(c.variables)}catch(E){w.error({err:E},`${y} failed to parse as a valid environment file`),process.exit(1)}}let d=new _e({baseURL:n.baseURL,apiKey:n.apiKey}),p=!1;(r.some(y=>y.endsWith(".yaml"))||r.some(y=>ts(y)))&&(p=!0);let f=new Set;if(p)w.info(r,"Reading tests from the following local file paths:"),r.forEach(y=>{if(!ts(y))throw new Error(`Path '${y}' does not exist.`);if(ul(y).isDirectory())Qe(y).map(E=>E.fullFilePath).forEach(E=>f.add(E));else if(y.endsWith(".yaml"))f.add(y);else throw new Error(`Path '${y}' is not a directory or a .yaml file.`)});else if(w.warn("The paths you specified are not files or directories that exist locally."),w.warn(`Fetching tests from Momentic Cloud (${n.baseURL}) instead...`),s)for(let y of await n.getAllTestIds())f.add(y);else f=new Set(r);let h=Array.from(f);w.info(h,`Identified ${h.length} tests to run locally:`);let m=[];for(let y=0;y<h.length;y+=l){let R=await Promise.all(h.slice(y,y+l).map(async E=>{let A="FAILED";try{A=await es({useLocalFiles:p,path:E,apiClient:n,generator:d,newBaseUrl:t,environment:c,noReport:a})}catch(F){let ze=F instanceof Error?F.message:`${F}`;w.error(`Test ${E} failed with error: ${ze}`)}return{runStatus:A,path:E}}));m=m.concat(R)}let g=m.filter(y=>y.runStatus==="PASSED");g.length>0&&(w.info(`Passed ${g.length} out of ${m.length} tests:`),g.forEach(y=>{w.info(`- ${y.path}`)}));let S=m.filter(y=>y.runStatus==="CANCELLED");S.length>0&&(w.warn(`Cancelled ${S.length} out of ${m.length} tests:`),S.forEach(y=>{w.warn(`- ${y.path}`)}));let v=m.filter(y=>y.runStatus==="FAILED");v.length>0&&(w.error(`Failed ${v.length} out of ${m.length} tests:`),v.forEach(y=>{w.error(`- ${y.path}`)}),process.exit(1)),process.exit(0)}async function os({tests:r,client:e,env:t,all:o}){let{queuedTests:n}=await e.queueTests({testPaths:r,env:t,all:o});w.info(`Successfully queued ${n.length} tests!`)}var ne=new gl;ne.name("momentic").description("Momentic CLI").version(Dn);ne.command("install-browsers").action(async()=>{await Hn()});ne.addOption(new ue("--log-level <level>").choices(["debug","info","warn","error"]).default("info")).on("option:log-level",r=>{w.setMinLevel(r.toLowerCase())});ne.command("init").addOption(be).addOption(Se).addOption(we).action(async r=>{let{yes:e,apiKey:t,server:o}=r,n=new re({baseURL:o,apiKey:t});await ye(n,e)});ne.command("app").addOption(Se).addOption(we).addOption(be).addOption(new ue("--port <port>","Port to run the app on. Defaults to 58888.").default(58888)).addOption(new ue("--mode <mode>","Mode to run the app in. Defaults to iframe.").choices(["iframe","web"]).default("iframe")).action(async r=>{let{apiKey:e,port:t,yes:o,server:n,mode:s}=r,a=new re({baseURL:n,apiKey:e});await ye(a,o);let i=bl(import.meta.url),l=wl(i),c=ns.resolve(l,"..","static"),d=ns.resolve(l,"..","assets");await Nn({momenticServerUrl:n,mode:s,apiKey:e,serverPort:t,appPort:t,staticDir:c,assetsDir:d})});ne.command("run").alias("run-tests").addOption(Se).addOption(we).addOption(Or).addOption(be).addOption(new ue("-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(Bn).addOption(new ue("-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 ue("--start <command>","Arbitrary setup command that will run before Momentic steps begin.")).addOption(new ue("--wait-on <url>","URL to wait to become accessible before Momentic tests begin.")).addOption(jn).addOption(new ue("--wait-on-timeout <timeout>","Max time to wait for the --wait-on URL to become accessible.").default(60,"one minute")).addOption(new ue("-p, --parallel <parallelization>","When running with the --local flag, the number of tests to run in parallel. Defaults to 1.").default(1)).addArgument(Vn).action(async(r,e)=>{let{apiKey:t,server:o,remote:n,local:s,all:a,env:i,yes:l}=e;!a&&(!r||!r.length)&&(w.error("You must pass at least one test path or the --all flag"),process.exit(1));let c=new re({baseURL:o,apiKey:t});if(await ye(c,l),s){try{await rs({tests:r,client:c,...e})}catch(d){w.error(d),process.exit(1)}return}if(n){for(let d of r)(d.endsWith(".yaml")||Sl(d))&&(w.error(yl`'${d}' looks like a local file or directory, but the --local flag was not supplied.
37
37
  Please supply the --local flag to run tests locally, or specify the test path without its file extension to queue tests remotely (e.g. 'hello-world').`),process.exit(1));await os({tests:r,client:c,all:a,env:i});return}w.error("One of --remote or --local must be specified"),process.exit(1)});ne.command("pull").description("Fetch one or more tests from Momentic Cloud and save it in to local disk in YAML format.").addOption(Se).addOption(we).addOption(Or).addOption(be).addArgument(Gn).action(async(r,e)=>{let{apiKey:t,server:o,all:n,yes:s}=e,a=new re({baseURL:o,apiKey:t});if(await ye(a,s),!n&&!r?.length)throw new Error("At least one test name or --all must be provided");await Jn({testsToFetch:r??[],client:a,all:n})});ne.command("push").description("Save one or more tests in YAML format to Momentic Cloud.").addOption(be).addOption(Se).addOption(we).addArgument(Wn).action(async(r,e)=>{let{apiKey:t,server:o,yes:n}=e;await ye(t,n);let s=new re({baseURL:o,apiKey:t});await Qn({paths:r,client:s,yes:n})});var ss=ne.command("env").description("Perform operations on Momentic environments");ss.command("push").description("Push one or more environments and associated variables to Momentic cloud.").addOption(be).addOption(Se).addOption(we).addOption(Mr).addArgument(Nr).action(async(r,e)=>{let{apiKey:t,server:o,yes:n,all:s}=e;!r?.length&&!s&&(w.error("At least one environment name or --all must be provided"),process.exit(1)),await ye(t,n);let a=new re({baseURL:o,apiKey:t});await $n({names:r,client:a,yes:n,all:s})});ss.command("pull").description("Pull one or more environments and associated variables from Momentic cloud.").addOption(be).addOption(Se).addOption(we).addOption(Mr).addArgument(Nr).action(async(r,e)=>{let{apiKey:t,server:o,yes:n,all:s}=e;!r?.length&&!s&&(w.error("At least one environment name or --all must be provided"),process.exit(1)),await ye(t,n);let a=new re({baseURL:o,apiKey:t});await Nt({envNames:r,client:a,skipPrompts:n,all:s})});async function vl(){try{await ne.parseAsync(process.argv)}catch(r){w.error(r)}}vl();