@shiplightai/mcp 0.1.12 → 0.1.13
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/index.js +12 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5588,6 +5588,8 @@ interface WhileLoop {
|
|
|
5588
5588
|
|
|
5589
5589
|
YAML is the format for local \`.test.yaml\` files used with \`shiplightai\`. These files are transpiled into Playwright test code and run with \`npx playwright test\`.
|
|
5590
5590
|
|
|
5591
|
+
**Prerequisites:** Before writing any \`.test.yaml\` files, you MUST call \`init_local_project\` to scaffold the project structure (package.json, playwright.config.ts, tests/ directory). Without it, the tests cannot be discovered or run.
|
|
5592
|
+
|
|
5591
5593
|
YAML is also accepted by cloud API tools (\`save_test_case\`, \`get_test_case\` with \`output_format: "yaml"\`).
|
|
5592
5594
|
|
|
5593
5595
|
For the JSON schema used by cloud APIs, see \`shiplight://schemas/testflow-json-v1.2.0\`.
|
|
@@ -5991,6 +5993,15 @@ ${Cxr(e,ND)}
|
|
|
5991
5993
|
\`\`\`json
|
|
5992
5994
|
{"verify": {"statement": "The success message is displayed", "description": "Verify submission succeeded"}}
|
|
5993
5995
|
\`\`\`
|
|
5996
|
+
|
|
5997
|
+
## Saving Flows as Local YAML Tests
|
|
5998
|
+
|
|
5999
|
+
After verifying a flow in the browser, you can save it as a rerunnable local test file. The YAML format supports embedding ActionEntity data (locator, xpath, frame_path) from your browser session into each step, which **speeds up test execution** by skipping AI element detection.
|
|
6000
|
+
|
|
6001
|
+
To create local test files:
|
|
6002
|
+
1. Call \`init_local_project\` to scaffold a Playwright project
|
|
6003
|
+
2. Read resource \`shiplight://schemas/testflow-yaml-v1.2.0\` to learn the YAML format
|
|
6004
|
+
3. Write \`.test.yaml\` files into the \`tests/\` directory, including action entities collected during your browser session via \`get_locator\`
|
|
5994
6005
|
`}function M6e(){return Hrt}async function L6e(e){switch(e){case"shiplight://schemas/testflow-json-v1.2.0":return Grt();case"shiplight://schemas/testflow-yaml-v1.2.0":return $rt();case"shiplight://schemas/action-entity":return await Vrt();default:return}}tE();import{z as Qn}from"zod";import{zodToJsonSchema as hG}from"zod-to-json-schema";import{z as Wo}from"zod";import{z as ede}from"zod";import{zodToJsonSchema as nb}from"zod-to-json-schema";import{z as Ro}from"zod";import{zodToJsonSchema as nde}from"zod-to-json-schema";import{z as Ci}from"zod";import{zodToJsonSchema as iv}from"zod-to-json-schema";import{z as Fn}from"zod";import{zodToJsonSchema as pG}from"zod-to-json-schema";import{v4 as Ghi}from"uuid";import{z as cs}from"zod";import{zodToJsonSchema as fG}from"zod-to-json-schema";import sv from"fs";import ode from"path";import Vhi from"os";import jhi from"axios";import{z as p0}from"zod";import{zodToJsonSchema as Rhr}from"zod-to-json-schema";var xhi=_n(PD(),1),Ihi=_n(MW(),1),Fhi=_n(Jst(),1),_hi=_n(MW(),1),Shi=_n(PD(),1);tE();tE();var Lhi=_n(PD(),1);var Ohi=_n(MW(),1);import*as ZPe from"path";import{fileURLToPath as Phi}from"url";import{exec as Chi,spawn as Vns}from"child_process";import{promisify as yhi}from"util";import Yns from"sharp";import{z as zns}from"zod";import{appendFileSync as _hr}from"fs";import{z as eis}from"zod";import{z as Pm}from"zod";import{z as za}from"zod";import{z as nv}from"zod";import{z as Ul}from"zod";var t$t=_n(eft(),1);fi();var r$t=_n(uwe(),1);import BOn from"node:os";import vOn from"node:fs";import DOn from"node:process";import Goe from"node:fs";import vGt from"node:path";import IOn from"node:fs";import FOn from"node:path";import xGt from"node:path";import _On from"node:fs/promises";import TOn from"node:fs";import kOn from"node:path";import NOn from"node:fs";import QOn from"node:path";import LOn from"node:fs/promises";import AS from"node:path";fi();var lwe=(e=>(e[e.consoleMessage=0]="consoleMessage",e[e.commandRequestMessage=1]="commandRequestMessage",e[e.commandResponseMessage=2]="commandResponseMessage",e[e.hookTriggerMessage=3]="hookTriggerMessage",e[e.hookResultMessage=4]="hookResultMessage",e[e.expectRequestMessage=5]="expectRequestMessage",e[e.expectResponseMessage=6]="expectResponseMessage",e[e.expectMatchersRequest=7]="expectMatchersRequest",e[e.expectMatchersResponse=8]="expectMatchersResponse",e[e.coverageMap=9]="coverageMap",e[e.customCommand=10]="customCommand",e[e.initiateBrowserStateRequest=11]="initiateBrowserStateRequest",e[e.initiateBrowserStateResponse=12]="initiateBrowserStateResponse",e[e.browserTestResult=13]="browserTestResult",e))(lwe||{});function Z8t(e){return{all:e=e||new Map,on:function(t,r){var n=e.get(t);n?n.push(r):e.set(t,[r])},off:function(t,r){var n=e.get(t);n&&(r?n.splice(n.indexOf(r)>>>0,1):e.set(t,[]))},emit:function(t,r){var n=e.get(t);n&&n.slice().map(function(i){i(r)}),(n=e.get("*"))&&n.slice().map(function(i){i(t,r)})}}}fi();fi();fi();var Bin=Object.defineProperty,vin=(e,t)=>{for(var r in t)Bin(e,r,{get:t[r],enumerable:!0})},Wr={NULL:"\uE000",Unidentified:"\uE000",Cancel:"\uE001",Help:"\uE002",Backspace:"\uE003","Back space":"\uE003",Tab:"\uE004",Clear:"\uE005",Return:"\uE006",Enter:"\uE007",Shift:"\uE008",Control:"\uE009","Control Left":"\uE009","Control Right":"\uE051",Alt:"\uE00A",Pause:"\uE00B",Escape:"\uE00C",Space:"\uE00D"," ":"\uE00D",PageUp:"\uE00E",Pageup:"\uE00E",Page_Up:"\uE00E",PageDown:"\uE00F",Pagedown:"\uE00F",Page_Down:"\uE00F",End:"\uE010",Home:"\uE011",ArrowLeft:"\uE012","Left arrow":"\uE012",Arrow_Left:"\uE012",ArrowUp:"\uE013","Up arrow":"\uE013",Arrow_Up:"\uE013",ArrowRight:"\uE014","Right arrow":"\uE014",Arrow_Right:"\uE014",ArrowDown:"\uE015","Down arrow":"\uE015",Arrow_Down:"\uE015",Insert:"\uE016",Delete:"\uE017",Semicolon:"\uE018",Equals:"\uE019","Numpad 0":"\uE01A","Numpad 1":"\uE01B","Numpad 2":"\uE01C","Numpad 3":"\uE01D","Numpad 4":"\uE01E","Numpad 5":"\uE01F","Numpad 6":"\uE020","Numpad 7":"\uE021","Numpad 8":"\uE022","Numpad 9":"\uE023",Multiply:"\uE024",Add:"\uE025",Separator:"\uE026",Subtract:"\uE027",Decimal:"\uE028",Divide:"\uE029",F1:"\uE031",F2:"\uE032",F3:"\uE033",F4:"\uE034",F5:"\uE035",F6:"\uE036",F7:"\uE037",F8:"\uE038",F9:"\uE039",F10:"\uE03A",F11:"\uE03B",F12:"\uE03C",Command:"\uE03D",Meta:"\uE03D",ZenkakuHankaku:"\uE040",Zenkaku_Hankaku:"\uE040"};var syt='"<Screenshot[base64]>"',Din='"<Script[base64]>"',eyt=/return \((async )?function (\w+)/;function xin(e){let t=e.__elementOverrides__?e.__elementOverrides__.value:{};for(let[r,n]of Object.entries(t)){if(typeof n!="function")throw new Error("overwriteCommand: commands be overwritten only with functions, command: "+r);if(!e[r])throw new Error("overwriteCommand: no command to be overwritten: "+r);let i=e[r].value;if(typeof i!="function")throw new Error("overwriteCommand: only functions can be overwritten, command: "+r);let s=i;delete e[r];let o=function(...a){let u=this;return n.apply(u,[function(...c){let d=this||u;return s.apply(d,arguments)},...a])};e[r]={value:o,configurable:!0}}delete e.__elementOverrides__,e.__elementOverrides__={value:{}}}function oyt(e,t,r=!1){let n=t.map(i=>(typeof i=="string"&&/^\s*(?:(?:async\s+)?function(?:\s+\w+)?\s*\(|!function\(|return\s+\(?(?:async\s+)?function|\([^)]*\)\s*=>|\w+\s*=>)/.test(i.trim())?i="<fn>":typeof i=="string"&&!e.startsWith("findElement")&&!e.startsWith("switch")&&hwe(i)?i=syt:typeof i=="string"?i=`"${i}"`:typeof i=="function"?i="<fn>":i===null?i="null":typeof i=="object"?i=r?JSON.stringify(i):"<object>":typeof i>"u"&&(i=typeof i),i)).join(", ");return`${e}(${n})`}function pwe(e){if(typeof e>"u")return"<empty result>";if(typeof e!="object"||!e)return e;if("file"in e&&typeof e.file=="string"&&hwe(e.file))return syt;if("script"in e&&typeof e.script=="string"&&hwe(e.script))return Din;if("script"in e&&typeof e.script=="string"&&e.script.match(eyt)){let t=e.script.match(eyt)[2];return{...e,script:`${t}(...) [${Buffer.byteLength(e.script,"utf-8")} bytes]`}}else if("script"in e&&typeof e.script=="string"&&e.script.startsWith("!function("))return{...e,script:`<minified function> [${Buffer.byteLength(e.script,"utf-8")} bytes]`};return e}async function ayt(e,t="default"){try{let r=await import(e);if(t in r)return r[t]}catch{throw new Error(`Couldn't import "${e}"! Do you have it installed? If not run "npm install ${e}"!`)}throw new Error(`Couldn't find "${t}" in module "${e}"`)}function hwe(e){if(typeof e!="string")throw new Error("Expected string but received invalid type.");let t=e.length,r=/[^A-Z0-9+/=]/i;if(!t||t%4!==0||r.test(e))return!1;let n=e.indexOf("=");return n===-1||n===t-1||n===t-2&&e[t-1]==="="}var fX=(e=0)=>new Promise(t=>setTimeout(t,e));function sr(e){let t=e;return t.parent?sr(t.parent):e}var cwe={browser:function(){},element:function(){}},tyt=Z8t,ryt=["on","off","emit","once","removeListener","removeAllListeners"];function dO(e,t,r={}){let n=cwe[r.scope?.value||"browser"];delete r.scope;let i=Object.create(n.prototype),s=lr("webdriver"),o=tyt(),a={on:o.on.bind(o),off:o.off.bind(o),emit:o.emit.bind(o),once:(l,c)=>{let d=(...p)=>{o.off(l,d),c(...p)};o.on(l,d)},removeListener:o.off.bind(o),removeAllListeners:l=>{l?o.off(l):Object.assign(o,tyt())}};function u(l,c,d){if(r.commandList={value:Object.keys(r)},r.options={value:e},"requestedCapabilities"in e&&(r.requestedCapabilities={value:e.requestedCapabilities}),typeof c=="function")for(let[C,{value:y}]of Object.entries(r))typeof y!="function"||Object.keys(ryt).includes(C)||(r[C].value=c(C,y,r),r[C].configurable=!0);xin.call(this,r);let{puppeteer:p,...f}=r;r.__propertiesObject__={value:f};let m=Object.create(i,r);return m.sessionId=l,n.name==="Browser"&&"capabilities"in e&&(m.capabilities=e.capabilities),typeof t=="function"&&(m=t(m,e)),m.addCommand=function(C,y,b=!1,v,x){let{attachToElement:I,disableElementImplicitWait:S,proto:R,instances:Q}=typeof b=="object"&&b!==null?b:{attachToElement:b,proto:v,instances:x},U=typeof c=="function"?c(C,y):y;if(I?(S&&d&&!d.includes(C)&&d.push(C),Q&&Object.values(Q).forEach(T=>{T.__propertiesObject__[C]={value:U}}),this.__propertiesObject__[C]={value:U}):u.lift(C,U,R),typeof process.send=="function"&&process.env.WDIO_WORKER_ID){let T={origin:"worker",name:"workerEvent",args:{type:lwe.customCommand,value:{commandName:C,cid:process.env.WDIO_WORKER_ID}}};process.send(T)}},m.overwriteCommand=function(C,y,b=!1,v,x){let I=typeof c=="function"?c(C,y):y;if(b)x?Object.values(x).forEach(S=>{S.__propertiesObject__.__elementOverrides__.value[C]=I}):this.__propertiesObject__.__elementOverrides__.value[C]=I;else if(m[C]){let S=m[C];delete m[C],u.lift(C,I,v,(...R)=>S.apply(this,R))}else throw new Error("overwriteCommand: no command to be overwritten: "+C)},m}u.lift=function(l,c,d,p){(d||i)[l]=function(...m){s.info("COMMAND",oyt(l,m)),this.emit("command",{command:l,body:m}),Object.defineProperty(c,"name",{value:l,writable:!1});try{let C=c.apply(this,p?[p,...m]:m);return Iin(C)?C.then(y=>{let b=y,v=y;b instanceof cwe.element?v=`WebdriverIO.Element<${b.elementId||b.selector}>`:y instanceof cwe.browser&&(v="WebdriverIO.Browser"),s.info("RESULT",v),this.emit("result",{command:l,result:{value:y},name:l})}).catch(y=>{this.emit("result",{command:l,result:{error:y}})}):this.emit("result",{command:l,result:{value:C}}),C}catch(C){throw this.emit("result",{command:l,result:{error:C}}),C}}};for(let l of ryt)i[l]=function(...c){switch(l==="on"&&c[0]==="dialog"&&a.emit("_dialogListenerRegistered"),l==="off"&&c[0]==="dialog"&&a.emit("_dialogListenerRemoved"),l){case"on":a.on(c[0],c[1]);break;case"off":case"removeListener":a.off(c[0],c[1]);break;case"emit":a.emit(c[0],c[1]);break;case"once":a.once(c[0],c[1]);break;case"removeAllListeners":a.removeAllListeners(c[0]);break}return this};return u}var Iin=e=>e!==null&&typeof e=="object"&&typeof e.then=="function"&&typeof e.catch=="function";var gTi=lr("@wdio/utils");var CTi=lr("@wdio/utils:initializeServices");var cO={};vin(cO,{every:()=>Oin,everySeries:()=>Pin,filter:()=>Uin,filterSeries:()=>qin,find:()=>kin,findIndex:()=>Nin,findIndexSeries:()=>Qin,findSeries:()=>Rin,forEach:()=>Fin,forEachSeries:()=>_in,map:()=>Sin,mapSeries:()=>Tin,reduce:()=>Hin,some:()=>Min,someSeries:()=>Lin});var Fin=async(e,t,r)=>{let n=[];for(let i=0;i<e.length;i++)if(i in e){let s=Promise.resolve(e[i]).then(o=>t.call(r||void 0,o,i,e));n.push(s)}await Promise.all(n)},_in=async(e,t,r)=>{for(let n=0;n<e.length;n++)n in e&&await t.call(r||void 0,await e[n],n,e)},Sin=async(e,t,r)=>{let n=[];for(let i=0;i<e.length;i++)i in e&&(n[i]=Promise.resolve(e[i]).then(s=>t.call(r||void 0,s,i,e)));return Promise.all(n)},Tin=async(e,t,r)=>{let n=[];for(let i=0;i<e.length;i++)i in e&&(n[i]=await t.call(r||void 0,await e[i],i,e));return n},kin=(e,t,r)=>new Promise((n,i)=>{if(e.length===0)return n(void 0);let s=1;for(let o=0;o<e.length;o++){let a=u=>{u?n(e[o]):s===e.length&&n(void 0),s++};Promise.resolve(e[o]).then(u=>t.call(r||void 0,u,o,e)).then(a).catch(i)}}),Rin=async(e,t,r)=>{for(let n=0;n<e.length;n++)if(await t.call(r||void 0,await e[n],n,e))return e[n]},Nin=(e,t,r)=>new Promise((n,i)=>{if(e.length===0)return n(-1);let s=1;for(let o=0;o<e.length;o++){let a=u=>{u?n(o):s===e.length&&n(-1),s++};Promise.resolve(e[o]).then(u=>t.call(r||void 0,u,o,e)).then(a).catch(i)}}),Qin=async(e,t,r)=>{for(let n=0;n<e.length;n++)if(await t.call(r||void 0,await e[n],n,e))return n},Min=(e,t,r)=>new Promise((n,i)=>{if(e.length===0)return n(!1);let s=1;for(let o=0;o<e.length;o++){if(!(o in e)){s++;continue}let a=u=>{u?n(!0):s===e.length&&n(!1),s++};Promise.resolve(e[o]).then(u=>t.call(r||void 0,u,o,e)).then(a).catch(i)}}),Lin=async(e,t,r)=>{for(let n=0;n<e.length;n++)if(n in e&&await t.call(r||void 0,await e[n],n,e))return!0;return!1},Oin=(e,t,r)=>new Promise((n,i)=>{if(e.length===0)return n(!0);let s=1;for(let o=0;o<e.length;o++){if(!(o in e)){s++;continue}let a=u=>{u?s===e.length&&n(!0):n(!1),s++};Promise.resolve(e[o]).then(u=>t.call(r||void 0,u,o,e)).then(a).catch(i)}}),Pin=async(e,t,r)=>{for(let n=0;n<e.length;n++)if(n in e&&!await t.call(r||void 0,await e[n],n,e))return!1;return!0},Uin=(e,t,r)=>new Promise((n,i)=>{let s=[];for(let o=0;o<e.length;o++)o in e&&(s[o]=Promise.resolve(e[o]).then(a=>t.call(r||void 0,a,o,e)).catch(i));return Promise.all(s.map(async(o,a)=>{if(await o)return await e[a]})).then(o=>o.filter(a=>typeof a<"u")).then(n,i)}),qin=async(e,t,r)=>{let n=[];for(let i=0;i<e.length;i++)i in e&&await t.call(r||void 0,await e[i],i,e)&&n.push(await e[i]);return n},Hin=async(e,t,r)=>{if(e.length===0&&r===void 0)throw TypeError("Reduce of empty array with no initial value");let n,i;for(r!==void 0?(i=r,n=0):(i=e[0],n=1),n;n<e.length;n++)n in e&&(i=await t(await i,await e[n],n,e));return i},dwe=lr("@wdio/utils:shim"),qI=!1,nyt=["$","$$","custom$","custom$$","shadow$","shadow$$","react$","react$$","nextElement","previousElement","parentElement"],Gin=["elementId","error","selector","parent","index","isReactElement","length"],$in=["action","actions"],Vin=["then","catch","finally"],jin=["getElement","getElements"];async function iyt(e,t=[],r=[]){Array.isArray(t)||(t=[t]),Array.isArray(r)||(r=[r]);let n=function(a,u){if(/^(sync|async) skip; aborting execution$/.test(a.message))return u(),!0;if(/^=> marked Pending/.test(a))return u(a),!0},i=t.map(a=>new Promise((u,l)=>{let c;try{c=a.apply(this,r)}catch(d){return n(d,l)?void 0:(dwe.error(d.stack),u(d))}if(c&&typeof c.then=="function")return c.then(u,d=>{n(d,l)||(dwe.error(d.stack||d.message),u(d))});u(c)})),s=Date.now(),o=await Promise.all(i);return i.length&&dwe.debug(`Finished to run "${e}" hook in ${Date.now()-s}ms`),o}function HI(e,t){async function r(...s){let o=[e,s];!qI&&this.options.beforeCommand&&(qI=!0,await iyt.call(this,"beforeCommand",this.options.beforeCommand,o),qI=!1);let a,u;try{a=await t.apply(this,s)}catch(l){u=l}if(!qI&&this.options.afterCommand){qI=!0;let l=[...o,a,u];await iyt.call(this,"afterCommand",this.options.afterCommand,l),qI=!1}if(u)throw u;return a}function n(s,o,a,u){return new Proxy(Promise.resolve(s).then(l=>o.call(l,...a)),{get:(l,c)=>{if(typeof c=="symbol"||c==="entries")return()=>({i:0,target:l,async next(){let p=await this.target;if(!Array.isArray(p))throw new Error("Can not iterate over non array");return this.i<p.length?c==="entries"?{value:[this.i,p[this.i++]],done:!1}:{value:p[this.i++],done:!1}:{done:!0}}});let d=parseInt(c,10);return isNaN(d)?nyt.includes(c)||c.endsWith("$")?HI(c,function(...p){return this[c].apply(this,p)}):e.endsWith("$$")&&typeof cO[c]=="function"?(...p)=>n(l,function(...f){return cO[c](this,...f)},p):Gin.includes(c)?l.then(p=>p[c]):Vin.includes(c)?l[c].bind(l):jin.includes(c)?()=>l:(...p)=>l.then(async f=>{if(!f){let m="Element could not be found",C=await s;throw Array.isArray(C)&&u&&u.prop==="get"&&(m=`Index out of bounds! $$(${u.args[0]}) returned only ${C.length} elements.`),new Error(m)}if(c==="toJSON")return{ELEMENT:f.elementId};if(typeof f[c]!="function")throw new Error(`Can't call "${c}" on element with selector "${f.selector}", it is not a function`);return f[c](...p)}):n(l,function(p){if(p>=this.length){let f=sr(this);return f.waitUntil(async()=>{let m=await this.parent[this.foundWith](this.selector);return m.length>p?m[p]:!1},{timeout:f.options.waitforTimeout,timeoutMsg:`Index out of bounds! $$(${this.selector}) returned only ${this.length} elements.`})}return this[p]},[c],{prop:c,args:a})}})}function i(...s){return n(this,r,s)}return function(...s){return(nyt.includes(e)||e.endsWith("$")?i:$in.includes(e)?t:r).apply(this,s)}}fi();fi();import zOn from"node:fs/promises";import KOn from"node:path";import Zgn from"node:os";var yon=_n(v4t(),1),bon=_n(Nwe(),1),won=_n(Lwe(),1),Vwe=_n(xX(),1),Bon=_n(R4t(),1);var FX=Vwe.default;fi();fi();var Don={sendCommand:{socket:{command:"send",description:"Send socket commands via WebDriver Bidi",ref:"https://github.com/w3c/webdriver-bidi",parameters:[{name:"params",type:"CommandData",description:"socket payload",required:!0}],returns:{type:"Object",name:"CommandResponse",description:"WebDriver Bidi response"}}},sendAsyncCommand:{socket:{command:"sendAsync",description:"Send asynchronous socket commands via WebDriver Bidi",ref:"https://github.com/w3c/webdriver-bidi",parameters:[{name:"params",type:"CommandData",description:"socket payload",required:!0}],returns:{type:"Number",name:"id",description:"id of WebDriver Bidi request"}}},"session.status":{socket:{command:"sessionStatus",description:'WebDriver Bidi command to send command method "session.status" with parameters.',ref:"https://w3c.github.io/webdriver-bidi/#command-session-status",parameters:[{name:"params",type:"`remote.EmptyParams`",description:"<pre>\\{\\}</pre>",required:!0}],returns:{type:"Object",name:"local.SessionStatusResult",description:"Command return value with the following interface:\n ```ts\n {\n ready: boolean;\n message: string;\n }\n ```"}}},"session.new":{socket:{command:"sessionNew",description:'WebDriver Bidi command to send command method "session.new" with parameters.',ref:"https://w3c.github.io/webdriver-bidi/#command-session-new",parameters:[{name:"params",type:"`remote.SessionNewParameters`",description:"<pre>\\{<br /> capabilities: SessionCapabilitiesRequest;<br />\\}</pre>",required:!0}],returns:{type:"Object",name:"local.SessionNewResult",description:`Command return value with the following interface:
|
|
5995
6006
|
\`\`\`ts
|
|
5996
6007
|
{
|
|
@@ -8078,7 +8089,7 @@ Returns the Playwright locator, xpath, frame path, tag name, and text content
|
|
|
8078
8089
|
for the element at the given index.
|
|
8079
8090
|
|
|
8080
8091
|
Use this to collect locator information for building test flows without
|
|
8081
|
-
actually interacting with the element.`};async function zhr(e,t){let{session_id:r,element_index:n}=t;if(!e.getSession(r))throw new Error(`Session ${r} not found`);return e.getLocator(r,n)}var tde=null,rde=null;function Khr(){return tde||(tde=ta.buildActionUnionSchemaForTools([...ND],!0),tde)}function Xhr(){if(rde)return rde;let e=Khr(),t=ede.array(e),r=Lhr();return rde={name:r.name,description:r.description,inputSchema:nb(ede.object({session_id:ede.string().describe("Session ID"),actions:t.describe("List of actions to execute"),stop_on_error:ede.boolean().optional().describe("Stop execution on first error (default: true)")}),{$refStrategy:"none"})},rde}var ude=class ov{constructor(t){this.backend=t}static navigateTool={...Nhr,inputSchema:nb(eUe,{$refStrategy:"none"})};async navigate(t){let r=eUe.parse(t),n=await Qhr(this.backend,r);return JSON.stringify(n)}static get actTool(){return Xhr()}async act(t){let r=Mhr.parse(t),n=await Ohr(this.backend,r);return JSON.stringify(n)}static updateVariablesTool={...Phr,inputSchema:nb(tUe,{$refStrategy:"none"})};async updateVariables(t){let r=tUe.parse(t),n=Uhr(this.backend,r);return JSON.stringify(n)}static clearExecutionHistoryTool={...qhr,inputSchema:nb(rUe,{$refStrategy:"none"})};async clearExecutionHistory(t){let r=rUe.parse(t),n=Hhr(this.backend,r);return JSON.stringify(n)}static getPageInfoTool={...Ghr,inputSchema:nb(nUe,{$refStrategy:"none"})};async getPageInfo(t){let r=nUe.parse(t),n=await $hr(this.backend,r);return JSON.stringify(n)}static getDomTool={...Vhr,inputSchema:nb(iUe,{$refStrategy:"none"})};async getDom(t){let r=iUe.parse(t),n=await jhr(this.backend,r);return JSON.stringify(n)}static takeScreenshotTool={...Whr,inputSchema:nb(sUe,{$refStrategy:"none"})};async takeScreenshot(t){let r=sUe.parse(t),n=await Yhr(this.backend,r);return JSON.stringify(n)}static getLocatorTool={...Jhr,inputSchema:nb(oUe,{$refStrategy:"none"})};async getLocator(t){let r=oUe.parse(t),n=await zhr(this.backend,r);return JSON.stringify(n)}static get toolDefinitions(){return[ov.navigateTool,ov.getPageInfoTool,ov.getDomTool,ov.takeScreenshotTool,ov.actTool,ov.getLocatorTool]}},lde=class AG{constructor(t){this.backend=t}static getConsoleLogsTool={name:"get_browser_console_logs",description:"Get console logs from the browser (errors, warnings, etc.)",inputSchema:nde(Ro.object({session_id:Ro.string().describe("Browser session ID"),since_timestamp:Ro.number().optional().describe("Only return logs after this timestamp"),log_types:Ro.array(Ro.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}=Ro.object({session_id:Ro.string(),since_timestamp:Ro.number().optional(),log_types:Ro.array(Ro.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:nde(Ro.object({session_id:Ro.string().describe("Browser session ID"),since_timestamp:Ro.number().optional().describe("Only return logs after this timestamp"),status_filter:Ro.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}=Ro.object({session_id:Ro.string(),since_timestamp:Ro.number().optional(),status_filter:Ro.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:nde(Ro.object({session_id:Ro.string().describe("Browser session ID")}),{$refStrategy:"none"})};async clearLogs(t){let{session_id:r}=Ro.object({session_id:Ro.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:nde(Ro.object({path:Ro.string().describe("Artifact file path from previous tool output"),return_format:Ro.enum(["base64","text"]).optional().describe("Return format (auto-detected if not specified)")}),{$refStrategy:"none"})};async getLocalArtifact(t){let{path:r}=Ro.object({path:Ro.string(),return_format:Ro.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=[AG.getConsoleLogsTool,AG.getNetworkLogsTool,AG.clearLogsTool,AG.getLocalArtifactTool]},cde=class ib{constructor(t){this.apiClient=t}requireApi(t){if(!this.apiClient)throw new Error(DQ(t));return this.apiClient}static listEnvironmentsTool={name:"list_environments",description:"List all testing environments (staging, production, etc.)",inputSchema:iv(Ci.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:iv(Ci.object({environment_id:Ci.number().optional().describe("Environment ID to filter by"),environment_url:Ci.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}=Ci.object({environment_id:Ci.number().optional(),environment_url:Ci.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:iv(Ci.object({test_account_id:Ci.number().describe("Test account ID")}),{$refStrategy:"none"})};async getTestAccount(t){let r=this.requireApi("get_test_account"),{test_account_id:n}=Ci.object({test_account_id:Ci.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:iv(Ci.object({environment_id:Ci.number().describe("Environment ID"),username:Ci.string().describe("Username"),password:Ci.string().describe("Password"),login_config:Ci.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}=Ci.object({environment_id:Ci.number(),username:Ci.string(),password:Ci.string(),login_config:Ci.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:iv(Ci.object({parent_id:Ci.number().nullable().optional().describe("Parent folder ID, null for root"),search:Ci.string().optional().describe("Search query")}),{$refStrategy:"none"})};async listFolders(t){let r=this.requireApi("list_folders"),{parent_id:n,search:i}=Ci.object({parent_id:Ci.number().nullable().optional(),search:Ci.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:iv(Ci.object({name:Ci.string().describe("Folder name"),description:Ci.string().optional().describe("Folder description"),parent_id:Ci.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}=Ci.object({name:Ci.string(),description:Ci.string().optional(),parent_id:Ci.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:iv(Ci.object({folder_id:Ci.number().describe("Folder ID")}),{$refStrategy:"none"})};async getFolder(t){let r=this.requireApi("get_folder"),{folder_id:n}=Ci.object({folder_id:Ci.number()}).parse(t),i=await r.getFolder(n);return JSON.stringify({folder:i})}static toolDefinitions=[ib.listEnvironmentsTool,ib.listTestAccountsTool,ib.getTestAccountTool,ib.createTestAccountTool,ib.listFoldersTool,ib.createFolderTool,ib.getFolderTool]};function $hi(e,t){let r=e.replace(/\/+$/,"");return t.find(n=>r===n.url.replace(/\/+$/,""))}var dde=class sde{constructor(t){this.apiClient=t}requireApi(t){if(!this.apiClient)throw new Error(DQ(t));return this.apiClient}parseFlowInput(t){return typeof t=="string"?Y5.parse(FQ(t)):Y5.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:pG(Fn.object({flow:Fn.union([Fn.string(),Fn.any()]).describe("Test flow as JSON object or YAML string"),folder_id:Fn.number().optional().describe("Folder ID"),name:Fn.string().optional().describe("Test case name"),environment_id:Fn.number().optional().describe("Environment ID for test execution"),test_account_id:Fn.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}=Fn.object({flow:Fn.any(),folder_id:Fn.number().optional(),name:Fn.string().optional(),environment_id:Fn.number().optional(),test_account_id:Fn.number().optional()}).parse(t),u;try{u=this.parseFlowInput(n)}catch(m){throw m instanceof Fn.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(),C=$hi(u.url,m);C&&(c=C.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:IQ(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:pG(Fn.object({test_case_id:Fn.number().describe("Test case ID"),flow:Fn.union([Fn.string(),Fn.any()]).describe("Test flow as JSON object or YAML string"),title:Fn.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}=Fn.object({test_case_id:Fn.number(),flow:Fn.any(),title:Fn.string().optional()}).parse(t),o;try{o=this.parseFlowInput(i)}catch(c){throw c instanceof Fn.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:pG(Fn.object({test_case_id:Fn.number().describe("Test case ID"),output_format:Fn.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}=Fn.object({test_case_id:Fn.number(),output_format:Fn.enum(["json","yaml"]).optional()}).parse(t),s=await r.getTestCase(n);if(i==="yaml"&&s.test_flow){let o=IQ(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:pG(Fn.object({test_case_id:Fn.number().describe("Test case ID to run"),environment_id:Fn.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}=Fn.object({test_case_id:Fn.number(),environment_id:Fn.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:pG(Fn.object({yaml:Fn.string().describe("YAML test flow string"),folder_id:Fn.number().optional().describe("Folder ID (only used on create)"),name:Fn.string().optional().describe("Test case name (defaults to flow goal)"),environment_id:Fn.number().optional().describe("Environment ID for test execution (only used on create)"),test_account_id:Fn.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}=Fn.object({yaml:Fn.string(),folder_id:Fn.number().optional(),name:Fn.string().optional(),environment_id:Fn.number().optional(),test_account_id:Fn.number().optional()}).parse(t),a=F6e(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:IQ(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:Ghi()};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=[sde.getTestCaseTool,sde.runTestCaseTool,sde.saveTestCaseTool]},Whi=3600*1e3,ide=ode.join(Vhi.tmpdir(),"shiplight-artifacts"),hde=class qT{constructor(t,r){this.apiClient=t,this.options=r||{}}options;requireApi(t){if(!this.apiClient)throw new Error(DQ(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(!sv.existsSync(ide))return;let t=Date.now(),r=sv.readdirSync(ide,{withFileTypes:!0});for(let n of r){if(!n.isDirectory())continue;let i=ode.join(ide,n.name);try{let s=sv.statSync(i);t-s.mtimeMs>Whi&&sv.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:fG(cs.object({test_plan_id:cs.number().optional().describe("Filter by test plan ID"),trigger:cs.string().optional().describe("Filter by trigger type (Manual, Scheduled, API, GITHUB_ACTION, Webhook, Automation)"),result:cs.string().optional().describe("Filter by result (Passed, Failed, Pending, Skipped, Queued)"),limit:cs.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}=cs.object({test_plan_id:cs.number().optional(),trigger:cs.string().optional(),result:cs.string().optional(),limit:cs.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:fG(cs.object({test_run_id:cs.number().describe("Test run ID")}),{$refStrategy:"none"})};async getTestRunDetails(t){let r=this.requireApi("get_test_run_details"),{test_run_id:n}=cs.object({test_run_id:cs.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:fG(cs.object({test_case_result_id:cs.number().describe("Test case result ID"),include_report:cs.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}=cs.object({test_case_result_id:cs.number(),include_report:cs.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 C=await r.getS3FileContents(p);if(C){let y=JSON.parse(C);f=Array.isArray(y)?y[0]:y}}catch(C){console.error("Failed to fetch S3 report, falling back to DB report:",C)}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:fG(cs.object({test_case_result_id:cs.number().describe("Test case result ID"),from_step:cs.number().describe("Start step index (0-based, inclusive)"),to_step:cs.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}=cs.object({test_case_result_id:cs.number(),from_step:cs.number(),to_step:cs.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:fG(cs.object({test_case_result_id:cs.number().describe("Test case result ID"),step_index:cs.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}=cs.object({test_case_result_id:cs.number(),step_index:cs.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,C=!!r.getArtifactPresignedUrl;if(!f&&!m&&!C)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:I}])=>[x,I]))});this.cleanupOldArtifacts();let y=ode.join(ide,`tcr-${n}`);sv.existsSync(y)||sv.mkdirSync(y,{recursive:!0});let b={},v=!!r.downloadArtifact;return await Promise.all(Object.entries(d).map(async([x,{s3Uri:I,filename:S}])=>{try{let R=null;if(!R&&f){let U=await r.getS3FileContents(I);U&&(R=U)}if(!R&&m&&v){let U=this.getProxyUrl(I);U&&(R=await r.downloadArtifact(U))}if(!R&&C){let U=await r.getArtifactPresignedUrl(I);if(U){let T=await jhi.get(U,{responseType:"arraybuffer"});R=Buffer.from(T.data)}}if(!R){b[x]=null;return}let Q=ode.join(y,S);sv.writeFileSync(Q,R),b[x]=Q}catch(R){console.error(`Failed to download ${x} artifact:`,R),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=[qT.listTestRunsTool,qT.getTestRunDetailsTool,qT.getTestCaseResultTool,qT.getTestCaseResultStepsTool,qT.getStepArtifactsTool]},pde=class Zhr{static initLocalProjectTool={name:"init_local_project",description:"Scaffold a ready-to-run local test project with Playwright and @shiplightai/sdk-pro. Generates package.json, playwright.config.ts, .gitignore, .env.example, and a tests/ directory. After scaffolding, the user runs: npm install && npx playwright install && npx playwright test",inputSchema:Rhr(p0.object({project_path:p0.string().describe("Absolute path where the project should be created")}),{$refStrategy:"none"})};async initLocalProject(t){let{project_path:r}=p0.object({project_path:p0.string()}).parse(t),n=U8.join(r,"tests");O6.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/sdk-pro":"^0.2.0",dotenv:"^16.4.7"}};O6.writeFileSync(U8.join(r,"package.json"),JSON.stringify(i,null,2)+`
|
|
8092
|
+
actually interacting with the element.`};async function zhr(e,t){let{session_id:r,element_index:n}=t;if(!e.getSession(r))throw new Error(`Session ${r} not found`);return e.getLocator(r,n)}var tde=null,rde=null;function Khr(){return tde||(tde=ta.buildActionUnionSchemaForTools([...ND],!0),tde)}function Xhr(){if(rde)return rde;let e=Khr(),t=ede.array(e),r=Lhr();return rde={name:r.name,description:r.description,inputSchema:nb(ede.object({session_id:ede.string().describe("Session ID"),actions:t.describe("List of actions to execute"),stop_on_error:ede.boolean().optional().describe("Stop execution on first error (default: true)")}),{$refStrategy:"none"})},rde}var ude=class ov{constructor(t){this.backend=t}static navigateTool={...Nhr,inputSchema:nb(eUe,{$refStrategy:"none"})};async navigate(t){let r=eUe.parse(t),n=await Qhr(this.backend,r);return JSON.stringify(n)}static get actTool(){return Xhr()}async act(t){let r=Mhr.parse(t),n=await Ohr(this.backend,r);return JSON.stringify(n)}static updateVariablesTool={...Phr,inputSchema:nb(tUe,{$refStrategy:"none"})};async updateVariables(t){let r=tUe.parse(t),n=Uhr(this.backend,r);return JSON.stringify(n)}static clearExecutionHistoryTool={...qhr,inputSchema:nb(rUe,{$refStrategy:"none"})};async clearExecutionHistory(t){let r=rUe.parse(t),n=Hhr(this.backend,r);return JSON.stringify(n)}static getPageInfoTool={...Ghr,inputSchema:nb(nUe,{$refStrategy:"none"})};async getPageInfo(t){let r=nUe.parse(t),n=await $hr(this.backend,r);return JSON.stringify(n)}static getDomTool={...Vhr,inputSchema:nb(iUe,{$refStrategy:"none"})};async getDom(t){let r=iUe.parse(t),n=await jhr(this.backend,r);return JSON.stringify(n)}static takeScreenshotTool={...Whr,inputSchema:nb(sUe,{$refStrategy:"none"})};async takeScreenshot(t){let r=sUe.parse(t),n=await Yhr(this.backend,r);return JSON.stringify(n)}static getLocatorTool={...Jhr,inputSchema:nb(oUe,{$refStrategy:"none"})};async getLocator(t){let r=oUe.parse(t),n=await zhr(this.backend,r);return JSON.stringify(n)}static get toolDefinitions(){return[ov.navigateTool,ov.getPageInfoTool,ov.getDomTool,ov.takeScreenshotTool,ov.actTool,ov.getLocatorTool]}},lde=class AG{constructor(t){this.backend=t}static getConsoleLogsTool={name:"get_browser_console_logs",description:"Get console logs from the browser (errors, warnings, etc.)",inputSchema:nde(Ro.object({session_id:Ro.string().describe("Browser session ID"),since_timestamp:Ro.number().optional().describe("Only return logs after this timestamp"),log_types:Ro.array(Ro.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}=Ro.object({session_id:Ro.string(),since_timestamp:Ro.number().optional(),log_types:Ro.array(Ro.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:nde(Ro.object({session_id:Ro.string().describe("Browser session ID"),since_timestamp:Ro.number().optional().describe("Only return logs after this timestamp"),status_filter:Ro.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}=Ro.object({session_id:Ro.string(),since_timestamp:Ro.number().optional(),status_filter:Ro.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:nde(Ro.object({session_id:Ro.string().describe("Browser session ID")}),{$refStrategy:"none"})};async clearLogs(t){let{session_id:r}=Ro.object({session_id:Ro.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:nde(Ro.object({path:Ro.string().describe("Artifact file path from previous tool output"),return_format:Ro.enum(["base64","text"]).optional().describe("Return format (auto-detected if not specified)")}),{$refStrategy:"none"})};async getLocalArtifact(t){let{path:r}=Ro.object({path:Ro.string(),return_format:Ro.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=[AG.getConsoleLogsTool,AG.getNetworkLogsTool,AG.clearLogsTool,AG.getLocalArtifactTool]},cde=class ib{constructor(t){this.apiClient=t}requireApi(t){if(!this.apiClient)throw new Error(DQ(t));return this.apiClient}static listEnvironmentsTool={name:"list_environments",description:"List all testing environments (staging, production, etc.)",inputSchema:iv(Ci.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:iv(Ci.object({environment_id:Ci.number().optional().describe("Environment ID to filter by"),environment_url:Ci.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}=Ci.object({environment_id:Ci.number().optional(),environment_url:Ci.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:iv(Ci.object({test_account_id:Ci.number().describe("Test account ID")}),{$refStrategy:"none"})};async getTestAccount(t){let r=this.requireApi("get_test_account"),{test_account_id:n}=Ci.object({test_account_id:Ci.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:iv(Ci.object({environment_id:Ci.number().describe("Environment ID"),username:Ci.string().describe("Username"),password:Ci.string().describe("Password"),login_config:Ci.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}=Ci.object({environment_id:Ci.number(),username:Ci.string(),password:Ci.string(),login_config:Ci.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:iv(Ci.object({parent_id:Ci.number().nullable().optional().describe("Parent folder ID, null for root"),search:Ci.string().optional().describe("Search query")}),{$refStrategy:"none"})};async listFolders(t){let r=this.requireApi("list_folders"),{parent_id:n,search:i}=Ci.object({parent_id:Ci.number().nullable().optional(),search:Ci.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:iv(Ci.object({name:Ci.string().describe("Folder name"),description:Ci.string().optional().describe("Folder description"),parent_id:Ci.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}=Ci.object({name:Ci.string(),description:Ci.string().optional(),parent_id:Ci.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:iv(Ci.object({folder_id:Ci.number().describe("Folder ID")}),{$refStrategy:"none"})};async getFolder(t){let r=this.requireApi("get_folder"),{folder_id:n}=Ci.object({folder_id:Ci.number()}).parse(t),i=await r.getFolder(n);return JSON.stringify({folder:i})}static toolDefinitions=[ib.listEnvironmentsTool,ib.listTestAccountsTool,ib.getTestAccountTool,ib.createTestAccountTool,ib.listFoldersTool,ib.createFolderTool,ib.getFolderTool]};function $hi(e,t){let r=e.replace(/\/+$/,"");return t.find(n=>r===n.url.replace(/\/+$/,""))}var dde=class sde{constructor(t){this.apiClient=t}requireApi(t){if(!this.apiClient)throw new Error(DQ(t));return this.apiClient}parseFlowInput(t){return typeof t=="string"?Y5.parse(FQ(t)):Y5.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:pG(Fn.object({flow:Fn.union([Fn.string(),Fn.any()]).describe("Test flow as JSON object or YAML string"),folder_id:Fn.number().optional().describe("Folder ID"),name:Fn.string().optional().describe("Test case name"),environment_id:Fn.number().optional().describe("Environment ID for test execution"),test_account_id:Fn.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}=Fn.object({flow:Fn.any(),folder_id:Fn.number().optional(),name:Fn.string().optional(),environment_id:Fn.number().optional(),test_account_id:Fn.number().optional()}).parse(t),u;try{u=this.parseFlowInput(n)}catch(m){throw m instanceof Fn.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(),C=$hi(u.url,m);C&&(c=C.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:IQ(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:pG(Fn.object({test_case_id:Fn.number().describe("Test case ID"),flow:Fn.union([Fn.string(),Fn.any()]).describe("Test flow as JSON object or YAML string"),title:Fn.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}=Fn.object({test_case_id:Fn.number(),flow:Fn.any(),title:Fn.string().optional()}).parse(t),o;try{o=this.parseFlowInput(i)}catch(c){throw c instanceof Fn.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:pG(Fn.object({test_case_id:Fn.number().describe("Test case ID"),output_format:Fn.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}=Fn.object({test_case_id:Fn.number(),output_format:Fn.enum(["json","yaml"]).optional()}).parse(t),s=await r.getTestCase(n);if(i==="yaml"&&s.test_flow){let o=IQ(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:pG(Fn.object({test_case_id:Fn.number().describe("Test case ID to run"),environment_id:Fn.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}=Fn.object({test_case_id:Fn.number(),environment_id:Fn.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:pG(Fn.object({yaml:Fn.string().describe("YAML test flow string"),folder_id:Fn.number().optional().describe("Folder ID (only used on create)"),name:Fn.string().optional().describe("Test case name (defaults to flow goal)"),environment_id:Fn.number().optional().describe("Environment ID for test execution (only used on create)"),test_account_id:Fn.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}=Fn.object({yaml:Fn.string(),folder_id:Fn.number().optional(),name:Fn.string().optional(),environment_id:Fn.number().optional(),test_account_id:Fn.number().optional()}).parse(t),a=F6e(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:IQ(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:Ghi()};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=[sde.getTestCaseTool,sde.runTestCaseTool,sde.saveTestCaseTool]},Whi=3600*1e3,ide=ode.join(Vhi.tmpdir(),"shiplight-artifacts"),hde=class qT{constructor(t,r){this.apiClient=t,this.options=r||{}}options;requireApi(t){if(!this.apiClient)throw new Error(DQ(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(!sv.existsSync(ide))return;let t=Date.now(),r=sv.readdirSync(ide,{withFileTypes:!0});for(let n of r){if(!n.isDirectory())continue;let i=ode.join(ide,n.name);try{let s=sv.statSync(i);t-s.mtimeMs>Whi&&sv.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:fG(cs.object({test_plan_id:cs.number().optional().describe("Filter by test plan ID"),trigger:cs.string().optional().describe("Filter by trigger type (Manual, Scheduled, API, GITHUB_ACTION, Webhook, Automation)"),result:cs.string().optional().describe("Filter by result (Passed, Failed, Pending, Skipped, Queued)"),limit:cs.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}=cs.object({test_plan_id:cs.number().optional(),trigger:cs.string().optional(),result:cs.string().optional(),limit:cs.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:fG(cs.object({test_run_id:cs.number().describe("Test run ID")}),{$refStrategy:"none"})};async getTestRunDetails(t){let r=this.requireApi("get_test_run_details"),{test_run_id:n}=cs.object({test_run_id:cs.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:fG(cs.object({test_case_result_id:cs.number().describe("Test case result ID"),include_report:cs.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}=cs.object({test_case_result_id:cs.number(),include_report:cs.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 C=await r.getS3FileContents(p);if(C){let y=JSON.parse(C);f=Array.isArray(y)?y[0]:y}}catch(C){console.error("Failed to fetch S3 report, falling back to DB report:",C)}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:fG(cs.object({test_case_result_id:cs.number().describe("Test case result ID"),from_step:cs.number().describe("Start step index (0-based, inclusive)"),to_step:cs.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}=cs.object({test_case_result_id:cs.number(),from_step:cs.number(),to_step:cs.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:fG(cs.object({test_case_result_id:cs.number().describe("Test case result ID"),step_index:cs.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}=cs.object({test_case_result_id:cs.number(),step_index:cs.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,C=!!r.getArtifactPresignedUrl;if(!f&&!m&&!C)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:I}])=>[x,I]))});this.cleanupOldArtifacts();let y=ode.join(ide,`tcr-${n}`);sv.existsSync(y)||sv.mkdirSync(y,{recursive:!0});let b={},v=!!r.downloadArtifact;return await Promise.all(Object.entries(d).map(async([x,{s3Uri:I,filename:S}])=>{try{let R=null;if(!R&&f){let U=await r.getS3FileContents(I);U&&(R=U)}if(!R&&m&&v){let U=this.getProxyUrl(I);U&&(R=await r.downloadArtifact(U))}if(!R&&C){let U=await r.getArtifactPresignedUrl(I);if(U){let T=await jhi.get(U,{responseType:"arraybuffer"});R=Buffer.from(T.data)}}if(!R){b[x]=null;return}let Q=ode.join(y,S);sv.writeFileSync(Q,R),b[x]=Q}catch(R){console.error(`Failed to download ${x} artifact:`,R),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=[qT.listTestRunsTool,qT.getTestRunDetailsTool,qT.getTestCaseResultTool,qT.getTestCaseResultStepsTool,qT.getStepArtifactsTool]},pde=class Zhr{static initLocalProjectTool={name:"init_local_project",description:"Scaffold a ready-to-run local test project with Playwright and @shiplightai/sdk-pro. 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:Rhr(p0.object({project_path:p0.string().describe("Absolute path where the project should be created")}),{$refStrategy:"none"})};async initLocalProject(t){let{project_path:r}=p0.object({project_path:p0.string()}).parse(t),n=U8.join(r,"tests");O6.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/sdk-pro":"^0.2.0",dotenv:"^16.4.7"}};O6.writeFileSync(U8.join(r,"package.json"),JSON.stringify(i,null,2)+`
|
|
8082
8093
|
`),O6.writeFileSync(U8.join(r,"playwright.config.ts"),`import 'dotenv/config';
|
|
8083
8094
|
import { defineConfig } from '@playwright/test';
|
|
8084
8095
|
|