@fasttest-ai/qa-agent 1.0.4-staging.2 → 1.0.4-staging.3

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/README.md CHANGED
@@ -160,3 +160,6 @@ Use `{{VAR_NAME}}` placeholders in test steps for secrets (passwords, API keys).
160
160
  ## License
161
161
 
162
162
  Apache-2.0
163
+
164
+ node /Users/rajat/reposs/afbp/local-skill/dist/install.js uninstall --ide claude-code
165
+ node /Users/rajat/reposs/afbp/local-skill/dist/install.js install --ide claude-code
package/commands/ftest.md CHANGED
@@ -49,7 +49,7 @@ Examine the input and determine which mode to use:
49
49
  3. **If input starts with `shield`** → use the `mcp__fasttest__vibe_shield` tool
50
50
  4. **If input starts with `run`** → use the `mcp__fasttest__run` tool
51
51
  5. **If input starts with `save`** → use the `mcp__fasttest__save_suite` tool
52
- 6. **If input starts with `suites` or `list`** → use the `mcp__fasttest__list_suites` tool
52
+ 6. **If input starts with `suites` or `list`** → use the `mcp__fasttest__list` tool with `type: "suites"`
53
53
  7. **If input starts with `setup`** → use the `mcp__fasttest__setup` tool
54
54
  8. **Otherwise** → use the `mcp__fasttest__test` tool (default)
55
55
 
@@ -67,7 +67,7 @@ Examples:
67
67
  - `/ftest shield localhost:3000` → vibe_shield tool, url=`http://localhost:3000`
68
68
  - `/ftest run checkout flow` → run tool, suite_name=`checkout flow`
69
69
  - `/ftest save login tests` → save_suite tool, suite_name=`login tests`
70
- - `/ftest suites` → list_suites tool (no parameters needed)
70
+ - `/ftest suites` → list tool with type=`suites` (no other parameters needed)
71
71
  - `/ftest setup` → setup tool (no parameters needed)
72
72
  - `/ftest checkout flow` → test tool, description=`checkout flow` (no URL — the tool will handle it)
73
73
 
package/commands/qa.md CHANGED
@@ -49,7 +49,7 @@ Examine the input and determine which mode to use:
49
49
  3. **If input starts with `shield`** → use the `mcp__fasttest__vibe_shield` tool
50
50
  4. **If input starts with `run`** → use the `mcp__fasttest__run` tool
51
51
  5. **If input starts with `save`** → use the `mcp__fasttest__save_suite` tool
52
- 6. **If input starts with `suites` or `list`** → use the `mcp__fasttest__list_suites` tool
52
+ 6. **If input starts with `suites` or `list`** → use the `mcp__fasttest__list` tool with `type: "suites"`
53
53
  7. **If input starts with `setup`** → use the `mcp__fasttest__setup` tool
54
54
  8. **Otherwise** → use the `mcp__fasttest__test` tool (default)
55
55
 
@@ -67,7 +67,7 @@ Examples:
67
67
  - `/qa shield localhost:3000` → vibe_shield tool, url=`http://localhost:3000`
68
68
  - `/qa run checkout flow` → run tool, suite_name=`checkout flow`
69
69
  - `/qa save login tests` → save_suite tool, suite_name=`login tests`
70
- - `/qa suites` → list_suites tool (no parameters needed)
70
+ - `/qa suites` → list tool with type=`suites` (no other parameters needed)
71
71
  - `/qa setup` → setup tool (no parameters needed)
72
72
  - `/qa checkout flow` → test tool, description=`checkout flow` (no URL — the tool will handle it)
73
73
 
