shiplightai 0.1.52 → 0.1.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/debugger-manager.cjs +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/reporter.cjs +1 -1
- package/dist/cli.js +70 -70
- package/dist/debugger-manager.d.ts +5 -1
- package/dist/debugger-manager.js +1 -1
- package/dist/index.js +1 -1
- package/dist/reporter.js +1 -1
- package/dist/static/assets/{index-CDlWEz3c.js → index-C25z-U4q.js} +10 -5
- package/dist/static/assets/{index-Cr4fXgHk.js → index-CFKeOCzg.js} +2 -2
- package/dist/static/index.html +1 -1
- package/package.json +3 -3
|
@@ -22,7 +22,7 @@ test('__shiplight_debug__', async ({ page, agent }) => {
|
|
|
22
22
|
// Keep alive until the server is closed externally (Ctrl+C kills the process)
|
|
23
23
|
await new Promise(() => {});
|
|
24
24
|
});
|
|
25
|
-
`}async function K(n){let{createServer:e}=await import("net");for(let t=n;t<n+20;t++)if(await new Promise(o=>{let r=e();r.once("error",()=>o(!1)),r.once("listening",()=>{r.close(()=>o(!0))}),r.listen(t,"127.0.0.1")}))return t;throw new Error(`No available port found in range ${n}-${n+19}`)}async function A(n){let{yamlFilePath:e,configPath:t,tempSuffix:i="",headed:o}=n,r=m.dirname(t),d=await K(16174),s;if(!y.existsSync(e))throw new Error(`Please select a test file before starting the debug session. File not found: ${e}`);try{let a=(0,D.parse)(y.readFileSync(e,"utf-8"));a?.use&&typeof a.use=="object"&&!Array.isArray(a.use)&&(s=a.use),a?.base_url&&!s?.baseURL&&(s={...s,baseURL:a.base_url}),a?.settings?.auto_dismiss_modal!==void 0&&(s={...s,autoDismissModal:!!a.settings.auto_dismiss_modal})}catch(a){console.error("[debugger] Could not parse YAML for `use` block:",a)}let c=m.dirname(m.resolve(e)),l=i?`-${i}`:"",g=m.join(c,`.__shiplight_debug__${l}.yaml.spec.ts`),h=V(e,d,s,r);y.writeFileSync(g,h);let f=()=>{try{y.unlinkSync(g)}catch{}},b=["playwright","test",g,...o?["--headed"]:[]],u=(0,H.spawn)("npx",b,{stdio:["ignore","pipe","pipe"],shell:!0,cwd:r,env:{...process.env,PWDEBUG:"console",SHIPLIGHT_REGISTRY_URL:""}});u.stdout?.on("data",a=>{process.stderr.write(a)}),u.stderr?.on("data",a=>{process.stderr.write(a)});let p=()=>{u.killed||u.kill("SIGTERM")};process.on("SIGTERM",p),process.on("SIGINT",p),process.on("exit",p),u.on("close",a=>{process.removeListener("SIGTERM",p),process.removeListener("SIGINT",p),process.removeListener("exit",p),f(),a!==0&&a!==null&&console.error(`[debugger] Playwright process exited with code ${a}`)}),console.error("[debugger] Waiting for Playwright sandbox to start...");let T=["127.0.0.1","::1"];async function x(a){try{let P=a.includes(":")?`[${a}]`:a,R=await fetch(`http://${P}:${d}/api/test-flow`);if(R.ok){try{await R.text()}catch{}return!0}}catch{}return!1}let w=null;for(let a=0;a<180;a++){if(u.exitCode!==null)throw f(),new Error(`Playwright process exited with code ${u.exitCode} before sandbox was ready`);for(let P of T)if(await x(P)){w=P;break}if(w){console.error(`[debugger] Playwright sandbox ready on ${w}:${d}`);break}if(a===179)throw p(),f(),new Error("Timed out waiting for Playwright sandbox to start (180s)");await new Promise(P=>setTimeout(P,1e3))}if(!w)throw p(),f(),new Error("Sandbox poll finished without a reachable host");return{port:d,host:w,pid:u.pid??0,cleanup:async()=>{p(),f()}}}var z=1e4,I=class{sessions=new Map;byYamlPath=new Map;options;spawner;headed;constructor(e={}){this.options=e,this.spawner=e.spawner??A,this.headed=e.headed??!
|
|
25
|
+
`}async function K(n){let{createServer:e}=await import("net");for(let t=n;t<n+20;t++)if(await new Promise(o=>{let r=e();r.once("error",()=>o(!1)),r.once("listening",()=>{r.close(()=>o(!0))}),r.listen(t,"127.0.0.1")}))return t;throw new Error(`No available port found in range ${n}-${n+19}`)}async function A(n){let{yamlFilePath:e,configPath:t,tempSuffix:i="",headed:o}=n,r=m.dirname(t),d=await K(16174),s;if(!y.existsSync(e))throw new Error(`Please select a test file before starting the debug session. File not found: ${e}`);try{let a=(0,D.parse)(y.readFileSync(e,"utf-8"));a?.use&&typeof a.use=="object"&&!Array.isArray(a.use)&&(s=a.use),a?.base_url&&!s?.baseURL&&(s={...s,baseURL:a.base_url}),a?.settings?.auto_dismiss_modal!==void 0&&(s={...s,autoDismissModal:!!a.settings.auto_dismiss_modal})}catch(a){console.error("[debugger] Could not parse YAML for `use` block:",a)}let c=m.dirname(m.resolve(e)),l=i?`-${i}`:"",g=m.join(c,`.__shiplight_debug__${l}.yaml.spec.ts`),h=V(e,d,s,r);y.writeFileSync(g,h);let f=()=>{try{y.unlinkSync(g)}catch{}},b=["playwright","test",g,...o?["--headed"]:[]],u=(0,H.spawn)("npx",b,{stdio:["ignore","pipe","pipe"],shell:!0,cwd:r,env:{...process.env,PWDEBUG:"console",SHIPLIGHT_REGISTRY_URL:""}});u.stdout?.on("data",a=>{process.stderr.write(a)}),u.stderr?.on("data",a=>{process.stderr.write(a)});let p=()=>{u.killed||u.kill("SIGTERM")};process.on("SIGTERM",p),process.on("SIGINT",p),process.on("exit",p),u.on("close",a=>{process.removeListener("SIGTERM",p),process.removeListener("SIGINT",p),process.removeListener("exit",p),f(),a!==0&&a!==null&&console.error(`[debugger] Playwright process exited with code ${a}`)}),console.error("[debugger] Waiting for Playwright sandbox to start...");let T=["127.0.0.1","::1"];async function x(a){try{let P=a.includes(":")?`[${a}]`:a,R=await fetch(`http://${P}:${d}/api/test-flow`);if(R.ok){try{await R.text()}catch{}return!0}}catch{}return!1}let w=null;for(let a=0;a<180;a++){if(u.exitCode!==null)throw f(),new Error(`Playwright process exited with code ${u.exitCode} before sandbox was ready`);for(let P of T)if(await x(P)){w=P;break}if(w){console.error(`[debugger] Playwright sandbox ready on ${w}:${d}`);break}if(a===179)throw p(),f(),new Error("Timed out waiting for Playwright sandbox to start (180s)");await new Promise(P=>setTimeout(P,1e3))}if(!w)throw p(),f(),new Error("Sandbox poll finished without a reachable host");return{port:d,host:w,pid:u.pid??0,cleanup:async()=>{p(),f()}}}var z=1e4,I=class{sessions=new Map;byYamlPath=new Map;options;spawner;headed;constructor(e={}){this.options=e,this.spawner=e.spawner??A,this.headed=e.headed??!1}log(e){this.options.onLog?this.options.onLog(e):console.error(e)}notifyStateChange(e){try{this.options.onSessionStateChange?.({...e})}catch(t){this.log(`[manager] onSessionStateChange listener threw: ${t.message}`)}}async openSession(e){let t=v.resolve(e),i=this.byYamlPath.get(t);if(i){let l=this.sessions.get(i);if(l&&l.session.status!=="ended")return{...l.session};this.byYamlPath.delete(t)}if(!j.existsSync(t))throw new Error(`YAML file not found: ${t}`);let o=O(v.dirname(t));if(!o)throw new Error(`No Playwright config found for ${t} (searched parents for playwright.config.{ts,js,mjs}).`);let r=`dbg-${(0,U.randomUUID)().slice(0,8)}`,d=new Date().toISOString(),s={sessionId:r,yamlPath:t,innerPort:0,innerHost:"127.0.0.1",pid:0,startedAt:d,status:"starting",exitInfo:null},c={session:s,cleanup:async()=>{},readyPromise:Promise.resolve()};this.sessions.set(r,c),this.byYamlPath.set(t,r),this.notifyStateChange(s);try{let l=z+18e4,g,h=new Promise((u,p)=>{g=setTimeout(()=>p(new Error(`Timed out after ${l/1e3}s waiting for inner playwright to register on a port.`)),l)}),f;try{f=await Promise.race([this.spawner({yamlFilePath:t,configPath:o,tempSuffix:r,headed:this.headed}),h])}finally{g&&clearTimeout(g)}s.innerPort=f.port,s.innerHost=f.host,s.pid=f.pid,s.status="running",c.cleanup=f.cleanup;let b=u=>{s.status!=="ended"&&(s.status="ended",s.exitInfo=u,this.notifyStateChange(s))};return c.livenessTimer=this.startLivenessProbe(r,b),this.notifyStateChange(s),this.log(`[manager] session ${r} running on ${s.innerHost}:${s.innerPort} (yaml=${v.basename(t)})`),{...s}}catch(l){throw this.sessions.delete(r),this.byYamlPath.get(t)===r&&this.byYamlPath.delete(t),s.status="ended",s.exitInfo=l.message,this.notifyStateChange(s),l}}startLivenessProbe(e,t){let r=0,d=setInterval(()=>{let s=this.sessions.get(e);if(!s||s.session.status==="ended"){clearInterval(d);return}let c=!1;try{process.kill(s.session.pid,0),c=!0}catch(l){l?.code!=="ESRCH"&&(c=!0)}c?r=0:(r+=1,r>=3&&(clearInterval(d),t("process-exited")))},3e3);return d.unref(),d}async closeSession(e){let t=this.sessions.get(e);if(t){this.sessions.delete(e),this.byYamlPath.get(t.session.yamlPath)===e&&this.byYamlPath.delete(t.session.yamlPath),t.livenessTimer&&(clearInterval(t.livenessTimer),t.livenessTimer=void 0),t.session.status!=="ended"&&(t.session.status="ended",t.session.exitInfo="SIGTERM",this.notifyStateChange(t.session));try{await t.cleanup()}catch(i){this.log(`[manager] closeSession ${e} cleanup error: ${i.message}`)}}}listSessions(){return Array.from(this.sessions.values()).map(e=>({...e.session}))}getSession(e){let t=this.sessions.get(e);return t?{...t.session}:void 0}httpProxyFor(e,t={}){let{liveviewUrlBuilder:i}=t;return async(o,r,d)=>{let s=this.sessions.get(e);if(!s){r.status(404).json({status:"error",message:"Session not found"});return}if(s.session.status==="ended"){r.status(410).json({status:"error",message:"Session has ended",exitInfo:s.session.exitInfo});return}let c=`${o.method} ${o.path}`;if(c==="POST /api/int-runner/create-session"){let g=await C(o),h={};if(g.length)try{h=JSON.parse(g.toString("utf-8"))}catch{r.status(400).json({status:"error",message:"Invalid JSON body"});return}h.testFilePath=s.session.yamlPath,await N(o,r,s.session.innerHost,s.session.innerPort,Buffer.from(JSON.stringify(h),"utf-8"),"application/json");return}if(c==="POST /api/int-runner/terminate-session"){await this.closeSession(e),r.json({status:"success",details:"Session terminated"});return}if(c==="POST /api/int-runner/liveview-url"){let g=i?.(o)??"";r.json({liveviewUrl:g,browserWsUrl:""});return}let l=await C(o);await N(o,r,s.session.innerHost,s.session.innerPort,l,o.headers["content-type"])}}wsUpgradeFor(e){return async(t,i,o,r)=>{let d=this.sessions.get(e);if(!d){E(i,`HTTP/1.1 404 Not Found\r
|
|
26
26
|
\r
|
|
27
27
|
`);return}if(d.session.status==="ended"){E(i,`HTTP/1.1 410 Gone\r
|
|
28
28
|
\r
|
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,No.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 Do=5;function Go(e,t){let i={expandingPaths:new Set([(0,xt.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>=Do)throw new Error(`Template expansion exceeded maximum depth of ${Do}. Check for deeply nested or circular template references.`);let n=(0,xt.resolve)((0,xt.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,bt.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,bt.stringify)(l);for(let[p,g]of Object.entries(s))h=h.split(`<<${p}>>`).join(String(g));l=(0,bt.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,yt.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,yt.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,yt.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=Nc(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,vt.mkdirSync)((0,Ko.dirname)(s),{recursive:!0}),(0,vt.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,vt.mkdirSync)((0,Ko.dirname)(s),{recursive:!0}),(0,vt.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.53";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),_t=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=_t.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=_t.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=_t.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(_t.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}),{reporter:[["list"],["shiplightai/reporter",{outputFolder:"shiplight-report",open:"never"}]]}}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();Ut();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 Ht(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
|
@@ -873,7 +873,7 @@
|
|
|
873
873
|
});
|
|
874
874
|
</script>
|
|
875
875
|
</body>
|
|
876
|
-
</html>`}var Pe="0.1.
|
|
876
|
+
</html>`}var Pe="0.1.53",Oe=Pe!=="dev"?Pe:void 0;var ao=3600*1e3,so=10080*60*1e3;var wt={before:0,main:1,teardown:2,after:3};function Ie(e){let t=e.split(".")[0];return wt[t]??1}function Le(e){return e.split(".").map(t=>{let r=Number(t);return Number.isNaN(r)?0:r})}function vt(e){return[...e].sort(([t],[r])=>{let i=Ie(t),o=Ie(r);if(i!==o)return i-o;let n=Le(t),a=Le(r);for(let c=0;c<Math.max(n.length,a.length);c++){let d=n[c]??-1,p=a[c]??-1;if(d!==p)return d-p}return 0})}function _t(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 St(e,t){let r=e.toLowerCase();return r.includes("before")?"before":r.includes("after")?"after":t}function ee(e,t="main",r,i,o){r===void 0&&(r=!_t(e).has("test.step")),o||(o=new Map);let n=[];for(let a of e){if(a.category==="fixture"||a.category==="test.attach")continue;if(a.category==="hook"){let d=St(a.title,t);n.push(...ee(a.steps,d,r,i,o));continue}if(a.category==="test.step"||r&&(a.category==="expect"||a.category==="pw:api")){let d=o.get(t)??0;o.set(t,d+1);let p=`${t}.${d}`,g={stepId:p,description:a.title,status:a.error?"failure":a.duration===-1?"skipped":"success",duration:a.duration>=0?a.duration:void 0};a.error&&(g.error=a.error.message??a.error.stack),i&&a.location&&i.set(p,a.location),n.push(g),a.steps.length>0&&n.push(...ee(a.steps,p,r,i,o))}}return n}var te=class{outputFolder;openMode;collected=[];config;runStartTime;constructor(t={}){this.outputFolder=t.outputFolder||"shiplight-report",this.openMode=t.open||"on-failure"}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 g=p.test.titlePath().join(" > "),y=r.get(g);y||(y=[],r.set(g,y)),y.push(p)}let i=[];for(let[,p]of r.entries()){let g=p[0].test.location.file,y=[],l,u,h;for(let b=0;b<p.length;b++){let{test:A,result:E}=p[b],M=await this.buildReportTest(A,E,g);l=M,u||(u=M.startTime),h=M.endTime,y.push({attemptNumber:b+1,status:E.status,duration:E.duration,steps:M.steps,error:M.error,videoPath:M.videoPath,tracePath:M.tracePath})}let m=y[y.length-1],{test:k}=p[p.length-1],v={title:k.title,baseTitle:l?.baseTitle,file:_.relative(process.cwd(),g),status:m.status,duration:m.duration,steps:m.steps,error:m.error,videoPath:m.videoPath,tracePath:m.tracePath,actionStepsMap:l?.actionStepsMap,tags:l?.tags,baseUrl:l?.baseUrl,skip:l?.skip,slow:l?.slow,timeout:l?.timeout,parameterSetName:l?.parameterSetName,startTime:u,endTime:h,suiteName:l?.suiteName},w=y.some(b=>b.status==="failed"||b.status==="timedOut");y.length>1&&w&&m.status==="passed"&&(v.flaky=!0,v.retries=y.length-1,v.attempts=y),i.push(v)}let o={tests:i,totalDuration:t.duration,timestamp:new Date().toISOString(),shiplightVersion:Oe},n=_.isAbsolute(this.outputFolder)?this.outputFolder:_.join(process.cwd(),this.outputFolder);T.mkdirSync(n,{recursive:!0});let a=_.join(n,"screenshots");for(let p=0;p<o.tests.length;p++){let g=o.tests[p],y=g.attempts&&g.attempts.length>0,l=[{obj:g,prefix:y?`test-${p}-attempt-0`:`test-${p}`,screenshotSubDir:`test-${p}`}];if(g.attempts)for(let u=0;u<g.attempts.length;u++)l.push({obj:g.attempts[u],prefix:`test-${p}-attempt-${u+1}`,screenshotSubDir:`test-${p}/attempt-${u}`});for(let{obj:u,prefix:h,screenshotSubDir:m}of l){let k=_.join(a,m),v=!1;for(let w of u.steps)if(w.screenshot&&_.isAbsolute(w.screenshot))try{v||(T.mkdirSync(k,{recursive:!0}),v=!0);let b=`${w.stepId.replace(/\./g,"-")}.png`;T.copyFileSync(w.screenshot,_.join(k,b)),w.screenshot=`screenshots/${m}/${b}`}catch(b){console.warn(`[reporter] Failed to copy screenshot for ${w.stepId}:`,b)}if(u.videoPath&&_.isAbsolute(u.videoPath)){let w=_.extname(u.videoPath)||".webm",b=`${h}-video${w}`;try{T.copyFileSync(u.videoPath,_.join(n,b)),u.videoPath=b}catch{u.videoPath=void 0}}if(u.tracePath&&_.isAbsolute(u.tracePath)){let w=_.extname(u.tracePath)||".zip",b=`${h}-trace${w}`;try{T.copyFileSync(u.tracePath,_.join(n,b)),u.tracePath=b}catch{u.tracePath=void 0}}}}let c=_.join(n,"report-data.json");T.writeFileSync(c,JSON.stringify(o,null,2),"utf-8");let d=_.join(n,"index.html");if(T.writeFileSync(d,Me(o),"utf-8"),console.log(`
|
|
877
877
|
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{}}printsToStdio(){return!1}async buildReportTest(t,r,i){let o={title:t.title,file:_.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(l=>l.message||l.stack||String(l)).join(`
|
|
878
878
|
|
|
879
879
|
`)),r.stdout.length>0&&(o.stdout=r.stdout.map(l=>typeof l=="string"?l:l.toString()).join("")),r.stderr.length>0&&(o.stderr=r.stderr.map(l=>typeof l=="string"?l:l.toString()).join(""));for(let l of r.attachments)l.name==="video"&&l.path&&(o.videoPath=l.path),l.name==="trace"&&l.path&&(o.tracePath=l.path);let n=r.attachments.find(l=>l.name==="shiplight-results"),a=null;if(n)try{if(n.body)a=JSON.parse(n.body.toString("utf-8"));else if(n.path){let l=T.readFileSync(n.path,"utf-8");a=JSON.parse(l)}}catch{}let c=i.replace(/\.yaml\.spec\.ts$/,".test.yaml"),d={},p=t.title.match(/^(.*)\s+\[([^\]]+)\]$/),g=p?p[1]:t.title,y=p?p[2]:void 0;if(y&&(o.parameterSetName=y),T.existsSync(c))try{let l=T.readFileSync(c,"utf-8"),u=Te(l,c);if(u.suite){let h=u.suite.tests.find(m=>m.name===g);h&&(d=z(h.testFlow),h.tags?.length&&(o.tags=h.tags),h.skip!==void 0&&(o.skip=h.skip),h.slow&&(o.slow=h.slow),h.timeout!==void 0&&(o.timeout=h.timeout),o.baseTitle=h.name||h.testFlow?.goal),o.suiteName=u.name,u.tags?.length&&(o.suiteTags=u.tags),u.use?.baseURL&&(o.baseUrl=u.use.baseURL)}else u.testFlow&&(d=z(u.testFlow),o.baseTitle=u.name||u.testFlow?.goal,u.tags?.length&&(o.tags=u.tags),u.use?.baseURL&&(o.baseUrl=u.use.baseURL))}catch{}if(a||Object.keys(d).length>0){let l=new Set([...Object.keys(d),...Object.keys(a||{})]),u=Array.from(l).map(m=>[m,null]),h=vt(u);for(let[m]of h){let k=d[m],v=a?.[m],w=k?.description;if(!w||w==="Action"||w==="Draft"){let A=k?.action_entity;w=A?.action_description||A?.action_data?.kwargs?.description||v?.description||m}let b={stepId:m,description:w,status:v?.status||"pending",duration:v?.duration};if(v?.message){let A=typeof v.message=="string"?v.message:JSON.stringify(v.message,null,2);v.status==="failure"?b.error=A:b.message=A}if(v?.screenshot){let A=v.screenshot,E=n?.path?_.dirname(n.path):"",M=_.isAbsolute(A)?A:_.join(E,A);T.existsSync(M)&&(b.screenshot=M)}v?.code&&(b.code=v.code),o.steps.push(b)}}if(a===null&&Object.keys(d).length===0&&!i.endsWith(".yaml.spec.ts")){let l=new Map;if(o.steps=ee(r.steps,"main",void 0,l),o.steps.length>0){let u=new Map;o.actionStepsMap=Object.fromEntries(o.steps.map(h=>{let m=l.get(h.stepId),k;if(m?.file){if(!u.has(m.file))try{u.set(m.file,T.readFileSync(m.file,"utf-8").split(`
|