shiplightai 0.1.9 → 0.1.10
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/dist/cjs/index.cjs +1 -1
- package/dist/cli.js +7 -5
- package/dist/index.js +1 -1
- package/package.json +3 -3
package/dist/cjs/index.cjs
CHANGED
|
@@ -4698,7 +4698,7 @@ Additional instructions: ${e.additional_prompt}
|
|
|
4698
4698
|
`);try{se.log("Level 3: Attempting agent-based login"),se.log(`Login prompt:
|
|
4699
4699
|
${i}`);let n=this.agentServices.knowledgeRetriever,s=!1;this.agentServices.hasKnowledgeRetriever()&&n&&(se.log("Setting up login knowledge retriever..."),this.agentServices.setKnowledgeRetriever(async(f,m,g,y)=>await n(f,m,g,"login")),s=!0,se.log("Login knowledge retriever configured"));let o;try{if(o=await this.run(r,i,"login"),!o.success)return se.log("Level 3 FAILED: Agent login failed"),F.info("Login: Failed"),{success:!1,page:r}}finally{s&&n&&(this.agentServices.setKnowledgeRetriever(n),se.log("Restored original knowledge retriever"))}let l=await r.context().storageState(),c;if(e.num_verification_exprs!==0)try{se.log("Generating validation locators for future login verification...");let f=await Ib(r,e.site_url,{verification_hint:e.verification_hint,num_verification_exprs:e.num_verification_exprs},async(m,g)=>this.run(m,g),async m=>this.getDOMText(m));f?(c=f,se.log(`Generated ${c.length} validation locator(s): ${JSON.stringify(c)}`)):se.log("Failed to generate validation locators, login will still succeed")}catch(f){se.log(`Error generating validation locators: ${f.message}`)}else se.log("Skipping validation locator generation (num_verification_exprs=0)");se.log("Level 3 SUCCESS: Agent login succeeded"),F.info("Login: Agent login succeeded");let u=o.actions||[],p=u.length>0?u:t?.cached_actions;return{success:!0,page:r,storage_state:l,cached_actions:p,validation_exprs:c}}catch(n){return se.error(`Agent login failed: ${n.message}`),F.info("Login: Failed"),{success:!1,page:r}}}async executeCachedLogin(r,e,t){let a=(await Promise.resolve().then(()=>(Cp(),kp))).default,i=new a;for(let n=0;n<e.length;n++){let s=e[n];se.log(`Executing cached action ${n+1}/${e.length}: ${s.action_description||s.action_data?.action_name}`);try{if(!s.action_data){se.log(`Action ${n+1} missing action_data, skipping`);continue}await i.execute(r,s,this.agentServices),await this.waitUntilStable(r,5e3)}catch(o){return se.error(`Cached action ${n+1} failed: ${o.message}`),{success:!1}}}return t&&t.length>0&&!await Km(r,t)?{success:!1}:{success:!0}}getCompletedExecutionHistory(){if(!this.context.executionHistory||this.context.executionHistory.length===0)return[];let r=this.context.executionHistory[this.context.executionHistory.length-1];return!r[1]||r[1].trim()===""?this.context.executionHistory.slice(0,-1):this.context.executionHistory}addToExecutionHistory(r,e){this.context.executionHistory||(this.context.executionHistory=[]),this.context.executionHistory.push([r,e])}async createStepResult(r,e,t){if(!this.context.stepTracking||this.context.stepTracking.results[e])return;this.context.stepTracking.currentStepId=e,this.context.stepTracking.onStepChange&&this.context.stepTracking.onStepChange(e),r=await this.agentServices.validatePage(r),await this.waitUntilStable(r);let a={description:t,startTime:Date.now(),artifacts:[]};if(this.context.stepTracking.artifactsDir){let i=fa.join(this.context.stepTracking.artifactsDir,e.replace(/\./g,"-"));Br.mkdirSync(i,{recursive:!0});let n=fa.join(i,"screenshot.png");await r.screenshot({type:"png",path:n}),a.screenshot=n}this.context.stepTracking.results[e]=a}saveDebugInfoToFiles(r,e,t){let a={};if(!this.context.stepTracking?.artifactsDir)return F.debug("[saveDebugInfoToFiles] No artifacts directory configured, skipping debug info save"),a;let i=fa.join(this.context.stepTracking.artifactsDir,r.replace(/\./g,"-"));try{if(Br.mkdirSync(i,{recursive:!0}),e.systemPrompt){let n=fa.join(i,"system_prompt.txt");Br.writeFileSync(n,e.systemPrompt),a.system_prompt_path=n,F.debug(`[saveDebugInfoToFiles] Saved system prompt to: ${n}`)}if(e.userPrompt)if(typeof e.userPrompt=="string"){let n=fa.join(i,"user_prompt.txt");Br.writeFileSync(n,e.userPrompt),a.user_prompt_path=n,F.debug(`[saveDebugInfoToFiles] Saved user prompt to: ${n}`)}else{let n=e.userPrompt.map(l=>Array.isArray(l.content)?{...l,content:l.content.map(c=>c.type==="image"&&c.file?.startsWith("data:")?{...c,file:"[base64 image data stripped]"}:c)}:l),s=fa.join(i,"messages.json"),o={system:e.systemPrompt,messages:n};Br.writeFileSync(s,JSON.stringify(o,null,2)),a.messages_path=s,F.debug(`[saveDebugInfoToFiles] Saved messages to: ${s}`)}if(e.rawLlmResponse){let n=t?t.replace(/[\/\\:]/g,"-"):"llm",s=fa.join(i,`${n}_response.txt`);Br.writeFileSync(s,e.rawLlmResponse),a.response_path=s,F.debug(`[saveDebugInfoToFiles] Saved LLM response to: ${s}`)}if(e.screenshotWithSom){let n=fa.join(i,"screenshot.png");Br.writeFileSync(n,Buffer.from(e.screenshotWithSom,"base64")),a.screenshot_path=n,F.debug(`[saveDebugInfoToFiles] Saved SOM screenshot to: ${n}`)}if(e.reasoningContent){let n=fa.join(i,"reasoning.txt");Br.writeFileSync(n,e.reasoningContent),a.reasoning_path=n,F.debug(`[saveDebugInfoToFiles] Saved reasoning to: ${n}`)}F.debug(`[saveDebugInfoToFiles] Saved ${Object.keys(a).length} artifacts for step ${r}`)}catch(n){F.error(`[saveDebugInfoToFiles] Failed to save debug info for step ${r}:`,n)}return a}async updateStepResult(r,e,t,a,i){if(!this.context.stepTracking)return;let n=this.context.stepTracking.results[r];if(n){if(e&&(n.status=e,this.context.stepTracking.currentStepId=void 0,this.context.stepTracking.onStepChange&&this.context.stepTracking.onStepChange(void 0)),t!==void 0&&(n.message=t),a&&n.artifacts.push(a),i){let s=i.tokenUsages?.[0]?.model,o=this.saveDebugInfoToFiles(r,i,s);Object.keys(o).length>0&&n.artifacts.push(o)}n.duration=Date.now()-n.startTime,this.context.stepTracking.onStepComplete&&this.context.stepTracking.onStepComplete(r,n)}}async writeExecutionResults(r,e){try{if(await Br.promises.mkdir(r,{recursive:!0}),this.context.stepTracking?.results&&Object.keys(this.context.stepTracking.results).length>0){let t=fa.join(r,"test-results.json");await Br.promises.writeFile(t,JSON.stringify(this.context.stepTracking.results,null,2)),F.debug(`Test results written to: ${t}`)}if(e?.tokenUsages&&this.context.tokenUsages&&this.context.tokenUsages.length>0){let t=fa.join(r,"token-usages.json");await Br.promises.writeFile(t,JSON.stringify(this.context.tokenUsages,null,2)),F.debug(`Token usages written to: ${t}`)}if(this.context.aiActionDetails&&this.context.aiActionDetails.length>0){let t=fa.join(r,"ai-actions.json");await Br.promises.writeFile(t,JSON.stringify(this.context.aiActionDetails,null,2)),F.debug(`AI action details written to: ${t}`)}}catch(t){throw F.error("Failed to write execution results:",t),t}}}});var HT,OG=T(()=>{"use strict";HT=(r=>(r.Started="started",r.Action="action",r.Completion="completion",r.Error="error",r.Aborted="aborted",r.Keepalive="keepalive",r))(HT||{})});function x2e(){let r=new Date,e=new Intl.DateTimeFormat("en-US",{timeZone:"America/Los_Angeles",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}).formatToParts(r),t=e.find(_=>_.type==="year").value,a=e.find(_=>_.type==="month").value,i=e.find(_=>_.type==="day").value,n=e.find(_=>_.type==="hour").value,s=e.find(_=>_.type==="minute").value,o=e.find(_=>_.type==="second").value,l=String(r.getMilliseconds()).padStart(3,"0"),c=`${t}-${a}-${i}T${n}:${s}:${o}.${l}`,u=r.toISOString().slice(0,-1),p=Date.parse(c+"Z"),f=Date.parse(u+"Z"),m=Math.round((f-p)/(1e3*60)),g=String(Math.floor(Math.abs(m)/60)).padStart(2,"0"),y=String(Math.abs(m)%60).padStart(2,"0"),v=m<=0?"+":"-";return`${t}-${a}-${i}T${n}:${s}:${o}.${l}${v}${g}:${y}`}function Qm(r){let e=r.variableStore;return e.has("currentTime")||e.set("currentTime",x2e()),{model:r.model,variableStore:e,organizationId:r.organizationId,organizationSettings:r.organizationSettings,executionHistory:r.executionHistory||[],testDataDir:r.testDataDir,downloadDir:r.downloadDir,useNativeGenerator:r.useNativeGenerator,autoDisableModal:r.autoDisableModal,tokenUsages:[],aiActionDetails:[]}}var RG=T(()=>{"use strict"});var MG={};sn(MG,{ActionHandler:()=>D4,ActionHelper:()=>Tm,Agent:()=>Zm,AgentServices:()=>zb,AgentStepEventTypes:()=>HT,BrowserManager:()=>Fz,DEFAULT_EVENT_LISTENER_LIMIT:()=>Zy,DomService:()=>Qi,EVENT_LISTENER_CANDIDATE_SELECTORS:()=>Yy,HistoryTreeProcessor:()=>Dq,INIT_SCRIPT:()=>Ip,INTERACTION_EVENT_TYPES:()=>mp,INTERACTIVE_ROLES:()=>N1,LogLevel:()=>fx,LoginType:()=>Wm,MCPToolProvider:()=>Ez,OpenAIToolProvider:()=>Z0,SDK_VERSION:()=>T2e,ToolRegistry:()=>kd,TwoFactorAuthType:()=>wT,VariableStore:()=>zm,WebAgent:()=>Zm,configureSdk:()=>K0,createAgent:()=>S2e,createAgentContext:()=>Qm,createToolRegistry:()=>_z,createToolRegistryWithCapabilities:()=>xz,ensureToolsRegistered:()=>wz,evaluateStatement:()=>Zc,executeStep:()=>Yc,exportMCPTools:()=>Az,filterInteractionListeners:()=>_q,generateActionStep:()=>vp,getActionEntityLocatorInfo:()=>Bt,getBrowserCdpUrl:()=>j4,getCapabilitySummary:()=>Sz,getFramePath:()=>x_,getModel:()=>zn,getPageInfo:()=>kV,getPageWsUrl:()=>AV,getPlatformFromDeviceName:()=>$z,getProviderOptions:()=>Gn,getSdkConfig:()=>ra,getToolRegistry:()=>vz,injectUserFunction:()=>Y3,isInteractionEventType:()=>vq,isInteractiveRole:()=>wq,loadKnowledgeMappings:()=>wk,loadKnowledges:()=>bk,loadUserFunctions:()=>gk,logger:()=>F,newBrowserContext:()=>EV,parseSSEStream:()=>_k,pickBestLocator:()=>fh,pickBestLocatorForElement:()=>sg,pickBestLocators:()=>Pk,replaceVariables:()=>Ab,runTask:()=>Qc,setWindowBounds:()=>U4,toolRegistry:()=>Zt});function S2e(r){let e=new zm,t=new Set(r.sensitiveKeys||[]);if(r.variables)for(let[i,n]of Object.entries(r.variables))e.set(i,n,t.has(i));let a=Qm({model:r.model,variableStore:e,testDataDir:r.testDataDir,downloadDir:r.downloadDir});return new Zm(a)}var T2e,VT=T(()=>{"use strict";yk();vk();xk();Tz();kz();Cz();bT();AS();yT();gT();Bz();jz();Uz();NG();F1();Xy();Jy();Q0();tg();Ad();Cf();Pf();$s();ph();hh();c4();Xc();I1();O1();gp();R1();M1();Nb();Pi();qT();ST();Kp();Ob();ao();Np();xl();Df();h1();OG();SS();e4();t4();r4();a4();i4();n4();s4();o4();u4();d4();B1();$1();U1();q1();H1();V1();W1();z1();G1();J1();Y1();Z1();Q1();em();tm();rm();am();im();nm();sm();om();lm();cm();vm();_m();xm();Sm();I4();j1();Dr();zl();vr();ri();yp();M4();Gt();RG();on();T2e="1.0.0"});var k2e={};sn(k2e,{VariableStore:()=>zm,WebAgent:()=>Zm,authSetup:()=>LG,configureSdk:()=>K0,createAgentContext:()=>Qm,resolveLoginConfig:()=>G0,shiplightConfig:()=>jG});module.exports=_J(k2e);var co=Je(require("fs"),1),ns=Je(require("path"),1),BG=Je(require("dotenv"),1),$G=require("glob");var fs=require("fs"),pk=require("path"),hk=require("glob");var H0=require("yaml");q0();var tk=require("fs"),uh=require("path"),ch=require("yaml"),ek=5;function rk(r,e){let t={expandingPaths:new Set([(0,uh.resolve)(e)]),depth:0,referencedPaths:new Set},a={...r};return Array.isArray(a.statements)&&(a.statements=Hl(a.statements,e,t)),Array.isArray(a.teardown)&&(a.teardown=Hl(a.teardown,e,t)),{doc:a,referencedTemplatePaths:Array.from(t.referencedPaths)}}function Hl(r,e,t){let a=[];for(let i of r)if(BJ(i)){let n=$J(i,e,t);a.push(...n)}else a.push(jJ(i,e,t));return a}function BJ(r){return typeof r=="object"&&r!==null&&typeof r.template=="string"}function $J(r,e,t){if(t.depth>=ek)throw new Error(`Template expansion exceeded maximum depth of ${ek}. Check for deeply nested or circular template references.`);let a=(0,uh.resolve)((0,uh.dirname)(e),r.template);if(t.expandingPaths.has(a))throw new Error(`Circular template reference detected: ${a} is already being expanded. Stack: ${Array.from(t.expandingPaths).join(" \u2192 ")} \u2192 ${a}`);t.referencedPaths.add(a);let i;try{i=(0,tk.readFileSync)(a,"utf-8")}catch(u){throw new Error(`Failed to read template file: ${a} (referenced from ${e}): ${u.message}`)}let n=(0,ch.parse)(i);if(!n||typeof n!="object")throw new Error(`Invalid template file: ${a} \u2014 expected a YAML object`);let s=n.params||[],o=r.params||{};for(let u of s)if(!(u in o))throw new Error(`Template ${r.template} requires param "${u}" but it was not provided. Required params: [${s.join(", ")}]`);let l=n.statements;if(!Array.isArray(l))throw new Error(`Template ${r.template} must have a "statements" array`);if(Object.keys(o).length>0){let p=(0,ch.stringify)(l);for(let[f,m]of Object.entries(o))p=p.replaceAll(`{{${f}}}`,String(m));l=(0,ch.parse)(p)}let c={expandingPaths:new Set([...t.expandingPaths,a]),depth:t.depth+1,referencedPaths:t.referencedPaths};return Hl(l,a,c)}function jJ(r,e,t){if(typeof r!="object"||r===null)return r;let a={...r};return Array.isArray(a.statements)&&(a.statements=Hl(a.statements,e,t)),Array.isArray(a.THEN)&&(a.THEN=Hl(a.THEN,e,t)),Array.isArray(a.ELSE)&&(a.ELSE=Hl(a.ELSE,e,t)),Array.isArray(a.DO)&&(a.DO=Hl(a.DO,e,t)),a}function ak(r,e){let t=(0,H0.parse)(r),a=t?.name,i=t?.tags,n=t?.use;t&&(t.name!==void 0||t.tags!==void 0||t.use!==void 0)&&(delete t.name,delete t.tags,delete t.use);let s=[];if(e&&t&&typeof t=="object"){let c=rk(t,e);t=c.doc,s=c.referencedTemplatePaths}let o=(0,H0.stringify)(t);return{testFlow:j0(o),name:a,tags:i,use:n,referencedTemplatePaths:s}}q0();function V0(r){return r.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t")}function Ao(r){return r.replace(/\r\n/g," ").replace(/\n/g," ").replace(/\r/g," ").trim()}function UJ(r){let e=r.frame_path;return!e||e.length===0?"page":`page.frameLocator('${e[0]}')`}function qJ(r){let e=r.xpath;return typeof e=="string"&&e.trim()?!e.startsWith("xpath=")&&!e.startsWith("/")&&!e.startsWith("//")?`xpath=//${e}`:e.startsWith("xpath=")?e:`xpath=${e}`:null}function K3(r){let e=UJ(r),t=r.locator;if(typeof t=="string"&&t.trim())return t=t.trim(),t.endsWith("first()")?`${e}.${t}`:`${e}.${t}.first()`;let a=qJ(r);if(a){let i=JSON.stringify(a);return`${e}.locator(${i}).first()`}return null}var HJ=["ai_action","ai_step","ai_assert","ai_extract","ai_wait_until","verify","assert"],VJ=["js_code","function","wait","wait_for_download_complete","wait_for_page_ready","extract_email_content","extract_activation_code"];function ik(r){let e=r.action_data?.action_name;return!e||(e==="verify"||e==="ai_assert"||e==="assert")&&r.action_data?.kwargs?.code?!1:HJ.includes(e)}function nk(r){let e=r.action_data?.action_name;return!e||(e==="verify"||e==="ai_assert"||e==="assert")&&r.action_data?.kwargs?.code?!1:!VJ.includes(e)}var ta=new Map;function rt(r,e){ta.set(r,e)}function lk(r){return ta.get(r)}function Vl(r,e,t=[]){let a=[...t];return e.locator?a.push(`locator: ${JSON.stringify(e.locator)}`):e.xpath&&a.push(`xpath: ${JSON.stringify(e.xpath)}`),e.frame_path&&e.frame_path.length>0&&a.push(`frame_path: ${JSON.stringify(e.frame_path)}`),a.length===0?[`await agent.execAction("${r}", page, {});`]:[`await agent.execAction("${r}", page, {`,...a.map(i=>` ${i},`),"});"]}rt("click",r=>{let e=K3(r);return e?[`await ${e}.click({ timeout: ${5e3} });`]:['await agent.execAction("click", page, {});']});rt("click_element",ta.get("click"));rt("click_element_by_index",ta.get("click"));rt("double_click",r=>Vl("double_click",r));rt("double_click_on_element",ta.get("double_click"));rt("right_click",r=>Vl("right_click",r));rt("right_click_on_element",ta.get("right_click"));rt("hover",r=>Vl("hover",r));rt("hover_element_by_index",ta.get("hover"));rt("input_text",r=>{let e=r.action_data?.kwargs?.text??r.action_data?.kwargs?.value??"";return Vl("input_text",r,[`action_data: { kwargs: { text: ${JSON.stringify(e)} } }`])});rt("fill",ta.get("input_text"));rt("clear_input",r=>Vl("clear_input",r));rt("press",r=>{let e=r.action_data?.kwargs?.keys;return[`await page.keyboard.press(${JSON.stringify(e)});`]});rt("send_keys",ta.get("press"));rt("send_keys_on_element",r=>{let e=K3(r),t=r.action_data?.kwargs?.keys||"";return e?[`await ${e}.press(${JSON.stringify(t)}, { timeout: ${5e3} });`]:['await agent.execAction("send_keys_on_element", page, {',` action_data: { kwargs: { keys: ${JSON.stringify(t)} } },`,"});"]});rt("select_dropdown_option",r=>{let e=r.action_data?.kwargs?.text||r.action_data?.kwargs?.option||"";return Vl("select_dropdown_option",r,[`action_data: { kwargs: { text: ${JSON.stringify(e)} } }`])});rt("scroll",r=>{let e=r.action_data?.kwargs?.down??!0;return[`await page.evaluate('window.scrollBy(0, window.innerHeight * ${(r.action_data?.kwargs?.num_pages??1)*(e?1:-1)})');`]});rt("scroll_down",ta.get("scroll"));rt("scroll_up",ta.get("scroll"));rt("scroll_element",ta.get("scroll"));rt("scroll_to_text",r=>{let e=r.action_data?.kwargs?.text||"";return[`await page.getByText(${JSON.stringify(e)}, { exact: false }).first().scrollIntoViewIfNeeded();`]});rt("scroll_on_element",r=>Vl("scroll_on_element",r,[`action_data: { kwargs: ${JSON.stringify(r.action_data?.kwargs||{})} }`]));rt("go_to_url",r=>{let e=r.action_data?.kwargs?.url||"";return r.action_data?.kwargs?.new_tab===!0?['await agent.execAction("go_to_url", page, {',` action_data: { kwargs: { url: ${JSON.stringify(e)}, new_tab: true } },`,"});"]:['await agent.execAction("go_to_url", page, {',` action_data: { kwargs: { url: ${JSON.stringify(e)} } },`,"});"]});rt("open_tab",ta.get("go_to_url"));rt("go_back",()=>['await agent.execAction("go_back", page, {});']);rt("reload_page",()=>['await agent.execAction("reload_page", page, {});']);rt("wait",r=>[`await page.waitForTimeout(${(r.action_data?.kwargs?.seconds||1)*1e3});`]);rt("wait_for_page_ready",()=>["await page.waitForLoadState('domcontentloaded');"]);rt("verify",(r,e)=>{let t=r.action_data?.kwargs;if(t?.code)return t.code.split(`
|
|
4700
4700
|
`);let a=t?.statement||r.action_description;return a?[`await agent.assert(page, ${JSON.stringify(a)}, '${e||""}');`]:["// Skipping verify: missing statement or code"]});rt("ai_assert",ta.get("verify"));rt("assert",ta.get("verify"));rt("ai_action",(r,e)=>{let t=r.action_data?.kwargs?.statement;if(!t)return["// Skipping ai_action: missing statement"];let a=JSON.stringify(t),i=r.action_data?.kwargs?.use_pure_vision;return[`await agent.execute(page, ${a}, '${e||""}', ${i});`]});rt("ai_step",(r,e)=>{let t=r.action_data?.kwargs?.statement;return t?[`await agent.run(page, ${JSON.stringify(t)}, '${e||""}');`]:["// Skipping ai_step: missing statement"]});rt("ai_extract",(r,e)=>{let t=r.action_data?.kwargs?.element_description,a=r.action_data?.kwargs?.variable_name;if(!t||!a)return["// Skipping ai_extract: missing element_description or variable_name"];let i=JSON.stringify(t),n=JSON.stringify(a);return[`await agent.extract(page, ${i}, ${n}, '${e||""}');`]});rt("ai_wait_until",(r,e)=>{let t=r.action_data?.kwargs?.condition,a=r.action_data?.kwargs?.timeout_seconds||60;return t?[`await agent.waitUntilCondition(page, ${JSON.stringify(t)}, ${a}, '${e||""}');`]:["// Skipping ai_wait_until: missing condition"]});rt("save_variable",r=>{let e=r.action_data?.kwargs?.name||"",t=r.action_data?.kwargs?.value;return['await agent.execAction("save_variable", page, {',` action_data: { kwargs: { name: ${JSON.stringify(e)}, value: ${JSON.stringify(t)} } },`,"});"]});rt("js_code",r=>{let e=r.action_data?.kwargs?.code;if(!e)return["// Skipping js_code: missing code"];let t=["{"],a=e.split(`
|
|
4701
|
-
`);for(let i of a)t.push(` ${i}`);return t.push("}"),t});rt("function",(r,e,t)=>{let a=r.action_data?.kwargs||{},i=a.functionName;if(i&&i.includes("#")){let[s,o]=i.split("#");if(s&&o){let l=s.replace(/\.(ts|js|mjs)$/,""),c=`import { ${o} } from '${l}';`;t?.imports?.add(c);let u={...a,functionName:o},p=sk(u);return p?[p.endsWith(";")?p:`${p};`]:["// Skipping function: invalid export pattern"]}}let n=sk(a);return n?[n.endsWith(";")?n:`${n};`]:["// Skipping function: missing functionName"]});rt("generate_2fa_code",r=>{let e=r.action_data?.kwargs?.otp_secret_key||"";return['await agent.execAction("generate_2fa_code", page, {',` action_data: { kwargs: { otp_secret_key: ${JSON.stringify(e)} } },`,"});"]});rt("upload_file",r=>{let e=r.action_data?.kwargs||{},t=[],a={};return e.paths?a.paths=e.paths:e.path&&(a.path=e.path),e.use_file_input&&(a.use_file_input=!0),t.push(`action_data: { kwargs: ${JSON.stringify(a)} }`),r.locator?t.push(`locator: ${JSON.stringify(r.locator)}`):r.xpath&&t.push(`xpath: ${JSON.stringify(r.xpath)}`),r.frame_path&&r.frame_path.length>0&&t.push(`frame_path: ${JSON.stringify(r.frame_path)}`),['await agent.execAction("upload_file", page, {',...t.map(i=>` ${i},`),"});"]});rt("wait_for_download_complete",r=>['await agent.execAction("wait_for_download_complete", page, {',` action_data: { kwargs: { timeout_seconds: ${r.action_data?.kwargs?.timeout_seconds||10} } },`,"});"]);rt("switch_tab",r=>['await agent.execAction("switch_tab", page, {',` action_data: { kwargs: { page_id: ${r.action_data?.kwargs?.page_id??0} } },`,"});"]);rt("close_tab",r=>{let e=r.action_data?.kwargs?.page_id;return e=e??r.action_data?.kwargs?.index,['await agent.execAction("close_tab", page, {',` action_data: { kwargs: { page_id: ${e} } },`,"});"]});rt("set_date_for_native_date_picker",r=>{let e=r.action_data?.kwargs?.date??"",t=[];return t.push(`action_data: { kwargs: { date: ${JSON.stringify(e)} } }`),r.locator?t.push(`locator: ${JSON.stringify(r.locator)}`):r.xpath&&t.push(`xpath: ${JSON.stringify(r.xpath)}`),r.frame_path&&r.frame_path.length>0&&t.push(`frame_path: ${JSON.stringify(r.frame_path)}`),['await agent.execAction("set_date_for_native_date_picker", page, {',...t.map(a=>` ${a},`),"});"]});rt("done",()=>["// Done - no action needed"]);function sk(r){let e=r.functionName;if(!e)return null;let t={},a=r.parameterNames||[],i=r.parameterValues||r.args||[];if(a.forEach((l,c)=>{c<i.length&&(t[l]=i[c])}),Object.keys(t).length===0)return`await ${e}()`;let n=["page","testContext","request","agentServices"],s=["undefined","null","true","false"],o=Object.entries(t).map(([l,c])=>{let u=String(c);return c==null?"undefined":n.includes(l)&&n.includes(u)||s.includes(u)||/^-?\d+(\.\d+)?$/.test(u)?u:u.startsWith("$")?`agent.agentServices.readVariable('${u.substring(1)}')`:`"${u}"`});return`await ${e}(${o.join(", ")})`}function hs(r,e,t,a="main"){let i=[];for(let n=0;n<r.length;n++){let s=r[n],o=`${a}.${n}`,l=WJ(s,e,o,t);l.length>0&&(i.push(...l),n<r.length-1&&i.push(""))}return i}function WJ(r,e,t,a){let i=" ".repeat(e);switch(r.type){case"DRAFT":return zJ(r,e,t);case"ACTION":return GJ(r,e,t,a);case"STEP":return KJ(r,e,t,a);case"IF_ELSE":return JJ(r,e,t,a);case"WHILE_LOOP":return XJ(r,e,t,a);default:return[`${i}// Unknown statement type: ${r.type}`]}}function zJ(r,e,t){let a=" ".repeat(e),i=r.description?.trim()||"";if(!i)return[`${a}// ${t}: Skipping - no description`];let n=JSON.stringify(i);return[`${a}// ${t}: ${Ao(i)}`,`${a}page = agent.agentServices.validatePage(page);`,`${a}await agent.run(page, ${n}, '${t}');`]}function GJ(r,e,t,a){let i=" ".repeat(e),n=r.description,s=r.uid,l=a.actionEntityStore?.entries[r.uid]?.action_entity??r.action_entity;if(!l){if(!n)return[`${i}// ${t}: Skipping - no description`];let M=JSON.stringify(n),N=!!r.use_pure_vision;return[`${i}// ${t}: ${Ao(n)}`,`${i}page = agent.agentServices.validatePage(page);`,`${i}await agent.execute(page, ${M}, '${t}', ${N});`]}let c=r.locator?{...l,locator:r.locator}:l;n&&n!==c.action_description&&(c={...c,action_description:n});let u=c.action_data?.action_name||"",p=c.action_description||"",f=lk(u);if(!f)return[`${i}// ${t}: Unknown action: ${u}`];let m={imports:a.imports},g=f(c,t,m);if(ik(c))return[`${i}// ${t}: ${Ao(p)}`,`${i}page = agent.agentServices.validatePage(page);`,...g.map(M=>`${i}${M}`)];let y=JSON.stringify(p),v=g.map(M=>`${i} ${M}`),_=nk(c),S=s?`'${s}'`:"undefined";return[`${i}// ${t}: ${Ao(p)}`,`${i}page = agent.agentServices.validatePage(page);`,`${i}await agent.step(page, async () => {`,...v,`${i}}, ${y}, '${t}', ${S}, ${_});`]}function KJ(r,e,t,a){let i=" ".repeat(e),n=[];r.description&&r.description.trim()&&n.push(`${i}// Step: ${Ao(r.description)}`);let s=hs(r.statements,e,a,t);return n.push(...s),n}function JJ(r,e,t,a){let i=" ".repeat(e),n=[];if(n.push(`${i}// ${t}: Conditional check`),r.condition.type==="JS_CODE")n.push(`${i}if (${r.condition.expression}) {`);else{n.push(`${i}// AI Condition: ${Ao(r.condition.expression)}`);let o=JSON.stringify(r.condition.expression);n.push(`${i}if (await agent.evaluate(page, ${o}, "${t}")) {`)}let s=hs(r.then,e+1,a,`${t}.then`);if(n.push(...s),r.else&&r.else.length>0){n.push(`${i}} else {`);let o=hs(r.else,e+1,a,`${t}.else`);n.push(...o)}return n.push(`${i}}`),n}function XJ(r,e,t,a){let i=" ".repeat(e),n=[];n.push(`${i}// ${t}: Loop`);let s=r.timeout_ms??U0,o=s/1e3,l=r.timeout_ms?`While loop exceeded timeout of ${o}s`:`While loop exceeded default timeout of ${o}s`,c=`loop_${t.replace(/\./g,"_")}`;if(n.push(`${i}const ${c}_start = Date.now();`),n.push(`${i}const ${c}_timeout = ${s};`),n.push(`${i}const ${c}_check = () => {`),n.push(`${i} if (Date.now() - ${c}_start > ${c}_timeout) {`),n.push(`${i} throw new Error('${l}');`),n.push(`${i} }`),n.push(`${i} return true;`),n.push(`${i}};`),r.condition.type==="JS_CODE")n.push(`${i}while (${c}_check() && (${r.condition.expression})) {`);else{n.push(`${i}// AI Loop Condition: ${Ao(r.condition.expression)}`);let p=JSON.stringify(r.condition.expression);n.push(`${i}while (${c}_check() && await agent.evaluate(page, ${p}, "${t}")) {`)}let u=hs(r.body,e+1,a,`${t}.body`);return n.push(...u),n.push(`${i}}`),n}var ck={name:"shiplightai",version:"0.1.
|
|
4701
|
+
`);for(let i of a)t.push(` ${i}`);return t.push("}"),t});rt("function",(r,e,t)=>{let a=r.action_data?.kwargs||{},i=a.functionName;if(i&&i.includes("#")){let[s,o]=i.split("#");if(s&&o){let l=s.replace(/\.(ts|js|mjs)$/,""),c=`import { ${o} } from '${l}';`;t?.imports?.add(c);let u={...a,functionName:o},p=sk(u);return p?[p.endsWith(";")?p:`${p};`]:["// Skipping function: invalid export pattern"]}}let n=sk(a);return n?[n.endsWith(";")?n:`${n};`]:["// Skipping function: missing functionName"]});rt("generate_2fa_code",r=>{let e=r.action_data?.kwargs?.otp_secret_key||"";return['await agent.execAction("generate_2fa_code", page, {',` action_data: { kwargs: { otp_secret_key: ${JSON.stringify(e)} } },`,"});"]});rt("upload_file",r=>{let e=r.action_data?.kwargs||{},t=[],a={};return e.paths?a.paths=e.paths:e.path&&(a.path=e.path),e.use_file_input&&(a.use_file_input=!0),t.push(`action_data: { kwargs: ${JSON.stringify(a)} }`),r.locator?t.push(`locator: ${JSON.stringify(r.locator)}`):r.xpath&&t.push(`xpath: ${JSON.stringify(r.xpath)}`),r.frame_path&&r.frame_path.length>0&&t.push(`frame_path: ${JSON.stringify(r.frame_path)}`),['await agent.execAction("upload_file", page, {',...t.map(i=>` ${i},`),"});"]});rt("wait_for_download_complete",r=>['await agent.execAction("wait_for_download_complete", page, {',` action_data: { kwargs: { timeout_seconds: ${r.action_data?.kwargs?.timeout_seconds||10} } },`,"});"]);rt("switch_tab",r=>['await agent.execAction("switch_tab", page, {',` action_data: { kwargs: { page_id: ${r.action_data?.kwargs?.page_id??0} } },`,"});"]);rt("close_tab",r=>{let e=r.action_data?.kwargs?.page_id;return e=e??r.action_data?.kwargs?.index,['await agent.execAction("close_tab", page, {',` action_data: { kwargs: { page_id: ${e} } },`,"});"]});rt("set_date_for_native_date_picker",r=>{let e=r.action_data?.kwargs?.date??"",t=[];return t.push(`action_data: { kwargs: { date: ${JSON.stringify(e)} } }`),r.locator?t.push(`locator: ${JSON.stringify(r.locator)}`):r.xpath&&t.push(`xpath: ${JSON.stringify(r.xpath)}`),r.frame_path&&r.frame_path.length>0&&t.push(`frame_path: ${JSON.stringify(r.frame_path)}`),['await agent.execAction("set_date_for_native_date_picker", page, {',...t.map(a=>` ${a},`),"});"]});rt("done",()=>["// Done - no action needed"]);function sk(r){let e=r.functionName;if(!e)return null;let t={},a=r.parameterNames||[],i=r.parameterValues||r.args||[];if(a.forEach((l,c)=>{c<i.length&&(t[l]=i[c])}),Object.keys(t).length===0)return`await ${e}()`;let n=["page","testContext","request","agentServices"],s=["undefined","null","true","false"],o=Object.entries(t).map(([l,c])=>{let u=String(c);return c==null?"undefined":n.includes(l)&&n.includes(u)||s.includes(u)||/^-?\d+(\.\d+)?$/.test(u)?u:u.startsWith("$")?`agent.agentServices.readVariable('${u.substring(1)}')`:`"${u}"`});return`await ${e}(${o.join(", ")})`}function hs(r,e,t,a="main"){let i=[];for(let n=0;n<r.length;n++){let s=r[n],o=`${a}.${n}`,l=WJ(s,e,o,t);l.length>0&&(i.push(...l),n<r.length-1&&i.push(""))}return i}function WJ(r,e,t,a){let i=" ".repeat(e);switch(r.type){case"DRAFT":return zJ(r,e,t);case"ACTION":return GJ(r,e,t,a);case"STEP":return KJ(r,e,t,a);case"IF_ELSE":return JJ(r,e,t,a);case"WHILE_LOOP":return XJ(r,e,t,a);default:return[`${i}// Unknown statement type: ${r.type}`]}}function zJ(r,e,t){let a=" ".repeat(e),i=r.description?.trim()||"";if(!i)return[`${a}// ${t}: Skipping - no description`];let n=JSON.stringify(i);return[`${a}// ${t}: ${Ao(i)}`,`${a}page = agent.agentServices.validatePage(page);`,`${a}await agent.run(page, ${n}, '${t}');`]}function GJ(r,e,t,a){let i=" ".repeat(e),n=r.description,s=r.uid,l=a.actionEntityStore?.entries[r.uid]?.action_entity??r.action_entity;if(!l){if(!n)return[`${i}// ${t}: Skipping - no description`];let M=JSON.stringify(n),N=!!r.use_pure_vision;return[`${i}// ${t}: ${Ao(n)}`,`${i}page = agent.agentServices.validatePage(page);`,`${i}await agent.execute(page, ${M}, '${t}', ${N});`]}let c=r.locator?{...l,locator:r.locator}:l;n&&n!==c.action_description&&(c={...c,action_description:n});let u=c.action_data?.action_name||"",p=c.action_description||"",f=lk(u);if(!f)return[`${i}// ${t}: Unknown action: ${u}`];let m={imports:a.imports},g=f(c,t,m);if(ik(c))return[`${i}// ${t}: ${Ao(p)}`,`${i}page = agent.agentServices.validatePage(page);`,...g.map(M=>`${i}${M}`)];let y=JSON.stringify(p),v=g.map(M=>`${i} ${M}`),_=nk(c),S=s?`'${s}'`:"undefined";return[`${i}// ${t}: ${Ao(p)}`,`${i}page = agent.agentServices.validatePage(page);`,`${i}await agent.step(page, async () => {`,...v,`${i}}, ${y}, '${t}', ${S}, ${_});`]}function KJ(r,e,t,a){let i=" ".repeat(e),n=[];r.description&&r.description.trim()&&n.push(`${i}// Step: ${Ao(r.description)}`);let s=hs(r.statements,e,a,t);return n.push(...s),n}function JJ(r,e,t,a){let i=" ".repeat(e),n=[];if(n.push(`${i}// ${t}: Conditional check`),r.condition.type==="JS_CODE")n.push(`${i}if (${r.condition.expression}) {`);else{n.push(`${i}// AI Condition: ${Ao(r.condition.expression)}`);let o=JSON.stringify(r.condition.expression);n.push(`${i}if (await agent.evaluate(page, ${o}, "${t}")) {`)}let s=hs(r.then,e+1,a,`${t}.then`);if(n.push(...s),r.else&&r.else.length>0){n.push(`${i}} else {`);let o=hs(r.else,e+1,a,`${t}.else`);n.push(...o)}return n.push(`${i}}`),n}function XJ(r,e,t,a){let i=" ".repeat(e),n=[];n.push(`${i}// ${t}: Loop`);let s=r.timeout_ms??U0,o=s/1e3,l=r.timeout_ms?`While loop exceeded timeout of ${o}s`:`While loop exceeded default timeout of ${o}s`,c=`loop_${t.replace(/\./g,"_")}`;if(n.push(`${i}const ${c}_start = Date.now();`),n.push(`${i}const ${c}_timeout = ${s};`),n.push(`${i}const ${c}_check = () => {`),n.push(`${i} if (Date.now() - ${c}_start > ${c}_timeout) {`),n.push(`${i} throw new Error('${l}');`),n.push(`${i} }`),n.push(`${i} return true;`),n.push(`${i}};`),r.condition.type==="JS_CODE")n.push(`${i}while (${c}_check() && (${r.condition.expression})) {`);else{n.push(`${i}// AI Loop Condition: ${Ao(r.condition.expression)}`);let p=JSON.stringify(r.condition.expression);n.push(`${i}while (${c}_check() && await agent.evaluate(page, ${p}, "${t}")) {`)}let u=hs(r.body,e+1,a,`${t}.body`);return n.push(...u),n.push(`${i}}`),n}var ck={name:"shiplightai",version:"0.1.10",type:"module",description:"Shiplight CLI for running and debugging .test.yaml files",main:"dist/index.js",types:"dist/index.d.ts",bin:{shiplight:"dist/cli.js"},exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/cjs/index.cjs",default:"./dist/index.js"},"./fixture":{types:"./dist/fixture.d.ts",import:"./dist/fixture.js",require:"./dist/cjs/fixture.cjs",default:"./dist/fixture.js"}},files:["dist","README.md"],publishConfig:{registry:"https://registry.npmjs.org",access:"public"},scripts:{build:"tsup","build:cli":"tsup",clean:"rm -rf dist",dev:"tsup --watch",test:"playwright test",typecheck:"tsc --noEmit"},dependencies:{"@babel/plugin-transform-typescript":"^7.27.0","@babel/preset-env":"^7.26.9","@babel/preset-typescript":"^7.27.0","@anthropic-ai/claude-agent-sdk":"^0.1.72","@modelcontextprotocol/sdk":"^0.5.0",axios:"^1.6.0",dotenv:"^16.0.3",express:"^4.21.0",glob:"^13.0.0",open:"^10.1.0",sharp:"^0.34.5",uuid:"^11.1.0",yaml:"^2.8.0",zod:"^3.22.0","zod-to-json-schema":"^3.24.6"},devDependencies:{"@playwright/test":"1.55.0","@types/express":"^4.17.21","@types/node":"^24.0.0",copilot3:"workspace:*","mcp-tools":"workspace:*","sdk-core":"workspace:*","sdk-internal":"workspace:*","shiplight-types":"workspace:*",tsup:"^8.3.5",typescript:"5.5.4","web-session":"workspace:*"},peerDependencies:{"@playwright/test":">=1.40.0"},engines:{node:">=22.0.0"},keywords:["playwright","yaml","testing","automation","ai","shiplight","mcp"],author:"Shiplight",license:"MIT"};var W0=ck.version;function uk(r,e){let t=[];t.push(`// @generated by shiplightai v${W0}`),t.push(...ZJ()),t.push(""),e?.use&&Object.keys(e.use).length>0&&(t.push(`test.use(${JSON.stringify(e.use,null,2)});`),t.push(""));let a=e?.testName||r.goal||"Generated test",i=e?.tags&&e.tags.length>0?e.tags.map(c=>`@${c}`).join(" ")+" ":"";t.push(`test('${i}${V0(a)}', async ({ page, agent }) => {`);let n=e?.startingUrl||r.url;if(n){let c;try{c=new URL(n).pathname}catch{c=n}t.push(` await page.goto('${V0(c)}', { waitUntil: 'domcontentloaded' });`),t.push("")}let s=new Set,o={imports:s};if(r.teardown&&r.teardown.length>0){if(t.push(" try {"),r.statements&&r.statements.length>0){t.push(" // Test steps");let u=hs(r.statements,2,o);t.push(...u)}t.push(" } finally {"),t.push(" // Teardown");let c=hs(r.teardown,2,o,"teardown");t.push(...c),t.push(" }")}else if(r.statements&&r.statements.length>0){t.push(" // Test steps");let c=hs(r.statements,1,o);t.push(...c)}if(t.push("});"),s.size>0){let c=0;for(let p=0;p<t.length;p++)t[p].startsWith("import ")&&(c=p+1);let u=Array.from(s);t.splice(c,0,...u)}return t.join(`
|
|
4702
4702
|
`)}function ZJ(){return["import { test, expect } from 'shiplightai/fixture';"]}function dk(r){try{return(0,fs.statSync)(r).mtimeMs}catch{return 0}}var QJ=`// @generated by shiplightai v${W0}`;function eX(r,e){if(!(0,fs.existsSync)(r)||(0,fs.readFileSync)(r,"utf-8").split(`
|
|
4703
4703
|
`,1)[0]!==QJ)return!1;let a=dk(r);for(let i of e)if(dk(i)>a)return!1;return!0}function fk(r){let e=(0,hk.globSync)("**/*.test.yaml",{cwd:r.cwd,ignore:["**/node_modules/**"]}),t=[];for(let a of e){let i=(0,pk.resolve)(r.cwd,a),n=i.replace(/\.test\.yaml$/,".yaml.spec.ts"),s=(0,fs.readFileSync)(i,"utf-8");try{let{testFlow:o,name:l,tags:c,use:u,referencedTemplatePaths:p}=ak(s,i);if(eX(n,[i,...p]))continue;let f=uk(o,{testName:l,tags:c,use:u});(0,fs.writeFileSync)(n,f)}catch(o){console.error(`[shiplight] Failed to transpile ${a}:`,o),t.push({file:a,error:o})}}if(t.length>0)throw new Error(`[shiplight] Transpilation failed for ${t.length} file(s):
|
|
4704
4704
|
`+t.map(a=>` - ${a.file}`).join(`
|
package/dist/cli.js
CHANGED
|
@@ -8082,12 +8082,14 @@ Returns the Playwright locator, xpath, frame path, tag name, and text content
|
|
|
8082
8082
|
for the element at the given index.
|
|
8083
8083
|
|
|
8084
8084
|
Use this to collect locator information for building test flows without
|
|
8085
|
-
actually interacting with the element.`};Obe=null,Mbe=null;$be=class fR{constructor(t){this.backend=t}static navigateTool={...ujr,inputSchema:lS(Zlt,{$refStrategy:"none"})};async navigate(t){let r=Zlt.parse(t),n=await ljr(this.backend,r);return JSON.stringify(n)}static get actTool(){return wjr()}async act(t){let r=cjr.parse(t),n=await Hbe(this.backend,r);return JSON.stringify(n)}static updateVariablesTool={...djr,inputSchema:lS(ect,{$refStrategy:"none"})};async updateVariables(t){let r=ect.parse(t),n=pjr(this.backend,r);return JSON.stringify(n)}static clearExecutionHistoryTool={...fjr,inputSchema:lS(tct,{$refStrategy:"none"})};async clearExecutionHistory(t){let r=tct.parse(t),n=hjr(this.backend,r);return JSON.stringify(n)}static getPageInfoTool={...mjr,inputSchema:lS(rct,{$refStrategy:"none"})};async getPageInfo(t){let r=rct.parse(t),n=await Ajr(this.backend,r);return JSON.stringify(n)}static getDomTool={...ure,inputSchema:lS(nct,{$refStrategy:"none"})};async getDom(t){let r=nct.parse(t),n=await Gbe(this.backend,r);return JSON.stringify(n)}static takeScreenshotTool={...gjr,inputSchema:lS(ict,{$refStrategy:"none"})};async takeScreenshot(t){let r=ict.parse(t),n=await Ejr(this.backend,r);return JSON.stringify(n)}static getLocatorTool={...yjr,inputSchema:lS(sct,{$refStrategy:"none"})};async getLocator(t){let r=sct.parse(t),n=await Cjr(this.backend,r);return JSON.stringify(n)}static get toolDefinitions(){return[fR.navigateTool,fR.getPageInfoTool,fR.getDomTool,fR.takeScreenshotTool,fR.actTool,fR.getLocatorTool]}},Vbe=class ore{constructor(t){this.backend=t}static getConsoleLogsTool={name:"get_browser_console_logs",description:"Get console logs from the browser (errors, warnings, etc.)",inputSchema:Lbe(Yu.object({session_id:Yu.string().describe("Browser session ID"),since_timestamp:Yu.number().optional().describe("Only return logs after this timestamp"),log_types:Yu.array(Yu.string()).optional().describe("Filter by log types (e.g., ['error', 'warning'])")}),{$refStrategy:"none"})};async getConsoleLogs(t){let{session_id:r,since_timestamp:n,log_types:i}=Yu.object({session_id:Yu.string(),since_timestamp:Yu.number().optional(),log_types:Yu.array(Yu.string()).optional()}).parse(t);if(!this.backend.getSession(r))throw new Error(`Session ${r} not found`);let o=this.backend.getConsoleLogs(r,{sinceTimestamp:n,logTypes:i});return JSON.stringify({session_id:r,log_count:o.length,logs:o.slice(-100)})}static getNetworkLogsTool={name:"get_browser_network_logs",description:"Get network request logs from the browser",inputSchema:Lbe(Yu.object({session_id:Yu.string().describe("Browser session ID"),since_timestamp:Yu.number().optional().describe("Only return logs after this timestamp"),status_filter:Yu.enum(["errors","success"]).optional().describe("Filter by HTTP status (errors: 4xx/5xx, success: 2xx)")}),{$refStrategy:"none"})};async getNetworkLogs(t){let{session_id:r,since_timestamp:n,status_filter:i}=Yu.object({session_id:Yu.string(),since_timestamp:Yu.number().optional(),status_filter:Yu.enum(["errors","success"]).optional()}).parse(t);if(!this.backend.getSession(r))throw new Error(`Session ${r} not found`);let o=this.backend.getNetworkLogs(r,{sinceTimestamp:n,statusFilter:i});return JSON.stringify({session_id:r,log_count:o.length,logs:o.slice(-100)})}static clearLogsTool={name:"clear_logs",description:"Clear console and network logs for a session",inputSchema:Lbe(Yu.object({session_id:Yu.string().describe("Browser session ID")}),{$refStrategy:"none"})};async clearLogs(t){let{session_id:r}=Yu.object({session_id:Yu.string()}).parse(t);if(!this.backend.getSession(r))throw new Error(`Session ${r} not found`);return this.backend.clearLogs(r),JSON.stringify({session_id:r,message:"Logs cleared successfully"})}static getLocalArtifactTool={name:"get_local_artifact",description:"Get a local artifact file (screenshot, DOM snapshot, etc.) from a live browser session. Only accepts local file paths, NOT S3 URIs. For test run artifacts, use get_step_artifacts instead.",inputSchema:Lbe(Yu.object({path:Yu.string().describe("Artifact file path from previous tool output"),return_format:Yu.enum(["base64","text"]).optional().describe("Return format (auto-detected if not specified)")}),{$refStrategy:"none"})};async getLocalArtifact(t){let{path:r}=Yu.object({path:Yu.string(),return_format:Yu.enum(["base64","text"]).optional()}).parse(t),n=await this.backend.getLocalArtifact(r);return JSON.stringify({path:r,format:n.format,size:n.size,data:n.data})}static toolDefinitions=[ore.getConsoleLogsTool,ore.getNetworkLogsTool,ore.clearLogsTool,ore.getLocalArtifactTool]},Wbe=class cS{constructor(t){this.apiClient=t}requireApi(t){if(!this.apiClient)throw new Error(SY(t));return this.apiClient}static listEnvironmentsTool={name:"list_environments",description:"List all testing environments (staging, production, etc.)",inputSchema:dR(to.object({}),{$refStrategy:"none"})};async listEnvironments(t){let n=await this.requireApi("list_environments").listEnvironments();return JSON.stringify({environments:n.map(i=>({id:i.id,name:i.name,url:i.url}))})}static listTestAccountsTool={name:"list_test_accounts",description:"List test accounts for a specific environment. Requires either environment_id or environment_url.",inputSchema:dR(to.object({environment_id:to.number().optional().describe("Environment ID to filter by"),environment_url:to.string().optional().describe("Environment URL to filter by (alternative to environment_id)")}),{$refStrategy:"none"})};async listTestAccounts(t){let r=this.requireApi("list_test_accounts"),{environment_id:n,environment_url:i}=to.object({environment_id:to.number().optional(),environment_url:to.string().optional()}).parse(t),s=await r.listTestAccounts(n,i);return JSON.stringify({test_accounts:s.map(o=>({id:o.id,name:o.name,username:o.username,environment_id:o.environmentId||o.environment_id}))})}static getTestAccountTool={name:"get_test_account",description:"Get a specific test account by ID",inputSchema:dR(to.object({test_account_id:to.number().describe("Test account ID")}),{$refStrategy:"none"})};async getTestAccount(t){let r=this.requireApi("get_test_account"),{test_account_id:n}=to.object({test_account_id:to.number()}).parse(t),i=await r.getTestAccount(n);return JSON.stringify({test_account:i})}static createTestAccountTool={name:"create_test_account",description:"Create a new test account",inputSchema:dR(to.object({environment_id:to.number().describe("Environment ID"),username:to.string().describe("Username"),password:to.string().describe("Password"),login_config:to.any().optional().describe("Login configuration")}),{$refStrategy:"none"})};async createTestAccount(t){let r=this.requireApi("create_test_account"),{environment_id:n,username:i,password:s,login_config:o}=to.object({environment_id:to.number(),username:to.string(),password:to.string(),login_config:to.any().optional()}).parse(t),a=await r.createTestAccount({environmentId:n,username:i,password:s,loginConfig:o});return JSON.stringify({test_account_id:a.id,message:"Test account created successfully"})}static listFoldersTool={name:"list_folders",description:"List test case folders",inputSchema:dR(to.object({parent_id:to.number().nullable().optional().describe("Parent folder ID, null for root"),search:to.string().optional().describe("Search query")}),{$refStrategy:"none"})};async listFolders(t){let r=this.requireApi("list_folders"),{parent_id:n,search:i}=to.object({parent_id:to.number().nullable().optional(),search:to.string().optional()}).parse(t),s=await r.listFolders(n,i);return JSON.stringify({folders:s.map(o=>({id:o.id,name:o.name,parent_id:o.parentId||o.parent_id,path:o.pathIds}))})}static createFolderTool={name:"create_folder",description:"Create a new test case folder",inputSchema:dR(to.object({name:to.string().describe("Folder name"),description:to.string().optional().describe("Folder description"),parent_id:to.number().nullable().optional().describe("Parent folder ID")}),{$refStrategy:"none"})};async createFolder(t){let r=this.requireApi("create_folder"),{name:n,description:i,parent_id:s}=to.object({name:to.string(),description:to.string().optional(),parent_id:to.number().nullable().optional()}).parse(t),o=await r.createFolder({name:n,description:i,parentId:s});return JSON.stringify({folder_id:o.id,message:"Folder created successfully"})}static getFolderTool={name:"get_folder",description:"Get folder details with full hierarchical path",inputSchema:dR(to.object({folder_id:to.number().describe("Folder ID")}),{$refStrategy:"none"})};async getFolder(t){let r=this.requireApi("get_folder"),{folder_id:n}=to.object({folder_id:to.number()}).parse(t),i=await r.getFolder(n);return JSON.stringify({folder:i})}static toolDefinitions=[cS.listEnvironmentsTool,cS.listTestAccountsTool,cS.getTestAccountTool,cS.createTestAccountTool,cS.listFoldersTool,cS.createFolderTool,cS.getFolderTool]};Ybe=class Ube{constructor(t){this.apiClient=t}requireApi(t){if(!this.apiClient)throw new Error(SY(t));return this.apiClient}parseFlowInput(t){return typeof t=="string"?Ey.parse(tT(t)):Ey.parse(t)}static createTestCaseTool={name:"create_test_case",description:"Create a test case from a flow (test flow JSON object or YAML string). IMPORTANT: Read 'shiplight://schemas/testflow-json-v1.2.0' for the required flow format. Accepts a test flow as JSON object or YAML string. Include action_entity with locator/xpath on ACTION statements when available - this speeds up test execution.",inputSchema:ire(Gi.object({flow:Gi.union([Gi.string(),Gi.any()]).describe("Test flow as JSON object or YAML string"),folder_id:Gi.number().optional().describe("Folder ID"),name:Gi.string().optional().describe("Test case name"),environment_id:Gi.number().optional().describe("Environment ID for test execution"),test_account_id:Gi.number().optional().describe("Optional test account ID for login")}),{$refStrategy:"none"})};async createTestCase(t){let r=this.requireApi("create_test_case"),{flow:n,folder_id:i,name:s,environment_id:o,test_account_id:a}=Gi.object({flow:Gi.any(),folder_id:Gi.number().optional(),name:Gi.string().optional(),environment_id:Gi.number().optional(),test_account_id:Gi.number().optional()}).parse(t),u;try{u=this.parseFlowInput(n)}catch(m){throw m instanceof Gi.ZodError?new Error(`Invalid TestFlow schema: ${JSON.stringify(m.errors)}`):m}let l={...u,version:"1.2.0",statements:this.regenerateUids(u.statements),teardown:u.teardown?this.regenerateUids(u.teardown):void 0},c=o;if(c===void 0&&u.url)try{let m=await r.listEnvironments(),g=Y6s(u.url,m);g&&(c=g.id)}catch{}let d;c!==void 0&&(d=[{environment_id:c,test_account_group:a?{type:"Specific",account_ids:[a]}:{type:"None",account_ids:[]},path:""}]);let p=await r.createTestCase({title:s||u.goal,testFlow:l,folderId:i,environmentConfigs:d}),f=this.computeFlowStats(u.statements,u.teardown);return JSON.stringify({...p,environment_id:c??null,test_account_id:a??null,yaml:eT(u,{test_case_id:p.test_case_id}),flow_stats:f})}static updateTestCaseTool={name:"update_test_case",description:"Update a test case from a flow (test flow JSON object or YAML string). IMPORTANT: Read 'shiplight://schemas/testflow-json-v1.2.0' for the required flow format. Accepts a test flow as JSON object or YAML string. Include action_entity with locator/xpath on ACTION statements when available - this speeds up test execution.",inputSchema:ire(Gi.object({test_case_id:Gi.number().describe("Test case ID"),flow:Gi.union([Gi.string(),Gi.any()]).describe("Test flow as JSON object or YAML string"),title:Gi.string().optional().describe("New title")}),{$refStrategy:"none"})};async updateTestCase(t){let r=this.requireApi("update_test_case"),{test_case_id:n,flow:i,title:s}=Gi.object({test_case_id:Gi.number(),flow:Gi.any(),title:Gi.string().optional()}).parse(t),o;try{o=this.parseFlowInput(i)}catch(c){throw c instanceof Gi.ZodError?new Error(`Invalid TestFlow schema: ${JSON.stringify(c.errors)}`):c}let a={...o,version:"1.2.0",statements:this.regenerateUids(o.statements),teardown:o.teardown?this.regenerateUids(o.teardown):void 0},u=await r.updateTestCase(n,{title:s||o.goal,testFlow:a}),l=this.computeFlowStats(o.statements,o.teardown);return JSON.stringify({...u,flow_stats:l})}static getTestCaseTool={name:"get_test_case",description:"Get a test case by ID. Use output_format 'yaml' to get the flow as a YAML string.",inputSchema:ire(Gi.object({test_case_id:Gi.number().describe("Test case ID"),output_format:Gi.enum(["json","yaml"]).optional().describe("Output format for the test flow (default: json)")}),{$refStrategy:"none"})};async getTestCase(t){let r=this.requireApi("get_test_case"),{test_case_id:n,output_format:i}=Gi.object({test_case_id:Gi.number(),output_format:Gi.enum(["json","yaml"]).optional()}).parse(t),s=await r.getTestCase(n);if(i==="yaml"&&s.test_flow){let o=eT(s.test_flow,{test_case_id:s.id});return JSON.stringify({test_case:{id:s.id,title:s.title,folder_id:s.folder_id},yaml:o})}return JSON.stringify({test_case:s})}static runTestCaseTool={name:"run_test_case",description:"Trigger cloud execution of a test case. Returns a test_run_id that can be used with get_test_run_details to poll for completion and results.",inputSchema:ire(Gi.object({test_case_id:Gi.number().describe("Test case ID to run"),environment_id:Gi.number().optional().describe("Environment ID to run against (uses test case default if not specified)")}),{$refStrategy:"none"})};async runTestCase(t){let r=this.requireApi("run_test_case"),{test_case_id:n,environment_id:i}=Gi.object({test_case_id:Gi.number(),environment_id:Gi.number().optional()}).parse(t),s=await r.runTestCase(n,i);return JSON.stringify({test_run_id:s.id,status:s.status,result:s.result,test_case_result_ids:s.test_case_result_ids,message:`Test run ${s.id} triggered. Use get_test_run_details(${s.id}) to check status and results.`})}static saveTestCaseTool={name:"save_test_case",description:"Save a test case from YAML \u2014 automatically creates or updates based on embedded test_case_id metadata. If the YAML contains a test_case_id field, updates that test case; otherwise creates a new one. Returns YAML with test_case_id embedded so you can write it back to the local file for future updates.",inputSchema:ire(Gi.object({yaml:Gi.string().describe("YAML test flow string"),folder_id:Gi.number().optional().describe("Folder ID (only used on create)"),name:Gi.string().optional().describe("Test case name (defaults to flow goal)"),environment_id:Gi.number().optional().describe("Environment ID for test execution (only used on create)"),test_account_id:Gi.number().optional().describe("Optional test account ID for login (only used on create)")}),{$refStrategy:"none"})};async saveTestCase(t){let{yaml:r,folder_id:n,name:i,environment_id:s,test_account_id:o}=Gi.object({yaml:Gi.string(),folder_id:Gi.number().optional(),name:Gi.string().optional(),environment_id:Gi.number().optional(),test_account_id:Gi.number().optional()}).parse(t),a=NY(r);if(a.test_case_id!==void 0){let u=await this.updateTestCase({test_case_id:a.test_case_id,flow:r,title:i}),l=JSON.parse(u),c=this.parseFlowInput(r);return JSON.stringify({...l,yaml:eT(c,{test_case_id:a.test_case_id})})}else return await this.createTestCase({flow:r,folder_id:n,name:i,environment_id:s,test_account_id:o})}computeFlowStats(t,r){let n={total:0,drafts:0,actions_with_locator:0,actions_without_locator:0,verifies:0,steps:0},i=o=>{for(let a of o)if(n.total++,a.type==="DRAFT")n.drafts++;else if(a.type==="ACTION"){let u=a.action_entity;u?.action_data?.action_name==="verify"?n.verifies++:u?.locator||u?.xpath?n.actions_with_locator++:n.actions_without_locator++}else a.type==="STEP"?(n.steps++,a.statements&&i(a.statements)):a.type==="IF_ELSE"?(a.then&&i(a.then),a.else&&i(a.else)):a.type==="WHILE_LOOP"&&a.body&&i(a.body)};i(t),r&&i(r);let s={...n};return n.drafts>0&&n.actions_with_locator===0?s.hint=`${n.drafts} DRAFT statements with no action_entities (~10-15s each at runtime). Include action_entities with locator/xpath for ~1s deterministic replay.`:n.drafts>0&&(s.hint=`${n.drafts} DRAFT statements remaining (~10-15s each at runtime vs ~1s with action_entities). Consider converting DRAFTs that don't depend on runtime state to ACTIONs.`),s}regenerateUids(t){return t.map(r=>{let n={...r,uid:W6s()};return r.type==="STEP"&&r.statements?n.statements=this.regenerateUids(r.statements):r.type==="IF_ELSE"?(r.then&&(n.then=this.regenerateUids(r.then)),r.else&&(n.else=this.regenerateUids(r.else))):r.type==="WHILE_LOOP"&&r.body&&(n.body=this.regenerateUids(r.body)),n})}static toolDefinitions=[Ube.getTestCaseTool,Ube.runTestCaseTool,Ube.saveTestCaseTool]},K6s=3600*1e3,Qbe=qbe.join(z6s.tmpdir(),"shiplight-artifacts"),zbe=class Ej{constructor(t,r){this.apiClient=t,this.options=r||{}}options;requireApi(t){if(!this.apiClient)throw new Error(SY(t));return this.apiClient}getProxyUrl(t){if(!this.options.artifactProxyBaseUrl)return null;let r=t.match(/^s3:\/\/[^/]+\/(.+)$/);if(!r)return null;let n=r[1];return`${this.options.artifactProxyBaseUrl}/api/artifacts/${n}`}cleanupOldArtifacts(){try{if(!pR.existsSync(Qbe))return;let t=Date.now(),r=pR.readdirSync(Qbe,{withFileTypes:!0});for(let n of r){if(!n.isDirectory())continue;let i=qbe.join(Qbe,n.name);try{let s=pR.statSync(i);t-s.mtimeMs>K6s&&pR.rmSync(i,{recursive:!0,force:!0})}catch{}}}catch{}}static listTestRunsTool={name:"list_test_runs",description:"List test runs with optional filtering by test plan, trigger type, or result status",inputSchema:sre(la.object({test_plan_id:la.number().optional().describe("Filter by test plan ID"),trigger:la.string().optional().describe("Filter by trigger type (Manual, Scheduled, API, GITHUB_ACTION, Webhook, Automation)"),result:la.string().optional().describe("Filter by result (Passed, Failed, Pending, Skipped, Queued)"),limit:la.number().optional().describe("Maximum number of results (default 20)")}),{$refStrategy:"none"})};async listTestRuns(t){let r=this.requireApi("list_test_runs"),{test_plan_id:n,trigger:i,result:s,limit:o}=la.object({test_plan_id:la.number().optional(),trigger:la.string().optional(),result:la.string().optional(),limit:la.number().optional()}).parse(t),a=await r.listTestRuns({testPlanId:n,trigger:i,result:s,limit:o??20});return JSON.stringify({test_runs:a.map(u=>({id:u.id,status:u.status,result:u.result,trigger:u.trigger,start_time:u.startTime||u.start_time,end_time:u.endTime||u.end_time,duration_ms:u.duration,total_test_case_count:u.totalTestCaseCount||u.total_test_case_count,passed_test_case_count:u.passedTestCaseCount||u.passed_test_case_count,failed_test_case_count:u.failedTestCaseCount||u.failed_test_case_count,skipped_test_case_count:u.skippedTestCaseCount||u.skipped_test_case_count,test_plan_id:u.testPlanId||u.test_plan_id})),count:a.length})}static getTestRunDetailsTool={name:"get_test_run_details",description:"Get detailed information about a test run including all test case results",inputSchema:sre(la.object({test_run_id:la.number().describe("Test run ID")}),{$refStrategy:"none"})};async getTestRunDetails(t){let r=this.requireApi("get_test_run_details"),{test_run_id:n}=la.object({test_run_id:la.number()}).parse(t),i=await r.getTestRunDetails(n),s=i.testRun||i,o=i.testCaseResults||[];return JSON.stringify({test_run:{id:s.id,status:s.status,result:s.result,trigger:s.trigger,start_time:s.startTime||s.start_time,end_time:s.endTime||s.end_time,duration_ms:s.duration,total_test_case_count:s.totalTestCaseCount||s.total_test_case_count,passed_test_case_count:s.passedTestCaseCount||s.passed_test_case_count,failed_test_case_count:s.failedTestCaseCount||s.failed_test_case_count,skipped_test_case_count:s.skippedTestCaseCount||s.skipped_test_case_count,test_plan_id:s.testPlanId||s.test_plan_id},test_case_results:o.map(a=>({id:a.id,test_case_id:a.testCaseId||a.test_case_id,result:a.result,status:a.status,duration_ms:a.duration,environment_name:a.environmentName||a.environment_name,environment_url:a.environmentUrl||a.environment_url,error:a.error}))})}static getTestCaseResultTool={name:"get_test_case_result",description:"Get detailed test case result including status, duration, and error information",inputSchema:sre(la.object({test_case_result_id:la.number().describe("Test case result ID"),include_report:la.boolean().optional().describe("Include full report with step details (default false)")}),{$refStrategy:"none"})};async getTestCaseResult(t){let r=this.requireApi("get_test_case_result"),{test_case_result_id:n,include_report:i}=la.object({test_case_result_id:la.number(),include_report:la.boolean().optional()}).parse(t),s=await r.getTestCaseResult(n),o=Array.isArray(s.report)?s.report[0]:s.report,a=o?.resultJson||{},u=Object.entries(a),l=u.length,c=null;for(let p=0;p<u.length;p++){let[,f]=u[p];if(f.status==="failed"||f.status==="failure"){c=p;break}}let d={id:s.id,test_case_id:s.testCaseId||s.test_case_id,test_run_id:s.testRunId||s.test_run_id,result:s.result,status:s.status,start_time:s.startTime||s.start_time,end_time:s.endTime||s.end_time,duration_ms:s.duration,environment_name:s.environmentName||s.environment_name,environment_id:s.environmentId||s.environment_id,environment_url:s.environmentUrl||s.environment_url,device:s.device,total_steps:l,failed_step_index:c,video_s3_uri:s.video,trace_s3_uri:s.trace,report_s3_uri:s.reportS3Uri||s.report_s3_uri};if(i){let p=s.reportS3Uri||s.report_s3_uri,f=null;if(p&&r.getS3FileContents)try{let g=await r.getS3FileContents(p);if(g){let C=JSON.parse(g);f=Array.isArray(C)?C[0]:C}}catch(g){console.error("Failed to fetch S3 report, falling back to DB report:",g)}let m=f||o;d.report={stdout:m?.stdout,stderr:m?.stderr,consoleLogs:m?.consoleLogs,flaky:m?.flaky,error:m?.error,step_results:m?.resultJson,video_s3_uri:m?.videoS3Uri,trace_s3_uri:m?.traceS3Uri,source_s3_uri:m?.sourceS3Uri}}return JSON.stringify(d)}static getTestCaseResultStepsTool={name:"get_test_case_result_steps",description:"Get step-by-step execution details for a range of steps. Use get_test_case_result first to find the failed_step_index, then request the range you need.",inputSchema:sre(la.object({test_case_result_id:la.number().describe("Test case result ID"),from_step:la.number().describe("Start step index (0-based, inclusive)"),to_step:la.number().describe("End step index (0-based, inclusive)")}),{$refStrategy:"none"})};async getTestCaseResultSteps(t){let r=this.requireApi("get_test_case_result_steps"),{test_case_result_id:n,from_step:i,to_step:s}=la.object({test_case_result_id:la.number(),from_step:la.number(),to_step:la.number()}).parse(t),o=await r.getTestCaseResult(n),u=(Array.isArray(o.report)?o.report[0]:o.report)?.resultJson||{},l=Object.entries(u).map(([d,p])=>{let f=p.artifacts?.[0];return{step_id:d,description:p.description,status:p.status,message:p.message,start_time:p.startTime,duration_ms:p.duration,has_screenshot:!!(f?.screenshot_s3_path||p.screenshotS3Uri),has_messages:!!f?.messages_s3_path,has_response:!!f?.response_s3_path,has_system_prompt:!!f?.system_prompt_s3_path}}),c=l.slice(i,s+1);return JSON.stringify({test_case_result_id:n,total_steps:l.length,from_step:i,to_step:s,steps:c})}static getStepArtifactsTool={name:"get_step_artifacts",description:"Download all artifacts for a step to local files. Returns local file paths - client decides which to read.",inputSchema:sre(la.object({test_case_result_id:la.number().describe("Test case result ID"),step_index:la.number().describe("Step index (0-based)")}),{$refStrategy:"none"})};async getStepArtifacts(t){let r=this.requireApi("get_step_artifacts"),{test_case_result_id:n,step_index:i}=la.object({test_case_result_id:la.number(),step_index:la.number()}).parse(t),s=await r.getTestCaseResult(n),a=(Array.isArray(s.report)?s.report[0]:s.report)?.resultJson||{},u=Object.values(a);if(i<0||i>=u.length)throw new Error(`Step index ${i} out of range (0-${u.length-1})`);let l=u[i],c=l.artifacts?.[0],d={},p=c?.screenshot_s3_path||l.screenshotS3Uri;p&&(d.screenshot={s3Uri:p,filename:`step_${i}_screenshot.png`}),c?.messages_s3_path&&(d.messages={s3Uri:c.messages_s3_path,filename:`step_${i}_messages.json`}),c?.response_s3_path&&(d.response={s3Uri:c.response_s3_path,filename:`step_${i}_response.txt`}),c?.system_prompt_s3_path&&(d.system_prompt={s3Uri:c.system_prompt_s3_path,filename:`step_${i}_system_prompt.txt`});let f=!!r.getS3FileContents,m=!!this.options.artifactProxyBaseUrl,g=!!r.getArtifactPresignedUrl;if(!f&&!m&&!g)return JSON.stringify({test_case_result_id:n,step_index:i,error:"Artifact download not supported by this API client",s3_uris:Object.fromEntries(Object.entries(d).map(([x,{s3Uri:D}])=>[x,D]))});this.cleanupOldArtifacts();let C=qbe.join(Qbe,`tcr-${n}`);pR.existsSync(C)||pR.mkdirSync(C,{recursive:!0});let b={},w=!!r.downloadArtifact;return await Promise.all(Object.entries(d).map(async([x,{s3Uri:D,filename:I}])=>{try{let k=null;if(!k&&f){let U=await r.getS3FileContents(D);U&&(k=U)}if(!k&&m&&w){let U=this.getProxyUrl(D);U&&(k=await r.downloadArtifact(U))}if(!k&&g){let U=await r.getArtifactPresignedUrl(D);if(U){let R=await J6s.get(U,{responseType:"arraybuffer"});k=Buffer.from(R.data)}}if(!k){b[x]=null;return}let P=qbe.join(C,I);pR.writeFileSync(P,k),b[x]=P}catch(k){console.error(`Failed to download ${x} artifact:`,k),b[x]=null}})),JSON.stringify({test_case_result_id:n,step_index:i,screenshot_path:b.screenshot,messages_path:b.messages,response_path:b.response,system_prompt_path:b.system_prompt})}static toolDefinitions=[Ej.listTestRunsTool,Ej.getTestRunDetailsTool,Ej.getTestCaseResultTool,Ej.getTestCaseResultStepsTool,Ej.getStepArtifactsTool]},Jbe=class vjr{static initLocalProjectTool={name:"init_local_project",description:"Scaffold a ready-to-run local test project with Playwright and shiplightai. Generates package.json, playwright.config.ts, .gitignore, .env.example, and a tests/ directory. IMPORTANT: You MUST call this tool before writing any .test.yaml files \u2014 it sets up the project structure required to run them. After scaffolding, the user runs: npm install && npx playwright install && npx playwright test",inputSchema:ajr(Rg.object({project_path:Rg.string().describe("Absolute path where the project should be created")}),{$refStrategy:"none"})};async initLocalProject(t){let{project_path:r}=Rg.object({project_path:Rg.string()}).parse(t),n=a7.join(r,"tests");Xw.mkdirSync(n,{recursive:!0});let i={name:"my-shiplight-tests",type:"module",scripts:{test:"playwright test","test:headed":"playwright test --headed"},dependencies:{"@playwright/test":"1.55.0",playwright:"1.55.0",shiplightai:"^0.1.0",dotenv:"^16.4.7"}};Xw.writeFileSync(a7.join(r,"package.json"),JSON.stringify(i,null,2)+`
|
|
8086
|
-
`),Xw.writeFileSync(a7.join(r,"playwright.config.ts"),`import '
|
|
8087
|
-
import {
|
|
8085
|
+
actually interacting with the element.`};Obe=null,Mbe=null;$be=class fR{constructor(t){this.backend=t}static navigateTool={...ujr,inputSchema:lS(Zlt,{$refStrategy:"none"})};async navigate(t){let r=Zlt.parse(t),n=await ljr(this.backend,r);return JSON.stringify(n)}static get actTool(){return wjr()}async act(t){let r=cjr.parse(t),n=await Hbe(this.backend,r);return JSON.stringify(n)}static updateVariablesTool={...djr,inputSchema:lS(ect,{$refStrategy:"none"})};async updateVariables(t){let r=ect.parse(t),n=pjr(this.backend,r);return JSON.stringify(n)}static clearExecutionHistoryTool={...fjr,inputSchema:lS(tct,{$refStrategy:"none"})};async clearExecutionHistory(t){let r=tct.parse(t),n=hjr(this.backend,r);return JSON.stringify(n)}static getPageInfoTool={...mjr,inputSchema:lS(rct,{$refStrategy:"none"})};async getPageInfo(t){let r=rct.parse(t),n=await Ajr(this.backend,r);return JSON.stringify(n)}static getDomTool={...ure,inputSchema:lS(nct,{$refStrategy:"none"})};async getDom(t){let r=nct.parse(t),n=await Gbe(this.backend,r);return JSON.stringify(n)}static takeScreenshotTool={...gjr,inputSchema:lS(ict,{$refStrategy:"none"})};async takeScreenshot(t){let r=ict.parse(t),n=await Ejr(this.backend,r);return JSON.stringify(n)}static getLocatorTool={...yjr,inputSchema:lS(sct,{$refStrategy:"none"})};async getLocator(t){let r=sct.parse(t),n=await Cjr(this.backend,r);return JSON.stringify(n)}static get toolDefinitions(){return[fR.navigateTool,fR.getPageInfoTool,fR.getDomTool,fR.takeScreenshotTool,fR.actTool,fR.getLocatorTool]}},Vbe=class ore{constructor(t){this.backend=t}static getConsoleLogsTool={name:"get_browser_console_logs",description:"Get console logs from the browser (errors, warnings, etc.)",inputSchema:Lbe(Yu.object({session_id:Yu.string().describe("Browser session ID"),since_timestamp:Yu.number().optional().describe("Only return logs after this timestamp"),log_types:Yu.array(Yu.string()).optional().describe("Filter by log types (e.g., ['error', 'warning'])")}),{$refStrategy:"none"})};async getConsoleLogs(t){let{session_id:r,since_timestamp:n,log_types:i}=Yu.object({session_id:Yu.string(),since_timestamp:Yu.number().optional(),log_types:Yu.array(Yu.string()).optional()}).parse(t);if(!this.backend.getSession(r))throw new Error(`Session ${r} not found`);let o=this.backend.getConsoleLogs(r,{sinceTimestamp:n,logTypes:i});return JSON.stringify({session_id:r,log_count:o.length,logs:o.slice(-100)})}static getNetworkLogsTool={name:"get_browser_network_logs",description:"Get network request logs from the browser",inputSchema:Lbe(Yu.object({session_id:Yu.string().describe("Browser session ID"),since_timestamp:Yu.number().optional().describe("Only return logs after this timestamp"),status_filter:Yu.enum(["errors","success"]).optional().describe("Filter by HTTP status (errors: 4xx/5xx, success: 2xx)")}),{$refStrategy:"none"})};async getNetworkLogs(t){let{session_id:r,since_timestamp:n,status_filter:i}=Yu.object({session_id:Yu.string(),since_timestamp:Yu.number().optional(),status_filter:Yu.enum(["errors","success"]).optional()}).parse(t);if(!this.backend.getSession(r))throw new Error(`Session ${r} not found`);let o=this.backend.getNetworkLogs(r,{sinceTimestamp:n,statusFilter:i});return JSON.stringify({session_id:r,log_count:o.length,logs:o.slice(-100)})}static clearLogsTool={name:"clear_logs",description:"Clear console and network logs for a session",inputSchema:Lbe(Yu.object({session_id:Yu.string().describe("Browser session ID")}),{$refStrategy:"none"})};async clearLogs(t){let{session_id:r}=Yu.object({session_id:Yu.string()}).parse(t);if(!this.backend.getSession(r))throw new Error(`Session ${r} not found`);return this.backend.clearLogs(r),JSON.stringify({session_id:r,message:"Logs cleared successfully"})}static getLocalArtifactTool={name:"get_local_artifact",description:"Get a local artifact file (screenshot, DOM snapshot, etc.) from a live browser session. Only accepts local file paths, NOT S3 URIs. For test run artifacts, use get_step_artifacts instead.",inputSchema:Lbe(Yu.object({path:Yu.string().describe("Artifact file path from previous tool output"),return_format:Yu.enum(["base64","text"]).optional().describe("Return format (auto-detected if not specified)")}),{$refStrategy:"none"})};async getLocalArtifact(t){let{path:r}=Yu.object({path:Yu.string(),return_format:Yu.enum(["base64","text"]).optional()}).parse(t),n=await this.backend.getLocalArtifact(r);return JSON.stringify({path:r,format:n.format,size:n.size,data:n.data})}static toolDefinitions=[ore.getConsoleLogsTool,ore.getNetworkLogsTool,ore.clearLogsTool,ore.getLocalArtifactTool]},Wbe=class cS{constructor(t){this.apiClient=t}requireApi(t){if(!this.apiClient)throw new Error(SY(t));return this.apiClient}static listEnvironmentsTool={name:"list_environments",description:"List all testing environments (staging, production, etc.)",inputSchema:dR(to.object({}),{$refStrategy:"none"})};async listEnvironments(t){let n=await this.requireApi("list_environments").listEnvironments();return JSON.stringify({environments:n.map(i=>({id:i.id,name:i.name,url:i.url}))})}static listTestAccountsTool={name:"list_test_accounts",description:"List test accounts for a specific environment. Requires either environment_id or environment_url.",inputSchema:dR(to.object({environment_id:to.number().optional().describe("Environment ID to filter by"),environment_url:to.string().optional().describe("Environment URL to filter by (alternative to environment_id)")}),{$refStrategy:"none"})};async listTestAccounts(t){let r=this.requireApi("list_test_accounts"),{environment_id:n,environment_url:i}=to.object({environment_id:to.number().optional(),environment_url:to.string().optional()}).parse(t),s=await r.listTestAccounts(n,i);return JSON.stringify({test_accounts:s.map(o=>({id:o.id,name:o.name,username:o.username,environment_id:o.environmentId||o.environment_id}))})}static getTestAccountTool={name:"get_test_account",description:"Get a specific test account by ID",inputSchema:dR(to.object({test_account_id:to.number().describe("Test account ID")}),{$refStrategy:"none"})};async getTestAccount(t){let r=this.requireApi("get_test_account"),{test_account_id:n}=to.object({test_account_id:to.number()}).parse(t),i=await r.getTestAccount(n);return JSON.stringify({test_account:i})}static createTestAccountTool={name:"create_test_account",description:"Create a new test account",inputSchema:dR(to.object({environment_id:to.number().describe("Environment ID"),username:to.string().describe("Username"),password:to.string().describe("Password"),login_config:to.any().optional().describe("Login configuration")}),{$refStrategy:"none"})};async createTestAccount(t){let r=this.requireApi("create_test_account"),{environment_id:n,username:i,password:s,login_config:o}=to.object({environment_id:to.number(),username:to.string(),password:to.string(),login_config:to.any().optional()}).parse(t),a=await r.createTestAccount({environmentId:n,username:i,password:s,loginConfig:o});return JSON.stringify({test_account_id:a.id,message:"Test account created successfully"})}static listFoldersTool={name:"list_folders",description:"List test case folders",inputSchema:dR(to.object({parent_id:to.number().nullable().optional().describe("Parent folder ID, null for root"),search:to.string().optional().describe("Search query")}),{$refStrategy:"none"})};async listFolders(t){let r=this.requireApi("list_folders"),{parent_id:n,search:i}=to.object({parent_id:to.number().nullable().optional(),search:to.string().optional()}).parse(t),s=await r.listFolders(n,i);return JSON.stringify({folders:s.map(o=>({id:o.id,name:o.name,parent_id:o.parentId||o.parent_id,path:o.pathIds}))})}static createFolderTool={name:"create_folder",description:"Create a new test case folder",inputSchema:dR(to.object({name:to.string().describe("Folder name"),description:to.string().optional().describe("Folder description"),parent_id:to.number().nullable().optional().describe("Parent folder ID")}),{$refStrategy:"none"})};async createFolder(t){let r=this.requireApi("create_folder"),{name:n,description:i,parent_id:s}=to.object({name:to.string(),description:to.string().optional(),parent_id:to.number().nullable().optional()}).parse(t),o=await r.createFolder({name:n,description:i,parentId:s});return JSON.stringify({folder_id:o.id,message:"Folder created successfully"})}static getFolderTool={name:"get_folder",description:"Get folder details with full hierarchical path",inputSchema:dR(to.object({folder_id:to.number().describe("Folder ID")}),{$refStrategy:"none"})};async getFolder(t){let r=this.requireApi("get_folder"),{folder_id:n}=to.object({folder_id:to.number()}).parse(t),i=await r.getFolder(n);return JSON.stringify({folder:i})}static toolDefinitions=[cS.listEnvironmentsTool,cS.listTestAccountsTool,cS.getTestAccountTool,cS.createTestAccountTool,cS.listFoldersTool,cS.createFolderTool,cS.getFolderTool]};Ybe=class Ube{constructor(t){this.apiClient=t}requireApi(t){if(!this.apiClient)throw new Error(SY(t));return this.apiClient}parseFlowInput(t){return typeof t=="string"?Ey.parse(tT(t)):Ey.parse(t)}static createTestCaseTool={name:"create_test_case",description:"Create a test case from a flow (test flow JSON object or YAML string). IMPORTANT: Read 'shiplight://schemas/testflow-json-v1.2.0' for the required flow format. Accepts a test flow as JSON object or YAML string. Include action_entity with locator/xpath on ACTION statements when available - this speeds up test execution.",inputSchema:ire(Gi.object({flow:Gi.union([Gi.string(),Gi.any()]).describe("Test flow as JSON object or YAML string"),folder_id:Gi.number().optional().describe("Folder ID"),name:Gi.string().optional().describe("Test case name"),environment_id:Gi.number().optional().describe("Environment ID for test execution"),test_account_id:Gi.number().optional().describe("Optional test account ID for login")}),{$refStrategy:"none"})};async createTestCase(t){let r=this.requireApi("create_test_case"),{flow:n,folder_id:i,name:s,environment_id:o,test_account_id:a}=Gi.object({flow:Gi.any(),folder_id:Gi.number().optional(),name:Gi.string().optional(),environment_id:Gi.number().optional(),test_account_id:Gi.number().optional()}).parse(t),u;try{u=this.parseFlowInput(n)}catch(m){throw m instanceof Gi.ZodError?new Error(`Invalid TestFlow schema: ${JSON.stringify(m.errors)}`):m}let l={...u,version:"1.2.0",statements:this.regenerateUids(u.statements),teardown:u.teardown?this.regenerateUids(u.teardown):void 0},c=o;if(c===void 0&&u.url)try{let m=await r.listEnvironments(),g=Y6s(u.url,m);g&&(c=g.id)}catch{}let d;c!==void 0&&(d=[{environment_id:c,test_account_group:a?{type:"Specific",account_ids:[a]}:{type:"None",account_ids:[]},path:""}]);let p=await r.createTestCase({title:s||u.goal,testFlow:l,folderId:i,environmentConfigs:d}),f=this.computeFlowStats(u.statements,u.teardown);return JSON.stringify({...p,environment_id:c??null,test_account_id:a??null,yaml:eT(u,{test_case_id:p.test_case_id}),flow_stats:f})}static updateTestCaseTool={name:"update_test_case",description:"Update a test case from a flow (test flow JSON object or YAML string). IMPORTANT: Read 'shiplight://schemas/testflow-json-v1.2.0' for the required flow format. Accepts a test flow as JSON object or YAML string. Include action_entity with locator/xpath on ACTION statements when available - this speeds up test execution.",inputSchema:ire(Gi.object({test_case_id:Gi.number().describe("Test case ID"),flow:Gi.union([Gi.string(),Gi.any()]).describe("Test flow as JSON object or YAML string"),title:Gi.string().optional().describe("New title")}),{$refStrategy:"none"})};async updateTestCase(t){let r=this.requireApi("update_test_case"),{test_case_id:n,flow:i,title:s}=Gi.object({test_case_id:Gi.number(),flow:Gi.any(),title:Gi.string().optional()}).parse(t),o;try{o=this.parseFlowInput(i)}catch(c){throw c instanceof Gi.ZodError?new Error(`Invalid TestFlow schema: ${JSON.stringify(c.errors)}`):c}let a={...o,version:"1.2.0",statements:this.regenerateUids(o.statements),teardown:o.teardown?this.regenerateUids(o.teardown):void 0},u=await r.updateTestCase(n,{title:s||o.goal,testFlow:a}),l=this.computeFlowStats(o.statements,o.teardown);return JSON.stringify({...u,flow_stats:l})}static getTestCaseTool={name:"get_test_case",description:"Get a test case by ID. Use output_format 'yaml' to get the flow as a YAML string.",inputSchema:ire(Gi.object({test_case_id:Gi.number().describe("Test case ID"),output_format:Gi.enum(["json","yaml"]).optional().describe("Output format for the test flow (default: json)")}),{$refStrategy:"none"})};async getTestCase(t){let r=this.requireApi("get_test_case"),{test_case_id:n,output_format:i}=Gi.object({test_case_id:Gi.number(),output_format:Gi.enum(["json","yaml"]).optional()}).parse(t),s=await r.getTestCase(n);if(i==="yaml"&&s.test_flow){let o=eT(s.test_flow,{test_case_id:s.id});return JSON.stringify({test_case:{id:s.id,title:s.title,folder_id:s.folder_id},yaml:o})}return JSON.stringify({test_case:s})}static runTestCaseTool={name:"run_test_case",description:"Trigger cloud execution of a test case. Returns a test_run_id that can be used with get_test_run_details to poll for completion and results.",inputSchema:ire(Gi.object({test_case_id:Gi.number().describe("Test case ID to run"),environment_id:Gi.number().optional().describe("Environment ID to run against (uses test case default if not specified)")}),{$refStrategy:"none"})};async runTestCase(t){let r=this.requireApi("run_test_case"),{test_case_id:n,environment_id:i}=Gi.object({test_case_id:Gi.number(),environment_id:Gi.number().optional()}).parse(t),s=await r.runTestCase(n,i);return JSON.stringify({test_run_id:s.id,status:s.status,result:s.result,test_case_result_ids:s.test_case_result_ids,message:`Test run ${s.id} triggered. Use get_test_run_details(${s.id}) to check status and results.`})}static saveTestCaseTool={name:"save_test_case",description:"Save a test case from YAML \u2014 automatically creates or updates based on embedded test_case_id metadata. If the YAML contains a test_case_id field, updates that test case; otherwise creates a new one. Returns YAML with test_case_id embedded so you can write it back to the local file for future updates.",inputSchema:ire(Gi.object({yaml:Gi.string().describe("YAML test flow string"),folder_id:Gi.number().optional().describe("Folder ID (only used on create)"),name:Gi.string().optional().describe("Test case name (defaults to flow goal)"),environment_id:Gi.number().optional().describe("Environment ID for test execution (only used on create)"),test_account_id:Gi.number().optional().describe("Optional test account ID for login (only used on create)")}),{$refStrategy:"none"})};async saveTestCase(t){let{yaml:r,folder_id:n,name:i,environment_id:s,test_account_id:o}=Gi.object({yaml:Gi.string(),folder_id:Gi.number().optional(),name:Gi.string().optional(),environment_id:Gi.number().optional(),test_account_id:Gi.number().optional()}).parse(t),a=NY(r);if(a.test_case_id!==void 0){let u=await this.updateTestCase({test_case_id:a.test_case_id,flow:r,title:i}),l=JSON.parse(u),c=this.parseFlowInput(r);return JSON.stringify({...l,yaml:eT(c,{test_case_id:a.test_case_id})})}else return await this.createTestCase({flow:r,folder_id:n,name:i,environment_id:s,test_account_id:o})}computeFlowStats(t,r){let n={total:0,drafts:0,actions_with_locator:0,actions_without_locator:0,verifies:0,steps:0},i=o=>{for(let a of o)if(n.total++,a.type==="DRAFT")n.drafts++;else if(a.type==="ACTION"){let u=a.action_entity;u?.action_data?.action_name==="verify"?n.verifies++:u?.locator||u?.xpath?n.actions_with_locator++:n.actions_without_locator++}else a.type==="STEP"?(n.steps++,a.statements&&i(a.statements)):a.type==="IF_ELSE"?(a.then&&i(a.then),a.else&&i(a.else)):a.type==="WHILE_LOOP"&&a.body&&i(a.body)};i(t),r&&i(r);let s={...n};return n.drafts>0&&n.actions_with_locator===0?s.hint=`${n.drafts} DRAFT statements with no action_entities (~10-15s each at runtime). Include action_entities with locator/xpath for ~1s deterministic replay.`:n.drafts>0&&(s.hint=`${n.drafts} DRAFT statements remaining (~10-15s each at runtime vs ~1s with action_entities). Consider converting DRAFTs that don't depend on runtime state to ACTIONs.`),s}regenerateUids(t){return t.map(r=>{let n={...r,uid:W6s()};return r.type==="STEP"&&r.statements?n.statements=this.regenerateUids(r.statements):r.type==="IF_ELSE"?(r.then&&(n.then=this.regenerateUids(r.then)),r.else&&(n.else=this.regenerateUids(r.else))):r.type==="WHILE_LOOP"&&r.body&&(n.body=this.regenerateUids(r.body)),n})}static toolDefinitions=[Ube.getTestCaseTool,Ube.runTestCaseTool,Ube.saveTestCaseTool]},K6s=3600*1e3,Qbe=qbe.join(z6s.tmpdir(),"shiplight-artifacts"),zbe=class Ej{constructor(t,r){this.apiClient=t,this.options=r||{}}options;requireApi(t){if(!this.apiClient)throw new Error(SY(t));return this.apiClient}getProxyUrl(t){if(!this.options.artifactProxyBaseUrl)return null;let r=t.match(/^s3:\/\/[^/]+\/(.+)$/);if(!r)return null;let n=r[1];return`${this.options.artifactProxyBaseUrl}/api/artifacts/${n}`}cleanupOldArtifacts(){try{if(!pR.existsSync(Qbe))return;let t=Date.now(),r=pR.readdirSync(Qbe,{withFileTypes:!0});for(let n of r){if(!n.isDirectory())continue;let i=qbe.join(Qbe,n.name);try{let s=pR.statSync(i);t-s.mtimeMs>K6s&&pR.rmSync(i,{recursive:!0,force:!0})}catch{}}}catch{}}static listTestRunsTool={name:"list_test_runs",description:"List test runs with optional filtering by test plan, trigger type, or result status",inputSchema:sre(la.object({test_plan_id:la.number().optional().describe("Filter by test plan ID"),trigger:la.string().optional().describe("Filter by trigger type (Manual, Scheduled, API, GITHUB_ACTION, Webhook, Automation)"),result:la.string().optional().describe("Filter by result (Passed, Failed, Pending, Skipped, Queued)"),limit:la.number().optional().describe("Maximum number of results (default 20)")}),{$refStrategy:"none"})};async listTestRuns(t){let r=this.requireApi("list_test_runs"),{test_plan_id:n,trigger:i,result:s,limit:o}=la.object({test_plan_id:la.number().optional(),trigger:la.string().optional(),result:la.string().optional(),limit:la.number().optional()}).parse(t),a=await r.listTestRuns({testPlanId:n,trigger:i,result:s,limit:o??20});return JSON.stringify({test_runs:a.map(u=>({id:u.id,status:u.status,result:u.result,trigger:u.trigger,start_time:u.startTime||u.start_time,end_time:u.endTime||u.end_time,duration_ms:u.duration,total_test_case_count:u.totalTestCaseCount||u.total_test_case_count,passed_test_case_count:u.passedTestCaseCount||u.passed_test_case_count,failed_test_case_count:u.failedTestCaseCount||u.failed_test_case_count,skipped_test_case_count:u.skippedTestCaseCount||u.skipped_test_case_count,test_plan_id:u.testPlanId||u.test_plan_id})),count:a.length})}static getTestRunDetailsTool={name:"get_test_run_details",description:"Get detailed information about a test run including all test case results",inputSchema:sre(la.object({test_run_id:la.number().describe("Test run ID")}),{$refStrategy:"none"})};async getTestRunDetails(t){let r=this.requireApi("get_test_run_details"),{test_run_id:n}=la.object({test_run_id:la.number()}).parse(t),i=await r.getTestRunDetails(n),s=i.testRun||i,o=i.testCaseResults||[];return JSON.stringify({test_run:{id:s.id,status:s.status,result:s.result,trigger:s.trigger,start_time:s.startTime||s.start_time,end_time:s.endTime||s.end_time,duration_ms:s.duration,total_test_case_count:s.totalTestCaseCount||s.total_test_case_count,passed_test_case_count:s.passedTestCaseCount||s.passed_test_case_count,failed_test_case_count:s.failedTestCaseCount||s.failed_test_case_count,skipped_test_case_count:s.skippedTestCaseCount||s.skipped_test_case_count,test_plan_id:s.testPlanId||s.test_plan_id},test_case_results:o.map(a=>({id:a.id,test_case_id:a.testCaseId||a.test_case_id,result:a.result,status:a.status,duration_ms:a.duration,environment_name:a.environmentName||a.environment_name,environment_url:a.environmentUrl||a.environment_url,error:a.error}))})}static getTestCaseResultTool={name:"get_test_case_result",description:"Get detailed test case result including status, duration, and error information",inputSchema:sre(la.object({test_case_result_id:la.number().describe("Test case result ID"),include_report:la.boolean().optional().describe("Include full report with step details (default false)")}),{$refStrategy:"none"})};async getTestCaseResult(t){let r=this.requireApi("get_test_case_result"),{test_case_result_id:n,include_report:i}=la.object({test_case_result_id:la.number(),include_report:la.boolean().optional()}).parse(t),s=await r.getTestCaseResult(n),o=Array.isArray(s.report)?s.report[0]:s.report,a=o?.resultJson||{},u=Object.entries(a),l=u.length,c=null;for(let p=0;p<u.length;p++){let[,f]=u[p];if(f.status==="failed"||f.status==="failure"){c=p;break}}let d={id:s.id,test_case_id:s.testCaseId||s.test_case_id,test_run_id:s.testRunId||s.test_run_id,result:s.result,status:s.status,start_time:s.startTime||s.start_time,end_time:s.endTime||s.end_time,duration_ms:s.duration,environment_name:s.environmentName||s.environment_name,environment_id:s.environmentId||s.environment_id,environment_url:s.environmentUrl||s.environment_url,device:s.device,total_steps:l,failed_step_index:c,video_s3_uri:s.video,trace_s3_uri:s.trace,report_s3_uri:s.reportS3Uri||s.report_s3_uri};if(i){let p=s.reportS3Uri||s.report_s3_uri,f=null;if(p&&r.getS3FileContents)try{let g=await r.getS3FileContents(p);if(g){let C=JSON.parse(g);f=Array.isArray(C)?C[0]:C}}catch(g){console.error("Failed to fetch S3 report, falling back to DB report:",g)}let m=f||o;d.report={stdout:m?.stdout,stderr:m?.stderr,consoleLogs:m?.consoleLogs,flaky:m?.flaky,error:m?.error,step_results:m?.resultJson,video_s3_uri:m?.videoS3Uri,trace_s3_uri:m?.traceS3Uri,source_s3_uri:m?.sourceS3Uri}}return JSON.stringify(d)}static getTestCaseResultStepsTool={name:"get_test_case_result_steps",description:"Get step-by-step execution details for a range of steps. Use get_test_case_result first to find the failed_step_index, then request the range you need.",inputSchema:sre(la.object({test_case_result_id:la.number().describe("Test case result ID"),from_step:la.number().describe("Start step index (0-based, inclusive)"),to_step:la.number().describe("End step index (0-based, inclusive)")}),{$refStrategy:"none"})};async getTestCaseResultSteps(t){let r=this.requireApi("get_test_case_result_steps"),{test_case_result_id:n,from_step:i,to_step:s}=la.object({test_case_result_id:la.number(),from_step:la.number(),to_step:la.number()}).parse(t),o=await r.getTestCaseResult(n),u=(Array.isArray(o.report)?o.report[0]:o.report)?.resultJson||{},l=Object.entries(u).map(([d,p])=>{let f=p.artifacts?.[0];return{step_id:d,description:p.description,status:p.status,message:p.message,start_time:p.startTime,duration_ms:p.duration,has_screenshot:!!(f?.screenshot_s3_path||p.screenshotS3Uri),has_messages:!!f?.messages_s3_path,has_response:!!f?.response_s3_path,has_system_prompt:!!f?.system_prompt_s3_path}}),c=l.slice(i,s+1);return JSON.stringify({test_case_result_id:n,total_steps:l.length,from_step:i,to_step:s,steps:c})}static getStepArtifactsTool={name:"get_step_artifacts",description:"Download all artifacts for a step to local files. Returns local file paths - client decides which to read.",inputSchema:sre(la.object({test_case_result_id:la.number().describe("Test case result ID"),step_index:la.number().describe("Step index (0-based)")}),{$refStrategy:"none"})};async getStepArtifacts(t){let r=this.requireApi("get_step_artifacts"),{test_case_result_id:n,step_index:i}=la.object({test_case_result_id:la.number(),step_index:la.number()}).parse(t),s=await r.getTestCaseResult(n),a=(Array.isArray(s.report)?s.report[0]:s.report)?.resultJson||{},u=Object.values(a);if(i<0||i>=u.length)throw new Error(`Step index ${i} out of range (0-${u.length-1})`);let l=u[i],c=l.artifacts?.[0],d={},p=c?.screenshot_s3_path||l.screenshotS3Uri;p&&(d.screenshot={s3Uri:p,filename:`step_${i}_screenshot.png`}),c?.messages_s3_path&&(d.messages={s3Uri:c.messages_s3_path,filename:`step_${i}_messages.json`}),c?.response_s3_path&&(d.response={s3Uri:c.response_s3_path,filename:`step_${i}_response.txt`}),c?.system_prompt_s3_path&&(d.system_prompt={s3Uri:c.system_prompt_s3_path,filename:`step_${i}_system_prompt.txt`});let f=!!r.getS3FileContents,m=!!this.options.artifactProxyBaseUrl,g=!!r.getArtifactPresignedUrl;if(!f&&!m&&!g)return JSON.stringify({test_case_result_id:n,step_index:i,error:"Artifact download not supported by this API client",s3_uris:Object.fromEntries(Object.entries(d).map(([x,{s3Uri:D}])=>[x,D]))});this.cleanupOldArtifacts();let C=qbe.join(Qbe,`tcr-${n}`);pR.existsSync(C)||pR.mkdirSync(C,{recursive:!0});let b={},w=!!r.downloadArtifact;return await Promise.all(Object.entries(d).map(async([x,{s3Uri:D,filename:I}])=>{try{let k=null;if(!k&&f){let U=await r.getS3FileContents(D);U&&(k=U)}if(!k&&m&&w){let U=this.getProxyUrl(D);U&&(k=await r.downloadArtifact(U))}if(!k&&g){let U=await r.getArtifactPresignedUrl(D);if(U){let R=await J6s.get(U,{responseType:"arraybuffer"});k=Buffer.from(R.data)}}if(!k){b[x]=null;return}let P=qbe.join(C,I);pR.writeFileSync(P,k),b[x]=P}catch(k){console.error(`Failed to download ${x} artifact:`,k),b[x]=null}})),JSON.stringify({test_case_result_id:n,step_index:i,screenshot_path:b.screenshot,messages_path:b.messages,response_path:b.response,system_prompt_path:b.system_prompt})}static toolDefinitions=[Ej.listTestRunsTool,Ej.getTestRunDetailsTool,Ej.getTestCaseResultTool,Ej.getTestCaseResultStepsTool,Ej.getStepArtifactsTool]},Jbe=class vjr{static initLocalProjectTool={name:"init_local_project",description:"Scaffold a ready-to-run local test project with Playwright and shiplightai. Generates package.json, playwright.config.ts, .gitignore, .env.example, and a tests/ directory. IMPORTANT: You MUST call this tool before writing any .test.yaml files \u2014 it sets up the project structure required to run them. After scaffolding, the user runs: npm install && npx playwright install chromium && npx playwright test",inputSchema:ajr(Rg.object({project_path:Rg.string().describe("Absolute path where the project should be created")}),{$refStrategy:"none"})};async initLocalProject(t){let{project_path:r}=Rg.object({project_path:Rg.string()}).parse(t),n=a7.join(r,"tests");Xw.mkdirSync(n,{recursive:!0});let i={name:"my-shiplight-tests",type:"module",scripts:{test:"playwright test","test:headed":"playwright test --headed"},dependencies:{"@playwright/test":"1.55.0",playwright:"1.55.0",shiplightai:"^0.1.0",dotenv:"^16.4.7"}};Xw.writeFileSync(a7.join(r,"package.json"),JSON.stringify(i,null,2)+`
|
|
8086
|
+
`),Xw.writeFileSync(a7.join(r,"playwright.config.ts"),`import { defineConfig } from '@playwright/test';
|
|
8087
|
+
import { shiplightConfig } from 'shiplightai';
|
|
8088
8088
|
|
|
8089
8089
|
export default defineConfig({
|
|
8090
|
+
...shiplightConfig(),
|
|
8090
8091
|
testDir: './tests',
|
|
8092
|
+
testMatch: ['**/*.test.ts', '**/*.yaml.spec.ts'],
|
|
8091
8093
|
timeout: 120_000,
|
|
8092
8094
|
expect: { timeout: 10_000 },
|
|
8093
8095
|
retries: 0,
|
|
@@ -8099,7 +8101,7 @@ export default defineConfig({
|
|
|
8099
8101
|
});
|
|
8100
8102
|
`);let o=["node_modules/","test-results/","playwright-report/",".env",""].join(`
|
|
8101
8103
|
`);Xw.writeFileSync(a7.join(r,".gitignore"),o);let a=["# Shiplight API token (required) \u2014 get yours at https://app.shiplight.ai/settings/api-tokens","SHIPLIGHT_API_TOKEN=","","# AI model API keys (at least one required)","GOOGLE_API_KEY=","ANTHROPIC_API_KEY=","","# Optional: override the default AI model (default: gemini-2.5-pro)","# SHIPLIGHT_MODEL=gemini-2.5-pro","","# Optional: override starting URL for all tests","# PLAYWRIGHT_STARTING_URL=",""].join(`
|
|
8102
|
-
`);return Xw.writeFileSync(a7.join(r,".env.example"),a),Xw.writeFileSync(a7.join(n,".gitkeep"),""),JSON.stringify({success:!0,project_path:r,files_created:["package.json","playwright.config.ts",".gitignore",".env.example","tests/.gitkeep"],next_steps:["Copy .env.example to .env and set SHIPLIGHT_API_TOKEN (from https://app.shiplight.ai/settings/api-tokens) and at least one AI model API key","Run: npm install","Run: npx playwright install","Write .test.yaml files into the tests/ directory","Run: npx playwright test"],do_this_now:"You MUST now: (1) Run 'npm install' in the project directory to install shiplightai and @playwright/test, (2) Run 'npx playwright install' to download browser
|
|
8104
|
+
`);return Xw.writeFileSync(a7.join(r,".env.example"),a),Xw.writeFileSync(a7.join(n,".gitkeep"),""),JSON.stringify({success:!0,project_path:r,files_created:["package.json","playwright.config.ts",".gitignore",".env.example","tests/.gitkeep"],next_steps:["Copy .env.example to .env and set SHIPLIGHT_API_TOKEN (from https://app.shiplight.ai/settings/api-tokens) and at least one AI model API key","Run: npm install","Run: npx playwright install chromium","Write .test.yaml files into the tests/ directory","Run: npx playwright test"],do_this_now:"You MUST now: (1) Run 'npm install' in the project directory to install shiplightai and @playwright/test, (2) Run 'npx playwright install chromium' to download the Chromium browser, (3) Read resource 'shiplight://schemas/testflow-yaml-v1.2.0' to learn the YAML test format before writing any .test.yaml files."})}static exportYamlToTestTool={name:"export_yaml_to_test",description:"Convert a YAML test flow into a standalone Playwright .test.ts file. The generated test uses shiplightai with an inline agent \u2014 no cloud runner needed. Use init_local_project first to scaffold the project, then export tests into the tests/ directory.",inputSchema:ajr(Rg.object({yaml:Rg.string().describe("YAML test flow string"),output_path:Rg.string().describe("Absolute path for the output .test.ts file (e.g., /path/to/project/tests/login.test.ts)"),test_name:Rg.string().optional().describe("Override the test name (defaults to the flow's goal)"),starting_url:Rg.string().optional().describe("Override the starting URL from the test flow")}),{$refStrategy:"none"})};async exportYamlToTest(t){let{yaml:r,output_path:n,test_name:i,starting_url:s}=Rg.object({yaml:Rg.string(),output_path:Rg.string(),test_name:Rg.string().optional(),starting_url:Rg.string().optional()}).parse(t),o=sjr(r,{testName:i,startingUrl:s}),a=a7.dirname(n);return Xw.mkdirSync(a,{recursive:!0}),Xw.writeFileSync(n,o),JSON.stringify({success:!0,output_path:n,size_bytes:Buffer.byteLength(o,"utf8")})}static toolDefinitions=[vjr.initLocalProjectTool]}});import{z as hR}from"zod";import{zodToJsonSchema as X6s}from"zod-to-json-schema";var L8a,Kbe=Q(()=>{"use strict";Mkt();Lkt();Qkt();Gkt();xjr();Kue();dle();xl();L8a=class Bjr{constructor(t){this.backend=t}static runTestFlowTool={name:"run_test_flow",description:`Execute a complete TestFlow in an existing browser session, returning per-statement pass/fail results. No test case is created. Use this to validate a test flow before saving it.
|
|
8103
8105
|
|
|
8104
8106
|
The flow is executed statement by statement:
|
|
8105
8107
|
- ACTION with action_entity: replays using cached locator (fast)
|
|
@@ -8826,7 +8828,7 @@ url: ${e}
|
|
|
8826
8828
|
statements:
|
|
8827
8829
|
- description: "TODO: Add your first step"
|
|
8828
8830
|
`});var h8n={};Hs(h8n,{runTests:()=>RBo});import{spawn as kBo}from"child_process";import*as p8n from"fs";import*as f8n from"path";async function RBo(e){(e.includes("--help")||e.includes("-h"))&&(console.log("Usage: shiplight run [playwright-args...]"),console.log(""),console.log("Delegates to `npx playwright test` with all arguments forwarded."),console.log("Auto-detects playwright.config.ts in the current directory."),console.log(""),console.log("Examples:"),console.log(" shiplight run # run all tests"),console.log(" shiplight run --headed # run tests with browser visible"),console.log(" shiplight run tests/login.test.ts # run a specific test"),console.log(" shiplight run --grep 'login' # filter tests by name"),process.exit(0));let t=process.cwd();["playwright.config.ts","playwright.config.js","playwright.config.mjs"].some(o=>p8n.existsSync(f8n.join(t,o)))||(console.warn("Warning: No playwright.config.ts found in current directory."),console.warn(`Make sure you're running from your project root.
|
|
8829
|
-
`));let i=kBo("npx",["playwright","test",...e],{stdio:"inherit",shell:!0,cwd:t}),s=await new Promise(o=>{i.on("close",a=>o(a??1))});process.exit(s)}var m8n=Q(()=>{"use strict"});var A8n=F((H_u,NBo)=>{NBo.exports={name:"shiplightai",version:"0.1.
|
|
8831
|
+
`));let i=kBo("npx",["playwright","test",...e],{stdio:"inherit",shell:!0,cwd:t}),s=await new Promise(o=>{i.on("close",a=>o(a??1))});process.exit(s)}var m8n=Q(()=>{"use strict"});var A8n=F((H_u,NBo)=>{NBo.exports={name:"shiplightai",version:"0.1.10",type:"module",description:"Shiplight CLI for running and debugging .test.yaml files",main:"dist/index.js",types:"dist/index.d.ts",bin:{shiplight:"dist/cli.js"},exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/cjs/index.cjs",default:"./dist/index.js"},"./fixture":{types:"./dist/fixture.d.ts",import:"./dist/fixture.js",require:"./dist/cjs/fixture.cjs",default:"./dist/fixture.js"}},files:["dist","README.md"],publishConfig:{registry:"https://registry.npmjs.org",access:"public"},scripts:{build:"tsup","build:cli":"tsup",clean:"rm -rf dist",dev:"tsup --watch",test:"playwright test",typecheck:"tsc --noEmit"},dependencies:{"@babel/plugin-transform-typescript":"^7.27.0","@babel/preset-env":"^7.26.9","@babel/preset-typescript":"^7.27.0","@anthropic-ai/claude-agent-sdk":"^0.1.72","@modelcontextprotocol/sdk":"^0.5.0",axios:"^1.6.0",dotenv:"^16.0.3",express:"^4.21.0",glob:"^13.0.0",open:"^10.1.0",sharp:"^0.34.5",uuid:"^11.1.0",yaml:"^2.8.0",zod:"^3.22.0","zod-to-json-schema":"^3.24.6"},devDependencies:{"@playwright/test":"1.55.0","@types/express":"^4.17.21","@types/node":"^24.0.0",copilot3:"workspace:*","mcp-tools":"workspace:*","sdk-core":"workspace:*","sdk-internal":"workspace:*","shiplight-types":"workspace:*",tsup:"^8.3.5",typescript:"5.5.4","web-session":"workspace:*"},peerDependencies:{"@playwright/test":">=1.40.0"},engines:{node:">=22.0.0"},keywords:["playwright","yaml","testing","automation","ai","shiplight","mcp"],author:"Shiplight",license:"MIT"}});import PBo from"dotenv";PBo.config();function g8n(){console.log(`
|
|
8830
8832
|
Usage: shiplight <command> [options]
|
|
8831
8833
|
|
|
8832
8834
|
Commands:
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire as __createRequire } from "module";
|
|
|
2
2
|
const require = __createRequire(import.meta.url);
|
|
3
3
|
import{p as nt,r as rt}from"./chunk-PUJJ3P7V.js";import"./chunk-RZSEU5IV.js";import"./chunk-7IODNB46.js";import"./chunk-WMT4NNJ7.js";import"./chunk-2N37B5MX.js";import{l as et}from"./chunk-OPPQ2WGO.js";import"./chunk-FL4TL6US.js";import"./chunk-BVOD5VWH.js";import{b as tt}from"./chunk-FBJSHHRD.js";import{Da as R,da as j}from"./chunk-5P7PVDTU.js";import"./chunk-A2VNNKWB.js";import"./chunk-STSHJEMG.js";import"./chunk-YU3XZJIJ.js";import"./chunk-CSINHOOD.js";import*as _ from"fs";import*as d from"path";import It from"dotenv";import{globSync as jt}from"glob";import{readFileSync as B,writeFileSync as At,statSync as Tt,existsSync as Ot}from"fs";import{resolve as vt}from"path";import{globSync as bt}from"glob";import{parse as lt,stringify as gt}from"yaml";import{readFileSync as st}from"fs";import{resolve as H,dirname as it}from"path";import{parse as J,stringify as at}from"yaml";var D=5;function F(t,e){let n={expandingPaths:new Set([H(e)]),depth:0,referencedPaths:new Set},r={...t};return Array.isArray(r.statements)&&(r.statements=$(r.statements,e,n)),Array.isArray(r.teardown)&&(r.teardown=$(r.teardown,e,n)),{doc:r,referencedTemplatePaths:Array.from(n.referencedPaths)}}function $(t,e,n){let r=[];for(let s of t)if(ot(s)){let i=ct(s,e,n);r.push(...i)}else r.push(pt(s,e,n));return r}function ot(t){return typeof t=="object"&&t!==null&&typeof t.template=="string"}function ct(t,e,n){if(n.depth>=D)throw new Error(`Template expansion exceeded maximum depth of ${D}. Check for deeply nested or circular template references.`);let r=H(it(e),t.template);if(n.expandingPaths.has(r))throw new Error(`Circular template reference detected: ${r} is already being expanded. Stack: ${Array.from(n.expandingPaths).join(" \u2192 ")} \u2192 ${r}`);n.referencedPaths.add(r);let s;try{s=st(r,"utf-8")}catch(l){throw new Error(`Failed to read template file: ${r} (referenced from ${e}): ${l.message}`)}let i=J(s);if(!i||typeof i!="object")throw new Error(`Invalid template file: ${r} \u2014 expected a YAML object`);let c=i.params||[],p=t.params||{};for(let l of c)if(!(l in p))throw new Error(`Template ${t.template} requires param "${l}" but it was not provided. Required params: [${c.join(", ")}]`);let g=i.statements;if(!Array.isArray(g))throw new Error(`Template ${t.template} must have a "statements" array`);if(Object.keys(p).length>0){let u=at(g);for(let[h,k]of Object.entries(p))u=u.replaceAll(`{{${h}}}`,String(k));g=J(u)}let a={expandingPaths:new Set([...n.expandingPaths,r]),depth:n.depth+1,referencedPaths:n.referencedPaths};return $(g,r,a)}function pt(t,e,n){if(typeof t!="object"||t===null)return t;let r={...t};return Array.isArray(r.statements)&&(r.statements=$(r.statements,e,n)),Array.isArray(r.THEN)&&(r.THEN=$(r.THEN,e,n)),Array.isArray(r.ELSE)&&(r.ELSE=$(r.ELSE,e,n)),Array.isArray(r.DO)&&(r.DO=$(r.DO,e,n)),r}function G(t,e){let n=lt(t),r=n?.name,s=n?.tags,i=n?.use;n&&(n.name!==void 0||n.tags!==void 0||n.use!==void 0)&&(delete n.name,delete n.tags,delete n.use);let c=[];if(e&&n&&typeof n=="object"){let a=F(n,e);n=a.doc,c=a.referencedTemplatePaths}let p=gt(n);return{testFlow:j(p),name:r,tags:s,use:i,referencedTemplatePaths:c}}function v(t){return t.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t")}function y(t){return t.replace(/\r\n/g," ").replace(/\n/g," ").replace(/\r/g," ").trim()}function ut(t){let e=t.frame_path;return!e||e.length===0?"page":`page.frameLocator('${e[0]}')`}function ft(t){let e=t.xpath;return typeof e=="string"&&e.trim()?!e.startsWith("xpath=")&&!e.startsWith("/")&&!e.startsWith("//")?`xpath=//${e}`:e.startsWith("xpath=")?e:`xpath=${e}`:null}function E(t){let e=ut(t),n=t.locator;if(typeof n=="string"&&n.trim())return n=n.trim(),n.endsWith("first()")?`${e}.${n}`:`${e}.${n}.first()`;let r=ft(t);if(r){let s=JSON.stringify(r);return`${e}.locator(${s}).first()`}return null}var dt=["ai_action","ai_step","ai_assert","ai_extract","ai_wait_until","verify","assert"],mt=["js_code","function","wait","wait_for_download_complete","wait_for_page_ready","extract_email_content","extract_activation_code"];function M(t){let e=t.action_data?.action_name;return!e||(e==="verify"||e==="ai_assert"||e==="assert")&&t.action_data?.kwargs?.code?!1:dt.includes(e)}function W(t){let e=t.action_data?.action_name;return!e||(e==="verify"||e==="ai_assert"||e==="assert")&&t.action_data?.kwargs?.code?!1:!mt.includes(e)}var f=new Map;function o(t,e){f.set(t,e)}function V(t){return f.get(t)}function S(t,e,n=[]){let r=[...n];return e.locator?r.push(`locator: ${JSON.stringify(e.locator)}`):e.xpath&&r.push(`xpath: ${JSON.stringify(e.xpath)}`),e.frame_path&&e.frame_path.length>0&&r.push(`frame_path: ${JSON.stringify(e.frame_path)}`),r.length===0?[`await agent.execAction("${t}", page, {});`]:[`await agent.execAction("${t}", page, {`,...r.map(s=>` ${s},`),"});"]}o("click",t=>{let e=E(t);return e?[`await ${e}.click({ timeout: ${5e3} });`]:['await agent.execAction("click", page, {});']});o("click_element",f.get("click"));o("click_element_by_index",f.get("click"));o("double_click",t=>S("double_click",t));o("double_click_on_element",f.get("double_click"));o("right_click",t=>S("right_click",t));o("right_click_on_element",f.get("right_click"));o("hover",t=>S("hover",t));o("hover_element_by_index",f.get("hover"));o("input_text",t=>{let e=t.action_data?.kwargs?.text??t.action_data?.kwargs?.value??"";return S("input_text",t,[`action_data: { kwargs: { text: ${JSON.stringify(e)} } }`])});o("fill",f.get("input_text"));o("clear_input",t=>S("clear_input",t));o("press",t=>{let e=t.action_data?.kwargs?.keys;return[`await page.keyboard.press(${JSON.stringify(e)});`]});o("send_keys",f.get("press"));o("send_keys_on_element",t=>{let e=E(t),n=t.action_data?.kwargs?.keys||"";return e?[`await ${e}.press(${JSON.stringify(n)}, { timeout: ${5e3} });`]:['await agent.execAction("send_keys_on_element", page, {',` action_data: { kwargs: { keys: ${JSON.stringify(n)} } },`,"});"]});o("select_dropdown_option",t=>{let e=t.action_data?.kwargs?.text||t.action_data?.kwargs?.option||"";return S("select_dropdown_option",t,[`action_data: { kwargs: { text: ${JSON.stringify(e)} } }`])});o("scroll",t=>{let e=t.action_data?.kwargs?.down??!0;return[`await page.evaluate('window.scrollBy(0, window.innerHeight * ${(t.action_data?.kwargs?.num_pages??1)*(e?1:-1)})');`]});o("scroll_down",f.get("scroll"));o("scroll_up",f.get("scroll"));o("scroll_element",f.get("scroll"));o("scroll_to_text",t=>{let e=t.action_data?.kwargs?.text||"";return[`await page.getByText(${JSON.stringify(e)}, { exact: false }).first().scrollIntoViewIfNeeded();`]});o("scroll_on_element",t=>S("scroll_on_element",t,[`action_data: { kwargs: ${JSON.stringify(t.action_data?.kwargs||{})} }`]));o("go_to_url",t=>{let e=t.action_data?.kwargs?.url||"";return t.action_data?.kwargs?.new_tab===!0?['await agent.execAction("go_to_url", page, {',` action_data: { kwargs: { url: ${JSON.stringify(e)}, new_tab: true } },`,"});"]:['await agent.execAction("go_to_url", page, {',` action_data: { kwargs: { url: ${JSON.stringify(e)} } },`,"});"]});o("open_tab",f.get("go_to_url"));o("go_back",()=>['await agent.execAction("go_back", page, {});']);o("reload_page",()=>['await agent.execAction("reload_page", page, {});']);o("wait",t=>[`await page.waitForTimeout(${(t.action_data?.kwargs?.seconds||1)*1e3});`]);o("wait_for_page_ready",()=>["await page.waitForLoadState('domcontentloaded');"]);o("verify",(t,e)=>{let n=t.action_data?.kwargs;if(n?.code)return n.code.split(`
|
|
4
4
|
`);let r=n?.statement||t.action_description;return r?[`await agent.assert(page, ${JSON.stringify(r)}, '${e||""}');`]:["// Skipping verify: missing statement or code"]});o("ai_assert",f.get("verify"));o("assert",f.get("verify"));o("ai_action",(t,e)=>{let n=t.action_data?.kwargs?.statement;if(!n)return["// Skipping ai_action: missing statement"];let r=JSON.stringify(n),s=t.action_data?.kwargs?.use_pure_vision;return[`await agent.execute(page, ${r}, '${e||""}', ${s});`]});o("ai_step",(t,e)=>{let n=t.action_data?.kwargs?.statement;return n?[`await agent.run(page, ${JSON.stringify(n)}, '${e||""}');`]:["// Skipping ai_step: missing statement"]});o("ai_extract",(t,e)=>{let n=t.action_data?.kwargs?.element_description,r=t.action_data?.kwargs?.variable_name;if(!n||!r)return["// Skipping ai_extract: missing element_description or variable_name"];let s=JSON.stringify(n),i=JSON.stringify(r);return[`await agent.extract(page, ${s}, ${i}, '${e||""}');`]});o("ai_wait_until",(t,e)=>{let n=t.action_data?.kwargs?.condition,r=t.action_data?.kwargs?.timeout_seconds||60;return n?[`await agent.waitUntilCondition(page, ${JSON.stringify(n)}, ${r}, '${e||""}');`]:["// Skipping ai_wait_until: missing condition"]});o("save_variable",t=>{let e=t.action_data?.kwargs?.name||"",n=t.action_data?.kwargs?.value;return['await agent.execAction("save_variable", page, {',` action_data: { kwargs: { name: ${JSON.stringify(e)}, value: ${JSON.stringify(n)} } },`,"});"]});o("js_code",t=>{let e=t.action_data?.kwargs?.code;if(!e)return["// Skipping js_code: missing code"];let n=["{"],r=e.split(`
|
|
5
|
-
`);for(let s of r)n.push(` ${s}`);return n.push("}"),n});o("function",(t,e,n)=>{let r=t.action_data?.kwargs||{},s=r.functionName;if(s&&s.includes("#")){let[c,p]=s.split("#");if(c&&p){let g=c.replace(/\.(ts|js|mjs)$/,""),a=`import { ${p} } from '${g}';`;n?.imports?.add(a);let l={...r,functionName:p},u=U(l);return u?[u.endsWith(";")?u:`${u};`]:["// Skipping function: invalid export pattern"]}}let i=U(r);return i?[i.endsWith(";")?i:`${i};`]:["// Skipping function: missing functionName"]});o("generate_2fa_code",t=>{let e=t.action_data?.kwargs?.otp_secret_key||"";return['await agent.execAction("generate_2fa_code", page, {',` action_data: { kwargs: { otp_secret_key: ${JSON.stringify(e)} } },`,"});"]});o("upload_file",t=>{let e=t.action_data?.kwargs||{},n=[],r={};return e.paths?r.paths=e.paths:e.path&&(r.path=e.path),e.use_file_input&&(r.use_file_input=!0),n.push(`action_data: { kwargs: ${JSON.stringify(r)} }`),t.locator?n.push(`locator: ${JSON.stringify(t.locator)}`):t.xpath&&n.push(`xpath: ${JSON.stringify(t.xpath)}`),t.frame_path&&t.frame_path.length>0&&n.push(`frame_path: ${JSON.stringify(t.frame_path)}`),['await agent.execAction("upload_file", page, {',...n.map(s=>` ${s},`),"});"]});o("wait_for_download_complete",t=>['await agent.execAction("wait_for_download_complete", page, {',` action_data: { kwargs: { timeout_seconds: ${t.action_data?.kwargs?.timeout_seconds||10} } },`,"});"]);o("switch_tab",t=>['await agent.execAction("switch_tab", page, {',` action_data: { kwargs: { page_id: ${t.action_data?.kwargs?.page_id??0} } },`,"});"]);o("close_tab",t=>{let e=t.action_data?.kwargs?.page_id;return e=e??t.action_data?.kwargs?.index,['await agent.execAction("close_tab", page, {',` action_data: { kwargs: { page_id: ${e} } },`,"});"]});o("set_date_for_native_date_picker",t=>{let e=t.action_data?.kwargs?.date??"",n=[];return n.push(`action_data: { kwargs: { date: ${JSON.stringify(e)} } }`),t.locator?n.push(`locator: ${JSON.stringify(t.locator)}`):t.xpath&&n.push(`xpath: ${JSON.stringify(t.xpath)}`),t.frame_path&&t.frame_path.length>0&&n.push(`frame_path: ${JSON.stringify(t.frame_path)}`),['await agent.execAction("set_date_for_native_date_picker", page, {',...n.map(r=>` ${r},`),"});"]});o("done",()=>["// Done - no action needed"]);function U(t){let e=t.functionName;if(!e)return null;let n={},r=t.parameterNames||[],s=t.parameterValues||t.args||[];if(r.forEach((g,a)=>{a<s.length&&(n[g]=s[a])}),Object.keys(n).length===0)return`await ${e}()`;let i=["page","testContext","request","agentServices"],c=["undefined","null","true","false"],p=Object.entries(n).map(([g,a])=>{let l=String(a);return a==null?"undefined":i.includes(g)&&i.includes(l)||c.includes(l)||/^-?\d+(\.\d+)?$/.test(l)?l:l.startsWith("$")?`agent.agentServices.readVariable('${l.substring(1)}')`:`"${l}"`});return`await ${e}(${p.join(", ")})`}function m(t,e,n,r="main"){let s=[];for(let i=0;i<t.length;i++){let c=t[i],p=`${r}.${i}`,g=_t(c,e,p,n);g.length>0&&(s.push(...g),i<t.length-1&&s.push(""))}return s}function _t(t,e,n,r){let s=" ".repeat(e);switch(t.type){case"DRAFT":return ht(t,e,n);case"ACTION":return wt(t,e,n,r);case"STEP":return yt(t,e,n,r);case"IF_ELSE":return $t(t,e,n,r);case"WHILE_LOOP":return St(t,e,n,r);default:return[`${s}// Unknown statement type: ${t.type}`]}}function ht(t,e,n){let r=" ".repeat(e),s=t.description?.trim()||"";if(!s)return[`${r}// ${n}: Skipping - no description`];let i=JSON.stringify(s);return[`${r}// ${n}: ${y(s)}`,`${r}page = agent.agentServices.validatePage(page);`,`${r}await agent.run(page, ${i}, '${n}');`]}function wt(t,e,n,r){let s=" ".repeat(e),i=t.description,c=t.uid,g=r.actionEntityStore?.entries[t.uid]?.action_entity??t.action_entity;if(!g){if(!i)return[`${s}// ${n}: Skipping - no description`];let w=JSON.stringify(i),Z=!!t.use_pure_vision;return[`${s}// ${n}: ${y(i)}`,`${s}page = agent.agentServices.validatePage(page);`,`${s}await agent.execute(page, ${w}, '${n}', ${Z});`]}let a=t.locator?{...g,locator:t.locator}:g;i&&i!==a.action_description&&(a={...a,action_description:i});let l=a.action_data?.action_name||"",u=a.action_description||"",h=V(l);if(!h)return[`${s}// ${n}: Unknown action: ${l}`];let k={imports:r.imports},T=h(a,n,k);if(M(a))return[`${s}// ${n}: ${y(u)}`,`${s}page = agent.agentServices.validatePage(page);`,...T.map(w=>`${s}${w}`)];let P=JSON.stringify(u),O=T.map(w=>`${s} ${w}`),L=W(a),I=c?`'${c}'`:"undefined";return[`${s}// ${n}: ${y(u)}`,`${s}page = agent.agentServices.validatePage(page);`,`${s}await agent.step(page, async () => {`,...O,`${s}}, ${P}, '${n}', ${I}, ${L});`]}function yt(t,e,n,r){let s=" ".repeat(e),i=[];t.description&&t.description.trim()&&i.push(`${s}// Step: ${y(t.description)}`);let c=m(t.statements,e,r,n);return i.push(...c),i}function $t(t,e,n,r){let s=" ".repeat(e),i=[];if(i.push(`${s}// ${n}: Conditional check`),t.condition.type==="JS_CODE")i.push(`${s}if (${t.condition.expression}) {`);else{i.push(`${s}// AI Condition: ${y(t.condition.expression)}`);let p=JSON.stringify(t.condition.expression);i.push(`${s}if (await agent.evaluate(page, ${p}, "${n}")) {`)}let c=m(t.then,e+1,r,`${n}.then`);if(i.push(...c),t.else&&t.else.length>0){i.push(`${s}} else {`);let p=m(t.else,e+1,r,`${n}.else`);i.push(...p)}return i.push(`${s}}`),i}function St(t,e,n,r){let s=" ".repeat(e),i=[];i.push(`${s}// ${n}: Loop`);let c=t.timeout_ms??R,p=c/1e3,g=t.timeout_ms?`While loop exceeded timeout of ${p}s`:`While loop exceeded default timeout of ${p}s`,a=`loop_${n.replace(/\./g,"_")}`;if(i.push(`${s}const ${a}_start = Date.now();`),i.push(`${s}const ${a}_timeout = ${c};`),i.push(`${s}const ${a}_check = () => {`),i.push(`${s} if (Date.now() - ${a}_start > ${a}_timeout) {`),i.push(`${s} throw new Error('${g}');`),i.push(`${s} }`),i.push(`${s} return true;`),i.push(`${s}};`),t.condition.type==="JS_CODE")i.push(`${s}while (${a}_check() && (${t.condition.expression})) {`);else{i.push(`${s}// AI Loop Condition: ${y(t.condition.expression)}`);let u=JSON.stringify(t.condition.expression);i.push(`${s}while (${a}_check() && await agent.evaluate(page, ${u}, "${n}")) {`)}let l=m(t.body,e+1,r,`${n}.body`);return i.push(...l),i.push(`${s}}`),i}var K={name:"shiplightai",version:"0.1.
|
|
5
|
+
`);for(let s of r)n.push(` ${s}`);return n.push("}"),n});o("function",(t,e,n)=>{let r=t.action_data?.kwargs||{},s=r.functionName;if(s&&s.includes("#")){let[c,p]=s.split("#");if(c&&p){let g=c.replace(/\.(ts|js|mjs)$/,""),a=`import { ${p} } from '${g}';`;n?.imports?.add(a);let l={...r,functionName:p},u=U(l);return u?[u.endsWith(";")?u:`${u};`]:["// Skipping function: invalid export pattern"]}}let i=U(r);return i?[i.endsWith(";")?i:`${i};`]:["// Skipping function: missing functionName"]});o("generate_2fa_code",t=>{let e=t.action_data?.kwargs?.otp_secret_key||"";return['await agent.execAction("generate_2fa_code", page, {',` action_data: { kwargs: { otp_secret_key: ${JSON.stringify(e)} } },`,"});"]});o("upload_file",t=>{let e=t.action_data?.kwargs||{},n=[],r={};return e.paths?r.paths=e.paths:e.path&&(r.path=e.path),e.use_file_input&&(r.use_file_input=!0),n.push(`action_data: { kwargs: ${JSON.stringify(r)} }`),t.locator?n.push(`locator: ${JSON.stringify(t.locator)}`):t.xpath&&n.push(`xpath: ${JSON.stringify(t.xpath)}`),t.frame_path&&t.frame_path.length>0&&n.push(`frame_path: ${JSON.stringify(t.frame_path)}`),['await agent.execAction("upload_file", page, {',...n.map(s=>` ${s},`),"});"]});o("wait_for_download_complete",t=>['await agent.execAction("wait_for_download_complete", page, {',` action_data: { kwargs: { timeout_seconds: ${t.action_data?.kwargs?.timeout_seconds||10} } },`,"});"]);o("switch_tab",t=>['await agent.execAction("switch_tab", page, {',` action_data: { kwargs: { page_id: ${t.action_data?.kwargs?.page_id??0} } },`,"});"]);o("close_tab",t=>{let e=t.action_data?.kwargs?.page_id;return e=e??t.action_data?.kwargs?.index,['await agent.execAction("close_tab", page, {',` action_data: { kwargs: { page_id: ${e} } },`,"});"]});o("set_date_for_native_date_picker",t=>{let e=t.action_data?.kwargs?.date??"",n=[];return n.push(`action_data: { kwargs: { date: ${JSON.stringify(e)} } }`),t.locator?n.push(`locator: ${JSON.stringify(t.locator)}`):t.xpath&&n.push(`xpath: ${JSON.stringify(t.xpath)}`),t.frame_path&&t.frame_path.length>0&&n.push(`frame_path: ${JSON.stringify(t.frame_path)}`),['await agent.execAction("set_date_for_native_date_picker", page, {',...n.map(r=>` ${r},`),"});"]});o("done",()=>["// Done - no action needed"]);function U(t){let e=t.functionName;if(!e)return null;let n={},r=t.parameterNames||[],s=t.parameterValues||t.args||[];if(r.forEach((g,a)=>{a<s.length&&(n[g]=s[a])}),Object.keys(n).length===0)return`await ${e}()`;let i=["page","testContext","request","agentServices"],c=["undefined","null","true","false"],p=Object.entries(n).map(([g,a])=>{let l=String(a);return a==null?"undefined":i.includes(g)&&i.includes(l)||c.includes(l)||/^-?\d+(\.\d+)?$/.test(l)?l:l.startsWith("$")?`agent.agentServices.readVariable('${l.substring(1)}')`:`"${l}"`});return`await ${e}(${p.join(", ")})`}function m(t,e,n,r="main"){let s=[];for(let i=0;i<t.length;i++){let c=t[i],p=`${r}.${i}`,g=_t(c,e,p,n);g.length>0&&(s.push(...g),i<t.length-1&&s.push(""))}return s}function _t(t,e,n,r){let s=" ".repeat(e);switch(t.type){case"DRAFT":return ht(t,e,n);case"ACTION":return wt(t,e,n,r);case"STEP":return yt(t,e,n,r);case"IF_ELSE":return $t(t,e,n,r);case"WHILE_LOOP":return St(t,e,n,r);default:return[`${s}// Unknown statement type: ${t.type}`]}}function ht(t,e,n){let r=" ".repeat(e),s=t.description?.trim()||"";if(!s)return[`${r}// ${n}: Skipping - no description`];let i=JSON.stringify(s);return[`${r}// ${n}: ${y(s)}`,`${r}page = agent.agentServices.validatePage(page);`,`${r}await agent.run(page, ${i}, '${n}');`]}function wt(t,e,n,r){let s=" ".repeat(e),i=t.description,c=t.uid,g=r.actionEntityStore?.entries[t.uid]?.action_entity??t.action_entity;if(!g){if(!i)return[`${s}// ${n}: Skipping - no description`];let w=JSON.stringify(i),Z=!!t.use_pure_vision;return[`${s}// ${n}: ${y(i)}`,`${s}page = agent.agentServices.validatePage(page);`,`${s}await agent.execute(page, ${w}, '${n}', ${Z});`]}let a=t.locator?{...g,locator:t.locator}:g;i&&i!==a.action_description&&(a={...a,action_description:i});let l=a.action_data?.action_name||"",u=a.action_description||"",h=V(l);if(!h)return[`${s}// ${n}: Unknown action: ${l}`];let k={imports:r.imports},T=h(a,n,k);if(M(a))return[`${s}// ${n}: ${y(u)}`,`${s}page = agent.agentServices.validatePage(page);`,...T.map(w=>`${s}${w}`)];let P=JSON.stringify(u),O=T.map(w=>`${s} ${w}`),L=W(a),I=c?`'${c}'`:"undefined";return[`${s}// ${n}: ${y(u)}`,`${s}page = agent.agentServices.validatePage(page);`,`${s}await agent.step(page, async () => {`,...O,`${s}}, ${P}, '${n}', ${I}, ${L});`]}function yt(t,e,n,r){let s=" ".repeat(e),i=[];t.description&&t.description.trim()&&i.push(`${s}// Step: ${y(t.description)}`);let c=m(t.statements,e,r,n);return i.push(...c),i}function $t(t,e,n,r){let s=" ".repeat(e),i=[];if(i.push(`${s}// ${n}: Conditional check`),t.condition.type==="JS_CODE")i.push(`${s}if (${t.condition.expression}) {`);else{i.push(`${s}// AI Condition: ${y(t.condition.expression)}`);let p=JSON.stringify(t.condition.expression);i.push(`${s}if (await agent.evaluate(page, ${p}, "${n}")) {`)}let c=m(t.then,e+1,r,`${n}.then`);if(i.push(...c),t.else&&t.else.length>0){i.push(`${s}} else {`);let p=m(t.else,e+1,r,`${n}.else`);i.push(...p)}return i.push(`${s}}`),i}function St(t,e,n,r){let s=" ".repeat(e),i=[];i.push(`${s}// ${n}: Loop`);let c=t.timeout_ms??R,p=c/1e3,g=t.timeout_ms?`While loop exceeded timeout of ${p}s`:`While loop exceeded default timeout of ${p}s`,a=`loop_${n.replace(/\./g,"_")}`;if(i.push(`${s}const ${a}_start = Date.now();`),i.push(`${s}const ${a}_timeout = ${c};`),i.push(`${s}const ${a}_check = () => {`),i.push(`${s} if (Date.now() - ${a}_start > ${a}_timeout) {`),i.push(`${s} throw new Error('${g}');`),i.push(`${s} }`),i.push(`${s} return true;`),i.push(`${s}};`),t.condition.type==="JS_CODE")i.push(`${s}while (${a}_check() && (${t.condition.expression})) {`);else{i.push(`${s}// AI Loop Condition: ${y(t.condition.expression)}`);let u=JSON.stringify(t.condition.expression);i.push(`${s}while (${a}_check() && await agent.evaluate(page, ${u}, "${n}")) {`)}let l=m(t.body,e+1,r,`${n}.body`);return i.push(...l),i.push(`${s}}`),i}var K={name:"shiplightai",version:"0.1.10",type:"module",description:"Shiplight CLI for running and debugging .test.yaml files",main:"dist/index.js",types:"dist/index.d.ts",bin:{shiplight:"dist/cli.js"},exports:{".":{types:"./dist/index.d.ts",import:"./dist/index.js",require:"./dist/cjs/index.cjs",default:"./dist/index.js"},"./fixture":{types:"./dist/fixture.d.ts",import:"./dist/fixture.js",require:"./dist/cjs/fixture.cjs",default:"./dist/fixture.js"}},files:["dist","README.md"],publishConfig:{registry:"https://registry.npmjs.org",access:"public"},scripts:{build:"tsup","build:cli":"tsup",clean:"rm -rf dist",dev:"tsup --watch",test:"playwright test",typecheck:"tsc --noEmit"},dependencies:{"@babel/plugin-transform-typescript":"^7.27.0","@babel/preset-env":"^7.26.9","@babel/preset-typescript":"^7.27.0","@anthropic-ai/claude-agent-sdk":"^0.1.72","@modelcontextprotocol/sdk":"^0.5.0",axios:"^1.6.0",dotenv:"^16.0.3",express:"^4.21.0",glob:"^13.0.0",open:"^10.1.0",sharp:"^0.34.5",uuid:"^11.1.0",yaml:"^2.8.0",zod:"^3.22.0","zod-to-json-schema":"^3.24.6"},devDependencies:{"@playwright/test":"1.55.0","@types/express":"^4.17.21","@types/node":"^24.0.0",copilot3:"workspace:*","mcp-tools":"workspace:*","sdk-core":"workspace:*","sdk-internal":"workspace:*","shiplight-types":"workspace:*",tsup:"^8.3.5",typescript:"5.5.4","web-session":"workspace:*"},peerDependencies:{"@playwright/test":">=1.40.0"},engines:{node:">=22.0.0"},keywords:["playwright","yaml","testing","automation","ai","shiplight","mcp"],author:"Shiplight",license:"MIT"};var b=K.version;function q(t,e){let n=[];n.push(`// @generated by shiplightai v${b}`),n.push(...kt()),n.push(""),e?.use&&Object.keys(e.use).length>0&&(n.push(`test.use(${JSON.stringify(e.use,null,2)});`),n.push(""));let r=e?.testName||t.goal||"Generated test",s=e?.tags&&e.tags.length>0?e.tags.map(a=>`@${a}`).join(" ")+" ":"";n.push(`test('${s}${v(r)}', async ({ page, agent }) => {`);let i=e?.startingUrl||t.url;if(i){let a;try{a=new URL(i).pathname}catch{a=i}n.push(` await page.goto('${v(a)}', { waitUntil: 'domcontentloaded' });`),n.push("")}let c=new Set,p={imports:c};if(t.teardown&&t.teardown.length>0){if(n.push(" try {"),t.statements&&t.statements.length>0){n.push(" // Test steps");let l=m(t.statements,2,p);n.push(...l)}n.push(" } finally {"),n.push(" // Teardown");let a=m(t.teardown,2,p,"teardown");n.push(...a),n.push(" }")}else if(t.statements&&t.statements.length>0){n.push(" // Test steps");let a=m(t.statements,1,p);n.push(...a)}if(n.push("});"),c.size>0){let a=0;for(let u=0;u<n.length;u++)n[u].startsWith("import ")&&(a=u+1);let l=Array.from(c);n.splice(a,0,...l)}return n.join(`
|
|
6
6
|
`)}function kt(){return["import { test, expect } from 'shiplightai/fixture';"]}function z(t){try{return Tt(t).mtimeMs}catch{return 0}}var Nt=`// @generated by shiplightai v${b}`;function Pt(t,e){if(!Ot(t)||B(t,"utf-8").split(`
|
|
7
7
|
`,1)[0]!==Nt)return!1;let r=z(t);for(let s of e)if(z(s)>r)return!1;return!0}function X(t){let e=bt("**/*.test.yaml",{cwd:t.cwd,ignore:["**/node_modules/**"]}),n=[];for(let r of e){let s=vt(t.cwd,r),i=s.replace(/\.test\.yaml$/,".yaml.spec.ts"),c=B(s,"utf-8");try{let{testFlow:p,name:g,tags:a,use:l,referencedTemplatePaths:u}=G(c,s);if(Pt(i,[s,...u]))continue;let h=q(p,{testName:g,tags:a,use:l});At(i,h)}catch(p){console.error(`[shiplight] Failed to transpile ${r}:`,p),n.push({file:r,error:p})}}if(n.length>0)throw new Error(`[shiplight] Transpilation failed for ${n.length} file(s):
|
|
8
8
|
`+n.map(r=>` - ${r.file}`).join(`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shiplightai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shiplight CLI for running and debugging .test.yaml files",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -53,11 +53,11 @@
|
|
|
53
53
|
"@types/node": "^24.0.0",
|
|
54
54
|
"tsup": "^8.3.5",
|
|
55
55
|
"typescript": "5.5.4",
|
|
56
|
+
"copilot3": "0.1.0",
|
|
56
57
|
"sdk-core": "0.1.0",
|
|
57
|
-
"sdk-internal": "0.1.1",
|
|
58
58
|
"mcp-tools": "1.0.0",
|
|
59
|
+
"sdk-internal": "0.1.1",
|
|
59
60
|
"web-session": "0.1.0",
|
|
60
|
-
"copilot3": "0.1.0",
|
|
61
61
|
"shiplight-types": "0.1.0"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|