package/dist/cli.js CHANGED
@@ -1,22 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import{readFileSync as ze}from"node:fs";import{join as Ge,dirname as Ze}from"node:path";import{fileURLToPath as Je}from"node:url";import{chromium as X,firefox as Q,webkit as Y,devices as Ae}from"playwright";import{execFileSync as Ce}from"node:child_process";import*as S from"node:fs";import*as C from"node:path";import*as ee from"node:os";var O=C.join(ee.homedir(),".fasttest","sessions"),Ee=/^(con|prn|aux|nul|com\d|lpt\d)$/i;function D(s){let t=s.replace(/[\/\\]/g,"_").replace(/\.\./g,"_").replace(/\0/g,"").replace(/^_+|_+$/g,"").replace(/^\./,"_")||"default";return Ee.test(t)?`_${t}`:t}var B=class s{browser=null;context=null;page=null;browserType;headless;orgSlug;deviceName;pendingDialogs=new WeakMap;networkEntries=[];environmentScope=null;constructor(e={}){this.browserType=e.browserType??"chromium",this.headless=e.headless??!0,this.orgSlug=D(e.orgSlug??"default"),this.deviceName=e.device}setOrgSlug(e){this.orgSlug=D(e)}setEnvironmentScope(e){this.environmentScope=e?D(e):null}getEnvironmentScope(){return this.environmentScope}sessionDir(){return this.environmentScope?C.join(O,this.orgSlug,this.environmentScope):C.join(O,this.orgSlug)}resolveSessionPath(e){if(this.environmentScope){let r=C.join(O,this.orgSlug,this.environmentScope,`${e}.json`);if(S.existsSync(r))return r}let t=C.join(O,this.orgSlug,`${e}.json`);return S.existsSync(t)?t:null}async setDevice(e){this.deviceName=e,this.page&&!this.page.isClosed()&&await this.page.close().catch(()=>{}),this.context&&await this.context.close().catch(()=>{}),this.page=null,this.context=null}getContextOptions(e){if(this.deviceName){let t=Ae[this.deviceName];if(!t)throw new Error(`Unknown Playwright device "${this.deviceName}". Use a name from Playwright's device registry (e.g. "iPhone 15", "Pixel 7").`);return{...t,ignoreHTTPSErrors:!0,...e}}return{viewport:{width:1280,height:720},ignoreHTTPSErrors:!0,...e}}async ensureBrowser(){if(this.page&&!this.page.isClosed())try{return await this.page.evaluate("1"),this.page}catch{}if(!this.browser||!this.browser.isConnected()){this.context=null,this.page=null;let e=this.browserType==="firefox"?Q:this.browserType==="webkit"?Y:X;try{this.browser=await e.launch({headless:this.headless,args:this.browserType==="chromium"?["--disable-blink-features=AutomationControlled"]:[]})}catch(t){let r=t instanceof Error?t.message:String(t);if(r.includes("Executable doesn't exist")||r.includes("browserType.launch")){let n=process.platform==="win32"?"npx.cmd":"npx";Ce(n,["playwright","install","--with-deps",this.browserType],{stdio:"inherit"}),this.browser=await e.launch({headless:this.headless,args:this.browserType==="chromium"?["--disable-blink-features=AutomationControlled"]:[]})}else throw t}}return this.context||(this.context=await this.browser.newContext(this.getContextOptions())),this.page=await this.context.newPage(),this.attachDialogListener(this.page),this.attachNetworkListener(this.page),this.page}async getPage(){return this.ensureBrowser()}async newContext(){return(!this.browser||!this.browser.isConnected())&&await this.ensureBrowser(),this.page&&!this.page.isClosed()&&await this.page.close().catch(()=>{}),this.context&&await this.context.close().catch(()=>{}),this.context=await this.browser.newContext(this.getContextOptions()),this.page=await this.context.newPage(),this.attachDialogListener(this.page),this.attachNetworkListener(this.page),this.page}async saveSession(e){if(!this.context)throw new Error("No browser context \u2014 nothing to save");let t=D(e),r=this.sessionDir();S.mkdirSync(r,{recursive:!0,mode:448});let n=C.join(r,`${t}.json`),i=await this.context.storageState();return S.writeFileSync(n,JSON.stringify(i,null,2),{mode:384}),n}async restoreSession(e){let t=D(e),r=this.resolveSessionPath(t);if(!r){let i=C.join(this.sessionDir(),`${t}.json`);throw new Error(`Session "${e}" not found at ${i}`)}let n=JSON.parse(S.readFileSync(r,"utf-8"));return(!this.browser||!this.browser.isConnected())&&await this.ensureBrowser(),this.page&&!this.page.isClosed()&&await this.page.close().catch(()=>{}),this.context&&await this.context.close().catch(()=>{}),this.context=await this.browser.newContext(this.getContextOptions({storageState:n})),this.page=await this.context.newPage(),this.attachDialogListener(this.page),this.attachNetworkListener(this.page),this.page}sessionExists(e){let t=D(e);return this.resolveSessionPath(t)!==null}listSessions(){let e=new Set;if(this.environmentScope){let r=C.join(O,this.orgSlug,this.environmentScope);if(S.existsSync(r))for(let n of S.readdirSync(r))n.endsWith(".json")&&e.add(n.replace(/\.json$/,""))}let t=C.join(O,this.orgSlug);if(S.existsSync(t))for(let r of S.readdirSync(t))r.endsWith(".json")&&S.statSync(C.join(t,r)).isFile()&&e.add(r.replace(/\.json$/,""));return[...e]}attachDialogListener(e){e.on("dialog",t=>{let r=this.pendingDialogs.get(e);r&&clearTimeout(r.dismissTimer);let n=setTimeout(()=>{this.pendingDialogs.get(e)?.dialog===t&&(t.dismiss().catch(()=>{}),this.pendingDialogs.delete(e))},3e4);this.pendingDialogs.set(e,{type:t.type(),message:t.message(),defaultValue:t.defaultValue(),dialog:t,dismissTimer:n})})}async handleDialog(e,t){let r=this.page,n=r?this.pendingDialogs.get(r):void 0;if(!n)throw new Error("No pending dialog to handle");return clearTimeout(n.dismissTimer),this.pendingDialogs.delete(r),e==="accept"?await n.dialog.accept(t):await n.dialog.dismiss(),{type:n.type,message:n.message}}static MAX_NETWORK_ENTRIES=1e3;requestStartTimes=new Map;attachNetworkListener(e){e.on("request",t=>{this.requestStartTimes.set(t,Date.now())}),e.on("response",t=>{let r=t.request(),n=r.url();if(!n.startsWith("http"))return;this.networkEntries.length>=s.MAX_NETWORK_ENTRIES&&this.networkEntries.shift();let i=this.requestStartTimes.get(r),o=i?Date.now()-i:0;this.requestStartTimes.delete(r),this.networkEntries.push({url:n,method:r.method(),status:t.status(),duration:o,mimeType:t.headers()["content-type"]??"",responseSize:parseInt(t.headers()["content-length"]??"0",10)})})}getNetworkSummary(){return[...this.networkEntries]}clearNetworkEntries(){this.networkEntries=[]}listPages(){return this.context?this.context.pages().map((e,t)=>({index:t,url:e.url(),title:""})):[]}async listPagesAsync(){if(!this.context)return[];let e=this.context.pages(),t=[];for(let r=0;r<e.length;r++)t.push({index:r,url:e[r].url(),title:await e[r].title().catch(()=>"")});return t}async createPage(e){this.context||await this.ensureBrowser();let t=await this.context.newPage();return this.attachDialogListener(t),this.attachNetworkListener(t),e&&await t.goto(e,{waitUntil:"domcontentloaded",timeout:3e4}),this.page=t,t}async switchToPage(e){if(!this.context)throw new Error("No browser context \u2014 no tabs to switch to");let t=this.context.pages();if(e<0||e>=t.length)throw new Error(`Tab index ${e} out of range (0-${t.length-1})`);return this.page=t[e],await this.page.bringToFront(),this.page}async closePage(e){if(!this.context)throw new Error("No browser context \u2014 no tabs to close");let t=this.context.pages();if(e<0||e>=t.length)throw new Error(`Tab index ${e} out of range (0-${t.length-1})`);await t[e].close();let n=this.context.pages();n.length>0?this.page=n[Math.min(e,n.length-1)]:this.page=null}async interactiveLogin(e,t){await this.close();let n=await(this.browserType==="firefox"?Q:this.browserType==="webkit"?Y:X).launch({headless:!1,args:this.browserType==="chromium"?["--disable-blink-features=AutomationControlled"]:[]}),i=await n.newContext(this.getContextOptions()),o=await i.newPage();await o.goto(e,{waitUntil:"domcontentloaded",timeout:3e4});let l=Date.now()+3e5;try{for(;Date.now()<l&&!(o.isClosed()||!n.isConnected());){await o.waitForTimeout(1500);let m=o.url(),p=new URL(m),v=new URL(e).origin,h=["sign-in","sign-up","login","auth","oauth","sso-callback","accounts.dev"],_=p.origin===v,$=h.some(x=>m.toLowerCase().includes(x));if(_&&!$){await o.waitForTimeout(2e3);break}}}catch{}let d=D(t),a=this.sessionDir();S.mkdirSync(a,{recursive:!0,mode:448});let f=C.join(a,`${d}.json`),u=!1;try{if(n.isConnected()){let m=await i.storageState();S.writeFileSync(f,JSON.stringify(m,null,2),{mode:384}),u=!0}}catch{}if(await o.close().catch(()=>{}),await i.close().catch(()=>{}),await n.close().catch(()=>{}),!u)throw new Error("Browser was closed before session could be saved. Please try again and wait for the login to complete before closing the window.");return await this.restoreSession(t),f}async close(){this.page&&!this.page.isClosed()&&await this.page.close().catch(()=>{}),this.context&&await this.context.close().catch(()=>{}),this.browser&&await this.browser.close().catch(()=>{}),this.page=null,this.context=null,this.browser=null}};var V=class extends Error{constructor(t,r,n){super(`Monthly run limit reached (${r}/${n}). Current plan: ${t}. Upgrade at https://fasttest.ai to continue.`);this.plan=t;this.used=r;this.limit=n;this.name="QuotaExceededError"}},M=class{apiKey;baseUrl;constructor(e){this.apiKey=e.apiKey,this.baseUrl=(e.baseUrl??"https://api.fasttest.ai").replace(/\/$/,"")}get dashboardUrl(){try{let e=new URL(this.baseUrl);return e.hostname=e.hostname.replace(/^api\./,""),e.pathname="/",e.origin}catch{return"https://fasttest.ai"}}static async requestDeviceCode(e){let t=`${e.replace(/\/$/,"")}/api/v1/auth/device-code`,r=await fetch(t,{method:"POST"});if(!r.ok){let n=await r.text();throw new Error(`Device code request failed (${r.status}): ${n}`)}return await r.json()}static async fetchPrompts(e){let t=`${e.replace(/\/$/,"")}/api/v1/qa/prompts`,r=await fetch(t,{signal:AbortSignal.timeout(5e3)});if(!r.ok)throw new Error(`Prompt fetch failed (${r.status})`);return await r.json()}static async pollDeviceCode(e,t){let r=`${e.replace(/\/$/,"")}/api/v1/auth/device-code/status?poll_token=${encodeURIComponent(t)}`,n=await fetch(r);if(!n.ok){let i=await n.text();throw new Error(`Device code poll failed (${n.status}): ${i}`)}return await n.json()}async request(e,t,r){let n=`${this.baseUrl}/api/v1${t}`,i={"x-api-key":this.apiKey,"Content-Type":"application/json"},o=2,l=1e3;for(let d=0;d<=o;d++){let a=new AbortController,f=setTimeout(()=>a.abort(),3e4);try{let u={method:e,headers:i,signal:a.signal};r!==void 0&&(u.body=JSON.stringify(r));let m=await fetch(n,u);if(clearTimeout(f),!m.ok){let p=await m.text();if(m.status>=500&&d<o){await new Promise(v=>setTimeout(v,l*2**d));continue}if(m.status===402){let v=p.match(/\((\d+)\/(\d+)\).*plan:\s*(\w+)/i);throw new V(v?.[3]??"unknown",v?parseInt(v[1]):0,v?parseInt(v[2]):0)}throw new Error(`Cloud API ${e} ${t} \u2192 ${m.status}: ${p}`)}return await m.json()}catch(u){if(clearTimeout(f),u instanceof Error&&(u.name==="AbortError"||u.message.includes("fetch failed"))&&d<o){await new Promise(p=>setTimeout(p,l*2**d));continue}throw u}}throw new Error(`Cloud API ${e} ${t}: max retries exceeded`)}async get(e){return this.request("GET",e)}async post(e,t){return this.request("POST",e,t)}async health(){let e=`${this.baseUrl}/health`;return await(await fetch(e)).json()}async listProjects(){return this.get("/qa/projects/")}async resolveProject(e,t){let r={name:e};return t&&(r.base_url=t),this.post("/qa/projects/resolve",r)}async listSuites(e){let t=e?`?search=${encodeURIComponent(e)}`:"";return this.get(`/qa/projects/suites/all${t}`)}async resolveSuite(e,t,r){let n={name:e};return t&&(n.project_id=t),r&&(n.exact=!0),this.post("/qa/projects/suites/resolve",n)}async getSuiteTestCases(e){return this.get(`/qa/execution/suites/${e}/test-cases`)}async createSuite(e,t){return this.post(`/qa/projects/${e}/test-suites`,{...t,project_id:e})}async updateSuite(e,t){return this.request("PUT",`/qa/execution/suites/${e}`,t)}async createTestCase(e){return this.post("/qa/test-cases/",e)}async recordInitialResults(e,t){return this.post("/qa/execution/record-initial",{suite_id:e,results:t})}async updateTestCase(e,t){return this.request("PUT",`/qa/test-cases/${e}`,t)}async applyHealing(e,t,r){return this.post(`/qa/test-cases/${e}/apply-healing`,{original_selector:t,healed_selector:r})}async detectSharedSteps(e,t){let r=new URLSearchParams;e&&r.set("project_id",e),t&&r.set("auto_create","true");let n=r.toString()?`?${r.toString()}`:"";return this.post(`/qa/shared-steps/detect${n}`,{})}async resolveEnvironment(e,t){return this.post("/qa/environments/resolve",{suite_id:e,name:t})}async startRun(e){return this.post("/qa/execution/run",e)}async reportResult(e,t){return this.post(`/qa/execution/executions/${e}/results`,t)}async completeExecution(e,t){return this.post(`/qa/execution/executions/${e}/complete`,{status:t})}async cancelExecution(e){return this.post(`/qa/execution/executions/${e}/cancel`,{})}async getExecutionStatus(e){return this.get(`/qa/execution/executions/${e}`)}async getExecutionDiff(e){return this.get(`/qa/execution/executions/${e}/diff`)}async notifyTestStarted(e,t,r){try{await this.post(`/qa/execution/executions/${e}/test-started`,{test_case_id:t,test_case_name:r})}catch{}}async notifyHealingStarted(e,t,r){try{await this.post(`/qa/execution/executions/${e}/healing-started`,{test_case_id:t,original_selector:r})}catch{}}async checkControlStatus(e){return(await this.get(`/qa/execution/executions/${e}/control-status`)).status}async setGithubToken(e){return this.request("PUT","/qa/github/token",{github_token:e})}async postPrComment(e){return this.post("/qa/github/pr-comment",e)}async createLiveSession(e){return this.post("/qa/live-sessions",e)}async updateLiveSession(e,t){return this.request("PATCH",`/qa/live-sessions/${e}`,t)}async startChaosSession(){return this.post("/qa/chaos/start",{})}async saveChaosReport(e,t){let r=e?`?project_id=${e}`:"";return this.post(`/qa/chaos/reports${r}`,t)}};async function te(s,e){try{let t=new URL(e,"http://localhost");return["http:","https:"].includes(t.protocol)?(await s.goto(e,{waitUntil:"domcontentloaded",timeout:3e4}),await s.waitForLoadState("networkidle",{timeout:5e3}).catch(()=>{}),{success:!0,data:{title:await s.title(),url:s.url()}}):{success:!1,error:`Disallowed URL scheme: ${t.protocol}`}}catch(t){return{success:!1,error:String(t)}}}async function se(s,e){try{return await s.click(e,{timeout:1e4}),await s.waitForLoadState("networkidle",{timeout:1e4}).catch(()=>{}),{success:!0}}catch(t){return{success:!1,error:String(t)}}}async function re(s,e,t){try{return await s.fill(e,t,{timeout:1e4}),{success:!0}}catch(r){return{success:!1,error:String(r)}}}async function ne(s,e){try{return await s.hover(e,{timeout:1e4}),{success:!0}}catch(t){return{success:!1,error:String(t)}}}async function ie(s,e,t){try{return await s.selectOption(e,t,{timeout:1e4}),{success:!0}}catch(r){return{success:!1,error:String(r)}}}async function oe(s,e,t=1e4){try{return await s.waitForSelector(e,{timeout:t}),{success:!0}}catch(r){return{success:!1,error:String(r)}}}async function ae(s,e=!1){try{return(await s.screenshot({type:"jpeg",quality:80,fullPage:e})).toString("base64")}catch{return null}}async function ce(s){let e=await s.locator("body").ariaSnapshot().catch(()=>""),t=await s.evaluate(()=>{let r=new Set(["A","BUTTON","INPUT","SELECT","TEXTAREA","DETAILS","SUMMARY"]),n=new Set(["button","link","textbox","checkbox","radio","combobox","switch","tab","menuitem","option","slider","spinbutton","searchbox"]),i=a=>a.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/]/g,"\\]"),o=[],l=new Set,d=document.querySelectorAll('a, button, input, select, textarea, details, summary, [role], [data-testid], [onclick], [tabindex="0"]');for(let a of d){if(l.has(a))continue;l.add(a);let f=a.tagName,u=a.getAttribute("role");if(!r.has(f)&&!n.has(u||"")&&!a.hasAttribute("data-testid")&&!a.hasAttribute("onclick"))continue;let m=window.getComputedStyle(a);if(m.display==="none"||m.visibility==="hidden")continue;let p=[],v=a.getAttribute("data-testid");v&&p.push(`[data-testid="${i(v)}"]`);let h=a.getAttribute("id");h&&!h.match(/^[:.\d]|[\s#>+~[\]"]/)&&p.push(`#${h}`);let _=a.getAttribute("aria-label");_&&p.push(`[aria-label="${i(_)}"]`);let $=a.getAttribute("name");$&&p.push(`${f.toLowerCase()}[name="${i($)}"]`);let x=a.type,y=a.getAttribute("placeholder");y&&p.push(`${f.toLowerCase()}[placeholder="${i(y)}"]`);let R=a.textContent?.trim().slice(0,60);R&&(f==="BUTTON"||f==="A"||u==="button"||u==="link")&&R.length<=40&&!R.includes('"')&&p.push(`${u||f.toLowerCase()}:has-text("${R}")`),p.length!==0&&o.push({tag:f.toLowerCase(),...u&&{role:u},...R&&R.length<=40&&{text:R},selectors:p,...x&&x!=="text"&&{type:x},...y&&{placeholder:y}})}return o}).catch(()=>[]);return{url:s.url(),title:await s.title(),accessibilityTree:e,...t.length>0&&{selectorMap:t}}}async function ue(s){try{return await s.goBack({waitUntil:"domcontentloaded",timeout:3e4})===null?{success:!1,error:"No previous page in history"}:{success:!0,data:{title:await s.title(),url:s.url()}}}catch(e){return{success:!1,error:String(e)}}}async function le(s){try{return await s.goForward({waitUntil:"domcontentloaded",timeout:3e4})===null?{success:!1,error:"No next page in history"}:{success:!0,data:{title:await s.title(),url:s.url()}}}catch(e){return{success:!1,error:String(e)}}}async function de(s,e){try{return await s.keyboard.press(e),{success:!0}}catch(t){return{success:!1,error:String(t)}}}var De=["/etc/","/var/","/root/","/proc/","/sys/","/dev/"];async function ge(s,e,t,r=!1){if(!r){for(let n of t)if(De.some(i=>n.startsWith(i)))return{success:!1,error:`Blocked upload of system path: ${n}`}}try{return await s.setInputFiles(e,t,{timeout:1e4}),{success:!0}}catch(n){return{success:!1,error:String(n)}}}async function pe(s,e){try{return{success:!0,data:{result:await s.evaluate(e)}}}catch(t){return{success:!1,error:String(t)}}}async function fe(s,e,t){try{return await s.dragAndDrop(e,t,{timeout:1e4}),await s.waitForLoadState("networkidle",{timeout:5e3}).catch(()=>{}),{success:!0}}catch(r){return{success:!1,error:String(r)}}}async function me(s,e,t){if(e<=0||t<=0)return{success:!1,error:`Viewport dimensions must be positive: got ${e}x${t}`};try{return await s.setViewportSize({width:e,height:t}),{success:!0,data:{width:e,height:t}}}catch(r){return{success:!1,error:String(r)}}}async function he(s,e){try{for(let[t,r]of Object.entries(e))await s.fill(t,r,{timeout:1e4});return{success:!0,data:{filled:Object.keys(e).length}}}catch(t){return{success:!1,error:String(t)}}}async function we(s,e){try{switch(e.type){case"element_visible":try{return await s.waitForSelector(e.selector,{state:"visible",timeout:5e3}),{pass:!0,actual:!0}}catch{return{pass:!1,actual:!1,error:"Element not visible within timeout"}}case"element_hidden":try{return await s.waitForSelector(e.selector,{state:"hidden",timeout:5e3}),{pass:!0,actual:!0}}catch{return{pass:!1,actual:!1,error:"Element is still visible"}}case"text_contains":{let t=s.locator(e.selector);if(await t.count()===0)return{pass:!1,error:"Element not found"};let n=await t.first().textContent();return{pass:n?.includes(e.text??"")??!1,actual:n??""}}case"text_equals":{let t=s.locator(e.selector);if(await t.count()===0)return{pass:!1,error:"Element not found"};let n=(await t.first().textContent())?.trim()??"";return{pass:n===e.text,actual:n}}case"url_contains":{let t=s.url(),r=e.url??e.text??"";return{pass:t.includes(r),actual:t}}case"url_equals":{let t=s.url(),r=e.url??e.text??"";return{pass:t===r,actual:t}}case"element_count":{let r=await s.locator(e.selector).count();return{pass:r===(e.count??1),actual:r}}case"attribute_value":{let t=s.locator(e.selector);if(await t.count()===0)return{pass:!1,error:"Element not found"};let n=await t.first().getAttribute(e.attribute??"");return{pass:n===e.value,actual:n??""}}case"evaluate_truthy":{if(!e.expression)return{pass:!1,error:"evaluate_truthy requires 'expression'"};try{let t=await s.evaluate(e.expression);return{pass:!!t,actual:String(t)}}catch(t){return{pass:!1,error:`Evaluation failed: ${String(t)}`}}}default:return{pass:!1,error:`Unknown assertion type: ${e.type}`}}}catch(t){return{pass:!1,error:String(t)}}}var _e={data_testid:.98,aria:.95,text:.9,structural:.85,ai:.75};async function xe(s,e,t,r,n,i,o,l){if(e)try{let a=await e.post("/qa/healing/classify",{failure_type:r,selector:t,page_url:i,error_message:n,...l?{test_case_id:l}:{}});if(a.is_real_bug)return{healed:!1,error:a.reason??"Classified as real bug"};if(a.pattern){let f=await L(s,a.pattern.healed_value),u=f&&await ye(s,a.pattern.healed_value,o);if(f&&u)return{healed:!0,newSelector:a.pattern.healed_value,strategy:a.pattern.strategy,confidence:a.pattern.confidence};a.pattern.id&&je(e,a.pattern.id,i)}}catch{}let d=[{name:"data_testid",fn:()=>Ie(s,t)},{name:"aria",fn:()=>Ue(s,t)},{name:"text",fn:()=>qe(s,t)},{name:"structural",fn:()=>Oe(s,t)}];for(let a of d){let f=await a.fn();if(f){if(!await ye(s,f,o))continue;return e&&await Le(e,r,t,f,a.name,_e[a.name]??.8,i),{healed:!0,newSelector:f,strategy:a.name,confidence:_e[a.name]}}}return{healed:!1,error:"Local healing strategies exhausted"}}async function L(s,e){try{return await s.locator(e).count()===1}catch{return!1}}async function ye(s,e,t){if(!t)return!0;try{let r=await s.locator(e).evaluate(o=>({tag:o.tagName.toLowerCase(),role:o.getAttribute("role"),type:o.type??null,contentEditable:o.getAttribute("contenteditable"),text:(o.textContent??"").trim().slice(0,200),ariaLabel:o.getAttribute("aria-label")??""})),n=t.action;if(n==="click"||n==="hover"){let o=["button","a","input","select","summary","details","label","option"],l=["button","link","tab","menuitem","checkbox","radio","switch","option"];if(!(o.includes(r.tag)||r.role!=null&&l.includes(r.role)))return!1}if((n==="fill"||n==="type")&&!(r.tag==="input"||r.tag==="textarea"||r.contentEditable==="true"||r.contentEditable==="")||n==="select"&&r.tag!=="select"&&r.role!=="listbox"&&r.role!=="combobox")return!1;let i=[t.description,t.intent].filter(Boolean);for(let o of i){let l=o.match(/['"]([^'"]+)['"]/);if(l){let d=l[1].toLowerCase();if(!(r.text+" "+r.ariaLabel).toLowerCase().includes(d))return!1}}return!0}catch{return!0}}async function Ie(s,e){try{let t=H(e);if(!t)return null;let r=[`[data-testid="${t}"]`,`[data-test="${t}"]`,`[data-test-id="${t}"]`];for(let n of r)if(await L(s,n))return n;return null}catch{return null}}async function Ue(s,e){try{let t=H(e);if(!t)return null;let r=[`[aria-label="${t}"]`];for(let n of r)if(await L(s,n))return n;return null}catch{return null}}async function qe(s,e){try{let t=H(e);if(!t)return null;let r=[`[title="${t}"]`,`[alt="${t}"]`,`[placeholder="${t}"]`,`role=button[name="${t}"]`,`role=link[name="${t}"]`,`text="${t}"`];for(let n of r)if(await L(s,n))return n;return null}catch{return null}}async function Oe(s,e){try{let r=e.match(/^([a-z]+)/i)?.[1]??"",n=H(e);if(!r&&!n)return null;let i=[];r&&n&&(i.push(`${r}[name="${n}"]`),i.push(`${r}[id*="${n}"]`),i.push(`${r}[class*="${n}"]`));for(let o of i)if(await L(s,o))return o;return null}catch{return null}}function H(s){let e=s.match(/\[(?:data-testid|data-test|data-test-id|id|name|aria-label)\s*[~|^$*]?=\s*["']([^"']+)["']\]/);if(e)return e[1];let t=s.match(/#([\w-]+)/);if(t)return t[1];let r=[...s.matchAll(/\.([\w-]+)/g)];if(r.length>0)return r[r.length-1][1];let n=s.match(/\[name=["']([^"']+)["']\]/);return n?n[1]:s.match(/[a-zA-Z][\w-]{2,}/)?.[0]??null}async function Le(s,e,t,r,n,i,o){try{await s.post("/qa/healing/patterns",{failure_type:e,original_value:t,healed_value:r,strategy:n,confidence:i,page_url:o})}catch{}}async function je(s,e,t){try{await s.post(`/qa/healing/patterns/${e}/failed`,{page_url:t})}catch{}}var Fe=/\{\{([A-Z][A-Z0-9_]*)\}\}/g;function A(s,e=process.env){let t=[],r=s.replace(Fe,(n,i)=>{let o=e[i];return o===void 0?(t.push(i),n):o});if(t.length>0)throw new Error(`Missing environment variable(s): ${t.join(", ")}. Set these before running tests. In GitHub Actions, add them as repository secrets.`);return r}function W(s,e){let t={...s};if(t.value!==void 0&&(t.value=A(t.value,e)),t.url!==void 0&&(t.url=A(t.url,e)),t.expression!==void 0&&(t.expression=A(t.expression,e)),t.key!==void 0&&(t.key=A(t.key,e)),t.name!==void 0&&(t.name=A(t.name,e)),t.fields!==void 0){let r={};for(let[n,i]of Object.entries(t.fields))r[n]=A(i,e);t.fields=r}return t}function be(s,e){let t={...s};return t.text!==void 0&&(t.text=A(t.text,e)),t.url!==void 0&&(t.url=A(t.url,e)),t.value!==void 0&&(t.value=A(t.value,e)),t.expected_value!==void 0&&(t.expected_value=A(t.expected_value,e)),t}function z(s,e){let t=new Set;function r(n){if(!n)return;let i=/\{\{([A-Z][A-Z0-9_]*)\}\}/g,o;for(;(o=i.exec(n))!==null;)t.add(o[1])}for(let n of s)if(r(n.value),r(n.url),r(n.expression),r(n.key),r(n.name),n.fields)for(let i of Object.values(n.fields))r(i);for(let n of e)r(n.text),r(n.url),r(n.value),r(n.expected_value);return Array.from(t).sort()}async function Se(s,e,t,r){await s.setDevice(t.device);let n=await e.startRun({suite_id:t.suiteId,environment_id:t.environmentId,browser:"chromium",test_case_ids:t.testCaseIds,device:t.device}),i=n.execution_id,o=n.test_cases,l=n.default_session??void 0,d=t.appUrlOverride??n.base_url??"";if(d)try{d=A(d)}catch(c){try{await e.completeExecution(i)}catch{}return{execution_id:i,status:"failed",total:o.length,passed:0,failed:o.length,skipped:0,duration_ms:0,results:o.map(g=>({id:g.id,name:g.name,status:"failed",duration_ms:0,error:String(c),step_results:[]})),healed:[]}}if(n.environment_name)s.setEnvironmentScope(n.environment_name);else if(d)try{let c=new URL(d),g=c.port&&c.port!=="80"&&c.port!=="443"?`${c.hostname}-${c.port}`:c.hostname;s.setEnvironmentScope(g)}catch{}let a=[];for(let c of o)for(let g of z(c.steps,c.assertions))a.includes(g)||a.push(g);if(n.setup){let c=Array.isArray(n.setup)?n.setup:Object.values(n.setup).flat();for(let g of z(c,[]))a.includes(g)||a.push(g)}let f=[l,...o.map(c=>c.session).filter(Boolean)].filter(Boolean);for(let c of f){let g=c.matchAll(/\{\{([A-Z][A-Z0-9_]*)\}\}/g);for(let w of g)a.includes(w[1])||a.push(w[1])}if(a.length>0){let c=[],g=[];for(let w of a)process.env[w]!==void 0?c.push(w):g.push(w);if(c.length>0&&process.stderr.write(`Environment variables resolved: ${c.join(", ")}
3
- `),g.length>0){let w=`Missing environment variable(s): ${g.join(", ")}. Set these before running tests. In GitHub Actions, add them as repository secrets.`;process.stderr.write(`ERROR: ${w}
4
- `);try{await e.completeExecution(i)}catch{}return{execution_id:i,status:"failed",total:o.length,passed:0,failed:o.length,skipped:0,duration_ms:0,results:o.map(k=>({id:k.id,name:k.name,status:"failed",duration_ms:0,error:w,step_results:[]})),healed:[]}}}let u=n.setup;if(u){let c;Array.isArray(u)?l?c={[l]:u}:(process.stderr.write(`Warning: suite has setup steps but no default_session set. Setup will be skipped. Set the suite's session field to enable CI login.
5
- `),c={}):c=u;for(let[g,w]of Object.entries(c)){if(s.sessionExists(g)){process.stderr.write(`Session "${g}" found locally \u2014 skipping setup.
6
- `);continue}if(w.length===0)continue;process.stderr.write(`Session "${g}" not found \u2014 running setup (${w.length} steps)...
7
- `);let k=await s.newContext(),U=!1;for(let N=0;N<w.length;N++){let b;try{b=W(w[N])}catch(I){let E=`Setup "${g}" step ${N+1} failed to resolve variables: ${I}`;process.stderr.write(`ERROR: ${E}
8
- `),U=!0;try{await e.completeExecution(i)}catch{}return{execution_id:i,status:"failed",total:o.length,passed:0,failed:o.length,skipped:0,duration_ms:0,results:o.map(q=>({id:q.id,name:q.name,status:"failed",duration_ms:0,error:E,step_results:[]})),healed:[]}}let T=await G(k,b,d,s);if(T.page&&(k=T.page),!T.success){let I=`Setup "${g}" step ${N+1} (${b.action}) failed: ${T.error}`;process.stderr.write(`ERROR: ${I}
9
- `),U=!0;try{await e.completeExecution(i)}catch{}return{execution_id:i,status:"failed",total:o.length,passed:0,failed:o.length,skipped:0,duration_ms:0,results:o.map(E=>({id:E.id,name:E.name,status:"failed",duration_ms:0,error:I,step_results:[]})),healed:[]}}}U||(await s.saveSession(g),process.stderr.write(`Setup complete \u2014 session "${g}" saved.
2
+ import{readFileSync as Ze}from"node:fs";import{join as Xe,dirname as Qe}from"node:path";import{fileURLToPath as Ye}from"node:url";import{chromium as Q,firefox as Y,webkit as ee,devices as Ee}from"playwright";import{execFileSync as De}from"node:child_process";import*as S from"node:fs";import*as C from"node:path";import*as te from"node:os";var j=C.join(te.homedir(),".fasttest","sessions"),Ne=/^(con|prn|aux|nul|com\d|lpt\d)$/i;function D(s){let t=s.replace(/[\/\\]/g,"_").replace(/\.\./g,"_").replace(/\0/g,"").replace(/^_+|_+$/g,"").replace(/^\./,"_")||"default";return Ne.test(t)?`_${t}`:t}var H=class s{browser=null;context=null;page=null;browserType;headless;orgSlug;deviceName;pendingDialogs=new WeakMap;networkEntries=[];environmentScope=null;constructor(e={}){this.browserType=e.browserType??"chromium",this.headless=e.headless??!0,this.orgSlug=D(e.orgSlug??"default"),this.deviceName=e.device}setOrgSlug(e){this.orgSlug=D(e)}setEnvironmentScope(e){this.environmentScope=e?D(e):null}getEnvironmentScope(){return this.environmentScope}sessionDir(){return this.environmentScope?C.join(j,this.orgSlug,this.environmentScope):C.join(j,this.orgSlug)}resolveSessionPath(e){if(this.environmentScope){let r=C.join(j,this.orgSlug,this.environmentScope,`${e}.json`);if(S.existsSync(r))return r}let t=C.join(j,this.orgSlug,`${e}.json`);return S.existsSync(t)?t:null}async setDevice(e){this.deviceName=e,this.page&&!this.page.isClosed()&&await this.page.close().catch(()=>{}),this.context&&await this.context.close().catch(()=>{}),this.page=null,this.context=null}getContextOptions(e){if(this.deviceName){let t=Ee[this.deviceName];if(!t)throw new Error(`Unknown Playwright device "${this.deviceName}". Use a name from Playwright's device registry (e.g. "iPhone 15", "Pixel 7").`);return{...t,ignoreHTTPSErrors:!0,...e}}return{viewport:{width:1280,height:720},ignoreHTTPSErrors:!0,...e}}async ensureBrowser(){if(this.page&&!this.page.isClosed())try{return await this.page.evaluate("1"),this.page}catch{await this.page.close().catch(()=>{}),this.page=null}if(!this.browser||!this.browser.isConnected()){this.context=null,this.page=null;let e=this.browserType==="firefox"?Y:this.browserType==="webkit"?ee:Q;try{this.browser=await e.launch({headless:this.headless,args:this.browserType==="chromium"?["--disable-blink-features=AutomationControlled"]:[]})}catch(t){let r=t instanceof Error?t.message:String(t);if(r.includes("Executable doesn't exist")||r.includes("browserType.launch")){let n=process.platform==="win32"?"npx.cmd":"npx";De(n,["playwright","install","--with-deps",this.browserType],{stdio:"pipe"}),this.browser=await e.launch({headless:this.headless,args:this.browserType==="chromium"?["--disable-blink-features=AutomationControlled"]:[]})}else throw t}}return this.context||(this.context=await this.browser.newContext(this.getContextOptions())),this.requestStartTimes.clear(),this.page=await this.context.newPage(),this.attachDialogListener(this.page),this.attachNetworkListener(this.page),this.page}async getPage(){return this.ensureBrowser()}async newContext(){return(!this.browser||!this.browser.isConnected())&&await this.ensureBrowser(),this.requestStartTimes.clear(),this.page&&!this.page.isClosed()&&await this.page.close().catch(()=>{}),this.context&&await this.context.close().catch(()=>{}),this.context=await this.browser.newContext(this.getContextOptions()),this.page=await this.context.newPage(),this.attachDialogListener(this.page),this.attachNetworkListener(this.page),this.page}async saveSession(e){if(!this.context)throw new Error("No browser context \u2014 nothing to save");let t=D(e),r=this.sessionDir();S.mkdirSync(r,{recursive:!0,mode:448});let n=C.join(r,`${t}.json`),i=await this.context.storageState();return S.writeFileSync(n,JSON.stringify(i,null,2),{mode:384}),n}async restoreSession(e){let t=D(e),r=this.resolveSessionPath(t);if(!r){let i=C.join(this.sessionDir(),`${t}.json`);throw new Error(`Session "${e}" not found at ${i}`)}let n=JSON.parse(S.readFileSync(r,"utf-8"));return(!this.browser||!this.browser.isConnected())&&await this.ensureBrowser(),this.page&&!this.page.isClosed()&&await this.page.close().catch(()=>{}),this.context&&await this.context.close().catch(()=>{}),this.context=await this.browser.newContext(this.getContextOptions({storageState:n})),this.page=await this.context.newPage(),this.attachDialogListener(this.page),this.attachNetworkListener(this.page),this.page}sessionExists(e){let t=D(e);return this.resolveSessionPath(t)!==null}listSessions(){let e=new Set;if(this.environmentScope){let r=C.join(j,this.orgSlug,this.environmentScope);if(S.existsSync(r))for(let n of S.readdirSync(r))n.endsWith(".json")&&e.add(n.replace(/\.json$/,""))}let t=C.join(j,this.orgSlug);if(S.existsSync(t))for(let r of S.readdirSync(t))r.endsWith(".json")&&S.statSync(C.join(t,r)).isFile()&&e.add(r.replace(/\.json$/,""));return[...e]}attachDialogListener(e){e.on("dialog",t=>{let r=this.pendingDialogs.get(e);r&&clearTimeout(r.dismissTimer);let n=setTimeout(()=>{this.pendingDialogs.get(e)?.dialog===t&&(t.dismiss().catch(()=>{}),this.pendingDialogs.delete(e))},3e4);this.pendingDialogs.set(e,{type:t.type(),message:t.message(),defaultValue:t.defaultValue(),dialog:t,dismissTimer:n})})}async handleDialog(e,t){let r=this.page,n=r?this.pendingDialogs.get(r):void 0;if(!n)throw new Error("No pending dialog to handle");return clearTimeout(n.dismissTimer),this.pendingDialogs.delete(r),e==="accept"?await n.dialog.accept(t):await n.dialog.dismiss(),{type:n.type,message:n.message}}static MAX_NETWORK_ENTRIES=1e3;requestStartTimes=new Map;attachNetworkListener(e){e.on("request",t=>{this.requestStartTimes.set(t,Date.now())}),e.on("requestfailed",t=>{this.requestStartTimes.delete(t)}),e.on("response",t=>{let r=t.request(),n=r.url();if(!n.startsWith("http"))return;this.networkEntries.length>=s.MAX_NETWORK_ENTRIES&&this.networkEntries.shift();let i=this.requestStartTimes.get(r),o=i?Date.now()-i:0;this.requestStartTimes.delete(r),this.networkEntries.push({url:n,method:r.method(),status:t.status(),duration:o,mimeType:t.headers()["content-type"]??"",responseSize:parseInt(t.headers()["content-length"]??"0",10)})})}getNetworkSummary(){return[...this.networkEntries]}clearNetworkEntries(){this.networkEntries=[]}listPages(){return this.context?this.context.pages().map((e,t)=>({index:t,url:e.url(),title:""})):[]}async listPagesAsync(){if(!this.context)return[];let e=this.context.pages(),t=[];for(let r=0;r<e.length;r++)t.push({index:r,url:e[r].url(),title:await e[r].title().catch(()=>"")});return t}async createPage(e){this.context||await this.ensureBrowser();let t=await this.context.newPage();return this.attachDialogListener(t),this.attachNetworkListener(t),e&&await t.goto(e,{waitUntil:"domcontentloaded",timeout:3e4}),this.page=t,t}async switchToPage(e){if(!this.context)throw new Error("No browser context \u2014 no tabs to switch to");let t=this.context.pages();if(e<0||e>=t.length)throw new Error(`Tab index ${e} out of range (0-${t.length-1})`);return this.page=t[e],await this.page.bringToFront(),this.page}async closePage(e){if(!this.context)throw new Error("No browser context \u2014 no tabs to close");let t=this.context.pages();if(e<0||e>=t.length)throw new Error(`Tab index ${e} out of range (0-${t.length-1})`);await t[e].close();let n=this.context.pages();n.length>0?this.page=n[Math.min(e,n.length-1)]:this.page=null}async interactiveLogin(e,t){let r=this.context?await this.context.storageState().catch(()=>null):null;await this.close();let i=await(this.browserType==="firefox"?Y:this.browserType==="webkit"?ee:Q).launch({headless:!1,args:this.browserType==="chromium"?["--disable-blink-features=AutomationControlled"]:[]}),o=await i.newContext(this.getContextOptions()),l=await o.newPage();await l.goto(e,{waitUntil:"domcontentloaded",timeout:3e4});let u=Date.now()+6e5,c=D(t),d=this.sessionDir();S.mkdirSync(d,{recursive:!0,mode:448});let g=C.join(d,`${c}.json`),p=!1,m=async()=>{try{if(i.isConnected()){let x={cookies:await o.cookies(),origins:[]};return S.writeFileSync(g,JSON.stringify(x,null,2),{mode:384}),!0}}catch{}return!1},h=async()=>{try{if(i.isConnected()){let w=await o.storageState();return S.writeFileSync(g,JSON.stringify(w,null,2),{mode:384}),!0}}catch{}return!1};p=await m();try{for(;Date.now()<u&&!(l.isClosed()||!i.isConnected());)await l.waitForTimeout(2e3),await m()&&(p=!0)}catch{}let k=await h();if(k&&(p=!0),k||await m()&&(p=!0),await l.close().catch(()=>{}),await o.close().catch(()=>{}),await i.close().catch(()=>{}),!p){if(r)try{await this.ensureBrowser();let w=await this.browser.newContext(this.getContextOptions({storageState:r}));this.context&&await this.context.close().catch(()=>{}),this.context=w,this.page=await w.newPage(),this.attachDialogListener(this.page),this.attachNetworkListener(this.page)}catch{}throw new Error("Browser was closed before session could be saved. Please try again and wait for the login to complete before closing the window.")}return await this.restoreSession(t),g}async close(){this.requestStartTimes.clear(),this.page&&!this.page.isClosed()&&await this.page.close().catch(()=>{}),this.context&&await this.context.close().catch(()=>{}),this.browser&&await this.browser.close().catch(()=>{}),this.page=null,this.context=null,this.browser=null}};var V=class extends Error{constructor(t,r,n){super(`Monthly run limit reached (${r}/${n}). Current plan: ${t}. Upgrade at https://fasttest.ai to continue.`);this.plan=t;this.used=r;this.limit=n;this.name="QuotaExceededError"}},M=class{apiKey;baseUrl;constructor(e){this.apiKey=e.apiKey,this.baseUrl=(e.baseUrl??"https://api.fasttest.ai").replace(/\/$/,"")}get dashboardUrl(){try{let e=new URL(this.baseUrl);return e.hostname=e.hostname.replace(/^api\./,""),e.pathname="/",e.origin}catch{return"https://fasttest.ai"}}static async requestDeviceCode(e){let t=`${e.replace(/\/$/,"")}/api/v1/auth/device-code`,r=await fetch(t,{method:"POST"});if(!r.ok){let n=await r.text();throw new Error(`Device code request failed (${r.status}): ${n}`)}return await r.json()}static async fetchPrompts(e){let t=`${e.replace(/\/$/,"")}/api/v1/qa/prompts`,r=await fetch(t,{signal:AbortSignal.timeout(5e3)});if(!r.ok)throw new Error(`Prompt fetch failed (${r.status})`);return await r.json()}static async pollDeviceCode(e,t){let r=`${e.replace(/\/$/,"")}/api/v1/auth/device-code/status?poll_token=${encodeURIComponent(t)}`,n=await fetch(r);if(!n.ok){let i=await n.text();throw new Error(`Device code poll failed (${n.status}): ${i}`)}return await n.json()}async request(e,t,r){let n=`${this.baseUrl}/api/v1${t}`,i={"x-api-key":this.apiKey,"Content-Type":"application/json"},o=2,l=1e3;for(let u=0;u<=o;u++){let c=new AbortController,d=setTimeout(()=>c.abort(),3e4);try{let g={method:e,headers:i,signal:c.signal};r!==void 0&&(g.body=JSON.stringify(r));let p=await fetch(n,g);if(clearTimeout(d),!p.ok){let m=await p.text();if(p.status>=500&&u<o){await new Promise(h=>setTimeout(h,l*2**u));continue}if(p.status===402){let h=m.match(/\((\d+)\/(\d+)\).*plan:\s*(\w+)/i);throw new V(h?.[3]??"unknown",h?parseInt(h[1]):0,h?parseInt(h[2]):0)}throw new Error(`Cloud API ${e} ${t} \u2192 ${p.status}: ${m}`)}return await p.json()}catch(g){if(clearTimeout(d),g instanceof Error&&(g.name==="AbortError"||g.message.includes("fetch failed"))&&u<o){await new Promise(m=>setTimeout(m,l*2**u));continue}throw g}}throw new Error(`Cloud API ${e} ${t}: max retries exceeded`)}async get(e){return this.request("GET",e)}async post(e,t){return this.request("POST",e,t)}async health(){let e=`${this.baseUrl}/health`;return await(await fetch(e,{signal:AbortSignal.timeout(5e3)})).json()}async listProjects(){return this.get("/qa/projects/")}async resolveProject(e,t){let r={name:e};return t&&(r.base_url=t),this.post("/qa/projects/resolve",r)}async listSuites(e){let t=e?`?search=${encodeURIComponent(e)}`:"";return this.get(`/qa/projects/suites/all${t}`)}async resolveSuite(e,t,r){let n={name:e};return t&&(n.project_id=t),r&&(n.exact=!0),this.post("/qa/projects/suites/resolve",n)}async getSuiteTestCases(e){return this.get(`/qa/execution/suites/${e}/test-cases`)}async createSuite(e,t){return this.post(`/qa/projects/${e}/test-suites`,{...t,project_id:e})}async updateSuite(e,t){return this.request("PUT",`/qa/execution/suites/${e}`,t)}async createTestCase(e){return this.post("/qa/test-cases/",e)}async recordInitialResults(e,t){return this.post("/qa/execution/record-initial",{suite_id:e,results:t})}async updateTestCase(e,t){return this.request("PUT",`/qa/test-cases/${e}`,t)}async applyHealing(e,t,r){return this.post(`/qa/test-cases/${e}/apply-healing`,{original_selector:t,healed_selector:r})}async detectSharedSteps(e,t){let r=new URLSearchParams;e&&r.set("project_id",e),t&&r.set("auto_create","true");let n=r.toString()?`?${r.toString()}`:"";return this.post(`/qa/shared-steps/detect${n}`,{})}async resolveEnvironment(e,t){return this.post("/qa/environments/resolve",{suite_id:e,name:t})}async resolveEnvironmentByProject(e,t){return this.post("/qa/environments/resolve",{project_id:e,name:t})}async createEnvironment(e,t){return this.post(`/qa/environments/projects/${e}/environments`,t)}async updateProject(e,t){return this.request("PUT",`/qa/projects/${e}`,t)}async startRun(e){return this.post("/qa/execution/run",e)}async reportResult(e,t){return this.post(`/qa/execution/executions/${e}/results`,t)}async completeExecution(e,t){return this.post(`/qa/execution/executions/${e}/complete`,{status:t})}async cancelExecution(e){return this.post(`/qa/execution/executions/${e}/cancel`,{})}async getExecutionStatus(e){return this.get(`/qa/execution/executions/${e}`)}async getExecutionDiff(e){return this.get(`/qa/execution/executions/${e}/diff`)}async notifyTestStarted(e,t,r){try{await this.post(`/qa/execution/executions/${e}/test-started`,{test_case_id:t,test_case_name:r})}catch{}}async notifyHealingStarted(e,t,r){try{await this.post(`/qa/execution/executions/${e}/healing-started`,{test_case_id:t,original_selector:r})}catch{}}async checkControlStatus(e){return(await this.get(`/qa/execution/executions/${e}/control-status`)).status}async setGithubToken(e){return this.request("PUT","/qa/github/token",{github_token:e})}async postPrComment(e){return this.post("/qa/github/pr-comment",e)}async createLiveSession(e){return this.post("/qa/live-sessions",e)}async updateLiveSession(e,t){return this.request("PATCH",`/qa/live-sessions/${e}`,t)}async startSecurityAuditSession(){return this.post("/qa/security-audit/start",{})}async saveSecurityAuditReport(e,t){return this.post("/qa/security-audit/reports",{...t,project_id:e??null})}};import Ie from"path";async function se(s,e){try{let t=new URL(e,"http://localhost");return["http:","https:"].includes(t.protocol)?(await s.goto(e,{waitUntil:"domcontentloaded",timeout:3e4}),await s.waitForLoadState("networkidle",{timeout:5e3}).catch(()=>{}),{success:!0,data:{title:await s.title(),url:s.url()}}):{success:!1,error:`Disallowed URL scheme: ${t.protocol}`}}catch(t){return{success:!1,error:String(t)}}}async function re(s,e){try{return await s.click(e,{timeout:1e4}),await s.waitForLoadState("networkidle",{timeout:1e4}).catch(()=>{}),{success:!0}}catch(t){return{success:!1,error:String(t)}}}async function ne(s,e,t){try{return await s.fill(e,t,{timeout:1e4}),{success:!0}}catch(r){return{success:!1,error:String(r)}}}async function ie(s,e,t,r){try{return await s.click(e,{timeout:1e4}),await s.locator(e).pressSequentially(t,{delay:r?.slowly?100:25,timeout:1e4}),r?.submit&&await s.keyboard.press("Enter"),{success:!0}}catch(n){return{success:!1,error:String(n)}}}async function oe(s,e){try{return await s.hover(e,{timeout:1e4}),{success:!0}}catch(t){return{success:!1,error:String(t)}}}async function ae(s,e,t){try{return await s.selectOption(e,t,{timeout:1e4}),{success:!0}}catch(r){return{success:!1,error:String(r)}}}async function ce(s,e,t=1e4){try{return await s.waitForSelector(e,{timeout:t}),{success:!0}}catch(r){return{success:!1,error:String(r)}}}async function ue(s,e=!1){try{return(await s.screenshot({type:"jpeg",quality:80,fullPage:e})).toString("base64")}catch{return null}}async function le(s){let e=await s.locator("body").ariaSnapshot().catch(()=>""),t=await s.evaluate(()=>{let r=new Set(["A","BUTTON","INPUT","SELECT","TEXTAREA","DETAILS","SUMMARY"]),n=new Set(["button","link","textbox","checkbox","radio","combobox","switch","tab","menuitem","option","slider","spinbutton","searchbox"]),i=c=>c.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/]/g,"\\]"),o=[],l=new Set,u=document.querySelectorAll('a, button, input, select, textarea, details, summary, [role], [data-testid], [onclick], [tabindex="0"]');for(let c of u){if(l.has(c))continue;l.add(c);let d=c.tagName,g=c.getAttribute("role");if(!r.has(d)&&!n.has(g||"")&&!c.hasAttribute("data-testid")&&!c.hasAttribute("onclick"))continue;let p=window.getComputedStyle(c);if(p.display==="none"||p.visibility==="hidden")continue;let m=[],h=c.getAttribute("data-testid");h&&m.push(`[data-testid="${i(h)}"]`);let k=c.getAttribute("id");k&&!k.match(/^[:.\d]|[\s#>+~[\]"]/)&&m.push(`#${k}`);let w=c.getAttribute("aria-label");w&&m.push(`[aria-label="${i(w)}"]`);let x=c.getAttribute("name");x&&m.push(`${d.toLowerCase()}[name="${i(x)}"]`);let $=c.type,b=c.getAttribute("placeholder");b&&m.push(`${d.toLowerCase()}[placeholder="${i(b)}"]`);let _=c.textContent?.trim().slice(0,60);_&&(d==="BUTTON"||d==="A"||g==="button"||g==="link")&&_.length<=40&&!_.includes('"')&&m.push(`${g||d.toLowerCase()}:has-text("${_}")`),m.length!==0&&o.push({tag:d.toLowerCase(),...g&&{role:g},..._&&_.length<=40&&{text:_},selectors:m,...$&&$!=="text"&&{type:$},...b&&{placeholder:b}})}return o}).catch(()=>[]);return{url:s.url(),title:await s.title(),accessibilityTree:e,...t.length>0&&{selectorMap:t}}}async function de(s){try{return await s.goBack({waitUntil:"domcontentloaded",timeout:3e4})===null?{success:!1,error:"No previous page in history"}:{success:!0,data:{title:await s.title(),url:s.url()}}}catch(e){return{success:!1,error:String(e)}}}async function ge(s){try{return await s.goForward({waitUntil:"domcontentloaded",timeout:3e4})===null?{success:!1,error:"No next page in history"}:{success:!0,data:{title:await s.title(),url:s.url()}}}catch(e){return{success:!1,error:String(e)}}}async function pe(s,e){try{return await s.keyboard.press(e),{success:!0}}catch(t){return{success:!1,error:String(t)}}}var qe=["/etc/","/var/","/root/","/proc/","/sys/","/dev/","/private/etc/","/private/var/"];async function fe(s,e,t,r=!1){if(!r)for(let n of t){let i=Ie.resolve(n.startsWith("~")?n.replace("~",process.env.HOME??"/"):n);if(qe.some(o=>i.startsWith(o)))return{success:!1,error:`Blocked upload of system path: ${n}`}}try{return await s.setInputFiles(e,t,{timeout:1e4}),{success:!0}}catch(n){return{success:!1,error:String(n)}}}async function me(s,e){try{return{success:!0,data:{result:await s.evaluate(e)}}}catch(t){return{success:!1,error:String(t)}}}async function he(s,e,t){try{return await s.dragAndDrop(e,t,{timeout:1e4}),await s.waitForLoadState("networkidle",{timeout:5e3}).catch(()=>{}),{success:!0}}catch(r){return{success:!1,error:String(r)}}}async function we(s,e,t){if(e<=0||t<=0)return{success:!1,error:`Viewport dimensions must be positive: got ${e}x${t}`};try{return await s.setViewportSize({width:e,height:t}),{success:!0,data:{width:e,height:t}}}catch(r){return{success:!1,error:String(r)}}}async function ye(s,e){try{for(let[t,r]of Object.entries(e))await s.fill(t,r,{timeout:1e4});return{success:!0,data:{filled:Object.keys(e).length}}}catch(t){return{success:!1,error:String(t)}}}async function _e(s,e){try{switch(e.type){case"element_visible":try{return await s.waitForSelector(e.selector,{state:"visible",timeout:5e3}),{pass:!0,actual:!0}}catch{return{pass:!1,actual:!1,error:"Element not visible within timeout"}}case"element_hidden":try{return await s.waitForSelector(e.selector,{state:"hidden",timeout:5e3}),{pass:!0,actual:!0}}catch{return{pass:!1,actual:!1,error:"Element is still visible"}}case"text_contains":{let t=s.locator(e.selector);if(await t.count()===0)return{pass:!1,error:"Element not found"};let n=await t.first().textContent();return{pass:n?.includes(e.text??"")??!1,actual:n??""}}case"text_equals":{let t=s.locator(e.selector);if(await t.count()===0)return{pass:!1,error:"Element not found"};let n=(await t.first().textContent())?.trim()??"";return{pass:n===e.text,actual:n}}case"url_contains":{let t=s.url(),r=e.url??e.text??"";return{pass:t.includes(r),actual:t}}case"url_equals":{let t=s.url(),r=e.url??e.text??"";return{pass:t===r,actual:t}}case"element_count":{let r=await s.locator(e.selector).count();return{pass:r===(e.count??1),actual:r}}case"attribute_value":{let t=s.locator(e.selector);if(await t.count()===0)return{pass:!1,error:"Element not found"};let n=await t.first().getAttribute(e.attribute??"");return{pass:n===e.value,actual:n??""}}case"evaluate_truthy":{if(!e.expression)return{pass:!1,error:"evaluate_truthy requires 'expression'"};try{let t=await s.evaluate(e.expression);return{pass:!!t,actual:String(t)}}catch(t){return{pass:!1,error:`Evaluation failed: ${String(t)}`}}}default:return{pass:!1,error:`Unknown assertion type: ${e.type}`}}}catch(t){return{pass:!1,error:String(t)}}}var xe={data_testid:.98,aria:.95,text:.9,structural:.85,ai:.75};async function be(s,e,t,r,n,i,o,l){if(e)try{let c=await e.post("/qa/healing/classify",{failure_type:r,selector:t,page_url:i,error_message:n,...l?{test_case_id:l}:{}});if(c.is_real_bug)return{healed:!1,error:c.reason??"Classified as real bug"};if(c.pattern){let d=await L(s,c.pattern.healed_value),g=d&&await ve(s,c.pattern.healed_value,o);if(d&&g)return{healed:!0,newSelector:c.pattern.healed_value,strategy:c.pattern.strategy,confidence:c.pattern.confidence};c.pattern.id&&He(e,c.pattern.id,i)}}catch{}let u=[{name:"data_testid",fn:()=>Oe(s,t)},{name:"aria",fn:()=>je(s,t)},{name:"text",fn:()=>Le(s,t)},{name:"structural",fn:()=>Fe(s,t)}];for(let c of u){let d=await c.fn();if(d){if(!await ve(s,d,o))continue;return e&&await Be(e,r,t,d,c.name,xe[c.name]??.8,i),{healed:!0,newSelector:d,strategy:c.name,confidence:xe[c.name]}}}return{healed:!1,error:"Local healing strategies exhausted"}}async function L(s,e){try{return await s.locator(e).count()===1}catch{return!1}}async function ve(s,e,t){if(!t)return!0;try{let r=await s.locator(e).evaluate(o=>({tag:o.tagName.toLowerCase(),role:o.getAttribute("role"),type:o.type??null,contentEditable:o.getAttribute("contenteditable"),text:(o.textContent??"").trim().slice(0,200),ariaLabel:o.getAttribute("aria-label")??""})),n=t.action;if(n==="click"||n==="hover"){let o=["button","a","input","select","summary","details","label","option"],l=["button","link","tab","menuitem","checkbox","radio","switch","option"];if(!(o.includes(r.tag)||r.role!=null&&l.includes(r.role)))return!1}if((n==="fill"||n==="type")&&!(r.tag==="input"||r.tag==="textarea"||r.contentEditable==="true"||r.contentEditable==="")||n==="select"&&r.tag!=="select"&&r.role!=="listbox"&&r.role!=="combobox")return!1;let i=[t.description,t.intent].filter(Boolean);for(let o of i){let l=o.match(/['"]([^'"]+)['"]/);if(l){let u=l[1].toLowerCase();if(!(r.text+" "+r.ariaLabel).toLowerCase().includes(u))return!1}}return!0}catch{return!0}}async function Oe(s,e){try{let t=K(e);if(!t)return null;let r=[`[data-testid="${t}"]`,`[data-test="${t}"]`,`[data-test-id="${t}"]`];for(let n of r)if(await L(s,n))return n;return null}catch{return null}}async function je(s,e){try{let t=K(e);if(!t)return null;let r=[`[aria-label="${t}"]`];for(let n of r)if(await L(s,n))return n;return null}catch{return null}}async function Le(s,e){try{let t=K(e);if(!t)return null;let r=[`[title="${t}"]`,`[alt="${t}"]`,`[placeholder="${t}"]`,`role=button[name="${t}"]`,`role=link[name="${t}"]`,`text="${t}"`];for(let n of r)if(await L(s,n))return n;return null}catch{return null}}async function Fe(s,e){try{let r=e.match(/^([a-z]+)/i)?.[1]??"",n=K(e);if(!r&&!n)return null;let i=[];r&&n&&(i.push(`${r}[name="${n}"]`),i.push(`${r}[id*="${n}"]`),i.push(`${r}[class*="${n}"]`));for(let o of i)if(await L(s,o))return o;return null}catch{return null}}function K(s){let e=s.match(/\[(?:data-testid|data-test|data-test-id|id|name|aria-label)\s*[~|^$*]?=\s*["']([^"']+)["']\]/);if(e)return e[1];let t=s.match(/#([\w-]+)/);if(t)return t[1];let r=[...s.matchAll(/\.([\w-]+)/g)];if(r.length>0)return r[r.length-1][1];let n=s.match(/\[name=["']([^"']+)["']\]/);return n?n[1]:s.match(/[a-zA-Z][\w-]{2,}/)?.[0]??null}async function Be(s,e,t,r,n,i,o){try{await s.post("/qa/healing/patterns",{failure_type:e,original_value:t,healed_value:r,strategy:n,confidence:i,page_url:o})}catch{}}async function He(s,e,t){try{await s.post(`/qa/healing/patterns/${e}/failed`,{page_url:t})}catch{}}var Me=/\{\{([A-Z][A-Z0-9_]*)\}\}/g;function A(s,e=process.env){let t=[],r=s.replace(Me,(n,i)=>{let o=e[i];return o===void 0?(t.push(i),n):o});if(t.length>0)throw new Error(`Missing environment variable(s): ${t.join(", ")}. Set these before running tests. In GitHub Actions, add them as repository secrets.`);return r}function z(s,e){let t={...s};if(t.value!==void 0&&(t.value=A(t.value,e)),t.url!==void 0&&(t.url=A(t.url,e)),t.expression!==void 0&&(t.expression=A(t.expression,e)),t.key!==void 0&&(t.key=A(t.key,e)),t.name!==void 0&&(t.name=A(t.name,e)),t.fields!==void 0){let r={};for(let[n,i]of Object.entries(t.fields))r[n]=A(i,e);t.fields=r}return t}function Se(s,e){let t={...s};return t.text!==void 0&&(t.text=A(t.text,e)),t.url!==void 0&&(t.url=A(t.url,e)),t.value!==void 0&&(t.value=A(t.value,e)),t.expected_value!==void 0&&(t.expected_value=A(t.expected_value,e)),t}function G(s,e){let t=new Set;function r(n){if(!n)return;let i=/\{\{([A-Z][A-Z0-9_]*)\}\}/g,o;for(;(o=i.exec(n))!==null;)t.add(o[1])}for(let n of s)if(r(n.value),r(n.url),r(n.expression),r(n.key),r(n.name),n.fields)for(let i of Object.values(n.fields))r(i);for(let n of e)r(n.text),r(n.url),r(n.value),r(n.expected_value);return Array.from(t).sort()}async function ke(s,e,t,r){await s.setDevice(t.device);let n=await e.startRun({suite_id:t.suiteId,environment_id:t.environmentId,browser:s.browserType,test_case_ids:t.testCaseIds,device:t.device}),i=n.execution_id,o=n.test_cases,l=n.default_session??void 0,u={...n.environment_variables??{},...process.env},c=t.appUrlOverride??n.base_url??"";if(c)try{c=A(c,u)}catch(a){try{await e.completeExecution(i)}catch{}return{execution_id:i,status:"failed",total:o.length,passed:0,failed:o.length,skipped:0,duration_ms:0,results:o.map(f=>({id:f.id,name:f.name,status:"failed",duration_ms:0,error:String(a),step_results:[]})),healed:[]}}if(n.environment_name)s.setEnvironmentScope(n.environment_name);else if(c)try{let a=new URL(c),f=a.port&&a.port!=="80"&&a.port!=="443"?`${a.hostname}-${a.port}`:a.hostname;s.setEnvironmentScope(f)}catch{}let d=[];for(let a of o)for(let f of G(a.steps,a.assertions))d.includes(f)||d.push(f);if(n.setup){let a=Array.isArray(n.setup)?n.setup:Object.values(n.setup).flat();for(let f of G(a,[]))d.includes(f)||d.push(f)}let g=[l,...o.map(a=>a.session).filter(Boolean)].filter(Boolean);for(let a of g){let f=a.matchAll(/\{\{([A-Z][A-Z0-9_]*)\}\}/g);for(let y of f)d.includes(y[1])||d.push(y[1])}if(d.length>0){let a=[],f=[];for(let y of d)u[y]!==void 0?a.push(y):f.push(y);if(a.length>0&&process.stderr.write(`Environment variables resolved: ${a.join(", ")}
3
+ `),f.length>0){let y=`Missing environment variable(s): ${f.join(", ")}. Set these before running tests, or configure them on the environment in the dashboard.`;process.stderr.write(`ERROR: ${y}
4
+ `);try{await e.completeExecution(i)}catch{}return{execution_id:i,status:"failed",total:o.length,passed:0,failed:o.length,skipped:0,duration_ms:0,results:o.map(R=>({id:R.id,name:R.name,status:"failed",duration_ms:0,error:y,step_results:[]})),healed:[]}}}let p=n.setup;if(p){let a;Array.isArray(p)?l?a={[l]:p}:(process.stderr.write(`Warning: suite has setup steps but no default_session set. Setup will be skipped. Set the suite's session field to enable CI login.
5
+ `),a={}):a=p;for(let[f,y]of Object.entries(a)){if(s.sessionExists(f)){process.stderr.write(`Session "${f}" found locally \u2014 skipping setup.
6
+ `);continue}if(y.length===0)continue;process.stderr.write(`Session "${f}" not found \u2014 running setup (${y.length} steps)...
7
+ `);let R=await s.newContext(),q=!1;for(let N=0;N<y.length;N++){let v;try{v=z(y[N],u)}catch(I){let E=`Setup "${f}" step ${N+1} failed to resolve variables: ${I}`;process.stderr.write(`ERROR: ${E}
8
+ `),q=!0;try{await e.completeExecution(i)}catch{}return{execution_id:i,status:"failed",total:o.length,passed:0,failed:o.length,skipped:0,duration_ms:0,results:o.map(U=>({id:U.id,name:U.name,status:"failed",duration_ms:0,error:E,step_results:[]})),healed:[]}}let T=await J(R,v,c,s);if(T.page&&(R=T.page),!T.success){let I=`Setup "${f}" step ${N+1} (${v.action}) failed: ${T.error}`;process.stderr.write(`ERROR: ${I}
9
+ `),q=!0;try{await e.completeExecution(i)}catch{}return{execution_id:i,status:"failed",total:o.length,passed:0,failed:o.length,skipped:0,duration_ms:0,results:o.map(E=>({id:E.id,name:E.name,status:"failed",duration_ms:0,error:I,step_results:[]})),healed:[]}}}q||(await s.saveSession(f),process.stderr.write(`Setup complete \u2014 session "${f}" saved.
10
10
  `))}}else l&&!s.sessionExists(l)&&process.stderr.write(`Warning: session "${l}" not found and no setup steps defined. Tests will run without auth. Add setup steps to the suite for CI support.
11
- `);let m=We(o);n.previous_statuses&&(m=Ve(m,n.previous_statuses));let p=[],v=[],h=Date.now(),_=!1,$=0,x=new Set,y=new Set(m.map(c=>c.id));for(let c of m){if(c.depends_on&&c.depends_on.length>0){let b=c.depends_on.filter(T=>y.has(T)&&!x.has(T));if(b.length>0){p.push({id:c.id,name:c.name,status:"skipped",duration_ms:0,error:`Skipped: dependency not met (${b.join(", ")})`,step_results:[]});continue}}try{let b=await e.checkControlStatus(i);if(b==="cancelled"){_=!0;break}if(b==="paused"){let T=!1,I=Date.now(),E=30*60*1e3;for(;!T;){if(Date.now()-I>E){process.stderr.write(`Pause exceeded 30-minute limit, auto-cancelling.
12
- `),_=!0;break}await new Promise(Te=>setTimeout(Te,2e3));let q=await e.checkControlStatus(i);if(q==="running"&&(T=!0),q==="cancelled"){_=!0;break}}if(_)break}}catch{}let g=c.retry_count??0,w,k=0;for(await e.notifyTestStarted(i,c.id,c.name);;){let b=(c.timeout_seconds||30)*1e3,T,I=new Promise((E,q)=>{T=setTimeout(()=>q(new Error(`Test case "${c.name}" timed out after ${c.timeout_seconds||30}s`)),b)});if(w=await Promise.race([Be(s,e,i,c,d,r,v,t.aiFallback,l),I]).finally(()=>clearTimeout(T)).catch(E=>({id:c.id,name:c.name,status:"failed",duration_ms:b,error:String(E),step_results:[]})),w.status==="passed"||k>=g)break;k++,process.stderr.write(`Retrying ${c.name} (attempt ${k}/${g})...
13
- `)}w.retry_attempts=k,w.status==="passed"&&x.add(c.id),p.push(w);let U=s.getNetworkSummary();s.clearNetworkEntries();let N=Ke(U);try{await e.reportResult(i,{test_case_id:c.id,status:w.status,duration_ms:w.duration_ms,error_message:w.error,console_logs:r.slice(-50),retry_attempt:k,step_results:w.step_results.map(b=>({step_index:b.step_index,action:b.action,success:b.success,error:b.error,duration_ms:b.duration_ms,screenshot_url:b.screenshot_url,healed:b.healed,heal_details:b.heal_details})),network_summary:N.length>0?N:void 0})}catch(b){$++,process.stderr.write(`Failed to report result for ${c.name}: ${b}
14
- `)}}let R=new Set(p.map(c=>c.id));for(let c of o)R.has(c.id)||p.push({id:c.id,name:c.name,status:"skipped",duration_ms:0,step_results:[]});let j=.9;if(v.length>0){let c=new Set;for(let g of v){if(g.confidence<j)continue;let w=`${g.test_case_id}:${g.original_selector}`;if(!c.has(w)){c.add(w);try{await e.applyHealing(g.test_case_id,g.original_selector,g.new_selector),process.stderr.write(`Auto-updated selector in "${g.test_case}": ${g.original_selector} \u2192 ${g.new_selector}
15
- `)}catch{}}}}let P=p.filter(c=>c.status==="passed").length,F=p.filter(c=>c.status==="failed").length,K=p.filter(c=>c.status==="skipped").length,ke=Date.now()-h;try{await e.completeExecution(i,_?"cancelled":void 0)}catch(c){process.stderr.write(`Failed to complete execution: ${c}
11
+ `);let m=Je(o);n.previous_statuses&&(m=Ge(m,n.previous_statuses));let h=[],k=[],w=Date.now(),x=!1,$=0,b=new Set,_=new Set(m.map(a=>a.id));for(let a of m){if(a.depends_on&&a.depends_on.length>0){let v=a.depends_on.filter(T=>_.has(T)&&!b.has(T));if(v.length>0){h.push({id:a.id,name:a.name,status:"skipped",duration_ms:0,error:`Skipped: dependency not met (${v.join(", ")})`,step_results:[]});continue}}try{let v=await e.checkControlStatus(i);if(v==="cancelled"){x=!0;break}if(v==="paused"){let T=!1,I=Date.now(),E=30*60*1e3;for(;!T;){if(Date.now()-I>E){process.stderr.write(`Pause exceeded 30-minute limit, auto-cancelling.
12
+ `),x=!0;break}await new Promise(Ce=>setTimeout(Ce,2e3));let U=await e.checkControlStatus(i);if(U==="running"&&(T=!0),U==="cancelled"){x=!0;break}}if(x)break}}catch{}let f=a.retry_count??0,y,R=0;for(await e.notifyTestStarted(i,a.id,a.name);;){let v=(a.timeout_seconds||30)*1e3,T,I=new Promise((E,U)=>{T=setTimeout(()=>U(new Error(`Test case "${a.name}" timed out after ${a.timeout_seconds||30}s`)),v)});if(y=await Promise.race([Ke(s,e,i,a,c,r,k,t.aiFallback,l,u),I]).finally(()=>clearTimeout(T)).catch(E=>({id:a.id,name:a.name,status:"failed",duration_ms:v,error:String(E),step_results:[]})),y.error?.includes("timed out")&&await s.newContext().catch(()=>{}),y.status==="passed"||R>=f)break;R++,process.stderr.write(`Retrying ${a.name} (attempt ${R}/${f})...
13
+ `)}y.retry_attempts=R,y.status==="passed"&&b.add(a.id),h.push(y);let q=s.getNetworkSummary();s.clearNetworkEntries();let N=ze(q);try{await e.reportResult(i,{test_case_id:a.id,status:y.status,duration_ms:y.duration_ms,error_message:y.error,console_logs:r.slice(-50),retry_attempt:R,step_results:y.step_results.map(v=>({step_index:v.step_index,action:v.action,success:v.success,error:v.error,duration_ms:v.duration_ms,screenshot_url:v.screenshot_url,healed:v.healed,heal_details:v.heal_details})),network_summary:N.length>0?N:void 0})}catch(v){$++,process.stderr.write(`Failed to report result for ${a.name}: ${v}
14
+ `)}}let O=new Set(h.map(a=>a.id));for(let a of o)O.has(a.id)||h.push({id:a.id,name:a.name,status:"skipped",duration_ms:0,step_results:[]});let F=.9;if(k.length>0){let a=new Set;for(let f of k){if(f.confidence<F)continue;let y=`${f.test_case_id}:${f.original_selector}`;if(!a.has(y)){a.add(y);try{await e.applyHealing(f.test_case_id,f.original_selector,f.new_selector),process.stderr.write(`Auto-updated selector in "${f.test_case}": ${f.original_selector} \u2192 ${f.new_selector}
15
+ `)}catch{}}}}let P=h.filter(a=>a.status==="passed").length,B=h.filter(a=>a.status==="failed").length,W=h.filter(a=>a.status==="skipped").length,Ae=Date.now()-w;try{await e.completeExecution(i,x?"cancelled":void 0)}catch(a){process.stderr.write(`Failed to complete execution: ${a}
16
16
  `)}$>0&&process.stderr.write(`Warning: ${$} result report(s) failed to send to cloud.
17
- `);let J;if(t.aiFallback)for(let c of p){if(c.status!=="failed")continue;let g=c.step_results.find(w=>!w.success&&w.ai_context);if(g?.ai_context){let k=m.find(U=>U.id===c.id)?.steps[g.step_index]??{};J={test_case_id:c.id,test_case_name:c.name,step_index:g.step_index,step:k,intent:g.ai_context.intent,error:g.error??c.error??"Unknown error",page_url:g.ai_context.page_url,snapshot:g.ai_context.snapshot};break}}return{execution_id:i,status:_?"cancelled":F===0?"passed":"failed",total:o.length,passed:P,failed:F,skipped:K,duration_ms:ke,results:p,healed:v,ai_fallback:J}}async function Be(s,e,t,r,n,i,o,l,d){let a=[],f=Date.now();try{let u=r.session??d,m;if(u)try{m=A(u)}catch(h){if(/\{\{[A-Z_]+\}\}/.test(u))return{id:r.id,name:r.name,status:"failed",duration_ms:Date.now()-f,error:`Session name "${u}" contains unresolved variable: ${h}`,step_results:[]};m=u}let p;if(m)try{p=await s.restoreSession(m)}catch(h){process.stderr.write(`Warning: session "${m}" not found, using fresh context: ${h}
18
- `),p=await s.newContext()}else p=await s.newContext();let v=h=>{i.push(`[${h.type()}] ${h.text()}`)};p.on("console",v);for(let h=0;h<r.steps.length;h++){let _=r.steps[h],$=Date.now(),x;try{x=W(_)}catch(P){return a.push({step_index:h,action:_.action,success:!1,error:String(P),duration_ms:Date.now()-$}),{id:r.id,name:r.name,status:"failed",duration_ms:Date.now()-f,error:`Step ${h+1} (${_.action}) failed: ${String(P)}`,step_results:a}}let y=await G(p,x,n,s);if(y.page&&(p=y.page),!y.success&&x.selector&&Me(y.error)){await e.notifyHealingStarted(t,r.id,x.selector);let P=await xe(p,e,x.selector,He(y.error),y.error??"unknown",p.url(),{action:x.action,description:x.description,intent:x.intent},r.id);if(P.healed&&P.newSelector){let F={...x,selector:P.newSelector};if(y=await G(p,F,n,s),y.success){o.push({test_case_id:r.id,test_case:r.name,step_index:h,original_selector:_.selector,new_selector:P.newSelector,strategy:P.strategy??"unknown",confidence:P.confidence??0});let K=await ve(p);a.push({step_index:h,action:_.action,success:!0,duration_ms:Date.now()-$,screenshot_url:K?.dataUrl,healed:!0,heal_details:{original_selector:_.selector,new_selector:P.newSelector,strategy:P.strategy??"unknown",confidence:P.confidence??0}});continue}}}let R=await ve(p),j;if(!y.success&&l)try{let P=await ce(p);j={intent:x.intent??x.description,page_url:p.url(),snapshot:P}}catch{}if(a.push({step_index:h,action:_.action,success:y.success,error:y.error,duration_ms:Date.now()-$,screenshot_url:R?.dataUrl,ai_context:j}),!y.success)return{id:r.id,name:r.name,status:"failed",duration_ms:Date.now()-f,error:`Step ${h+1} (${_.action}) failed: ${y.error}`,step_results:a}}for(let h=0;h<r.assertions.length;h++){let _=r.assertions[h],$=Date.now(),x;try{x=be(_)}catch(R){return a.push({step_index:r.steps.length+h,action:`assert:${_.type}`,success:!1,error:String(R),duration_ms:Date.now()-$}),{id:r.id,name:r.name,status:"failed",duration_ms:Date.now()-f,error:`Assertion ${h+1} (${_.type}) failed: ${String(R)}`,step_results:a}}let y=await Pe(p,x);if(a.push({step_index:r.steps.length+h,action:`assert:${_.type}`,success:y.pass,error:y.error,duration_ms:Date.now()-$}),!y.pass)return{id:r.id,name:r.name,status:"failed",duration_ms:Date.now()-f,error:`Assertion ${h+1} (${_.type}) failed: ${y.error??"expected value mismatch"}`,step_results:a}}return{id:r.id,name:r.name,status:"passed",duration_ms:Date.now()-f,step_results:a}}catch(u){return{id:r.id,name:r.name,status:"failed",duration_ms:Date.now()-f,error:String(u),step_results:a}}}async function ve(s){let e=await ae(s,!1);if(e)return{dataUrl:`data:image/jpeg;base64,${e}`}}async function G(s,e,t,r){let n=e.action;try{switch(n){case"navigate":{let i=e.url??e.value??"";if(i&&!i.startsWith("http")){if(!t)return{success:!1,error:`Navigate step has a relative URL "${i}" but no base URL is configured. Set a base URL on your project or environment.`};i=t.replace(/\/$/,"")+i}return await te(s,i)}case"click":return await se(s,e.selector??"");case"type":case"fill":return await re(s,e.selector??"",e.value??"");case"fill_form":{let i=e.fields??{};return await he(s,i)}case"drag":return await fe(s,e.selector??"",e.target??"");case"resize":return await me(s,e.width??1280,e.height??720);case"hover":return await ne(s,e.selector??"");case"select":return await ie(s,e.selector??"",e.value??"");case"wait_for":return e.condition==="navigation"?(await s.waitForLoadState("domcontentloaded",{timeout:(e.timeout??10)*1e3}),await s.waitForLoadState("networkidle",{timeout:5e3}).catch(()=>{}),{success:!0}):await oe(s,e.selector??"",(e.timeout??10)*1e3);case"scroll":return e.selector?await s.locator(e.selector).scrollIntoViewIfNeeded():await s.evaluate(()=>window.scrollTo(0,document.body.scrollHeight)),{success:!0};case"press_key":return await de(s,e.key??e.value??"Enter");case"upload_file":{let i=e.file_paths??(e.value?[e.value]:null);return!i||i.length===0?{success:!1,error:"upload_file step missing file_paths"}:await ge(s,e.selector??"",i)}case"evaluate":return await pe(s,e.expression??e.value??"");case"go_back":return await ue(s);case"go_forward":return await le(s);case"restore_session":{if(!r)return{success:!1,error:"restore_session requires browser manager"};let i=e.value??e.name??"";return i?{success:!0,page:await r.restoreSession(i)}:{success:!1,error:"restore_session step missing session name (set 'value' or 'name')"}}case"save_session":{if(!r)return{success:!1,error:"save_session requires browser manager"};let i=e.value??e.name??"";return i?(await r.saveSession(i),{success:!0}):{success:!1,error:"save_session step missing session name (set 'value' or 'name')"}}case"assert":return Pe(s,e).then(i=>({success:i.pass,error:i.error}));default:return{success:!1,error:`Unknown action: ${n}`}}}catch(i){return{success:!1,error:String(i)}}}async function Pe(s,e){return we(s,{type:e.type,selector:e.selector,text:e.text??e.expected_value,url:e.url,count:e.count,attribute:e.attribute,value:e.value??e.expected_value,expression:e.expression,description:e.description})}function Me(s){if(!s)return!1;let e=s.toLowerCase();return e.includes("navigation")||e.includes("net::")||e.includes("page.goto")?!1:e.includes("selector")||e.includes("not found")||e.includes("waiting for selector")||e.includes("no element")||e.includes("waiting for locator")||e.includes("locator")}function He(s){if(!s)return"UNKNOWN";let e=s.toLowerCase();return e.includes("timeout")?"TIMEOUT":e.includes("not found")||e.includes("no element")||e.includes("selector")?"ELEMENT_NOT_FOUND":e.includes("navigation")||e.includes("net::")?"NAVIGATION_FAILED":"UNKNOWN"}function Ke(s){return s.filter(e=>{let t=e.mimeType.toLowerCase();return!!(t.includes("json")||t.includes("text/html")||t.includes("text/plain")||e.status>=400)})}function Ve(s,e){let t=new Set(s.map(o=>o.id)),r=new Set;for(let o of s)if(o.depends_on)for(let l of o.depends_on)r.add(l);let n=[],i=[];for(let o of s){let l=e[o.id],d=o.depends_on?.some(a=>t.has(a))??!1;l==="failed"&&!r.has(o.id)&&!d?n.push(o):i.push(o)}return[...n,...i]}function We(s){let e=new Set(s.map(d=>d.id));if(!s.some(d=>d.depends_on&&d.depends_on.some(a=>e.has(a))))return s;let r=new Map(s.map(d=>[d.id,d])),n=new Set,i=new Set,o=[];function l(d){if(n.has(d))return!0;if(i.has(d))return!1;i.add(d);let a=r.get(d);if(a?.depends_on){for(let f of a.depends_on)if(e.has(f)&&!l(f))return!1}return i.delete(d),n.add(d),a&&o.push(a),!0}for(let d of s)if(!l(d.id))return process.stderr.write(`Warning: dependency cycle detected, using original test order.
19
- `),s;return o}var Xe=Ze(Je(import.meta.url)),Qe=(()=>{try{return JSON.parse(ze(Ge(Xe,"..","package.json"),"utf-8")).version??"0.0.0"}catch{return"0.0.0"}})();function Ye(){let s=process.argv.slice(2),e="",t="",r="",n="https://api.fasttest.ai",i,o,l,d="chromium",a,f=!1;for(let u=0;u<s.length;u++)switch(s[u]){case"--api-key":e=s[++u]??"";break;case"--suite":r=s[++u]??"";break;case"--suite-id":t=s[++u]??"";break;case"--base-url":n=s[++u]??n;break;case"--app-url":i=s[++u];break;case"--environment":o=s[++u];break;case"--pr-url":l=s[++u];break;case"--browser":d=s[++u]??"chromium";break;case"--test-case-ids":a=(s[++u]??"").split(",").map(m=>m.trim()).filter(Boolean);break;case"--json":f=!0;break}return(!e||!t&&!r)&&(console.error(`Usage: fasttest-ci --api-key <key> --suite "Suite Name" [options]
17
+ `);let X;if(t.aiFallback)for(let a of h){if(a.status!=="failed")continue;let f=a.step_results.find(y=>!y.success&&y.ai_context);if(f?.ai_context){let R=m.find(q=>q.id===a.id)?.steps[f.step_index]??{};X={test_case_id:a.id,test_case_name:a.name,step_index:f.step_index,step:R,intent:f.ai_context.intent,error:f.error??a.error??"Unknown error",page_url:f.ai_context.page_url,snapshot:f.ai_context.snapshot};break}}return{execution_id:i,status:x?"cancelled":B===0?"passed":"failed",total:o.length,passed:P,failed:B,skipped:W,duration_ms:Ae,results:h,healed:k,ai_fallback:X}}async function Ke(s,e,t,r,n,i,o,l,u,c=process.env){let d=[],g=Date.now();try{let p=r.session??u,m;if(p)try{m=A(p,c)}catch(w){if(/\{\{[A-Z_]+\}\}/.test(p))return{id:r.id,name:r.name,status:"failed",duration_ms:Date.now()-g,error:`Session name "${p}" contains unresolved variable: ${w}`,step_results:[]};m=p}let h;if(m)try{h=await s.restoreSession(m)}catch(w){process.stderr.write(`Warning: session "${m}" not found, using fresh context: ${w}
18
+ `),h=await s.newContext()}else h=await s.newContext();let k=w=>{i.push(`[${w.type()}] ${w.text()}`)};h.on("console",k);try{for(let w=0;w<r.steps.length;w++){let x=r.steps[w],$=Date.now(),b;try{b=z(x,c)}catch(P){return d.push({step_index:w,action:x.action,success:!1,error:String(P),duration_ms:Date.now()-$}),{id:r.id,name:r.name,status:"failed",duration_ms:Date.now()-g,error:`Step ${w+1} (${x.action}) failed: ${String(P)}`,step_results:d}}let _=await J(h,b,n,s);if(_.page&&(h=_.page),!_.success&&b.selector&&We(_.error)){await e.notifyHealingStarted(t,r.id,b.selector);let P=await be(h,e,b.selector,Ve(_.error),_.error??"unknown",h.url(),{action:b.action,description:b.description,intent:b.intent},r.id);if(P.healed&&P.newSelector){let B={...b,selector:P.newSelector};if(_=await J(h,B,n,s),_.success){o.push({test_case_id:r.id,test_case:r.name,step_index:w,original_selector:x.selector,new_selector:P.newSelector,strategy:P.strategy??"unknown",confidence:P.confidence??0});let W=await Pe(h);d.push({step_index:w,action:x.action,success:!0,duration_ms:Date.now()-$,screenshot_url:W?.dataUrl,healed:!0,heal_details:{original_selector:x.selector,new_selector:P.newSelector,strategy:P.strategy??"unknown",confidence:P.confidence??0}});continue}}}let O=await Pe(h),F;if(!_.success&&l)try{let P=await le(h);F={intent:b.intent??b.description,page_url:h.url(),snapshot:P}}catch{}if(d.push({step_index:w,action:x.action,success:_.success,error:_.error,duration_ms:Date.now()-$,screenshot_url:O?.dataUrl,ai_context:F}),!_.success)return{id:r.id,name:r.name,status:"failed",duration_ms:Date.now()-g,error:`Step ${w+1} (${x.action}) failed: ${_.error}`,step_results:d}}for(let w=0;w<r.assertions.length;w++){let x=r.assertions[w],$=Date.now(),b;try{b=Se(x,c)}catch(O){return d.push({step_index:r.steps.length+w,action:`assert:${x.type}`,success:!1,error:String(O),duration_ms:Date.now()-$}),{id:r.id,name:r.name,status:"failed",duration_ms:Date.now()-g,error:`Assertion ${w+1} (${x.type}) failed: ${String(O)}`,step_results:d}}let _=await $e(h,b);if(d.push({step_index:r.steps.length+w,action:`assert:${x.type}`,success:_.pass,error:_.error,duration_ms:Date.now()-$}),!_.pass)return{id:r.id,name:r.name,status:"failed",duration_ms:Date.now()-g,error:`Assertion ${w+1} (${x.type}) failed: ${_.error??"expected value mismatch"}`,step_results:d}}return{id:r.id,name:r.name,status:"passed",duration_ms:Date.now()-g,step_results:d}}finally{h.off("console",k)}}catch(p){return{id:r.id,name:r.name,status:"failed",duration_ms:Date.now()-g,error:String(p),step_results:d}}}async function Pe(s){let e=await ue(s,!1);if(e)return{dataUrl:`data:image/jpeg;base64,${e}`}}async function J(s,e,t,r){let n=e.action;try{switch(n){case"navigate":{let i=e.url??e.value??"";if(i&&!i.startsWith("http")){if(!t)return{success:!1,error:`Navigate step has a relative URL "${i}" but no base URL is configured. Set a base URL on your project or environment.`};i=t.replace(/\/$/,"")+i}return await se(s,i)}case"click":return await re(s,e.selector??"");case"type":return await ie(s,e.selector??"",e.value??"");case"fill":return await ne(s,e.selector??"",e.value??"");case"fill_form":{let i=e.fields??{};return await ye(s,i)}case"drag":return await he(s,e.selector??"",e.target??"");case"resize":return await we(s,e.width??1280,e.height??720);case"hover":return await oe(s,e.selector??"");case"select":return await ae(s,e.selector??"",e.value??"");case"wait_for":return e.condition==="navigation"?(await s.waitForLoadState("domcontentloaded",{timeout:(e.timeout??10)*1e3}),await s.waitForLoadState("networkidle",{timeout:5e3}).catch(()=>{}),{success:!0}):await ce(s,e.selector??"",(e.timeout??10)*1e3);case"scroll":return e.selector?await s.locator(e.selector).scrollIntoViewIfNeeded():await s.evaluate(()=>window.scrollTo(0,document.body.scrollHeight)),{success:!0};case"press_key":return await pe(s,e.key??e.value??"Enter");case"upload_file":{let i=e.file_paths??(e.value?[e.value]:null);return!i||i.length===0?{success:!1,error:"upload_file step missing file_paths"}:await fe(s,e.selector??"",i)}case"evaluate":return await me(s,e.expression??e.value??"");case"go_back":return await de(s);case"go_forward":return await ge(s);case"restore_session":{if(!r)return{success:!1,error:"restore_session requires browser manager"};let i=e.value??e.name??"";return i?{success:!0,page:await r.restoreSession(i)}:{success:!1,error:"restore_session step missing session name (set 'value' or 'name')"}}case"save_session":{if(!r)return{success:!1,error:"save_session requires browser manager"};let i=e.value??e.name??"";return i?(await r.saveSession(i),{success:!0}):{success:!1,error:"save_session step missing session name (set 'value' or 'name')"}}case"assert":return $e(s,e).then(i=>({success:i.pass,error:i.error}));default:return{success:!1,error:`Unknown action: ${n}`}}}catch(i){return{success:!1,error:String(i)}}}async function $e(s,e){return _e(s,{type:e.type,selector:e.selector,text:e.text??e.expected_value,url:e.url,count:e.count,attribute:e.attribute,value:e.value??e.expected_value,expression:e.expression,description:e.description})}function We(s){if(!s)return!1;let e=s.toLowerCase();return e.includes("navigation")||e.includes("net::")||e.includes("page.goto")?!1:e.includes("selector")||e.includes("not found")||e.includes("waiting for selector")||e.includes("no element")||e.includes("waiting for locator")||e.includes("locator")}function Ve(s){if(!s)return"UNKNOWN";let e=s.toLowerCase();return e.includes("timeout")?"TIMEOUT":e.includes("not found")||e.includes("no element")||e.includes("selector")?"ELEMENT_NOT_FOUND":e.includes("navigation")||e.includes("net::")?"NAVIGATION_FAILED":"UNKNOWN"}function ze(s){return s.filter(e=>{let t=e.mimeType.toLowerCase();return!!(t.includes("json")||t.includes("text/html")||t.includes("text/plain")||e.status>=400)})}function Ge(s,e){let t=new Set(s.map(o=>o.id)),r=new Set;for(let o of s)if(o.depends_on)for(let l of o.depends_on)r.add(l);let n=[],i=[];for(let o of s){let l=e[o.id],u=o.depends_on?.some(c=>t.has(c))??!1;l==="failed"&&!r.has(o.id)&&!u?n.push(o):i.push(o)}return[...n,...i]}function Je(s){let e=new Set(s.map(u=>u.id));if(!s.some(u=>u.depends_on&&u.depends_on.some(c=>e.has(c))))return s;let r=new Map(s.map(u=>[u.id,u])),n=new Set,i=new Set,o=[];function l(u){if(n.has(u))return!0;if(i.has(u))return!1;i.add(u);let c=r.get(u);if(c?.depends_on){for(let d of c.depends_on)if(e.has(d)&&!l(d))return!1}return i.delete(u),n.add(u),c&&o.push(c),!0}for(let u of s)if(!l(u.id))return process.stderr.write(`Warning: dependency cycle detected, using original test order.
19
+ `),s;return o}var et=Qe(Ye(import.meta.url)),tt=(()=>{try{return JSON.parse(Ze(Xe(et,"..","package.json"),"utf-8")).version??"0.0.0"}catch{return"0.0.0"}})();function st(){let s=process.argv.slice(2),e="",t="",r="",n="https://api.fasttest.ai",i,o,l,u="chromium",c,d=!1;for(let g=0;g<s.length;g++)switch(s[g]){case"--api-key":e=s[++g]??"";break;case"--suite":r=s[++g]??"";break;case"--suite-id":t=s[++g]??"";break;case"--base-url":n=s[++g]??n;break;case"--app-url":i=s[++g];break;case"--environment":o=s[++g];break;case"--pr-url":l=s[++g];break;case"--browser":u=s[++g]??"chromium";break;case"--test-case-ids":c=(s[++g]??"").split(",").map(p=>p.trim()).filter(Boolean);break;case"--json":d=!0;break}return(!e||!t&&!r)&&(console.error(`Usage: fasttest-ci --api-key <key> --suite "Suite Name" [options]
20
20
  fasttest-ci --api-key <key> --suite-id <id> [options]
21
21
 
22
22
  Options:
@@ -28,7 +28,7 @@ Options:
28
28
  --pr-url GitHub PR URL for posting results
29
29
  --browser chromium | firefox | webkit
30
30
  --test-case-ids Comma-separated test case IDs
31
- --json Output results as JSON`),process.exit(1)),{apiKey:e,suiteId:t,suiteName:r||void 0,baseUrl:n,appUrl:i,environment:o,prUrl:l,browser:d,testCaseIds:a,json:f}}function et(s){let e=s.status==="passed"?"PASSED":"FAILED";console.log(`--- Results: ${e} ---`),console.log(`Execution: ${s.execution_id}`),console.log(`Total: ${s.total} | Passed: ${s.passed} | Failed: ${s.failed} | Skipped: ${s.skipped}`),console.log(`Duration: ${(s.duration_ms/1e3).toFixed(1)}s`),console.log("");for(let t of s.results){let r=t.status==="passed"?"PASS":t.status==="failed"?"FAIL":"SKIP";console.log(` [${r}] ${t.name} (${t.duration_ms}ms)`),t.error&&console.log(` Error: ${t.error}`)}if(s.healed.length>0){console.log(""),console.log(`--- Self-Healed: ${s.healed.length} selector(s) ---`);for(let t of s.healed)console.log(` "${t.test_case}" step ${t.step_index+1}`),console.log(` ${t.original_selector} -> ${t.new_selector}`),console.log(` Strategy: ${t.strategy} (${Math.round(t.confidence*100)}% confidence)`)}}async function tt(){let s=Ye(),e=D(s.apiKey.split("_")[1]??"default"),t=new B({browserType:s.browser,headless:!0,orgSlug:e});Z=t;let r=new M({apiKey:s.apiKey,baseUrl:s.baseUrl}),n=[];if(console.log(`FastTest CI Runner v${Qe}`),!s.suiteId&&s.suiteName)try{let l=await r.resolveSuite(s.suiteName);s.suiteId=l.id,console.log(`Suite: "${l.name}" \u2192 ${l.id}`)}catch(l){console.error(`Failed to resolve suite "${s.suiteName}": ${l}`),process.exit(1)}else console.log(`Suite: ${s.suiteId}`);console.log(`Browser: ${s.browser}`),s.environment&&console.log(`Environment: ${s.environment}`),s.appUrl&&console.log(`App URL: ${s.appUrl}`),console.log("");let i;if(s.environment)try{let l=await r.resolveEnvironment(s.suiteId,s.environment);i=l.id,console.log(`Resolved environment "${s.environment}" \u2192 ${l.base_url}`)}catch(l){console.error(`Failed to resolve environment "${s.environment}": ${l}`),process.exit(1)}let o;try{o=await Se(t,r,{suiteId:s.suiteId,testCaseIds:s.testCaseIds,appUrlOverride:s.appUrl,environmentId:i},n)}catch(l){console.error(`Fatal: ${l}`),await $e(t),process.exit(1)}if(s.json?console.log(JSON.stringify(o,null,2)):et(o),s.prUrl)try{let l,d;try{let u=await r.getExecutionDiff(o.execution_id);u.regressions?.length&&(l=u.regressions.map(m=>({name:m.name,previous_status:m.previous_status,current_status:m.current_status,error:m.error}))),u.fixes?.length&&(d=u.fixes.map(m=>({name:m.name,previous_status:m.previous_status,current_status:m.current_status})))}catch{}let f=(await r.postPrComment({pr_url:s.prUrl,execution_id:o.execution_id,status:o.status,total:o.total,passed:o.passed,failed:o.failed,skipped:o.skipped,duration_seconds:Math.round(o.duration_ms/1e3),test_results:o.results.map(u=>({name:u.name,status:u.status,error:u.error})),healed:o.healed.map(u=>({original_selector:u.original_selector,new_selector:u.new_selector,strategy:u.strategy,confidence:u.confidence})),regressions:l,fixes:d})).comment_url;console.log(`
32
- PR comment posted: ${f??s.prUrl}`)}catch(l){console.error(`
33
- Failed to post PR comment: ${l}`)}await $e(t),process.exit(o.status==="passed"?0:1)}async function $e(s){await Promise.race([s.close(),new Promise(e=>setTimeout(e,5e3))])}var Z=null;async function Re(s){console.log(`
34
- ${s} received, shutting down\u2026`),Z&&await Promise.race([Z.close(),new Promise(e=>setTimeout(e,5e3))]),process.exit(0)}process.on("SIGTERM",()=>Re("SIGTERM"));process.on("SIGINT",()=>Re("SIGINT"));tt().catch(s=>{console.error("Fatal:",s),process.exit(1)});
31
+ --json Output results as JSON`),process.exit(1)),{apiKey:e,suiteId:t,suiteName:r||void 0,baseUrl:n,appUrl:i,environment:o,prUrl:l,browser:u,testCaseIds:c,json:d}}function rt(s){let e=s.status==="passed"?"PASSED":"FAILED";console.log(`--- Results: ${e} ---`),console.log(`Execution: ${s.execution_id}`),console.log(`Total: ${s.total} | Passed: ${s.passed} | Failed: ${s.failed} | Skipped: ${s.skipped}`),console.log(`Duration: ${(s.duration_ms/1e3).toFixed(1)}s`),console.log("");for(let t of s.results){let r=t.status==="passed"?"PASS":t.status==="failed"?"FAIL":"SKIP";console.log(` [${r}] ${t.name} (${t.duration_ms}ms)`),t.error&&console.log(` Error: ${t.error}`)}if(s.healed.length>0){console.log(""),console.log(`--- Self-Healed: ${s.healed.length} selector(s) ---`);for(let t of s.healed)console.log(` "${t.test_case}" step ${t.step_index+1}`),console.log(` ${t.original_selector} -> ${t.new_selector}`),console.log(` Strategy: ${t.strategy} (${Math.round(t.confidence*100)}% confidence)`)}}async function nt(){let s=st(),e=s.apiKey.split("_"),t=D(e.length>=3?e.slice(1,-1).join("_"):e[1]??"default"),r=new H({browserType:s.browser,headless:!0,orgSlug:t});Z=r;let n=new M({apiKey:s.apiKey,baseUrl:s.baseUrl}),i=[];if(console.log(`FastTest CI Runner v${tt}`),!s.suiteId&&s.suiteName)try{let u=await n.resolveSuite(s.suiteName,void 0,!0);s.suiteId=u.id,console.log(`Suite: "${u.name}" \u2192 ${u.id}`)}catch(u){console.error(`Failed to resolve suite "${s.suiteName}": ${u}`),process.exit(1)}else console.log(`Suite: ${s.suiteId}`);console.log(`Browser: ${s.browser}`),s.environment&&console.log(`Environment: ${s.environment}`),s.appUrl&&console.log(`App URL: ${s.appUrl}`),console.log("");let o;if(s.environment)try{let u=await n.resolveEnvironment(s.suiteId,s.environment);o=u.id,console.log(`Resolved environment "${s.environment}" \u2192 ${u.base_url}`)}catch(u){console.error(`Failed to resolve environment "${s.environment}": ${u}`),process.exit(1)}let l;try{l=await ke(r,n,{suiteId:s.suiteId,testCaseIds:s.testCaseIds,appUrlOverride:s.appUrl,environmentId:o},i)}catch(u){console.error(`Fatal: ${u}`),await Re(r),process.exit(1)}if(s.json?console.log(JSON.stringify(l,null,2)):rt(l),s.prUrl)try{let u,c;try{let p=await n.getExecutionDiff(l.execution_id);p.regressions?.length&&(u=p.regressions.map(m=>({name:m.name,previous_status:m.previous_status,current_status:m.current_status,error:m.error}))),p.fixes?.length&&(c=p.fixes.map(m=>({name:m.name,previous_status:m.previous_status,current_status:m.current_status})))}catch{}let g=(await n.postPrComment({pr_url:s.prUrl,execution_id:l.execution_id,status:l.status,total:l.total,passed:l.passed,failed:l.failed,skipped:l.skipped,duration_seconds:Math.round(l.duration_ms/1e3),test_results:l.results.map(p=>({name:p.name,status:p.status,error:p.error})),healed:l.healed.map(p=>({original_selector:p.original_selector,new_selector:p.new_selector,strategy:p.strategy,confidence:p.confidence})),regressions:u,fixes:c})).comment_url;console.log(`
32
+ PR comment posted: ${g??s.prUrl}`)}catch(u){console.error(`
33
+ Failed to post PR comment: ${u}`)}await Re(r),process.exit(l.status==="passed"?0:1)}async function Re(s){await Promise.race([s.close(),new Promise(e=>setTimeout(e,5e3))])}var Z=null;async function Te(s){console.log(`
34
+ ${s} received, shutting down\u2026`),Z&&await Promise.race([Z.close(),new Promise(e=>setTimeout(e,5e3))]),process.exit(0)}process.on("SIGTERM",()=>Te("SIGTERM"));process.on("SIGINT",()=>Te("SIGINT"));nt().catch(s=>{console.error("Fatal:",s),process.exit(1)});