shiplightai 0.1.50 → 0.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/reporter.cjs +1 -1
- package/dist/cli.js +2 -2
- package/dist/fixture.d.ts +7 -0
- package/dist/index.js +1 -1
- package/dist/reporter.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
package/dist/cjs/index.cjs
CHANGED
|
@@ -4364,7 +4364,7 @@ ${p.join(`
|
|
|
4364
4364
|
`)}function Rc(e,t){let i=[],n=t?.version||"unknown";i.push(`// @generated by shiplightai v${n}`),i.push(...Ho()),i.push(""),t?.use&&Object.keys(t.use).length>0&&(i.push(`test.use(${JSON.stringify(t.use,null,2)});`),i.push(""));let a=new Set,o={imports:a,actionEntityStore:t?.actionEntityStore},r=t?.testName||"Test Suite",s=Yn(t?.tags);i.push(`test.describe.serial('${s}${Se(r)}', () => {`),e.beforeAll&&e.beforeAll.length>0&&(i.push(...$i("beforeAll",e.beforeAll,o,1)),i.push("")),e.beforeEach&&e.beforeEach.length>0&&(i.push(...$i("beforeEach",e.beforeEach,o,1)),i.push(""));for(let c=0;c<e.tests.length;c++){let d=e.tests[c],h=d.timeout||d.skip!==void 0||d.fail!==void 0||d.only||d.slow?{timeout:d.timeout,skip:d.skip,fail:d.fail,only:d.only,slow:d.slow}:void 0;if(d.parameters&&d.parameters.length>0)for(let p of d.parameters){let g=Fo(d.testFlow,p.values);i.push(...Ci(g,`${Se(d.name)} [${Se(p.name)}]`,o,1,h)),i.push("")}else i.push(...Ci(d.testFlow,Se(d.name),o,1,h)),(c<e.tests.length-1||e.afterEach||e.afterAll)&&i.push("")}return e.afterEach&&e.afterEach.length>0&&(i.push(...$i("afterEach",e.afterEach,o,1)),i.push("")),e.afterAll&&e.afterAll.length>0&&i.push(...$i("afterAll",e.afterAll,o,1)),i.push("});"),Wo(i,a),i.join(`
|
|
4365
4365
|
`)}function Yn(e){return e&&e.length>0?e.map(t=>`@${t}`).join(" ")+" ":""}var Fc=["testContext","request"];function Ii(e){let t=new Set;function i(n){for(let a of n)switch(a.type){case me.ACTION:{let r=a.action_entity?.action_data?.kwargs;if(r?.args&&Array.isArray(r.args))for(let s of r.args)typeof s=="string"&&Fc.includes(s)&&t.add(s);break}case me.STEP:i(a.statements);break;case me.IF_ELSE:{let o=a;i(o.then),o.else&&i(o.else);break}case me.WHILE_LOOP:i(a.body);break}}return i(e),t}function Hc(e){let t=Ii(e.statements??[]);if(e.teardown)for(let i of Ii(e.teardown))t.add(i);return t}function qn(e){return`{ ${["page","agent",...Array.from(e).sort()].join(", ")} }`}function Ci(e,t,i,n=0,a){let o=" ".repeat(n),r=[],s=Hc(e),l=qn(s),c=a?.only?"test.only":"test";r.push(`${o}${c}('${t}', async (${l}) => {`),a?.skip===!0?r.push(`${o} test.skip();`):typeof a?.skip=="string"&&r.push(`${o} test.skip(true, '${Se(a.skip)}');`),a?.fail===!0?r.push(`${o} test.fail();`):typeof a?.fail=="string"&&r.push(`${o} test.fail(true, '${Se(a.fail)}');`),a?.slow&&r.push(`${o} test.slow();`),a?.timeout&&r.push(`${o} test.setTimeout(${a.timeout});`);let d=e.teardown&&e.teardown.length>0,h=n+1;if(d){if(r.push(`${o} try {`),e.statements&&e.statements.length>0){r.push(`${o} // Test steps`);let g=ge(e.statements,h+1,i);r.push(...g)}r.push(`${o} } finally {`),r.push(`${o} // Teardown`);let p=ge(e.teardown,h+1,i,"teardown");r.push(...p),r.push(`${o} }`)}else if(e.statements&&e.statements.length>0){r.push(`${o} // Test steps`);let p=ge(e.statements,h,i);r.push(...p)}return r.push(`${o}});`),r}function Lo(e,t,i){let n=[],a=Ro(t),o=Ii(a),r=qn(o);return n.push(`test.${e}(async (${r}) => {`),n.push(...ge(a,1,i,e)),n.push("});"),n}function $i(e,t,i,n){let a=" ".repeat(n),o=[],r=Ro(t);if(e==="beforeAll"||e==="afterAll"){let l={...i,noAgent:!0};o.push(`${a}test.${e}(async ({ browser }, workerInfo) => {`),o.push(`${a} const page = await browser.newPage({ baseURL: workerInfo.project.use.baseURL });`),o.push(...ge(r,n+1,l,e)),o.push(`${a} await page.close();`),o.push(`${a}});`)}else{let l=Ii(r),c=qn(l);o.push(`${a}test.${e}(async (${c}) => {`),o.push(...ge(r,n+1,i,e)),o.push(`${a}});`)}return o}function Ro(e){let i=(0,Do.stringify)({goal:"_hook",statements:e});return pe(i).statements??[]}function Fo(e,t){let i=jn(e);for(let[n,a]of Object.entries(t))i=i.split(`<<${n}>>`).join(String(a));return pe(i)}function Ho(){return["import { test, expect } from 'shiplightai/fixture';"]}function Wo(e,t){if(t.size>0){let i=0;for(let a=0;a<e.length;a++)e[a].startsWith("import ")&&(i=a+1);let n=Array.from(t);e.splice(i,0,...n)}}var No=5;function Go(e,t){let i={expandingPaths:new Set([(0,yt.resolve)(t)]),depth:0,referencedPaths:new Set},n={...e};Array.isArray(n.statements)&&(n.statements=Re(n.statements,t,i)),Array.isArray(n.teardown)&&(n.teardown=Re(n.teardown,t,i));for(let a of["beforeAll","afterAll","beforeEach","afterEach"])Array.isArray(n[a])&&(n[a]=Re(n[a],t,i));return{doc:n,referencedTemplatePaths:Array.from(i.referencedPaths)}}function Re(e,t,i){let n=[];for(let a of e)if(Wc(a)){let o=Uc(a,t,i);n.push(...o)}else n.push(Bc(a,t,i));return n}function Wc(e){return typeof e=="object"&&e!==null&&typeof e.template=="string"}function Uc(e,t,i){if(i.depth>=No)throw new Error(`Template expansion exceeded maximum depth of ${No}. Check for deeply nested or circular template references.`);let n=(0,yt.resolve)((0,yt.dirname)(t),e.template);if(i.expandingPaths.has(n))throw new Error(`Circular template reference detected: ${n} is already being expanded. Stack: ${Array.from(i.expandingPaths).join(" \u2192 ")} \u2192 ${n}`);i.referencedPaths.add(n);let a;try{a=(0,Bo.readFileSync)(n,"utf-8")}catch(d){throw new Error(`Failed to read template file: ${n} (referenced from ${t}): ${d.message}`)}let o=(0,wt.parse)(a);if(!o||typeof o!="object")throw new Error(`Invalid template file: ${n} \u2014 expected a YAML object`);let r=o.params||[],s=e.params||{};for(let d of r)if(!(d in s))throw new Error(`Template ${e.template} requires param "${d}" but it was not provided. Required params: [${r.join(", ")}]`);let l=o.statements;if(!Array.isArray(l))throw new Error(`Template ${e.template} must have a "statements" array`);if(Object.keys(s).length>0){let h=(0,wt.stringify)(l);for(let[p,g]of Object.entries(s))h=h.split(`<<${p}>>`).join(String(g));l=(0,wt.parse)(h)}let c={expandingPaths:new Set([...i.expandingPaths,n]),depth:i.depth+1,referencedPaths:i.referencedPaths};return Re(l,n,c)}function Bc(e,t,i){if(typeof e!="object"||e===null)return e;let n={...e};return Array.isArray(n.statements)&&(n.statements=Re(n.statements,t,i)),Array.isArray(n.THEN)&&(n.THEN=Re(n.THEN,t,i)),Array.isArray(n.ELSE)&&(n.ELSE=Re(n.ELSE,t,i)),Array.isArray(n.DO)&&(n.DO=Re(n.DO,t,i)),n}var Zn=class extends Error{constructor(e){super(e),this.name="YamlValidationError"}};function Qn(e,t){let i=(0,bt.parse)(e),n=i?.name,a=i?.tags,o=i?.use;if(i&&(i.name!==void 0||i.tags!==void 0||i.use!==void 0)&&(delete i.name,delete i.tags,delete i.use),i?.suite){if(i.goal||i.statements)throw new Zn('YAML file cannot have both "suite" and top-level "goal"/"statements". Use either suite format or single-test format.');return jc(i,n,a,o,t)}return Gc(i,n,a,o,t)}function Gc(e,t,i,n,a){let o=e?.beforeEach,r=e?.afterEach,s=jo(e?.parameters),l=e?.timeout,c=e?.skip,d=e?.fail,h=e?.only,p=e?.slow;if(e&&(delete e.beforeEach,delete e.afterEach,delete e.parameters,delete e.timeout,delete e.skip,delete e.fail,delete e.only,delete e.slow),e?.url)throw new Zn(`The "url" field is not supported in local YAML tests. Use "base_url: ${e.url}" and add "- URL: /" as the first statement instead.`);e&&!e.goal&&t&&(e.goal=t);let g=[];if(a&&e&&typeof e=="object"){let b=Go(e,a);e=b.doc,g=b.referencedTemplatePaths}let f=(0,bt.stringify)(e),x=pe(f);return a&&(Ye(x.statements??[],a,"main"),x.teardown&&Ye(x.teardown,a,"teardown")),{testFlow:x,name:t,tags:i,use:n,beforeEach:o,afterEach:r,parameters:s,timeout:l,skip:c,fail:d,only:h,slow:p,referencedTemplatePaths:g}}function jc(e,t,i,n,a){let o=e.suite;if(!Array.isArray(o.tests)||o.tests.length===0)throw new Error('Suite must have a non-empty "tests" array.');let r=o.beforeAll,s=o.afterAll,l=o.beforeEach,c=o.afterEach,d=[],h=o.tests.map(f=>{if(!f.name)throw new Error('Each test in a suite must have a "name" field.');if(!Array.isArray(f.statements)||f.statements.length===0)throw new Error(`Suite test "${f.name}" must have a non-empty "statements" array.`);let x={goal:f.name,statements:f.statements};f.teardown&&(x.teardown=f.teardown);let b=[],m=x;if(a&&typeof x=="object"){let A=Go(x,a);m=A.doc,b=A.referencedTemplatePaths,d.push(...b)}let y=(0,bt.stringify)(m),S=pe(y),E=jo(f.parameters);return{testFlow:S,name:f.name,tags:Array.isArray(f.tags)?f.tags:void 0,parameters:E,timeout:f.timeout,skip:f.skip,fail:f.fail,only:f.only,slow:f.slow}}),p=o.base_url,g=p?{...n,baseURL:p}:n;return{suite:{beforeAll:r,afterAll:s,beforeEach:l,afterEach:c,tests:h},name:t,tags:i,use:g,referencedTemplatePaths:d}}function jo(e){if(!(!Array.isArray(e)||e.length===0))return e.map((t,i)=>{if(!t.name)throw new Error(`Parameter set at index ${i} must have a "name" field.`);if(!t.values||typeof t.values!="object")throw new Error(`Parameter set "${t.name}" must have a "values" object.`);return{name:t.name,values:t.values}})}function Ye(e,t,i){for(let n=0;n<e.length;n++){let a=e[n],o=`${i}.${n}`,r=a.description||"";if(a.uid=Kc(t,o,r),a.type===me.STEP)Ye(a.statements,t,o);else if(a.type===me.IF_ELSE){let s=a;Ye(s.then,t,`${o}.then`),s.else&&Ye(s.else,t,`${o}.else`)}else a.type===me.WHILE_LOOP&&Ye(a.body,t,`${o}.body`)}}function Kc(e,t,i){let n=(0,Uo.createHash)("sha256").update(`${e}:${t}:${i}`).digest("hex");return`${n.slice(0,8)}-${n.slice(8,12)}-${n.slice(12,16)}-${n.slice(16,20)}-${n.slice(20,32)}`}function zo(e,t,i){let n=/\btemplate:\s/.test(e),a=/^suite:/m.test(e),o=n||a?null:zn(e);if(o&&!o.valid)return{valid:!1,errors:o.errors,warnings:[],stats:o.stats};let r,s,l=[];try{let c=i?.parsed??Qn(e,t);l=c.referencedTemplatePaths;let d={version:i?.version,actionEntityStore:i?.actionEntityStore},h=c.testFlow?.baseURL?{...c.use,baseURL:c.testFlow.baseURL}:c.use;c.suite?r=Rc(c.suite,{...d,testName:c.name,tags:c.tags,use:c.use}):r=Dc(c.testFlow,{...d,testName:c.name,tags:c.tags,use:h,beforeEach:c.beforeEach,afterEach:c.afterEach,parameters:c.parameters,timeout:c.timeout,skip:c.skip,fail:c.fail,only:c.only,slow:c.slow});let p=r.split(`
|
|
4366
4366
|
`).filter(g=>!g.startsWith("import ")).join(`
|
|
4367
|
-
`);new Function(p),s=t.replace(/\.test\.yaml$/,".yaml.spec.ts"),(0,xt.mkdirSync)((0,Ko.dirname)(s),{recursive:!0}),(0,xt.writeFileSync)(s,r)}catch(c){let d=c instanceof Zn?"":c.message.includes("Unexpected token")?" This usually means a YAML escaping issue \u2014 in double-quoted strings, use \\\\/ instead of \\/ for regex patterns, or use single quotes / block scalars.":" This may indicate a transpiler bug \u2014 please report it.";return{valid:!1,errors:[`Transpilation failed: ${c.message}.${d}`],warnings:[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},referencedTemplatePaths:l}}return{valid:!0,errors:[],warnings:o?.warnings??[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},specFile:s,referencedTemplatePaths:l}}var ea="0.1.
|
|
4367
|
+
`);new Function(p),s=t.replace(/\.test\.yaml$/,".yaml.spec.ts"),(0,xt.mkdirSync)((0,Ko.dirname)(s),{recursive:!0}),(0,xt.writeFileSync)(s,r)}catch(c){let d=c instanceof Zn?"":c.message.includes("Unexpected token")?" This usually means a YAML escaping issue \u2014 in double-quoted strings, use \\\\/ instead of \\/ for regex patterns, or use single quotes / block scalars.":" This may indicate a transpiler bug \u2014 please report it.";return{valid:!1,errors:[`Transpilation failed: ${c.message}.${d}`],warnings:[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},referencedTemplatePaths:l}}return{valid:!0,errors:[],warnings:o?.warnings??[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},specFile:s,referencedTemplatePaths:l}}var ea="0.1.51";function Vo(e){try{return(0,Fe.statSync)(e).mtimeMs}catch{return 0}}var zc=`// @generated by shiplightai v${ea}`;function Vc(e,t){if(!(0,Fe.existsSync)(e)||(0,Fe.readFileSync)(e,"utf-8").split(`
|
|
4368
4368
|
`,1)[0]!==zc)return!1;let n=Vo(e);for(let a of t)if(Vo(a)>n)return!1;return!0}function Xc(e){let t=process.argv.slice(2),i=[],n=(0,Je.resolve)(e);for(let a of t){if(a.startsWith("-"))continue;let o=a.endsWith(".yaml.spec.ts")?a.replace(/\.yaml\.spec\.ts$/,".test.yaml"):a;if(!o.endsWith(".test.yaml"))continue;let r=(0,Je.resolve)(e,o);(0,Fe.existsSync)(r)&&i.push(r.startsWith(n)?r.slice(n.length+1):o)}return i.length>0?i:null}function Yo(e){let t=Xc(e.cwd),i=t??(0,Xo.globSync)("**/*.test.yaml",{cwd:e.cwd,ignore:["**/node_modules/**"]}),n=[];for(let a of i){let o=(0,Je.resolve)(e.cwd,a),r=o.replace(/\.test\.yaml$/,".yaml.spec.ts"),s=(0,Fe.readFileSync)(o,"utf-8");try{let l=Qn(s,o),c=(0,Je.relative)(e.cwd,o),d=e.actionEntityStores?.get(c)??e.actionEntityStores?.get("*");if(!(d&&Object.keys(d.entries).length>0)&&Vc(r,[o,...l.referencedTemplatePaths]))continue;let p=zo(s,o,{version:ea,actionEntityStore:d,parsed:l});if(!p.valid)throw new Error(p.errors.join("; "))}catch(l){console.error(`[shiplight] Failed to transpile ${a}:`,l),n.push({file:a,error:l})}}if(n.length>0){let a=`[shiplight] Transpilation failed for ${n.length} file(s):
|
|
4369
4369
|
`+n.map(o=>` - ${o.file}`).join(`
|
|
4370
4370
|
`);if(t)throw new Error(a);console.warn(a+" (skipped)")}}var re=J(require("fs"),1),vt=J(require("path"),1),Zo=require("glob");function Qo(e){return process.env.CI&&process.env.SHIPLIGHT_API_TOKEN?new aa:new na(e)}var qc=".shiplight/action-cache";function qo(e){return e.replace(/\//g,"__")+".json"}function Zc(e){return e.replace(/\.json$/,"").replace(/__/g,"/")}var na=class{constructor(t){this.cwd=t;this.cacheDir=vt.join(t,qc)}isCloud=!1;cacheDir;async lookup(t){let i=new Map;if(t.length===0||!re.existsSync(this.cacheDir))return i;for(let n of t){let a=vt.join(this.cacheDir,qo(n));try{if(re.existsSync(a)){let o=re.readFileSync(a,"utf-8");i.set(n,JSON.parse(o))}}catch{}}return i}async update(t){if(t.size===0)return 0;re.mkdirSync(this.cacheDir,{recursive:!0});let i=0;for(let[n,a]of t)try{let o=vt.join(this.cacheDir,qo(n)),r=Vn();if(re.existsSync(o))try{r=JSON.parse(re.readFileSync(o,"utf-8"))}catch{}let s={...r,entries:{...r.entries,...a.entries}};re.writeFileSync(o,JSON.stringify(s,null,2)),i++}catch{}return i}loadAll(){if(!re.existsSync(this.cacheDir))return;let t=(0,Zo.globSync)("*.json",{cwd:this.cacheDir});if(t.length===0)return;let i=new Map,n=0;for(let a of t)try{let o=re.readFileSync(vt.join(this.cacheDir,a),"utf-8"),r=JSON.parse(o),s=Zc(a);i.set(s,r),n+=Object.keys(r.entries??{}).length}catch{}if(i.size!==0)return console.log(`[shiplight] Cache: loaded ${n} cached action entit${n===1?"y":"ies"} for ${i.size} test file${i.size!==1?"s":""}`),i}},aa=class{isCloud=!0;async lookup(t){let{lookupActionStores:i}=await Promise.resolve().then(()=>(ia(),ta));return i(t)}async update(t){let{updateActionStores:i}=await Promise.resolve().then(()=>(ia(),ta));return i(t)}loadAll(){}};function nr(e={}){e.dotenv!==!1&&Qc(e.scanDir||process.cwd());let t=e.scanDir||process.cwd(),n=Qo(t).loadAll();return Yo({cwd:t,actionEntityStores:n}),e.apiKey&&(process.env.__SHIPLIGHT_API_KEY=e.apiKey),{reporter:[["list"],["shiplightai/reporter",{outputFolder:"shiplight-report",open:"never",reportToCloud:e.reportToCloud,apiKey:e.apiKey}]]}}function ar(e,...t){return(0,ir.defineConfig)(e,...t)}function Qc(e){let t=[],i=Be.resolve(e),n=Be.resolve(process.cwd());for(;;){let a=Be.join(i,".env");if(er.existsSync(a)&&t.push(a),i===n)break;let o=Be.dirname(i);if(o===i)break;i=o}for(let a of t)tr.default.config({path:a})}F();F();Wt();tt();var Ya=require("zod"),Ju=Ya.z.object({instruction:Ya.z.string().describe('The instruction of the operation to perform. Can only include one operation. Do not inlcude element indexes just describe the element, e.g. "select the text "Hello, world!" in "Hello, world!""')});function xl(e){e.register({name:"perform_accurate_operation",description:"Perform an operation that requires accurate interaction like dragging or interacting with a specific area of an element. Only use this action when neccecary.",schema:Ju,usesElementIndex:!1,async execute(t,i){let{instruction:n}=t,a={page:i.page,agentServices:i.agentServices,domService:i.domService},o=await et(n,a,{});if(o.status==="error"||!o.actionEntity)return{success:!1,actionEntity:{action_description:n,action_data:{action_name:"perform_accurate_operation",kwargs:{instruction:n}}},error:o.error||"Failed to generate action"};let{actionEntity:r}=o,s=await Ft(r,a);return{success:s.success,actionEntity:r,message:s.success?`Successfully executed action: ${r.action_data?.action_name}`:void 0,error:s.error}}})}$e();F();var vl=require("ai"),_l=require("html-to-text");async function qu(e,t,i){let{apiKey:n,domain:a}=e;if(!n||!a)throw new Error("Mailgun configuration missing. Please provide apiKey and domain");let o=[];try{let r=`https://api.mailgun.net/v3/${a}/events`,s={event:"accepted",limit:"10",ascending:"yes",recipient:t};if(i.from_email&&(s.from=i.from_email),i.since)s.begin=Math.floor(i.since/1e3).toString();else{let d=new Date(Date.now()-6e5);s.begin=Math.floor(d.getTime()/1e3).toString()}u.info(`Mailgun params: ${JSON.stringify(s)}`);let l=await fetch(r+"?"+new URLSearchParams(s),{method:"GET",headers:{Authorization:`Basic ${Buffer.from(`api:${n}`).toString("base64")}`}});if(!l.ok){let d=await l.text();throw new Error(`Mailgun events API error: ${d}`)}let c=(await l.json()).items||[];for(let d of c.slice(0,10)){if(d.event!=="accepted")continue;let h=(d.storage||{}).url;if(u.info(`message_url: ${h}`),!h){let f=(d.message||{}).headers||{},x=f.subject||"",b=f.from||"",m=f.to||"";if(i.from_email&&!b.toLowerCase().includes(i.from_email.toLowerCase())||i.to_email&&!m.toLowerCase().includes(i.to_email.toLowerCase())||i.subject&&!x.toLowerCase().includes(i.subject.toLowerCase()))continue;o.push({subject:x,from:b,to:m,date:new Date(d.timestamp*1e3).toUTCString(),body:"Message body not available (Mailgun storage disabled)",message_id:f["message-id"]||""});continue}let p=h.split("/"),g=p[p.length-1];if(u.info(`Storage key: ${g}`),g){let f=`https://api.mailgun.net/v3/domains/${a}/messages/${g}`;try{let x=await fetch(f,{method:"GET",headers:{Authorization:`Basic ${Buffer.from(`api:${n}`).toString("base64")}`,Accept:"application/json"}});if(x.ok){let b=await x.json(),m=b.Subject||"",y=b.From||"",S=b.To||"",E=b.Date||"",A=b["Message-Id"]||"";u.info(`subject: ${m}`),u.info(`from_addr: ${y}`),u.info(`to_addr: ${S}`),u.info(`date: ${E}`),u.info(`message_id: ${A}`);let M=b["body-html"]||b["body-plain"]||"";if(M&&M.includes("<")&&(M=(0,_l.convert)(M)),u.info(`Body: ${M.substring(0,200)}...`),i.subject&&!m.toLowerCase().includes(i.subject.toLowerCase())||i.body_contains&&!M.toLowerCase().includes(i.body_contains.toLowerCase()))continue;o.push({subject:m,from:y,to:S,date:E,body:M,message_id:A});continue}else u.warn(`Messages API returned ${x.status}`)}catch(x){u.warn(`Failed to parse JSON response: ${x}`)}}try{let f=await fetch(h,{method:"GET",headers:{Authorization:`Basic ${Buffer.from(`api:${n}`).toString("base64")}`}});if(!f.ok){u.warn(`Could not fetch stored message: ${f.status}`);continue}let x=await f.text();u.info(`Fallback: Raw email length: ${x.length}`);let b=x.split(`
|
package/dist/cjs/reporter.cjs
CHANGED
|
@@ -877,6 +877,6 @@ Shiplight cloud report: ${f.data.reportUrl}`)}var C,N,ze,Ve,G,Je=ct(()=>{"use st
|
|
|
877
877
|
</html>`}var Yt={before:0,main:1,teardown:2,after:3};function Xe(e){let t=e.split(".")[0];return Yt[t]??1}function qe(e){return e.split(".").map(t=>{let r=Number(t);return Number.isNaN(r)?0:r})}function Jt(e){return[...e].sort(([t],[r])=>{let i=Xe(t),o=Xe(r);if(i!==o)return i-o;let n=qe(t),s=qe(r);for(let c=0;c<Math.max(n.length,s.length);c++){let d=n[c]??-1,p=s[c]??-1;if(d!==p)return d-p}return 0})}function Xt(e){let t=new Set;for(let r of e)if(t.add(r.category),r.category==="hook")for(let i of r.steps)t.add(i.category);return t}function qt(e,t){let r=e.toLowerCase();return r.includes("before")?"before":r.includes("after")?"after":t}function de(e,t="main",r,i,o){r===void 0&&(r=!Xt(e).has("test.step")),o||(o=new Map);let n=[];for(let s of e){if(s.category==="fixture"||s.category==="test.attach")continue;if(s.category==="hook"){let d=qt(s.title,t);n.push(...de(s.steps,d,r,i,o));continue}if(s.category==="test.step"||r&&(s.category==="expect"||s.category==="pw:api")){let d=o.get(t)??0;o.set(t,d+1);let p=`${t}.${d}`,m={stepId:p,description:s.title,status:s.error?"failure":s.duration===-1?"skipped":"success",duration:s.duration>=0?s.duration:void 0};s.error&&(m.error=s.error.message??s.error.stack),i&&s.location&&i.set(p,s.location),n.push(m),s.steps.length>0&&n.push(...de(s.steps,p,r,i,o))}}return n}var ue=class{outputFolder;openMode;reportToCloud;apiKey;collected=[];config;runStartTime;constructor(t={}){this.outputFolder=t.outputFolder||"shiplight-report",this.openMode=t.open||"on-failure",this.reportToCloud=t.reportToCloud??!1,this.apiKey=t.apiKey||process.env.__SHIPLIGHT_API_KEY}onBegin(t,r){this.config=t,this.runStartTime=new Date().toISOString()}onTestEnd(t,r){this.collected.push({test:t,result:r})}async onEnd(t){if(this.collected.length===0)return;let r=new Map;for(let p of this.collected){let m=p.test.titlePath().join(" > "),b=r.get(m);b||(b=[],r.set(m,b)),b.push(p)}let i=[];for(let[,p]of r.entries()){let m=p[0].test.location.file,b=[],u,h,f;for(let v=0;v<p.length;v++){let{test:T,result:_}=p[v],k=await this.buildReportTest(T,_,m);u=k,h||(h=k.startTime),f=k.endTime,b.push({attemptNumber:v+1,status:_.status,duration:_.duration,steps:k.steps,error:k.error,videoPath:k.videoPath,tracePath:k.tracePath})}let a=b[b.length-1],{test:S}=p[p.length-1],y={title:S.title,baseTitle:u?.baseTitle,file:x.relative(process.cwd(),m),status:a.status,duration:a.duration,steps:a.steps,error:a.error,videoPath:a.videoPath,tracePath:a.tracePath,actionStepsMap:u?.actionStepsMap,tags:u?.tags,baseUrl:u?.baseUrl,skip:u?.skip,slow:u?.slow,timeout:u?.timeout,parameterSetName:u?.parameterSetName,startTime:h,endTime:f,suiteName:u?.suiteName},w=b.some(v=>v.status==="failed"||v.status==="timedOut");b.length>1&&w&&a.status==="passed"&&(y.flaky=!0,y.retries=b.length-1,y.attempts=b),i.push(y)}let o={tests:i,totalDuration:t.duration,timestamp:new Date().toISOString()},n=x.isAbsolute(this.outputFolder)?this.outputFolder:x.join(process.cwd(),this.outputFolder);E.mkdirSync(n,{recursive:!0});let s=x.join(n,"screenshots");for(let p=0;p<o.tests.length;p++){let m=o.tests[p],b=m.attempts&&m.attempts.length>0,u=[{obj:m,prefix:b?`test-${p}-attempt-0`:`test-${p}`,screenshotSubDir:`test-${p}`}];if(m.attempts)for(let h=0;h<m.attempts.length;h++)u.push({obj:m.attempts[h],prefix:`test-${p}-attempt-${h+1}`,screenshotSubDir:`test-${p}/attempt-${h}`});for(let{obj:h,prefix:f,screenshotSubDir:a}of u){let S=x.join(s,a),y=!1;for(let w of h.steps)if(w.screenshot&&x.isAbsolute(w.screenshot))try{y||(E.mkdirSync(S,{recursive:!0}),y=!0);let v=`${w.stepId.replace(/\./g,"-")}.png`;E.copyFileSync(w.screenshot,x.join(S,v)),w.screenshot=`screenshots/${a}/${v}`}catch(v){console.warn(`[reporter] Failed to copy screenshot for ${w.stepId}:`,v)}if(h.videoPath&&x.isAbsolute(h.videoPath)){let w=x.extname(h.videoPath)||".webm",v=`${f}-video${w}`;try{E.copyFileSync(h.videoPath,x.join(n,v)),h.videoPath=v}catch{h.videoPath=void 0}}if(h.tracePath&&x.isAbsolute(h.tracePath)){let w=x.extname(h.tracePath)||".zip",v=`${f}-trace${w}`;try{E.copyFileSync(h.tracePath,x.join(n,v)),h.tracePath=v}catch{h.tracePath=void 0}}}}let c=x.join(n,"report-data.json");E.writeFileSync(c,JSON.stringify(o,null,2),"utf-8");let d=x.join(n,"index.html");if(E.writeFileSync(d,He(o),"utf-8"),console.log(`
|
|
878
878
|
Shiplight report written to: ${d}`),this.openMode==="always"||this.openMode==="on-failure"&&t.status!=="passed")try{let p=(await import("open")).default;await p(d)}catch{}if(this.reportToCloud){if(!this.apiKey){console.warn("[reporter] reportToCloud is enabled but no apiKey found, skipping cloud upload.");return}try{let{uploadToCloud:p}=await Promise.resolve().then(()=>(Je(),Ye));await p(o,n,this.runStartTime,this.apiKey)}catch(p){console.warn("[reporter] Cloud upload failed:",p)}}}printsToStdio(){return!1}async buildReportTest(t,r,i){let o={title:t.title,file:x.relative(process.cwd(),i),status:r.status,duration:r.duration,steps:[],startTime:new Date(r.startTime).toISOString(),endTime:new Date(r.startTime.getTime()+r.duration).toISOString()};r.errors.length>0&&(o.error=r.errors.map(u=>u.message||u.stack||String(u)).join(`
|
|
879
879
|
|
|
880
|
-
`)),r.stdout.length>0&&(o.stdout=r.stdout.map(u=>typeof u=="string"?u:u.toString()).join("")),r.stderr.length>0&&(o.stderr=r.stderr.map(u=>typeof u=="string"?u:u.toString()).join(""));for(let u of r.attachments)u.name==="video"&&u.path&&(o.videoPath=u.path),u.name==="trace"&&u.path&&(o.tracePath=u.path);let n=r.attachments.find(u=>u.name==="shiplight-results"),s=null;if(n)try{if(n.body)s=JSON.parse(n.body.toString("utf-8"));else if(n.path){let u=E.readFileSync(n.path,"utf-8");s=JSON.parse(u)}}catch{}let c=i.replace(/\.yaml\.spec\.ts$/,".test.yaml"),d={},p=t.title.match(/^(.*)\s+\[([^\]]+)\]$/),m=p?p[1]:t.title,b=p?p[2]:void 0;if(b&&(o.parameterSetName=b),E.existsSync(c))try{let u=E.readFileSync(c,"utf-8"),h=Ue(u,c);if(h.suite){let f=h.suite.tests.find(a=>a.name===m);f&&(d=ee(f.testFlow),f.tags?.length&&(o.tags=f.tags),f.skip!==void 0&&(o.skip=f.skip),f.slow&&(o.slow=f.slow),f.timeout!==void 0&&(o.timeout=f.timeout),o.baseTitle=f.name||f.testFlow?.goal),o.suiteName=h.name,h.tags?.length&&(o.suiteTags=h.tags),h.use?.baseURL&&(o.baseUrl=h.use.baseURL)}else h.testFlow&&(d=ee(h.testFlow),o.baseTitle=h.name||h.testFlow?.goal,h.tags?.length&&(o.tags=h.tags),h.use?.baseURL&&(o.baseUrl=h.use.baseURL))}catch{}if(s||Object.keys(d).length>0){let u=new Set([...Object.keys(d),...Object.keys(s||{})]),h=Array.from(u).map(a=>[a,null]),f=Jt(h);for(let[a]of f){let S=d[a],y=s?.[a],w=S?.description;if(!w||w==="Action"||w==="Draft"){let T=S?.action_entity;w=T?.action_description||T?.action_data?.kwargs?.description||y?.description||a}let v={stepId:a,description:w,status:y?.status||"pending",duration:y?.duration};if(y?.message){let T=typeof y.message=="string"?y.message:JSON.stringify(y.message,null,2);y.status==="failure"?v.error=T:v.message=T}if(y?.screenshot){let T=y.screenshot,_=n?.path?x.dirname(n.path):"",k=x.isAbsolute(T)?T:x.join(_,T);E.existsSync(k)&&(v.screenshot=k)}v.code=y.code,o.steps.push(v)}}if(s===null&&Object.keys(d).length===0&&!i.endsWith(".yaml.spec.ts")){let u=new Map;if(o.steps=de(r.steps,"main",void 0,u),o.steps.length>0){let h=new Map;o.actionStepsMap=Object.fromEntries(o.steps.map(f=>{let a=u.get(f.stepId),S;if(a?.file){if(!h.has(a.file))try{h.set(a.file,E.readFileSync(a.file,"utf-8").split(`
|
|
880
|
+
`)),r.stdout.length>0&&(o.stdout=r.stdout.map(u=>typeof u=="string"?u:u.toString()).join("")),r.stderr.length>0&&(o.stderr=r.stderr.map(u=>typeof u=="string"?u:u.toString()).join(""));for(let u of r.attachments)u.name==="video"&&u.path&&(o.videoPath=u.path),u.name==="trace"&&u.path&&(o.tracePath=u.path);let n=r.attachments.find(u=>u.name==="shiplight-results"),s=null;if(n)try{if(n.body)s=JSON.parse(n.body.toString("utf-8"));else if(n.path){let u=E.readFileSync(n.path,"utf-8");s=JSON.parse(u)}}catch{}let c=i.replace(/\.yaml\.spec\.ts$/,".test.yaml"),d={},p=t.title.match(/^(.*)\s+\[([^\]]+)\]$/),m=p?p[1]:t.title,b=p?p[2]:void 0;if(b&&(o.parameterSetName=b),E.existsSync(c))try{let u=E.readFileSync(c,"utf-8"),h=Ue(u,c);if(h.suite){let f=h.suite.tests.find(a=>a.name===m);f&&(d=ee(f.testFlow),f.tags?.length&&(o.tags=f.tags),f.skip!==void 0&&(o.skip=f.skip),f.slow&&(o.slow=f.slow),f.timeout!==void 0&&(o.timeout=f.timeout),o.baseTitle=f.name||f.testFlow?.goal),o.suiteName=h.name,h.tags?.length&&(o.suiteTags=h.tags),h.use?.baseURL&&(o.baseUrl=h.use.baseURL)}else h.testFlow&&(d=ee(h.testFlow),o.baseTitle=h.name||h.testFlow?.goal,h.tags?.length&&(o.tags=h.tags),h.use?.baseURL&&(o.baseUrl=h.use.baseURL))}catch{}if(s||Object.keys(d).length>0){let u=new Set([...Object.keys(d),...Object.keys(s||{})]),h=Array.from(u).map(a=>[a,null]),f=Jt(h);for(let[a]of f){let S=d[a],y=s?.[a],w=S?.description;if(!w||w==="Action"||w==="Draft"){let T=S?.action_entity;w=T?.action_description||T?.action_data?.kwargs?.description||y?.description||a}let v={stepId:a,description:w,status:y?.status||"pending",duration:y?.duration};if(y?.message){let T=typeof y.message=="string"?y.message:JSON.stringify(y.message,null,2);y.status==="failure"?v.error=T:v.message=T}if(y?.screenshot){let T=y.screenshot,_=n?.path?x.dirname(n.path):"",k=x.isAbsolute(T)?T:x.join(_,T);E.existsSync(k)&&(v.screenshot=k)}y?.code&&(v.code=y.code),o.steps.push(v)}}if(s===null&&Object.keys(d).length===0&&!i.endsWith(".yaml.spec.ts")){let u=new Map;if(o.steps=de(r.steps,"main",void 0,u),o.steps.length>0){let h=new Map;o.actionStepsMap=Object.fromEntries(o.steps.map(f=>{let a=u.get(f.stepId),S;if(a?.file){if(!h.has(a.file))try{h.set(a.file,E.readFileSync(a.file,"utf-8").split(`
|
|
881
881
|
`))}catch{h.set(a.file,[])}let w=h.get(a.file);S=w[a.line-1]?.trim();let v=a.line-2,T=a.line,_=[],k=a.line;v>=0&&(_.push(w[v]??""),k=a.line-1),_.push(w[a.line-1]??""),T<w.length&&_.push(w[T]??""),f.code=_.join(`
|
|
882
882
|
`),f.codeStartLine=k,f.codeLine=a.line}let y={description:f.description,...S&&{action_entity:{action_description:f.description,action_data:{action_name:"js_code",args:[],kwargs:{code:S}}}}};return[f.stepId,y]}))}}return Object.keys(d).length>0?o.actionStepsMap=d:s&&o.steps.length>0&&(o.actionStepsMap=Object.fromEntries(o.steps.map(u=>{let h=s[u.stepId],f=h?.type,a={description:u.description,...f&&{action_entity:{action_description:u.description,action_data:{action_name:f==="step"?"js_code":f,args:[],kwargs:f==="step"?{code:h?.code??u.description}:{statement:u.description}}}}};return[u.stepId,a]}))),o}},Ze=ue;
|
package/dist/cli.js
CHANGED
|
@@ -1019,9 +1019,9 @@ Merged ${c.length} tests from ${p} shards into: ${g}`),await so(u,o),r&&En(c),t)
|
|
|
1019
1019
|
`;for(let c of a)s+=`- ${c}
|
|
1020
1020
|
`;s+=`
|
|
1021
1021
|
</details>
|
|
1022
|
-
`}return s}function En(e){let t=process.env.GITHUB_STEP_SUMMARY;if(!t){console.warn("Warning: $GITHUB_STEP_SUMMARY not set, skipping GitHub summary.");return}T.appendFileSync(t,ao(e)),console.log("GitHub step summary written.")}var lo=_(()=>{"use strict";Qr();oo()});var po,uo=_(()=>{"use strict";po="0.1.
|
|
1022
|
+
`}return s}function En(e){let t=process.env.GITHUB_STEP_SUMMARY;if(!t){console.warn("Warning: $GITHUB_STEP_SUMMARY not set, skipping GitHub summary.");return}T.appendFileSync(t,ao(e)),console.log("GitHub step summary written.")}var lo=_(()=>{"use strict";Qr();oo()});var po,uo=_(()=>{"use strict";po="0.1.51"});var ho={};te(ho,{runTranspile:()=>$n});import*as Be from"path";import{glob as Pn}from"glob";async function $n(e){(e.includes("--help")||e.includes("-h"))&&(console.log("Usage: shiplight transpile [glob]"),console.log(""),console.log("Transpiles YAML test files to Playwright spec files (.yaml.spec.ts)."),console.log("Validates syntax and reports action coverage warnings."),console.log("Default glob: **/*.test.yaml"),console.log(""),console.log("Examples:"),console.log(" shiplight transpile # transpile all YAML tests"),console.log(' shiplight transpile "tests/**/*.test.yaml" # transpile specific directory'),console.log(" shiplight transpile tests/login.test.yaml # transpile a single file"),process.exit(0));let t=e[0]||"**/*.test.yaml",r=process.cwd(),o=await Pn(t,{cwd:r,ignore:["node_modules/**","*.yaml.spec.ts"]});o.length===0&&(console.log(`No files matched: ${t}`),process.exit(0));let i=0,n=0,s=0;for(let a of o.sort()){let c=Be.resolve(r,a),l=Or(c,{version:po});if(!l.valid){i++,console.log(`
|
|
1023
1023
|
\u2717 ${a}`);for(let u of l.errors)console.log(` ERROR: ${u}`);continue}s++;let p=Be.basename(l.specFile);if(l.warnings.length>0){n++,console.log(`\u26A0 ${a} \u2192 ${p}`);for(let u of l.warnings)console.log(` WARNING: ${u}`)}else console.log(`\u2713 ${a} \u2192 ${p}`)}console.log(`
|
|
1024
|
-
${o.length} file(s): ${s} transpiled, ${i} error(s), ${n} warning(s)`),process.exit(i>0?1:0)}var fo=_(()=>{"use strict";ct();uo()});var yo={};te(yo,{runInspect:()=>Mn});import*as Ge from"fs";import*as go from"path";async function Mn(e){(e.includes("--help")||e.includes("-h")||e.length===0)&&(console.log("Usage: shiplight inspect <file.test.yaml> [options]"),console.log(""),console.log("Parse a YAML test file and output the resulting TestFlow JSON."),console.log("Useful for verifying YAML \u2192 JSON conversion."),console.log(""),console.log("Options:"),console.log(" --pretty Pretty-print JSON (default)"),console.log(" --compact Compact JSON output"),console.log(" --stats Show statement statistics only"),console.log(""),console.log("Examples:"),console.log(" shiplight inspect tests/login.test.yaml"),console.log(" shiplight inspect tests/suite.test.yaml --stats"),console.log(" shiplight inspect tests/login.test.yaml --compact | jq ."),process.exit(e.length===0?1:0));let t=e.includes("--compact"),r=e.includes("--stats"),o=e.find(s=>!s.startsWith("--"));o||(console.error("Error: no file specified"),process.exit(1));let i=go.resolve(process.cwd(),o);Ge.existsSync(i)||(console.error(`Error: file not found: ${i}`),process.exit(1));let n=Ge.readFileSync(i,"utf-8");try{let s=we(n),a=L(n);if(r)In(a,s);else{let c={...s.test_case_id!==void 0?{test_case_id:s.test_case_id}:{},...s.name?{name:s.name}:{},testFlow:a};console.log(JSON.stringify(c,null,t?0:2))}}catch(s){console.error(`Error parsing ${o}: ${s.message}`),process.exit(1)}}function In(e,t){if(console.log(`File: ${t.name||"(unnamed)"}`),t.test_case_id!==void 0&&console.log(`Cloud ID: ${t.test_case_id}`),console.log(`Version: ${e.version||"unknown"}`),e.testGroup){let r=e.testGroup;console.log("Type: suite (testGroup)"),console.log(`Tests: ${r.tests.length}`);for(let o of r.tests){let i=o.skip?` [SKIP${typeof o.skip=="string"?`: ${o.skip}`:""}]`:"";console.log(` - ${o.name}: ${o.statements.length} statements${o.teardown?`, ${o.teardown.length} teardown`:""}${i}`)}r.beforeAll?.length&&console.log(`Hooks: beforeAll (${r.beforeAll.length})`),r.afterAll?.length&&console.log(`Hooks: afterAll (${r.afterAll.length})`),r.beforeEach?.length&&console.log(`Hooks: beforeEach (${r.beforeEach.length})`),r.afterEach?.length&&console.log(`Hooks: afterEach (${r.afterEach.length})`)}else{console.log("Type: single test"),console.log(`Goal: ${e.goal}`),e.url&&console.log(`URL: ${e.url}`),e.baseURL&&console.log(`Base URL: ${e.baseURL}`),console.log(`Statements: ${e.statements?.length??0}`),e.teardown?.length&&console.log(`Teardown: ${e.teardown.length}`);let r=mo(e.statements??[]);console.log(` DRAFT: ${r.drafts}, ACTION: ${r.actions}, STEP: ${r.steps}`)}}function mo(e){let t={drafts:0,actions:0,steps:0};for(let r of e)if(r.type==="DRAFT")t.drafts++;else if(r.type==="ACTION")t.actions++;else if(r.type==="STEP"){t.steps++;let o=mo(r.statements??[]);t.drafts+=o.drafts,t.actions+=o.actions,t.steps+=o.steps}return t}var wo=_(()=>{"use strict";se()});var bo=ko((hl,On)=>{On.exports={name:"shiplightai",version:"0.1.
|
|
1024
|
+
${o.length} file(s): ${s} transpiled, ${i} error(s), ${n} warning(s)`),process.exit(i>0?1:0)}var fo=_(()=>{"use strict";ct();uo()});var yo={};te(yo,{runInspect:()=>Mn});import*as Ge from"fs";import*as go from"path";async function Mn(e){(e.includes("--help")||e.includes("-h")||e.length===0)&&(console.log("Usage: shiplight inspect <file.test.yaml> [options]"),console.log(""),console.log("Parse a YAML test file and output the resulting TestFlow JSON."),console.log("Useful for verifying YAML \u2192 JSON conversion."),console.log(""),console.log("Options:"),console.log(" --pretty Pretty-print JSON (default)"),console.log(" --compact Compact JSON output"),console.log(" --stats Show statement statistics only"),console.log(""),console.log("Examples:"),console.log(" shiplight inspect tests/login.test.yaml"),console.log(" shiplight inspect tests/suite.test.yaml --stats"),console.log(" shiplight inspect tests/login.test.yaml --compact | jq ."),process.exit(e.length===0?1:0));let t=e.includes("--compact"),r=e.includes("--stats"),o=e.find(s=>!s.startsWith("--"));o||(console.error("Error: no file specified"),process.exit(1));let i=go.resolve(process.cwd(),o);Ge.existsSync(i)||(console.error(`Error: file not found: ${i}`),process.exit(1));let n=Ge.readFileSync(i,"utf-8");try{let s=we(n),a=L(n);if(r)In(a,s);else{let c={...s.test_case_id!==void 0?{test_case_id:s.test_case_id}:{},...s.name?{name:s.name}:{},testFlow:a};console.log(JSON.stringify(c,null,t?0:2))}}catch(s){console.error(`Error parsing ${o}: ${s.message}`),process.exit(1)}}function In(e,t){if(console.log(`File: ${t.name||"(unnamed)"}`),t.test_case_id!==void 0&&console.log(`Cloud ID: ${t.test_case_id}`),console.log(`Version: ${e.version||"unknown"}`),e.testGroup){let r=e.testGroup;console.log("Type: suite (testGroup)"),console.log(`Tests: ${r.tests.length}`);for(let o of r.tests){let i=o.skip?` [SKIP${typeof o.skip=="string"?`: ${o.skip}`:""}]`:"";console.log(` - ${o.name}: ${o.statements.length} statements${o.teardown?`, ${o.teardown.length} teardown`:""}${i}`)}r.beforeAll?.length&&console.log(`Hooks: beforeAll (${r.beforeAll.length})`),r.afterAll?.length&&console.log(`Hooks: afterAll (${r.afterAll.length})`),r.beforeEach?.length&&console.log(`Hooks: beforeEach (${r.beforeEach.length})`),r.afterEach?.length&&console.log(`Hooks: afterEach (${r.afterEach.length})`)}else{console.log("Type: single test"),console.log(`Goal: ${e.goal}`),e.url&&console.log(`URL: ${e.url}`),e.baseURL&&console.log(`Base URL: ${e.baseURL}`),console.log(`Statements: ${e.statements?.length??0}`),e.teardown?.length&&console.log(`Teardown: ${e.teardown.length}`);let r=mo(e.statements??[]);console.log(` DRAFT: ${r.drafts}, ACTION: ${r.actions}, STEP: ${r.steps}`)}}function mo(e){let t={drafts:0,actions:0,steps:0};for(let r of e)if(r.type==="DRAFT")t.drafts++;else if(r.type==="ACTION")t.actions++;else if(r.type==="STEP"){t.steps++;let o=mo(r.statements??[]);t.drafts+=o.drafts,t.actions+=o.actions,t.steps+=o.steps}return t}var wo=_(()=>{"use strict";se()});var bo=ko((hl,On)=>{On.exports={name:"shiplightai",version:"0.1.51",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"},"./debugger-pw":{types:"./dist/debugger-pw.d.ts",import:"./dist/debugger-pw.js",require:"./dist/cjs/debugger-pw.cjs",default:"./dist/debugger-pw.js"},"./reporter":{types:"./dist/reporter.d.ts",import:"./dist/reporter.js",require:"./dist/cjs/reporter.cjs",default:"./dist/reporter.js"}},files:["dist","!dist/**/*.map","README.md"],publishConfig:{registry:"https://registry.npmjs.org",access:"public"},scripts:{prebuild:"pnpm typecheck",build:"tsup",pack:"pnpm build && pnpm pack",clean:"rm -rf dist",dev:"tsup --watch",test:"playwright test","test:unit":"tsx --test 'src/**/*.test.ts'",typecheck:"tsc --noEmit"},dependencies:{"@ai-sdk/anthropic":"^3.0.1","@ai-sdk/google":"^3.0.1","@ai-sdk/google-vertex":"^4.0.1","@ai-sdk/openai":"^3.0.1","@ai-sdk/provider":"^3.0.1","@anthropic-ai/claude-agent-sdk":"^0.1.72","@babel/parser":"^7.28.5","@babel/plugin-transform-typescript":"^7.27.0","@google/genai":"^1.34.0","google-auth-library":"^10.0.0","@babel/preset-env":"^7.26.9","@babel/preset-typescript":"^7.27.0","@modelcontextprotocol/sdk":"^1.29.0",ai:"^6.0.3",axios:"^1.15.0",chalk:"^4.1.2",commander:"^11.0.0",dotenv:"^16.0.3",express:"^5.2.1","fs-extra":"^11.2.0",glob:"^13.0.0","html-to-text":"^9.0.5",open:"^10.1.0",openai:"^6.25.0",ora:"^5.4.1",otplib:"^13.4.0","p-retry":"^6.2.1",sharp:"^0.34.5",uuid:"^11.1.0",yaml:"^2.8.3",zod:"^3.22.0","zod-to-json-schema":"^3.24.6"},devDependencies:{"@playwright/test":"1.58.2","@types/express":"^4.17.21","@types/node":"^24.0.0","mcp-tools":"workspace:*","sdk-core":"workspace:*","sdk-internal":"workspace:*","shiplight-tools":"workspace:*","shiplight-types":"workspace:*","@loggia/common":"workspace:*",tsup:"^8.3.5",typescript:"5.5.4"},peerDependencies:{"@playwright/test":"1.58.2"},engines:{node:">=22.0.0"},keywords:["playwright","yaml","testing","automation","ai","shiplight","mcp"],author:"Shiplight",license:"MIT"}});import Ln from"dotenv";import*as M from"fs";import*as W from"path";import*as We from"os";import{execFileSync as Ao}from"child_process";var Eo="0.1.51",Po="https://registry.npmjs.org/shiplightai/latest",$o=3600*1e3,Mo=10080*60*1e3;function Ke(){return W.join(We.homedir(),".shiplight","version-check.json")}function Io(){return W.join(We.homedir(),".shiplight","npm-prefix.json")}function Oo(e=Io()){try{let r=M.readFileSync(e,"utf-8"),o=JSON.parse(r);if(typeof o.prefix=="string"&&typeof o.fetchedAt=="number"&&Date.now()-o.fetchedAt<Mo)return o.prefix}catch{}let t;try{t=Ao("npm",["config","get","prefix"],{encoding:"utf-8",stdio:["ignore","pipe","ignore"]}).trim()}catch{return null}if(!t)return null;try{M.mkdirSync(W.dirname(e),{recursive:!0}),M.writeFileSync(e,JSON.stringify({prefix:t,fetchedAt:Date.now()}))}catch{}return t}function Tt(e={}){let t=e.scriptPath??process.argv[1],r=e.getPrefix??(()=>Oo()),o=e.warn??(s=>console.warn(s)),i=e.error??(s=>console.error(s)),n=e.exit??(s=>process.exit(s));try{if(!t)return;let s=r();if(!s)return;let a,c;try{a=M.realpathSync(t),c=M.realpathSync(s)}catch{return}if(a.startsWith(c+W.sep)){i(`
|
|
1025
1025
|
shiplightai cannot be run from a global install.
|
|
1026
1026
|
Global installs don't auto-update and cause version skew.
|
|
1027
1027
|
|
package/dist/fixture.d.ts
CHANGED
|
@@ -55,6 +55,13 @@ declare function createTestContext(variableStore: VariableStore): TestContext;
|
|
|
55
55
|
* spread `process.env` into this object — the allowlist exists so that user
|
|
56
56
|
* test code cannot exfiltrate arbitrary shell state through the SDK.
|
|
57
57
|
*
|
|
58
|
+
* **Not on this list: `WEB_AGENT_MODEL` / `COMPUTER_USE_MODEL`.** Those are
|
|
59
|
+
* read by `resolveWebAgentModelFromEnv` / `resolveComputerUseModelFromEnv`
|
|
60
|
+
* from `shiplight-types`, called directly with `process.env` in the agent
|
|
61
|
+
* fixture below. The resolved model string is passed as a constructor
|
|
62
|
+
* parameter to `createAgentContext`, not through `SdkConfig.env`. They do
|
|
63
|
+
* not need to be on the allowlist.
|
|
64
|
+
*
|
|
58
65
|
* Keep in sync with the "Environment variables" section of the public
|
|
59
66
|
* documentation at `docs.shiplight.ai/local/cli-reference`.
|
|
60
67
|
*/
|
package/dist/index.js
CHANGED
|
@@ -4366,7 +4366,7 @@ ${p.join(`
|
|
|
4366
4366
|
`)}function tc(e,t){let i=[],n=t?.version||"unknown";i.push(`// @generated by shiplightai v${n}`),i.push(...so()),i.push(""),t?.use&&Object.keys(t.use).length>0&&(i.push(`test.use(${JSON.stringify(t.use,null,2)});`),i.push(""));let a=new Set,o={imports:a,actionEntityStore:t?.actionEntityStore},r=t?.testName||"Test Suite",s=$n(t?.tags);i.push(`test.describe.serial('${s}${_e(r)}', () => {`),e.beforeAll&&e.beforeAll.length>0&&(i.push(...gi("beforeAll",e.beforeAll,o,1)),i.push("")),e.beforeEach&&e.beforeEach.length>0&&(i.push(...gi("beforeEach",e.beforeEach,o,1)),i.push(""));for(let c=0;c<e.tests.length;c++){let d=e.tests[c],h=d.timeout||d.skip!==void 0||d.fail!==void 0||d.only||d.slow?{timeout:d.timeout,skip:d.skip,fail:d.fail,only:d.only,slow:d.slow}:void 0;if(d.parameters&&d.parameters.length>0)for(let p of d.parameters){let g=ro(d.testFlow,p.values);i.push(...bi(g,`${_e(d.name)} [${_e(p.name)}]`,o,1,h)),i.push("")}else i.push(...bi(d.testFlow,_e(d.name),o,1,h)),(c<e.tests.length-1||e.afterEach||e.afterAll)&&i.push("")}return e.afterEach&&e.afterEach.length>0&&(i.push(...gi("afterEach",e.afterEach,o,1)),i.push("")),e.afterAll&&e.afterAll.length>0&&i.push(...gi("afterAll",e.afterAll,o,1)),i.push("});"),lo(i,a),i.join(`
|
|
4367
4367
|
`)}function $n(e){return e&&e.length>0?e.map(t=>`@${t}`).join(" ")+" ":""}var ic=["testContext","request"];function wi(e){let t=new Set;function i(n){for(let a of n)switch(a.type){case ue.ACTION:{let r=a.action_entity?.action_data?.kwargs;if(r?.args&&Array.isArray(r.args))for(let s of r.args)typeof s=="string"&&ic.includes(s)&&t.add(s);break}case ue.STEP:i(a.statements);break;case ue.IF_ELSE:{let o=a;i(o.then),o.else&&i(o.else);break}case ue.WHILE_LOOP:i(a.body);break}}return i(e),t}function nc(e){let t=wi(e.statements??[]);if(e.teardown)for(let i of wi(e.teardown))t.add(i);return t}function In(e){return`{ ${["page","agent",...Array.from(e).sort()].join(", ")} }`}function bi(e,t,i,n=0,a){let o=" ".repeat(n),r=[],s=nc(e),l=In(s),c=a?.only?"test.only":"test";r.push(`${o}${c}('${t}', async (${l}) => {`),a?.skip===!0?r.push(`${o} test.skip();`):typeof a?.skip=="string"&&r.push(`${o} test.skip(true, '${_e(a.skip)}');`),a?.fail===!0?r.push(`${o} test.fail();`):typeof a?.fail=="string"&&r.push(`${o} test.fail(true, '${_e(a.fail)}');`),a?.slow&&r.push(`${o} test.slow();`),a?.timeout&&r.push(`${o} test.setTimeout(${a.timeout});`);let d=e.teardown&&e.teardown.length>0,h=n+1;if(d){if(r.push(`${o} try {`),e.statements&&e.statements.length>0){r.push(`${o} // Test steps`);let g=pe(e.statements,h+1,i);r.push(...g)}r.push(`${o} } finally {`),r.push(`${o} // Teardown`);let p=pe(e.teardown,h+1,i,"teardown");r.push(...p),r.push(`${o} }`)}else if(e.statements&&e.statements.length>0){r.push(`${o} // Test steps`);let p=pe(e.statements,h,i);r.push(...p)}return r.push(`${o}});`),r}function io(e,t,i){let n=[],a=oo(t),o=wi(a),r=In(o);return n.push(`test.${e}(async (${r}) => {`),n.push(...pe(a,1,i,e)),n.push("});"),n}function gi(e,t,i,n){let a=" ".repeat(n),o=[],r=oo(t);if(e==="beforeAll"||e==="afterAll"){let l={...i,noAgent:!0};o.push(`${a}test.${e}(async ({ browser }, workerInfo) => {`),o.push(`${a} const page = await browser.newPage({ baseURL: workerInfo.project.use.baseURL });`),o.push(...pe(r,n+1,l,e)),o.push(`${a} await page.close();`),o.push(`${a}});`)}else{let l=wi(r),c=In(l);o.push(`${a}test.${e}(async (${c}) => {`),o.push(...pe(r,n+1,i,e)),o.push(`${a}});`)}return o}function oo(e){let i=Wl({goal:"_hook",statements:e});return me(i).statements??[]}function ro(e,t){let i=kn(e);for(let[n,a]of Object.entries(t))i=i.split(`<<${n}>>`).join(String(a));return me(i)}function so(){return["import { test, expect } from 'shiplightai/fixture';"]}function lo(e,t){if(t.size>0){let i=0;for(let a=0;a<e.length;a++)e[a].startsWith("import ")&&(i=a+1);let n=Array.from(t);e.splice(i,0,...n)}}var ao=5;function ho(e,t){let i={expandingPaths:new Set([uo(t)]),depth:0,referencedPaths:new Set},n={...e};Array.isArray(n.statements)&&(n.statements=Ne(n.statements,t,i)),Array.isArray(n.teardown)&&(n.teardown=Ne(n.teardown,t,i));for(let a of["beforeAll","afterAll","beforeEach","afterEach"])Array.isArray(n[a])&&(n[a]=Ne(n[a],t,i));return{doc:n,referencedTemplatePaths:Array.from(i.referencedPaths)}}function Ne(e,t,i){let n=[];for(let a of e)if(cc(a)){let o=dc(a,t,i);n.push(...o)}else n.push(uc(a,t,i));return n}function cc(e){return typeof e=="object"&&e!==null&&typeof e.template=="string"}function dc(e,t,i){if(i.depth>=ao)throw new Error(`Template expansion exceeded maximum depth of ${ao}. Check for deeply nested or circular template references.`);let n=uo(sc(t),e.template);if(i.expandingPaths.has(n))throw new Error(`Circular template reference detected: ${n} is already being expanded. Stack: ${Array.from(i.expandingPaths).join(" \u2192 ")} \u2192 ${n}`);i.referencedPaths.add(n);let a;try{a=rc(n,"utf-8")}catch(d){throw new Error(`Failed to read template file: ${n} (referenced from ${t}): ${d.message}`)}let o=no(a);if(!o||typeof o!="object")throw new Error(`Invalid template file: ${n} \u2014 expected a YAML object`);let r=o.params||[],s=e.params||{};for(let d of r)if(!(d in s))throw new Error(`Template ${e.template} requires param "${d}" but it was not provided. Required params: [${r.join(", ")}]`);let l=o.statements;if(!Array.isArray(l))throw new Error(`Template ${e.template} must have a "statements" array`);if(Object.keys(s).length>0){let h=lc(l);for(let[p,g]of Object.entries(s))h=h.split(`<<${p}>>`).join(String(g));l=no(h)}let c={expandingPaths:new Set([...i.expandingPaths,n]),depth:i.depth+1,referencedPaths:i.referencedPaths};return Ne(l,n,c)}function uc(e,t,i){if(typeof e!="object"||e===null)return e;let n={...e};return Array.isArray(n.statements)&&(n.statements=Ne(n.statements,t,i)),Array.isArray(n.THEN)&&(n.THEN=Ne(n.THEN,t,i)),Array.isArray(n.ELSE)&&(n.ELSE=Ne(n.ELSE,t,i)),Array.isArray(n.DO)&&(n.DO=Ne(n.DO,t,i)),n}var Cn=class extends Error{constructor(e){super(e),this.name="YamlValidationError"}};function Pn(e,t){let i=oc(e),n=i?.name,a=i?.tags,o=i?.use;if(i&&(i.name!==void 0||i.tags!==void 0||i.use!==void 0)&&(delete i.name,delete i.tags,delete i.use),i?.suite){if(i.goal||i.statements)throw new Cn('YAML file cannot have both "suite" and top-level "goal"/"statements". Use either suite format or single-test format.');return mc(i,n,a,o,t)}return hc(i,n,a,o,t)}function hc(e,t,i,n,a){let o=e?.beforeEach,r=e?.afterEach,s=mo(e?.parameters),l=e?.timeout,c=e?.skip,d=e?.fail,h=e?.only,p=e?.slow;if(e&&(delete e.beforeEach,delete e.afterEach,delete e.parameters,delete e.timeout,delete e.skip,delete e.fail,delete e.only,delete e.slow),e?.url)throw new Cn(`The "url" field is not supported in local YAML tests. Use "base_url: ${e.url}" and add "- URL: /" as the first statement instead.`);e&&!e.goal&&t&&(e.goal=t);let g=[];if(a&&e&&typeof e=="object"){let b=ho(e,a);e=b.doc,g=b.referencedTemplatePaths}let f=co(e),x=me(f);return a&&(Ve(x.statements??[],a,"main"),x.teardown&&Ve(x.teardown,a,"teardown")),{testFlow:x,name:t,tags:i,use:n,beforeEach:o,afterEach:r,parameters:s,timeout:l,skip:c,fail:d,only:h,slow:p,referencedTemplatePaths:g}}function mc(e,t,i,n,a){let o=e.suite;if(!Array.isArray(o.tests)||o.tests.length===0)throw new Error('Suite must have a non-empty "tests" array.');let r=o.beforeAll,s=o.afterAll,l=o.beforeEach,c=o.afterEach,d=[],h=o.tests.map(f=>{if(!f.name)throw new Error('Each test in a suite must have a "name" field.');if(!Array.isArray(f.statements)||f.statements.length===0)throw new Error(`Suite test "${f.name}" must have a non-empty "statements" array.`);let x={goal:f.name,statements:f.statements};f.teardown&&(x.teardown=f.teardown);let b=[],m=x;if(a&&typeof x=="object"){let A=ho(x,a);m=A.doc,b=A.referencedTemplatePaths,d.push(...b)}let y=co(m),S=me(y),E=mo(f.parameters);return{testFlow:S,name:f.name,tags:Array.isArray(f.tags)?f.tags:void 0,parameters:E,timeout:f.timeout,skip:f.skip,fail:f.fail,only:f.only,slow:f.slow}}),p=o.base_url,g=p?{...n,baseURL:p}:n;return{suite:{beforeAll:r,afterAll:s,beforeEach:l,afterEach:c,tests:h},name:t,tags:i,use:g,referencedTemplatePaths:d}}function mo(e){if(!(!Array.isArray(e)||e.length===0))return e.map((t,i)=>{if(!t.name)throw new Error(`Parameter set at index ${i} must have a "name" field.`);if(!t.values||typeof t.values!="object")throw new Error(`Parameter set "${t.name}" must have a "values" object.`);return{name:t.name,values:t.values}})}function Ve(e,t,i){for(let n=0;n<e.length;n++){let a=e[n],o=`${i}.${n}`,r=a.description||"";if(a.uid=pc(t,o,r),a.type===ue.STEP)Ve(a.statements,t,o);else if(a.type===ue.IF_ELSE){let s=a;Ve(s.then,t,`${o}.then`),s.else&&Ve(s.else,t,`${o}.else`)}else a.type===ue.WHILE_LOOP&&Ve(a.body,t,`${o}.body`)}}function pc(e,t,i){let n=ac("sha256").update(`${e}:${t}:${i}`).digest("hex");return`${n.slice(0,8)}-${n.slice(8,12)}-${n.slice(12,16)}-${n.slice(16,20)}-${n.slice(20,32)}`}function po(e,t,i){let n=/\btemplate:\s/.test(e),a=/^suite:/m.test(e),o=n||a?null:En(e);if(o&&!o.valid)return{valid:!1,errors:o.errors,warnings:[],stats:o.stats};let r,s,l=[];try{let c=i?.parsed??Pn(e,t);l=c.referencedTemplatePaths;let d={version:i?.version,actionEntityStore:i?.actionEntityStore},h=c.testFlow?.baseURL?{...c.use,baseURL:c.testFlow.baseURL}:c.use;c.suite?r=tc(c.suite,{...d,testName:c.name,tags:c.tags,use:c.use}):r=ec(c.testFlow,{...d,testName:c.name,tags:c.tags,use:h,beforeEach:c.beforeEach,afterEach:c.afterEach,parameters:c.parameters,timeout:c.timeout,skip:c.skip,fail:c.fail,only:c.only,slow:c.slow});let p=r.split(`
|
|
4368
4368
|
`).filter(g=>!g.startsWith("import ")).join(`
|
|
4369
|
-
`);new Function(p),s=t.replace(/\.test\.yaml$/,".yaml.spec.ts"),fc(wc(s),{recursive:!0}),gc(s,r)}catch(c){let d=c instanceof Cn?"":c.message.includes("Unexpected token")?" This usually means a YAML escaping issue \u2014 in double-quoted strings, use \\\\/ instead of \\/ for regex patterns, or use single quotes / block scalars.":" This may indicate a transpiler bug \u2014 please report it.";return{valid:!1,errors:[`Transpilation failed: ${c.message}.${d}`],warnings:[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},referencedTemplatePaths:l}}return{valid:!0,errors:[],warnings:o?.warnings??[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},specFile:s,referencedTemplatePaths:l}}var On="0.1.
|
|
4369
|
+
`);new Function(p),s=t.replace(/\.test\.yaml$/,".yaml.spec.ts"),fc(wc(s),{recursive:!0}),gc(s,r)}catch(c){let d=c instanceof Cn?"":c.message.includes("Unexpected token")?" This usually means a YAML escaping issue \u2014 in double-quoted strings, use \\\\/ instead of \\/ for regex patterns, or use single quotes / block scalars.":" This may indicate a transpiler bug \u2014 please report it.";return{valid:!1,errors:[`Transpilation failed: ${c.message}.${d}`],warnings:[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},referencedTemplatePaths:l}}return{valid:!0,errors:[],warnings:o?.warnings??[],stats:o?.stats??{total:0,action:0,draft:0,coverage:0},specFile:s,referencedTemplatePaths:l}}var On="0.1.51";function go(e){try{return bc(e).mtimeMs}catch{return 0}}var vc=`// @generated by shiplightai v${On}`;function _c(e,t){if(!wo(e)||fo(e,"utf-8").split(`
|
|
4370
4370
|
`,1)[0]!==vc)return!1;let n=go(e);for(let a of t)if(go(a)>n)return!1;return!0}function kc(e){let t=process.argv.slice(2),i=[],n=Ln(e);for(let a of t){if(a.startsWith("-"))continue;let o=a.endsWith(".yaml.spec.ts")?a.replace(/\.yaml\.spec\.ts$/,".test.yaml"):a;if(!o.endsWith(".test.yaml"))continue;let r=Ln(e,o);wo(r)&&i.push(r.startsWith(n)?r.slice(n.length+1):o)}return i.length>0?i:null}function bo(e){let t=kc(e.cwd),i=t??xc("**/*.test.yaml",{cwd:e.cwd,ignore:["**/node_modules/**"]}),n=[];for(let a of i){let o=Ln(e.cwd,a),r=o.replace(/\.test\.yaml$/,".yaml.spec.ts"),s=fo(o,"utf-8");try{let l=Pn(s,o),c=yc(e.cwd,o),d=e.actionEntityStores?.get(c)??e.actionEntityStores?.get("*");if(!(d&&Object.keys(d.entries).length>0)&&_c(r,[o,...l.referencedTemplatePaths]))continue;let p=po(s,o,{version:On,actionEntityStore:d,parsed:l});if(!p.valid)throw new Error(p.errors.join("; "))}catch(l){console.error(`[shiplight] Failed to transpile ${a}:`,l),n.push({file:a,error:l})}}if(n.length>0){let a=`[shiplight] Transpilation failed for ${n.length} file(s):
|
|
4371
4371
|
`+n.map(o=>` - ${o.file}`).join(`
|
|
4372
4372
|
`);if(t)throw new Error(a);console.warn(a+" (skipped)")}}import*as ae from"fs";import*as mt from"path";import{globSync as Tc}from"glob";function vo(e){return process.env.CI&&process.env.SHIPLIGHT_API_TOKEN?new Fn:new Rn(e)}var Ac=".shiplight/action-cache";function xo(e){return e.replace(/\//g,"__")+".json"}function $c(e){return e.replace(/\.json$/,"").replace(/__/g,"/")}var Rn=class{constructor(t){this.cwd=t;this.cacheDir=mt.join(t,Ac)}isCloud=!1;cacheDir;async lookup(t){let i=new Map;if(t.length===0||!ae.existsSync(this.cacheDir))return i;for(let n of t){let a=mt.join(this.cacheDir,xo(n));try{if(ae.existsSync(a)){let o=ae.readFileSync(a,"utf-8");i.set(n,JSON.parse(o))}}catch{}}return i}async update(t){if(t.size===0)return 0;ae.mkdirSync(this.cacheDir,{recursive:!0});let i=0;for(let[n,a]of t)try{let o=mt.join(this.cacheDir,xo(n)),r=Tn();if(ae.existsSync(o))try{r=JSON.parse(ae.readFileSync(o,"utf-8"))}catch{}let s={...r,entries:{...r.entries,...a.entries}};ae.writeFileSync(o,JSON.stringify(s,null,2)),i++}catch{}return i}loadAll(){if(!ae.existsSync(this.cacheDir))return;let t=Tc("*.json",{cwd:this.cacheDir});if(t.length===0)return;let i=new Map,n=0;for(let a of t)try{let o=ae.readFileSync(mt.join(this.cacheDir,a),"utf-8"),r=JSON.parse(o),s=$c(a);i.set(s,r),n+=Object.keys(r.entries??{}).length}catch{}if(i.size!==0)return console.log(`[shiplight] Cache: loaded ${n} cached action entit${n===1?"y":"ies"} for ${i.size} test file${i.size!==1?"s":""}`),i}},Fn=class{isCloud=!0;async lookup(t){let{lookupActionStores:i}=await Promise.resolve().then(()=>(Dn(),Nn));return i(t)}async update(t){let{updateActionStores:i}=await Promise.resolve().then(()=>(Dn(),Nn));return i(t)}loadAll(){}};function Cc(e={}){e.dotenv!==!1&&Oc(e.scanDir||process.cwd());let t=e.scanDir||process.cwd(),n=vo(t).loadAll();return bo({cwd:t,actionEntityStores:n}),e.apiKey&&(process.env.__SHIPLIGHT_API_KEY=e.apiKey),{reporter:[["list"],["shiplightai/reporter",{outputFolder:"shiplight-report",open:"never",reportToCloud:e.reportToCloud,apiKey:e.apiKey}]]}}function Pc(e,...t){return Ic(e,...t)}function Oc(e){let t=[],i=We.resolve(e),n=We.resolve(process.cwd());for(;;){let a=We.join(i,".env");if(_o.existsSync(a)&&t.push(a),i===n)break;let o=We.dirname(i);if(o===i)break;i=o}for(let a of t)Mc.config({path:a})}R();R();It();Ze();import{z as Rs}from"zod";var qu=Rs.object({instruction:Rs.string().describe('The instruction of the operation to perform. Can only include one operation. Do not inlcude element indexes just describe the element, e.g. "select the text "Hello, world!" in "Hello, world!""')});function Fs(e){e.register({name:"perform_accurate_operation",description:"Perform an operation that requires accurate interaction like dragging or interacting with a specific area of an element. Only use this action when neccecary.",schema:qu,usesElementIndex:!1,async execute(t,i){let{instruction:n}=t,a={page:i.page,agentServices:i.agentServices,domService:i.domService},o=await qe(n,a,{});if(o.status==="error"||!o.actionEntity)return{success:!1,actionEntity:{action_description:n,action_data:{action_name:"perform_accurate_operation",kwargs:{instruction:n}}},error:o.error||"Failed to generate action"};let{actionEntity:r}=o,s=await $t(r,a);return{success:s.success,actionEntity:r,message:s.success?`Successfully executed action: ${r.action_data?.action_name}`:void 0,error:s.error}}})}Te();R();import{generateText as Zu}from"ai";import{convert as Qu}from"html-to-text";async function eh(e,t,i){let{apiKey:n,domain:a}=e;if(!n||!a)throw new Error("Mailgun configuration missing. Please provide apiKey and domain");let o=[];try{let r=`https://api.mailgun.net/v3/${a}/events`,s={event:"accepted",limit:"10",ascending:"yes",recipient:t};if(i.from_email&&(s.from=i.from_email),i.since)s.begin=Math.floor(i.since/1e3).toString();else{let d=new Date(Date.now()-6e5);s.begin=Math.floor(d.getTime()/1e3).toString()}u.info(`Mailgun params: ${JSON.stringify(s)}`);let l=await fetch(r+"?"+new URLSearchParams(s),{method:"GET",headers:{Authorization:`Basic ${Buffer.from(`api:${n}`).toString("base64")}`}});if(!l.ok){let d=await l.text();throw new Error(`Mailgun events API error: ${d}`)}let c=(await l.json()).items||[];for(let d of c.slice(0,10)){if(d.event!=="accepted")continue;let h=(d.storage||{}).url;if(u.info(`message_url: ${h}`),!h){let f=(d.message||{}).headers||{},x=f.subject||"",b=f.from||"",m=f.to||"";if(i.from_email&&!b.toLowerCase().includes(i.from_email.toLowerCase())||i.to_email&&!m.toLowerCase().includes(i.to_email.toLowerCase())||i.subject&&!x.toLowerCase().includes(i.subject.toLowerCase()))continue;o.push({subject:x,from:b,to:m,date:new Date(d.timestamp*1e3).toUTCString(),body:"Message body not available (Mailgun storage disabled)",message_id:f["message-id"]||""});continue}let p=h.split("/"),g=p[p.length-1];if(u.info(`Storage key: ${g}`),g){let f=`https://api.mailgun.net/v3/domains/${a}/messages/${g}`;try{let x=await fetch(f,{method:"GET",headers:{Authorization:`Basic ${Buffer.from(`api:${n}`).toString("base64")}`,Accept:"application/json"}});if(x.ok){let b=await x.json(),m=b.Subject||"",y=b.From||"",S=b.To||"",E=b.Date||"",A=b["Message-Id"]||"";u.info(`subject: ${m}`),u.info(`from_addr: ${y}`),u.info(`to_addr: ${S}`),u.info(`date: ${E}`),u.info(`message_id: ${A}`);let M=b["body-html"]||b["body-plain"]||"";if(M&&M.includes("<")&&(M=Qu(M)),u.info(`Body: ${M.substring(0,200)}...`),i.subject&&!m.toLowerCase().includes(i.subject.toLowerCase())||i.body_contains&&!M.toLowerCase().includes(i.body_contains.toLowerCase()))continue;o.push({subject:m,from:y,to:S,date:E,body:M,message_id:A});continue}else u.warn(`Messages API returned ${x.status}`)}catch(x){u.warn(`Failed to parse JSON response: ${x}`)}}try{let f=await fetch(h,{method:"GET",headers:{Authorization:`Basic ${Buffer.from(`api:${n}`).toString("base64")}`}});if(!f.ok){u.warn(`Could not fetch stored message: ${f.status}`);continue}let x=await f.text();u.info(`Fallback: Raw email length: ${x.length}`);let b=x.split(`
|
package/dist/reporter.js
CHANGED
|
@@ -879,6 +879,6 @@ Shiplight cloud report: ${f.data.reportUrl}`)}var We=Je(()=>{"use strict"});impo
|
|
|
879
879
|
</html>`}var Gt={before:0,main:1,teardown:2,after:3};function Ge(e){let t=e.split(".")[0];return Gt[t]??1}function He(e){return e.split(".").map(t=>{let r=Number(t);return Number.isNaN(r)?0:r})}function Ht(e){return[...e].sort(([t],[r])=>{let i=Ge(t),o=Ge(r);if(i!==o)return i-o;let n=He(t),s=He(r);for(let c=0;c<Math.max(n.length,s.length);c++){let d=n[c]??-1,p=s[c]??-1;if(d!==p)return d-p}return 0})}function Kt(e){let t=new Set;for(let r of e)if(t.add(r.category),r.category==="hook")for(let i of r.steps)t.add(i.category);return t}function jt(e,t){let r=e.toLowerCase();return r.includes("before")?"before":r.includes("after")?"after":t}function se(e,t="main",r,i,o){r===void 0&&(r=!Kt(e).has("test.step")),o||(o=new Map);let n=[];for(let s of e){if(s.category==="fixture"||s.category==="test.attach")continue;if(s.category==="hook"){let d=jt(s.title,t);n.push(...se(s.steps,d,r,i,o));continue}if(s.category==="test.step"||r&&(s.category==="expect"||s.category==="pw:api")){let d=o.get(t)??0;o.set(t,d+1);let p=`${t}.${d}`,m={stepId:p,description:s.title,status:s.error?"failure":s.duration===-1?"skipped":"success",duration:s.duration>=0?s.duration:void 0};s.error&&(m.error=s.error.message??s.error.stack),i&&s.location&&i.set(p,s.location),n.push(m),s.steps.length>0&&n.push(...se(s.steps,p,r,i,o))}}return n}var ae=class{outputFolder;openMode;reportToCloud;apiKey;collected=[];config;runStartTime;constructor(t={}){this.outputFolder=t.outputFolder||"shiplight-report",this.openMode=t.open||"on-failure",this.reportToCloud=t.reportToCloud??!1,this.apiKey=t.apiKey||process.env.__SHIPLIGHT_API_KEY}onBegin(t,r){this.config=t,this.runStartTime=new Date().toISOString()}onTestEnd(t,r){this.collected.push({test:t,result:r})}async onEnd(t){if(this.collected.length===0)return;let r=new Map;for(let p of this.collected){let m=p.test.titlePath().join(" > "),b=r.get(m);b||(b=[],r.set(m,b)),b.push(p)}let i=[];for(let[,p]of r.entries()){let m=p[0].test.location.file,b=[],u,h,f;for(let v=0;v<p.length;v++){let{test:T,result:_}=p[v],k=await this.buildReportTest(T,_,m);u=k,h||(h=k.startTime),f=k.endTime,b.push({attemptNumber:v+1,status:_.status,duration:_.duration,steps:k.steps,error:k.error,videoPath:k.videoPath,tracePath:k.tracePath})}let a=b[b.length-1],{test:S}=p[p.length-1],y={title:S.title,baseTitle:u?.baseTitle,file:x.relative(process.cwd(),m),status:a.status,duration:a.duration,steps:a.steps,error:a.error,videoPath:a.videoPath,tracePath:a.tracePath,actionStepsMap:u?.actionStepsMap,tags:u?.tags,baseUrl:u?.baseUrl,skip:u?.skip,slow:u?.slow,timeout:u?.timeout,parameterSetName:u?.parameterSetName,startTime:h,endTime:f,suiteName:u?.suiteName},w=b.some(v=>v.status==="failed"||v.status==="timedOut");b.length>1&&w&&a.status==="passed"&&(y.flaky=!0,y.retries=b.length-1,y.attempts=b),i.push(y)}let o={tests:i,totalDuration:t.duration,timestamp:new Date().toISOString()},n=x.isAbsolute(this.outputFolder)?this.outputFolder:x.join(process.cwd(),this.outputFolder);$.mkdirSync(n,{recursive:!0});let s=x.join(n,"screenshots");for(let p=0;p<o.tests.length;p++){let m=o.tests[p],b=m.attempts&&m.attempts.length>0,u=[{obj:m,prefix:b?`test-${p}-attempt-0`:`test-${p}`,screenshotSubDir:`test-${p}`}];if(m.attempts)for(let h=0;h<m.attempts.length;h++)u.push({obj:m.attempts[h],prefix:`test-${p}-attempt-${h+1}`,screenshotSubDir:`test-${p}/attempt-${h}`});for(let{obj:h,prefix:f,screenshotSubDir:a}of u){let S=x.join(s,a),y=!1;for(let w of h.steps)if(w.screenshot&&x.isAbsolute(w.screenshot))try{y||($.mkdirSync(S,{recursive:!0}),y=!0);let v=`${w.stepId.replace(/\./g,"-")}.png`;$.copyFileSync(w.screenshot,x.join(S,v)),w.screenshot=`screenshots/${a}/${v}`}catch(v){console.warn(`[reporter] Failed to copy screenshot for ${w.stepId}:`,v)}if(h.videoPath&&x.isAbsolute(h.videoPath)){let w=x.extname(h.videoPath)||".webm",v=`${f}-video${w}`;try{$.copyFileSync(h.videoPath,x.join(n,v)),h.videoPath=v}catch{h.videoPath=void 0}}if(h.tracePath&&x.isAbsolute(h.tracePath)){let w=x.extname(h.tracePath)||".zip",v=`${f}-trace${w}`;try{$.copyFileSync(h.tracePath,x.join(n,v)),h.tracePath=v}catch{h.tracePath=void 0}}}}let c=x.join(n,"report-data.json");$.writeFileSync(c,JSON.stringify(o,null,2),"utf-8");let d=x.join(n,"index.html");if($.writeFileSync(d,De(o),"utf-8"),console.log(`
|
|
880
880
|
Shiplight report written to: ${d}`),this.openMode==="always"||this.openMode==="on-failure"&&t.status!=="passed")try{let p=(await import("open")).default;await p(d)}catch{}if(this.reportToCloud){if(!this.apiKey){console.warn("[reporter] reportToCloud is enabled but no apiKey found, skipping cloud upload.");return}try{let{uploadToCloud:p}=await Promise.resolve().then(()=>(We(),Fe));await p(o,n,this.runStartTime,this.apiKey)}catch(p){console.warn("[reporter] Cloud upload failed:",p)}}}printsToStdio(){return!1}async buildReportTest(t,r,i){let o={title:t.title,file:x.relative(process.cwd(),i),status:r.status,duration:r.duration,steps:[],startTime:new Date(r.startTime).toISOString(),endTime:new Date(r.startTime.getTime()+r.duration).toISOString()};r.errors.length>0&&(o.error=r.errors.map(u=>u.message||u.stack||String(u)).join(`
|
|
881
881
|
|
|
882
|
-
`)),r.stdout.length>0&&(o.stdout=r.stdout.map(u=>typeof u=="string"?u:u.toString()).join("")),r.stderr.length>0&&(o.stderr=r.stderr.map(u=>typeof u=="string"?u:u.toString()).join(""));for(let u of r.attachments)u.name==="video"&&u.path&&(o.videoPath=u.path),u.name==="trace"&&u.path&&(o.tracePath=u.path);let n=r.attachments.find(u=>u.name==="shiplight-results"),s=null;if(n)try{if(n.body)s=JSON.parse(n.body.toString("utf-8"));else if(n.path){let u=$.readFileSync(n.path,"utf-8");s=JSON.parse(u)}}catch{}let c=i.replace(/\.yaml\.spec\.ts$/,".test.yaml"),d={},p=t.title.match(/^(.*)\s+\[([^\]]+)\]$/),m=p?p[1]:t.title,b=p?p[2]:void 0;if(b&&(o.parameterSetName=b),$.existsSync(c))try{let u=$.readFileSync(c,"utf-8"),h=Le(u,c);if(h.suite){let f=h.suite.tests.find(a=>a.name===m);f&&(d=X(f.testFlow),f.tags?.length&&(o.tags=f.tags),f.skip!==void 0&&(o.skip=f.skip),f.slow&&(o.slow=f.slow),f.timeout!==void 0&&(o.timeout=f.timeout),o.baseTitle=f.name||f.testFlow?.goal),o.suiteName=h.name,h.tags?.length&&(o.suiteTags=h.tags),h.use?.baseURL&&(o.baseUrl=h.use.baseURL)}else h.testFlow&&(d=X(h.testFlow),o.baseTitle=h.name||h.testFlow?.goal,h.tags?.length&&(o.tags=h.tags),h.use?.baseURL&&(o.baseUrl=h.use.baseURL))}catch{}if(s||Object.keys(d).length>0){let u=new Set([...Object.keys(d),...Object.keys(s||{})]),h=Array.from(u).map(a=>[a,null]),f=Ht(h);for(let[a]of f){let S=d[a],y=s?.[a],w=S?.description;if(!w||w==="Action"||w==="Draft"){let T=S?.action_entity;w=T?.action_description||T?.action_data?.kwargs?.description||y?.description||a}let v={stepId:a,description:w,status:y?.status||"pending",duration:y?.duration};if(y?.message){let T=typeof y.message=="string"?y.message:JSON.stringify(y.message,null,2);y.status==="failure"?v.error=T:v.message=T}if(y?.screenshot){let T=y.screenshot,_=n?.path?x.dirname(n.path):"",k=x.isAbsolute(T)?T:x.join(_,T);$.existsSync(k)&&(v.screenshot=k)}v.code=y.code,o.steps.push(v)}}if(s===null&&Object.keys(d).length===0&&!i.endsWith(".yaml.spec.ts")){let u=new Map;if(o.steps=se(r.steps,"main",void 0,u),o.steps.length>0){let h=new Map;o.actionStepsMap=Object.fromEntries(o.steps.map(f=>{let a=u.get(f.stepId),S;if(a?.file){if(!h.has(a.file))try{h.set(a.file,$.readFileSync(a.file,"utf-8").split(`
|
|
882
|
+
`)),r.stdout.length>0&&(o.stdout=r.stdout.map(u=>typeof u=="string"?u:u.toString()).join("")),r.stderr.length>0&&(o.stderr=r.stderr.map(u=>typeof u=="string"?u:u.toString()).join(""));for(let u of r.attachments)u.name==="video"&&u.path&&(o.videoPath=u.path),u.name==="trace"&&u.path&&(o.tracePath=u.path);let n=r.attachments.find(u=>u.name==="shiplight-results"),s=null;if(n)try{if(n.body)s=JSON.parse(n.body.toString("utf-8"));else if(n.path){let u=$.readFileSync(n.path,"utf-8");s=JSON.parse(u)}}catch{}let c=i.replace(/\.yaml\.spec\.ts$/,".test.yaml"),d={},p=t.title.match(/^(.*)\s+\[([^\]]+)\]$/),m=p?p[1]:t.title,b=p?p[2]:void 0;if(b&&(o.parameterSetName=b),$.existsSync(c))try{let u=$.readFileSync(c,"utf-8"),h=Le(u,c);if(h.suite){let f=h.suite.tests.find(a=>a.name===m);f&&(d=X(f.testFlow),f.tags?.length&&(o.tags=f.tags),f.skip!==void 0&&(o.skip=f.skip),f.slow&&(o.slow=f.slow),f.timeout!==void 0&&(o.timeout=f.timeout),o.baseTitle=f.name||f.testFlow?.goal),o.suiteName=h.name,h.tags?.length&&(o.suiteTags=h.tags),h.use?.baseURL&&(o.baseUrl=h.use.baseURL)}else h.testFlow&&(d=X(h.testFlow),o.baseTitle=h.name||h.testFlow?.goal,h.tags?.length&&(o.tags=h.tags),h.use?.baseURL&&(o.baseUrl=h.use.baseURL))}catch{}if(s||Object.keys(d).length>0){let u=new Set([...Object.keys(d),...Object.keys(s||{})]),h=Array.from(u).map(a=>[a,null]),f=Ht(h);for(let[a]of f){let S=d[a],y=s?.[a],w=S?.description;if(!w||w==="Action"||w==="Draft"){let T=S?.action_entity;w=T?.action_description||T?.action_data?.kwargs?.description||y?.description||a}let v={stepId:a,description:w,status:y?.status||"pending",duration:y?.duration};if(y?.message){let T=typeof y.message=="string"?y.message:JSON.stringify(y.message,null,2);y.status==="failure"?v.error=T:v.message=T}if(y?.screenshot){let T=y.screenshot,_=n?.path?x.dirname(n.path):"",k=x.isAbsolute(T)?T:x.join(_,T);$.existsSync(k)&&(v.screenshot=k)}y?.code&&(v.code=y.code),o.steps.push(v)}}if(s===null&&Object.keys(d).length===0&&!i.endsWith(".yaml.spec.ts")){let u=new Map;if(o.steps=se(r.steps,"main",void 0,u),o.steps.length>0){let h=new Map;o.actionStepsMap=Object.fromEntries(o.steps.map(f=>{let a=u.get(f.stepId),S;if(a?.file){if(!h.has(a.file))try{h.set(a.file,$.readFileSync(a.file,"utf-8").split(`
|
|
883
883
|
`))}catch{h.set(a.file,[])}let w=h.get(a.file);S=w[a.line-1]?.trim();let v=a.line-2,T=a.line,_=[],k=a.line;v>=0&&(_.push(w[v]??""),k=a.line-1),_.push(w[a.line-1]??""),T<w.length&&_.push(w[T]??""),f.code=_.join(`
|
|
884
884
|
`),f.codeStartLine=k,f.codeLine=a.line}let y={description:f.description,...S&&{action_entity:{action_description:f.description,action_data:{action_name:"js_code",args:[],kwargs:{code:S}}}}};return[f.stepId,y]}))}}return Object.keys(d).length>0?o.actionStepsMap=d:s&&o.steps.length>0&&(o.actionStepsMap=Object.fromEntries(o.steps.map(u=>{let h=s[u.stepId],f=h?.type,a={description:u.description,...f&&{action_entity:{action_description:u.description,action_data:{action_name:f==="step"?"js_code":f,args:[],kwargs:f==="step"?{code:h?.code??u.description}:{statement:u.description}}}}};return[u.stepId,a]}))),o}},zt=ae;export{zt as default};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shiplightai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.51",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shiplight CLI for running and debugging .test.yaml files",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -84,11 +84,11 @@
|
|
|
84
84
|
"tsup": "^8.3.5",
|
|
85
85
|
"typescript": "5.5.4",
|
|
86
86
|
"mcp-tools": "1.0.0",
|
|
87
|
+
"sdk-internal": "0.1.1",
|
|
87
88
|
"sdk-core": "0.1.0",
|
|
89
|
+
"shiplight-tools": "1.0.0",
|
|
88
90
|
"shiplight-types": "0.1.0",
|
|
89
|
-
"
|
|
90
|
-
"@loggia/common": "1.0.0",
|
|
91
|
-
"shiplight-tools": "1.0.0"
|
|
91
|
+
"@loggia/common": "1.0.0"
|
|
92
92
|
},
|
|
93
93
|
"peerDependencies": {
|
|
94
94
|
"@playwright/test": "1.58.2"
|