@stacksjs/testing 0.75.341 → 0.75.342

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/browser.d.ts CHANGED
@@ -4,6 +4,8 @@ import process from 'node:process';
4
4
  * after `timeoutMs` (default 12 s) with `label` in the message.
5
5
  */
6
6
  export declare function waitFor<T>(read: () => Promise<T> | T, timeoutMs?: number, options?: { intervalMs?: number, label?: string }): Promise<NonNullable<T>>;
7
+ /** Scroll, wait for layout and hit testing, then click through Chrome's real input path. */
8
+ export declare function click(cdp: Cdp, target: string | { selector: string, text?: string }, options?: { timeoutMs?: number }): Promise<void>;
7
9
  /**
8
10
  * Whether `url` matches a `Fetch.enable` URL pattern the way Chrome matches
9
11
  * it: the whole URL, `*` any run of characters (none included), `?` exactly
package/dist/browser.js CHANGED
@@ -1,8 +1,24 @@
1
1
  // @bun
2
- import{closeSync as ye,existsSync as ee,mkdtempSync as ve,openSync as Pe,readSync as Se,rmSync as D}from"fs";import{tmpdir as xe}from"os";import{join as N}from"path";import L from"process";import{existsSync as S,mkdirSync as fe,mkdtempSync as me,readdirSync as _,renameSync as W,rmSync as ge}from"fs";import{tmpdir as we}from"os";import{basename as j,join as C}from"path";import q from"process";var K=["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome","/Applications/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing","/Applications/Chromium.app/Contents/MacOS/Chromium","/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"],be=["google-chrome","google-chrome-stable","chromium","chromium-browser","chrome"],J=["KSProductID","KSUpdateURL","KSChannelID","KSVersion"],G="dev.stacks.testing.chrome";function z(){if(q.env.CHROME_PATH)return q.env.CHROME_PATH;if(q.platform==="darwin"){let e=K.find((t)=>S(t));if(e)return e}for(let e of be){let t=Bun.which(e);if(t)return t}return null}function Y(e){let t=/^(.*?\.app)\/Contents\/MacOS\/[^/]+$/.exec(e);return t?t[1]:null}function P(e,t){let r=Bun.spawnSync(["/usr/libexec/PlistBuddy","-c",t,e],{stdout:"pipe",stderr:"pipe"});return{ok:r.exitCode===0,out:r.stdout.toString().trim()}}function X(e){let t=Y(e);if(!t||q.platform!=="darwin")return!1;let r=C(t,"Contents/Info.plist");return S(r)&&J.some((n)=>P(r,`Print :${n}`).ok)}function Q(e){let t=[];for(let r of _(e,{withFileTypes:!0})){if(!r.isDirectory())continue;let n=C(e,r.name);if(t.push(...Q(n)),!r.name.endsWith(".framework")||!S(C(n,"Versions")))continue;let o=P(C(n,"Resources/Info.plist"),"Print :CFBundleIdentifier");if(!o.ok||!o.out)throw Error(`Framework has no signing identifier: ${n}`);for(let s of _(C(n,"Versions"),{withFileTypes:!0}).filter((i)=>i.isDirectory()).sort((i,a)=>i.name.localeCompare(a.name)))t.push({path:C(n,"Versions",s.name),identifier:o.out})}return t}function Ce(e){return Bun.spawnSync(["codesign","--verify","--deep","--strict",e],{stdout:"pipe",stderr:"pipe"}).exitCode===0}function Z(e={}){if(q.platform!=="darwin")throw Error("prepareMacChrome is for macOS; elsewhere launch Chrome as installed");let t=e.source??K[0],r=Y(t);if(!r||!S(r))throw Error(`No Chrome app bundle at ${t}`);let n=C(r,"Contents/Info.plist"),o=P(n,"Print :CFBundleShortVersionString").out||"unknown",s=P(n,"Print :CFBundleExecutable").out||"Google Chrome",i=e.directory??C(we(),`stacks-chrome-${o.replace(/[^\w.-]/g,"_")}`),a=C(i,"Chrome.app"),c=S(i)?_(i,{withFileTypes:!0}).filter((d)=>d.isDirectory()&&(d.name==="Chrome.app"||d.name.startsWith("Chrome-")&&d.name.endsWith(".app"))).map((d)=>C(i,d.name)).sort():[];for(let d of c){let h=C(d,"Contents/MacOS",s),m=C(d,"Contents/Info.plist");if(S(h)&&P(m,"Print :CFBundleIdentifier").out===G&&P(m,"Print :CFBundleShortVersionString").out===o&&Ce(d))return{executable:h,app:d,reused:!0}}fe(i,{recursive:!0});let l=me(C(i,"staging-")),f=C(l,"Chrome.app"),g=(d,h)=>{let m=Bun.spawnSync(d,{stdout:"pipe",stderr:"pipe"});if(m.exitCode!==0)throw Error(`${h} failed: ${m.stderr.toString().trim()}`)};try{g(["/bin/cp","-cR",r,f],"Cloning Chrome"),g(["/usr/bin/xattr","-cr",f],"Clearing extended attributes");let d=C(f,"Contents/Info.plist");for(let p of J)P(d,`Delete :${p}`);g(["/usr/libexec/PlistBuddy","-c",`Set :CFBundleIdentifier ${G}`,d],"Renaming the Chrome bundle");let h=Q(f);for(let p of h)g(["codesign","--force","--deep","--sign","-",p.path],"Re-signing framework code");for(let p of h)g(["codesign","--force","--sign","-","--requirements",`=designated => identifier ${JSON.stringify(p.identifier)}`,p.path],"Sealing a framework version");g(["codesign","--force",...h.length?[]:["--deep"],"--sign","-",f],"Sealing Chrome"),g(["codesign","--verify","--deep","--strict",f],"Verifying Chrome");let m=S(a)?C(i,`Chrome-${j(l)}.app`):a;try{W(f,m)}catch(p){if(m!==a||!["EEXIST","ENOTEMPTY"].includes(p.code??""))throw p;m=C(i,`Chrome-${j(l)}.app`),W(f,m)}return{executable:C(m,"Contents/MacOS",s),app:m,reused:!1}}finally{ge(l,{recursive:!0,force:!0})}}async function ce(e,t=12000,r={}){let n=Date.now(),o=await e();while(!o&&Date.now()-n<t)await Bun.sleep(r.intervalMs??100),o=await e();if(!o)throw Error(`Timed out after ${t}ms${r.label?` waiting for ${r.label}`:""}`);return o}var Te=new Set(["Fetch.continueRequest","Fetch.fulfillRequest","Fetch.failRequest","Fetch.continueResponse"]);function Re(e,t){let r="";for(let n=0;n<e.length;n++){let o=e[n];if(o==="\\"&&n+1<e.length)r+=te(e[++n]);else if(o==="*")r+="[\\s\\S]*";else if(o==="?")r+="[\\s\\S]";else r+=te(o)}return new RegExp(`^${r}$`).test(t)}function te(e){return e.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&")}function re(e,t){let r=t.responseStatusCode!=null||t.responseErrorReason!=null?"Response":"Request";if((e.requestStage??"Request")!==r)return!1;if(e.resourceType&&e.resourceType!==t.resourceType)return!1;return Re(e.urlPattern??"*",t.request?.url??"")}class le{socket;id=0;pending=new Map;listeners=new Map;commandTimeoutMs;interceptors=[];ownFetch=null;answered=new Map;frameLoaders=new Map;requestLoaders=new Map;errors=[];inFlight=new Map;closed=!1;constructor(e,t={}){this.socket=e;this.commandTimeoutMs=t.commandTimeoutMs??30000,e.addEventListener("close",()=>this.markClosed()),e.addEventListener("message",(r)=>this.receive(String(r.data)))}markClosed(){this.closed=!0,this.frameLoaders.clear(),this.requestLoaders.clear();for(let[e,t]of this.pending)clearTimeout(t.timer),t.reject(Error("Chrome closed the connection")),this.pending.delete(e)}receive(e){let t;try{t=JSON.parse(e)}catch{return}if(t.id){let r=this.pending.get(t.id);if(!r)return;if(this.pending.delete(t.id),clearTimeout(r.timer),t.error)r.reject(Error(t.error.message));else r.resolve(t.result);return}if(this.track(t.method,t.params??{}),t.method==="Fetch.requestPaused"&&!this.routePaused(t.params??{}))return;this.dispatch(t.method,t.params??{})}dispatch(e,t){for(let r of[...this.listeners.get(e)??[]])try{r(t)}catch(n){this.errors.push(`Listener for ${e} threw: ${n?.message??String(n)}`)}}routePaused(e){this.remember(e.requestId,!1);let t=this.interceptors.filter((o)=>o.patterns.some((s)=>re(s,e)));for(let o of t)try{let s=o.handler(e,this);if(s&&typeof s.catch==="function")s.catch((i)=>this.errors.push(`Fetch handler threw: ${i?.message??String(i)}`))}catch(s){this.errors.push(`Fetch handler threw: ${s?.message??String(s)}`)}let r=(this.listeners.get("Fetch.requestPaused")?.size??0)>0,n=this.ownFetch?this.ownFetch.patterns.some((o)=>re(o,e)):!1;if(!t.length&&!n&&!r)this.send("Fetch.continueRequest",{requestId:e.requestId}).catch(()=>{});return r&&(n||!t.length)}remember(e,t){if(this.answered.delete(e),this.answered.set(e,t),this.answered.size>2000){for(let[r]of this.answered)if(this.answered.delete(r),this.answered.size<=1000)break}}async applyFetch(){let e=[...this.interceptors.flatMap((t)=>t.patterns),...this.ownFetch?.patterns??[]];if(!e.length)return await this.command("Fetch.disable");return await this.command("Fetch.enable",{...this.ownFetch?.params,patterns:e})}async intercept(e,t){if(!e.length)throw TypeError("intercept needs at least one pattern");let r={patterns:e.map((o)=>({...o})),handler:t};this.interceptors.push(r);try{await this.applyFetch()}catch(o){throw this.interceptors=this.interceptors.filter((s)=>s!==r),o}let n=!1;return async()=>{if(n)return;if(n=!0,this.interceptors=this.interceptors.filter((o)=>o!==r),!this.closed)await this.applyFetch()}}finishRequest(e){this.inFlight.delete(e),this.requestLoaders.delete(e)}retireFrameRequests(e,t){for(let[r,n]of this.requestLoaders)if(n.frameId===e&&(t===void 0||n.loaderId===t))this.finishRequest(r)}track(e,t){if(e==="Network.requestWillBeSent"){let{requestId:r,frameId:n,loaderId:o}=t;if(this.requestLoaders.delete(r),n&&o)this.requestLoaders.set(r,{frameId:n,loaderId:o});this.inFlight.set(t.requestId,`${t.request?.method} ${t.request?.url}`)}else if(e==="Network.loadingFinished"||e==="Network.loadingFailed")this.finishRequest(t.requestId);else if(e==="Network.responseReceived"&&t.response?.mimeType==="text/event-stream")this.finishRequest(t.requestId);else if(e==="Page.frameNavigated"&&t.frame?.id&&t.frame?.loaderId){let{id:r,loaderId:n}=t.frame,o=this.frameLoaders.get(r);if(o&&o!==n)this.retireFrameRequests(r,o);this.frameLoaders.set(r,n)}else if(e==="Page.frameDetached"&&t.reason==="remove")this.frameLoaders.delete(t.frameId),this.retireFrameRequests(t.frameId);if(e==="Runtime.exceptionThrown")this.errors.push(t.exceptionDetails?.exception?.description||t.exceptionDetails?.text||"Runtime exception");else if(e==="Runtime.consoleAPICalled"&&t.type==="error")this.errors.push((t.args||[]).map((r)=>r.value??r.description??"").join(" "));else if(e==="Log.entryAdded"&&t.entry?.level==="error"&&!String(t.entry.text).includes("favicon"))this.errors.push(t.entry.text);else if(e==="Network.loadingFailed"&&!t.canceled)this.errors.push(`Network: ${t.errorText||"request failed"}`)}on(e,t){let r=this.listeners.get(e);if(!r)this.listeners.set(e,r=new Set);return r.add(t),()=>this.off(e,t)}off(e,t){this.listeners.get(e)?.delete(t)}once(e,t=()=>!0,r=this.commandTimeoutMs){return new Promise((n,o)=>{if(this.closed)return o(Error("Chrome closed the connection"));let s=setTimeout(()=>{a(),clearInterval(i),o(Error(`Timed out after ${r}ms waiting for ${e}`))},r),i=setInterval(()=>{if(!this.closed)return;clearTimeout(s),clearInterval(i),a(),o(Error("Chrome closed the connection"))},100),a=this.on(e,(c)=>{if(!t(c))return;clearTimeout(s),clearInterval(i),a(),n(c)})})}send(e,t={}){if(e==="Fetch.enable"){let{patterns:r,...n}=t;return this.ownFetch={patterns:r?.length?r.map((o)=>({...o})):[{urlPattern:"*"}],params:n},this.closed?this.command(e,t):this.applyFetch()}if(e==="Fetch.disable")return this.ownFetch=null,this.closed?this.command(e,t):this.applyFetch();if(Te.has(e)&&typeof t.requestId==="string"){if(this.answered.get(t.requestId)===!0)return Promise.resolve({});if(this.answered.has(t.requestId))this.remember(t.requestId,!0)}return this.command(e,t)}command(e,t={}){let r=++this.id;return new Promise((n,o)=>{if(this.closed)return o(Error("Chrome closed the connection"));let s=setTimeout(()=>{this.pending.delete(r),o(Error(`Chrome command timed out: ${e}`))},this.commandTimeoutMs);this.pending.set(r,{resolve:n,reject:o,timer:s}),this.socket.send(JSON.stringify({id:r,method:e,params:t}))})}async evaluate(e){let t=await this.send("Runtime.evaluate",{expression:e,awaitPromise:!0,returnByValue:!0});if(t.exceptionDetails)throw Error(t.exceptionDetails.exception?.description||t.exceptionDetails.text);return t.result?.value}disconnect(){this.markClosed();try{this.socket.close()}catch{}}}async function H(e,t={}){let r=new WebSocket(e);return await new Promise((n,o)=>{let s=(l)=>{if(clearTimeout(c),r.removeEventListener("open",i),r.removeEventListener("error",a),r.removeEventListener("close",a),l){try{r.close()}catch{}o(l)}else n()},i=()=>s(),a=()=>s(Error(`Could not connect to Chrome at ${e}`)),c=setTimeout(()=>s(Error(`Timed out connecting to Chrome at ${e}`)),t.connectTimeoutMs??30000);r.addEventListener("open",i,{once:!0}),r.addEventListener("error",a,{once:!0}),r.addEventListener("close",a,{once:!0})}),new le(r,t)}function Ee(){return z()}function qe(e){return X(e)}function $e(e={}){return Z(e)}function v(e,t){e.phase=t,e.phaseStartedMs[t]=Date.now()-e.started}class U extends Error{diagnostics;constructor(e,t){super(`${e instanceof Error?e.message:String(e)}
3
- Chrome startup: ${JSON.stringify(t)}`,{cause:e});this.diagnostics=t;this.name="ChromeLaunchError"}}async function M(e,t){let r;try{return await Promise.race([e.then(()=>!0),new Promise((n)=>{r=setTimeout(()=>n(!1),t)})])}finally{clearTimeout(r)}}function B(e){let t;try{t=Pe(e,"r");let r=Buffer.alloc(1024);return r.subarray(0,Se(t,r,0,r.length,0)).toString("utf8")}catch{return null}finally{if(t!==void 0)try{ye(t)}catch{}}}var Me=["*/_stx/hmr*"];async function Le(e,t=Me){if(!t.length)return async()=>{};return await e.intercept(t.map((r)=>({urlPattern:r})),(r,n)=>{n.send("Fetch.fulfillRequest",{requestId:r.requestId,responseCode:200,responseHeaders:[{name:"Content-Type",value:"text/event-stream"}],body:Buffer.from(`retry: 86400000
2
+ import{closeSync as Ce,existsSync as te,mkdtempSync as ve,openSync as Pe,readSync as Se,rmSync as k}from"fs";import{tmpdir as xe}from"os";import{join as _}from"path";import F from"process";import{existsSync as S,mkdirSync as me,mkdtempSync as fe,readdirSync as N,renameSync as j,rmSync as ge}from"fs";import{tmpdir as we}from"os";import{basename as G,join as y}from"path";import q from"process";var K=["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome","/Applications/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing","/Applications/Chromium.app/Contents/MacOS/Chromium","/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"],be=["google-chrome","google-chrome-stable","chromium","chromium-browser","chrome"],z=["KSProductID","KSUpdateURL","KSChannelID","KSVersion"],J="dev.stacks.testing.chrome";function Y(){if(q.env.CHROME_PATH)return q.env.CHROME_PATH;if(q.platform==="darwin"){let e=K.find((t)=>S(t));if(e)return e}for(let e of be){let t=Bun.which(e);if(t)return t}return null}function X(e){let t=/^(.*?\.app)\/Contents\/MacOS\/[^/]+$/.exec(e);return t?t[1]:null}function P(e,t){let r=Bun.spawnSync(["/usr/libexec/PlistBuddy","-c",t,e],{stdout:"pipe",stderr:"pipe"});return{ok:r.exitCode===0,out:r.stdout.toString().trim()}}function Q(e){let t=X(e);if(!t||q.platform!=="darwin")return!1;let r=y(t,"Contents/Info.plist");return S(r)&&z.some((i)=>P(r,`Print :${i}`).ok)}function Z(e){let t=[];for(let r of N(e,{withFileTypes:!0})){if(!r.isDirectory())continue;let i=y(e,r.name);if(t.push(...Z(i)),!r.name.endsWith(".framework")||!S(y(i,"Versions")))continue;let n=P(y(i,"Resources/Info.plist"),"Print :CFBundleIdentifier");if(!n.ok||!n.out)throw Error(`Framework has no signing identifier: ${i}`);for(let o of N(y(i,"Versions"),{withFileTypes:!0}).filter((s)=>s.isDirectory()).sort((s,a)=>s.name.localeCompare(a.name)))t.push({path:y(i,"Versions",o.name),identifier:n.out})}return t}function ye(e){return Bun.spawnSync(["codesign","--verify","--deep","--strict",e],{stdout:"pipe",stderr:"pipe"}).exitCode===0}function ee(e={}){if(q.platform!=="darwin")throw Error("prepareMacChrome is for macOS; elsewhere launch Chrome as installed");let t=e.source??K[0],r=X(t);if(!r||!S(r))throw Error(`No Chrome app bundle at ${t}`);let i=y(r,"Contents/Info.plist"),n=P(i,"Print :CFBundleShortVersionString").out||"unknown",o=P(i,"Print :CFBundleExecutable").out||"Google Chrome",s=e.directory??y(we(),`stacks-chrome-${n.replace(/[^\w.-]/g,"_")}`),a=y(s,"Chrome.app"),c=S(s)?N(s,{withFileTypes:!0}).filter((d)=>d.isDirectory()&&(d.name==="Chrome.app"||d.name.startsWith("Chrome-")&&d.name.endsWith(".app"))).map((d)=>y(s,d.name)).sort():[];for(let d of c){let h=y(d,"Contents/MacOS",o),f=y(d,"Contents/Info.plist");if(S(h)&&P(f,"Print :CFBundleIdentifier").out===J&&P(f,"Print :CFBundleShortVersionString").out===n&&ye(d))return{executable:h,app:d,reused:!0}}me(s,{recursive:!0});let l=fe(y(s,"staging-")),m=y(l,"Chrome.app"),g=(d,h)=>{let f=Bun.spawnSync(d,{stdout:"pipe",stderr:"pipe"});if(f.exitCode!==0)throw Error(`${h} failed: ${f.stderr.toString().trim()}`)};try{g(["/bin/cp","-cR",r,m],"Cloning Chrome"),g(["/usr/bin/xattr","-cr",m],"Clearing extended attributes");let d=y(m,"Contents/Info.plist");for(let p of z)P(d,`Delete :${p}`);g(["/usr/libexec/PlistBuddy","-c",`Set :CFBundleIdentifier ${J}`,d],"Renaming the Chrome bundle");let h=Z(m);for(let p of h)g(["codesign","--force","--deep","--sign","-",p.path],"Re-signing framework code");for(let p of h)g(["codesign","--force","--sign","-","--requirements",`=designated => identifier ${JSON.stringify(p.identifier)}`,p.path],"Sealing a framework version");g(["codesign","--force",...h.length?[]:["--deep"],"--sign","-",m],"Sealing Chrome"),g(["codesign","--verify","--deep","--strict",m],"Verifying Chrome");let f=S(a)?y(s,`Chrome-${G(l)}.app`):a;try{j(m,f)}catch(p){if(f!==a||!["EEXIST","ENOTEMPTY"].includes(p.code??""))throw p;f=y(s,`Chrome-${G(l)}.app`),j(m,f)}return{executable:y(f,"Contents/MacOS",o),app:f,reused:!1}}finally{ge(l,{recursive:!0,force:!0})}}async function U(e,t=12000,r={}){let i=Date.now(),n=await e();while(!n&&Date.now()-i<t)await Bun.sleep(r.intervalMs??100),n=await e();if(!n)throw Error(`Timed out after ${t}ms${r.label?` waiting for ${r.label}`:""}`);return n}async function tt(e,t,r={}){let i=typeof t==="string"?t:t.selector,n=typeof t==="string"?void 0:t.text,o=await U(()=>e.evaluate(`(async () => {
3
+ const matches = [...document.querySelectorAll(${JSON.stringify(i)})].filter(node => ${n===void 0?"true":`node.textContent.trim() === ${JSON.stringify(n)}`})
4
+ if (matches.length > 1) throw new Error('Ambiguous click target: ' + ${JSON.stringify(i)})
5
+ const node = matches[0]
6
+ if (!node || node.matches(':disabled') || node.getAttribute('aria-disabled') === 'true') return null
7
+ node.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' })
8
+ // A synchronous bounding rect can precede the compositor's scroll/hit-test update.
9
+ await new Promise(requestAnimationFrame)
10
+ await new Promise(requestAnimationFrame)
11
+ if (!node.isConnected || node.matches(':disabled')) return null
12
+ const rect = node.getBoundingClientRect()
13
+ if (!rect.width || !rect.height || rect.bottom <= 0 || rect.right <= 0 || rect.top >= innerHeight || rect.left >= innerWidth) return null
14
+ const x = Math.max(0, Math.min(innerWidth - 1, rect.x + rect.width / 2))
15
+ const y = Math.max(0, Math.min(innerHeight - 1, rect.y + rect.height / 2))
16
+ const hit = document.elementFromPoint(x, y)
17
+ return hit && node.contains(hit) ? { x, y } : null
18
+ })()`),r.timeoutMs??12000,{label:`an enabled, unobscured ${i}${n===void 0?"":` named ${JSON.stringify(n)}`}`});await e.send("Input.dispatchMouseEvent",{type:"mousePressed",...o,button:"left",clickCount:1}),await e.send("Input.dispatchMouseEvent",{type:"mouseReleased",...o,button:"left",clickCount:1})}var Te=new Set(["Fetch.continueRequest","Fetch.fulfillRequest","Fetch.failRequest","Fetch.continueResponse"]);function Re(e,t){let r="";for(let i=0;i<e.length;i++){let n=e[i];if(n==="\\"&&i+1<e.length)r+=re(e[++i]);else if(n==="*")r+="[\\s\\S]*";else if(n==="?")r+="[\\s\\S]";else r+=re(n)}return new RegExp(`^${r}$`).test(t)}function re(e){return e.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&")}function ne(e,t){let r=t.responseStatusCode!=null||t.responseErrorReason!=null?"Response":"Request";if((e.requestStage??"Request")!==r)return!1;if(e.resourceType&&e.resourceType!==t.resourceType)return!1;return Re(e.urlPattern??"*",t.request?.url??"")}class le{socket;id=0;pending=new Map;listeners=new Map;commandTimeoutMs;interceptors=[];ownFetch=null;answered=new Map;frameLoaders=new Map;requestLoaders=new Map;errors=[];inFlight=new Map;closed=!1;constructor(e,t={}){this.socket=e;this.commandTimeoutMs=t.commandTimeoutMs??30000,e.addEventListener("close",()=>this.markClosed()),e.addEventListener("message",(r)=>this.receive(String(r.data)))}markClosed(){this.closed=!0,this.frameLoaders.clear(),this.requestLoaders.clear();for(let[e,t]of this.pending)clearTimeout(t.timer),t.reject(Error("Chrome closed the connection")),this.pending.delete(e)}receive(e){let t;try{t=JSON.parse(e)}catch{return}if(t.id){let r=this.pending.get(t.id);if(!r)return;if(this.pending.delete(t.id),clearTimeout(r.timer),t.error)r.reject(Error(t.error.message));else r.resolve(t.result);return}if(this.track(t.method,t.params??{}),t.method==="Fetch.requestPaused"&&!this.routePaused(t.params??{}))return;this.dispatch(t.method,t.params??{})}dispatch(e,t){for(let r of[...this.listeners.get(e)??[]])try{r(t)}catch(i){this.errors.push(`Listener for ${e} threw: ${i?.message??String(i)}`)}}routePaused(e){this.remember(e.requestId,!1);let t=this.interceptors.filter((n)=>n.patterns.some((o)=>ne(o,e)));for(let n of t)try{let o=n.handler(e,this);if(o&&typeof o.catch==="function")o.catch((s)=>this.errors.push(`Fetch handler threw: ${s?.message??String(s)}`))}catch(o){this.errors.push(`Fetch handler threw: ${o?.message??String(o)}`)}let r=(this.listeners.get("Fetch.requestPaused")?.size??0)>0,i=this.ownFetch?this.ownFetch.patterns.some((n)=>ne(n,e)):!1;if(!t.length&&!i&&!r)this.send("Fetch.continueRequest",{requestId:e.requestId}).catch(()=>{});return r&&(i||!t.length)}remember(e,t){if(this.answered.delete(e),this.answered.set(e,t),this.answered.size>2000){for(let[r]of this.answered)if(this.answered.delete(r),this.answered.size<=1000)break}}async applyFetch(){let e=[...this.interceptors.flatMap((t)=>t.patterns),...this.ownFetch?.patterns??[]];if(!e.length)return await this.command("Fetch.disable");return await this.command("Fetch.enable",{...this.ownFetch?.params,patterns:e})}async intercept(e,t){if(!e.length)throw TypeError("intercept needs at least one pattern");let r={patterns:e.map((n)=>({...n})),handler:t};this.interceptors.push(r);try{await this.applyFetch()}catch(n){throw this.interceptors=this.interceptors.filter((o)=>o!==r),n}let i=!1;return async()=>{if(i)return;if(i=!0,this.interceptors=this.interceptors.filter((n)=>n!==r),!this.closed)await this.applyFetch()}}finishRequest(e){this.inFlight.delete(e),this.requestLoaders.delete(e)}retireFrameRequests(e,t){for(let[r,i]of this.requestLoaders)if(i.frameId===e&&(t===void 0||i.loaderId===t))this.finishRequest(r)}track(e,t){if(e==="Network.requestWillBeSent"){let{requestId:r,frameId:i,loaderId:n}=t;if(this.requestLoaders.delete(r),i&&n)this.requestLoaders.set(r,{frameId:i,loaderId:n});this.inFlight.set(t.requestId,`${t.request?.method} ${t.request?.url}`)}else if(e==="Network.loadingFinished"||e==="Network.loadingFailed")this.finishRequest(t.requestId);else if(e==="Network.responseReceived"&&t.response?.mimeType==="text/event-stream")this.finishRequest(t.requestId);else if(e==="Page.frameNavigated"&&t.frame?.id&&t.frame?.loaderId){let{id:r,loaderId:i}=t.frame,n=this.frameLoaders.get(r);if(n&&n!==i)this.retireFrameRequests(r,n);this.frameLoaders.set(r,i)}else if(e==="Page.frameDetached"&&t.reason==="remove")this.frameLoaders.delete(t.frameId),this.retireFrameRequests(t.frameId);if(e==="Runtime.exceptionThrown")this.errors.push(t.exceptionDetails?.exception?.description||t.exceptionDetails?.text||"Runtime exception");else if(e==="Runtime.consoleAPICalled"&&t.type==="error")this.errors.push((t.args||[]).map((r)=>r.value??r.description??"").join(" "));else if(e==="Log.entryAdded"&&t.entry?.level==="error"&&!String(t.entry.text).includes("favicon"))this.errors.push(t.entry.text);else if(e==="Network.loadingFailed"&&!t.canceled)this.errors.push(`Network: ${t.errorText||"request failed"}`)}on(e,t){let r=this.listeners.get(e);if(!r)this.listeners.set(e,r=new Set);return r.add(t),()=>this.off(e,t)}off(e,t){this.listeners.get(e)?.delete(t)}once(e,t=()=>!0,r=this.commandTimeoutMs){return new Promise((i,n)=>{if(this.closed)return n(Error("Chrome closed the connection"));let o=setTimeout(()=>{a(),clearInterval(s),n(Error(`Timed out after ${r}ms waiting for ${e}`))},r),s=setInterval(()=>{if(!this.closed)return;clearTimeout(o),clearInterval(s),a(),n(Error("Chrome closed the connection"))},100),a=this.on(e,(c)=>{if(!t(c))return;clearTimeout(o),clearInterval(s),a(),i(c)})})}send(e,t={}){if(e==="Fetch.enable"){let{patterns:r,...i}=t;return this.ownFetch={patterns:r?.length?r.map((n)=>({...n})):[{urlPattern:"*"}],params:i},this.closed?this.command(e,t):this.applyFetch()}if(e==="Fetch.disable")return this.ownFetch=null,this.closed?this.command(e,t):this.applyFetch();if(Te.has(e)&&typeof t.requestId==="string"){if(this.answered.get(t.requestId)===!0)return Promise.resolve({});if(this.answered.has(t.requestId))this.remember(t.requestId,!0)}return this.command(e,t)}command(e,t={}){let r=++this.id;return new Promise((i,n)=>{if(this.closed)return n(Error("Chrome closed the connection"));let o=setTimeout(()=>{this.pending.delete(r),n(Error(`Chrome command timed out: ${e}`))},this.commandTimeoutMs);this.pending.set(r,{resolve:i,reject:n,timer:o}),this.socket.send(JSON.stringify({id:r,method:e,params:t}))})}async evaluate(e){let t=await this.send("Runtime.evaluate",{expression:e,awaitPromise:!0,returnByValue:!0});if(t.exceptionDetails)throw Error(t.exceptionDetails.exception?.description||t.exceptionDetails.text);return t.result?.value}disconnect(){this.markClosed();try{this.socket.close()}catch{}}}async function W(e,t={}){let r=new WebSocket(e);return await new Promise((i,n)=>{let o=(l)=>{if(clearTimeout(c),r.removeEventListener("open",s),r.removeEventListener("error",a),r.removeEventListener("close",a),l){try{r.close()}catch{}n(l)}else i()},s=()=>o(),a=()=>o(Error(`Could not connect to Chrome at ${e}`)),c=setTimeout(()=>o(Error(`Timed out connecting to Chrome at ${e}`)),t.connectTimeoutMs??30000);r.addEventListener("open",s,{once:!0}),r.addEventListener("error",a,{once:!0}),r.addEventListener("close",a,{once:!0})}),new le(r,t)}function Ee(){return Y()}function qe(e){return Q(e)}function Me(e={}){return ee(e)}function v(e,t){e.phase=t,e.phaseStartedMs[t]=Date.now()-e.started}class H extends Error{diagnostics;constructor(e,t){super(`${e instanceof Error?e.message:String(e)}
19
+ Chrome startup: ${JSON.stringify(t)}`,{cause:e});this.diagnostics=t;this.name="ChromeLaunchError"}}async function M(e,t){let r;try{return await Promise.race([e.then(()=>!0),new Promise((i)=>{r=setTimeout(()=>i(!1),t)})])}finally{clearTimeout(r)}}function B(e){let t;try{t=Pe(e,"r");let r=Buffer.alloc(1024);return r.subarray(0,Se(t,r,0,r.length,0)).toString("utf8")}catch{return null}finally{if(t!==void 0)try{Ce(t)}catch{}}}var $e=["*/_stx/hmr*"];async function Fe(e,t=$e){if(!t.length)return async()=>{};return await e.intercept(t.map((r)=>({urlPattern:r})),(r,i)=>{i.send("Fetch.fulfillRequest",{requestId:r.requestId,responseCode:200,responseHeaders:[{name:"Content-Type",value:"text/event-stream"}],body:Buffer.from(`retry: 86400000
4
20
 
5
- `).toString("base64")}).catch(()=>{})})}var ne=new Map;function Fe(e){let t=e.chromePath??L.env.CHROME_PATH;if(t)return t;let r=Ee();if(!r)throw Error("No Chrome found: install Google Chrome or Chromium, or set CHROME_PATH");if((e.stableMacChrome??!0)&&L.platform==="darwin"&&qe(r)){let o=ne.get(r);if(o&&[...R.values()].some((i)=>i.chromePath===o&&!i.exited()))return o;let s=$e({source:r}).executable;return ne.set(r,s),s}return r}async function ue(e,t,r,n,o){if(v(o,"process-start"),!ee(t))throw Error(`Chrome was not found at ${t}`);let s=e.profile||ve(N(xe(),"stacks-browser-"));D(N(s,"DevToolsActivePort"),{force:!0});let i=N(s,"DevToolsActivePort"),a=(()=>{try{return Bun.spawn([t,...e.headless===!1?[]:["--headless=new"],"--remote-debugging-port=0",`--user-data-dir=${s}`,"--no-first-run","--no-default-browser-check","--disable-background-networking","--disable-component-update","--disable-extensions","--hide-scrollbars","--use-mock-keychain",...e.args??[],"about:blank"],{stdin:"ignore",stdout:"ignore",stderr:"pipe"})}catch(u){let w=null;try{if(!e.profile)D(s,{recursive:!0,force:!0})}catch(b){w=String(b)}throw new U(u,{pid:null,chromePath:t,profile:s,temporaryProfile:!e.profile,elapsedMs:Date.now()-r,phase:o.phase,phaseStartedMs:{...o.phaseStartedMs},exitCodeBeforeCleanup:null,signalCodeBeforeCleanup:null,exitCode:null,signalCode:null,portFileExisted:!1,portFileContents:null,stderrTail:"",stderrTruncated:!1,cleanupError:w})}})(),c="",l=!1,f,g=Promise.resolve(),d,h=()=>d??=(async()=>{let u=!1,w=[];try{if(a.exitCode===null&&a.signalCode===null)try{a.kill("SIGTERM")}catch{}if(!await M(a.exited,2000)){try{a.kill("SIGKILL")}catch{}if(!await M(a.exited,2000))throw Error(`Chrome process ${a.pid} did not exit after SIGKILL`)}u=!0}catch(b){w.push(String(b)),u=a.exitCode!==null||a.signalCode!==null}finally{try{if(!await M(g,100)&&f){if(!await M(f.cancel(),100))throw Error("Chrome stderr cancellation did not finish")}if(!await M(g,100))throw Error("Chrome stderr reader did not finish")}catch(b){w.push(String(b))}finally{if(u&&!e.profile)try{D(s,{recursive:!0,force:!0})}catch(b){w.push(String(b))}}}if(w.length)throw Error(`Chrome cleanup failed: ${w.join("; ")}`)})(),m=()=>a.exitCode!==null||a.signalCode!==null,p=()=>{let u=n-(Date.now()-r);if(u<=0)throw Error(`Timed out after ${n}ms starting Chrome`);if(m())throw Error("Chrome exited during startup");return u},x=async(u,w,b=o)=>{let T=Date.now()-b.started,E=b.phase,O={...b.phaseStartedMs},{exitCode:F,signalCode:I}=a,he=ee(i),pe=B(i),V=null;if(w)try{await h()}catch(A){V=A instanceof Error?A.message:String(A)}return new U(u,{pid:a.pid,chromePath:t,profile:s,temporaryProfile:!e.profile,elapsedMs:T,phase:E,phaseStartedMs:O,exitCodeBeforeCleanup:F,signalCodeBeforeCleanup:I,exitCode:a.exitCode,signalCode:a.signalCode,portFileExisted:he,portFileContents:pe,stderrTail:c,stderrTruncated:l,cleanupError:V})};try{f=a.stderr.getReader();let u=f;g=(async()=>{let E=new TextDecoder;for(;;){let{value:O,done:F}=await u.read(),I=E.decode(O,{stream:!F});if(l||=c.length+I.length>16384,c=(c+I).slice(-16384),F)return}})().catch((E)=>{c=`${c}
6
- [stderr read failed: ${String(E)}]`.slice(-16384)}),v(o,"devtools-port"),await ce(()=>(p(),B(i)?.includes(`
7
- `)),p(),{label:"Chrome to start"});let[w,b=""]=(B(i)??"").split(`
8
- `),T=Number(w);if(!Number.isInteger(T)||T<1||T>65535)throw Error("Chrome wrote an invalid DevToolsActivePort");return{pid:a.pid,chromePath:t,profile:s,port:T,browserPath:b.trim(),remaining:p,close:h,failure:x,exited:m}}catch(u){throw await x(u,!0)}}async function k(e,t,r){let n;try{return await Promise.race([e,new Promise((o,s)=>{n=setTimeout(()=>s(Error(`Timed out after ${t}ms ${r}`)),t)})])}finally{clearTimeout(n)}}async function de(e,t){if(await Promise.all([e.send("Page.enable"),e.send("Runtime.enable"),e.send("Network.enable"),e.send("Log.enable"),...t.viewport===!1?[]:[e.send("Emulation.setDeviceMetricsOverride",{deviceScaleFactor:1,mobile:!1,width:1440,height:1000,...t.viewport})]]),t.userAgent!==!1){let r=typeof t.userAgent==="string"?t.userAgent:String((await e.send("Browser.getVersion")).userAgent||"").replace("HeadlessChrome","Chrome");if(r)await e.send("Network.setUserAgentOverride",{userAgent:r})}if(t.quietLiveReload!==!1)await Le(e,t.quietLiveReload);if(t.acceptDialogs!==!1)e.on("Page.javascriptDialogOpening",(r)=>{e.errors.push(`Dialog (${r.type}) on ${r.url}: ${r.message||"(no message)"}`),e.send("Page.handleJavaScriptDialog",{accept:!0}).catch(()=>{})})}var Ie=1500,y=new Map,R=new Map,oe=!1;function De(){if(oe)return;oe=!0,L.once("exit",()=>{for(let[e,{profile:t}]of R){try{L.kill(e,"SIGKILL")}catch{}try{if(t)D(t,{recursive:!0,force:!0})}catch{}}})}async function tt(){let e=[...y.values()];y.clear(),await Promise.allSettled(e.map(async(t)=>{let r=await t;if(clearTimeout(r.linger),r.browser.disconnect(),await r.chrome.close(),r.chrome.pid!==null)R.delete(r.chrome.pid)}))}async function ke(e,t,r,n,o,s){let i=y.get(e);if(i){let c=o-(Date.now()-n);if(c<=0)throw Error(`Timed out after ${o}ms starting Chrome`);let l=await k(i.catch(()=>null),c,"waiting for shared Chrome");if(l&&!l.browser.closed&&!l.chrome.exited())return clearTimeout(l.linger),l.sessions++,l;if(y.get(e)===i)y.delete(e)}let a=(async()=>{let c=await ue(t,r,n,o,s);if(c.pid!==null)R.set(c.pid,{profile:c.profile,chromePath:c.chromePath,exited:c.exited});try{if(!c.browserPath.startsWith("/devtools/browser/"))throw Error("Chrome wrote no browser endpoint in DevToolsActivePort");v(s,"browser-connect");let l=await H(`ws://127.0.0.1:${c.port}${c.browserPath}`,{...t,connectTimeoutMs:Math.min(t.connectTimeoutMs??30000,c.remaining())});return{chrome:c,browser:l,sessions:0}}catch(l){if(c.pid!==null)R.delete(c.pid);throw await c.failure(l,!0)}})();y.set(e,a),De();try{let c=await a;return c.sessions++,c}catch(c){if(y.get(e)===a)y.delete(e);throw c}}function se(e,t,r=!1){if(t.sessions=Math.max(0,t.sessions-1),t.sessions>0)return Promise.resolve();let n=async()=>{if(t.sessions>0)return;let o=y.get(e);if(o&&await o.catch(()=>null)===t)y.delete(e);if(t.browser.disconnect(),await t.chrome.close(),t.chrome.pid!==null)R.delete(t.chrome.pid)};if(r)return n();return clearTimeout(t.linger),t.linger=setTimeout(()=>{n().catch(()=>{})},Ie),Promise.resolve()}async function Oe(e,t,r,n,o){let s=JSON.stringify([t,e.headless!==!1,e.args??[]]),i=await ke(s,e,t,r,n,o),a=()=>{let g=n-(Date.now()-r);if(g<=0)throw Error(`Timed out after ${n}ms starting Chrome`);return g},c,l,f=async()=>{if(c&&!i.browser.closed)await i.browser.send("Target.disposeBrowserContext",{browserContextId:c}).catch(()=>{})};try{v(o,"context-create"),c=(await k(i.browser.send("Target.createBrowserContext",{disposeOnDetach:!1}),a(),"creating a browser context")).browserContextId,v(o,"target-create");let{targetId:g}=await k(i.browser.send("Target.createTarget",{url:"about:blank",browserContextId:c}),a(),"opening a page"),d=`ws://127.0.0.1:${i.chrome.port}/devtools/page/${g}`;v(o,"page-connect");let h=l=await H(d,{...e,connectTimeoutMs:Math.min(e.connectTimeoutMs??30000,a())});v(o,"page-prepare"),await k(de(h,e),a(),"preparing the page"),a();let m;return{cdp:h,close:()=>m??=(async()=>{h.disconnect(),await f(),await se(s,i)})(),target:d,port:i.chrome.port,chromePath:t}}catch(g){l?.disconnect(),await f();let d=i.sessions<=1,h=await i.chrome.failure(g,!1,o);throw await se(s,i,d).catch((m)=>{h.diagnostics.cleanupError=m instanceof Error?m.message:String(m)}),h}}async function Ae(e,t,r,n,o){let s=await ue(e,t,r,n,o),i,a,c=()=>a??=(async()=>{i?.disconnect(),await s.close()})(),l=setTimeout(()=>i?.disconnect(),Math.max(1,n-(Date.now()-r)));try{let f=s.port;v(o,"target-discovery");let g=await ce(async()=>{let h=await fetch(`http://127.0.0.1:${f}/json/list`,{signal:AbortSignal.timeout(Math.min(1000,s.remaining()))}).then((m)=>m.json()).catch(()=>[]);return Array.isArray(h)&&h.length?h:null},Math.min(12000,s.remaining()),{label:"a page target"}),d=g.find((h)=>h.type==="page"&&h.url==="about:blank")||g.find((h)=>h.type==="page");if(!d)throw Error("Chrome did not expose a page target");return v(o,"page-connect"),i=await H(d.webSocketDebuggerUrl,{...e,connectTimeoutMs:Math.min(e.connectTimeoutMs??30000,s.remaining())}),s.remaining(),v(o,"page-prepare"),await de(i,e),s.remaining(),clearTimeout(l),{cdp:i,close:c,target:d.webSocketDebuggerUrl,port:f,chromePath:t}}catch(f){throw clearTimeout(l),i?.disconnect(),await s.failure(f,!0)}}async function _e(e={}){let t=e.startTimeoutMs??30000;if(!Number.isFinite(t)||t<=0)throw RangeError("startTimeoutMs must be positive and finite");let r=Fe(e),n=Date.now(),o={started:n,phase:"process-acquire",phaseStartedMs:{"process-acquire":0}};return!e.profile&&e.shared!==!1&&L.env.STACKS_BROWSER_SHARED!=="0"?await Oe(e,r,n,t,o):await Ae(e,r,n,t,o)}async function Ne(e,t={}){let r=t.quietMs??1000,n=t.timeoutMs??15000,o=Date.now(),s=0;while(Date.now()-o<n){if(e.closed)throw Error("Chrome closed the connection");if(e.inFlight.size>0||await e.evaluate("document.readyState !== 'complete'").catch(()=>!0))s=0;else if(!s)s=Date.now();else if(Date.now()-s>=r)return!0;await Bun.sleep(100)}return!1}async function Be(e,t,r={}){let n=await e.send("Page.navigate",{url:t});if(n?.errorText)throw Error(`Could not open ${t}: ${n.errorText}`);return await Ne(e,r)}var Ue=(e)=>e.pathname.startsWith("/api/")&&!e.pathname.endsWith("/events");function He(e){let t={};for(let r of e)t[r]=(t[r]||0)+1;return Object.fromEntries(Object.entries(t).filter(([,r])=>r>1))}function ie(e){try{let t=new URL(e);return`${t.origin}${t.pathname}`}catch{return e.replace(/[?#].*$/,"")}}function Ve(e,t,r="navigation"){let n=[{url:e,apiRequests:[]}],o=!1;for(let s of t){let i=n.at(-1);if(s.type==="request"){i.apiRequests.push(s.request);continue}let a=r==="navigation"&&o&&(s.sameDocument?ie(s.url)!==ie(i.url):!0);if(o=!0,a)n.push({url:s.url,apiRequests:[]});else i.url=s.url}return n.map((s)=>({...s,duplicates:He(s.apiRequests)}))}function We(e){let t={};for(let r of e)for(let[n,o]of Object.entries(r.duplicates))t[n]=Math.max(t[n]??0,o);return t}async function rt(e,t={}){let r=Date.now(),n=new URL(e).origin,o=t.isApiRequest??Ue,s=[],i=[],a=[],c=null,l=t.cdp,f=[],g=l?.errors.length??0,d,h=e,m=async(p,x=[])=>{let u=Ve(e,i,t.pageLoads),w=h;if(l&&!l.closed){let b=await l.evaluate("location.href").catch(()=>null);if(b&&b!=="about:blank"&&!b.startsWith("chrome-error:"))w=b}return{url:e,finalUrl:w,apiRequests:s,duplicates:We(u),loads:u,failed:a,errors:[...l?.errors.slice(g)??[],...x],settled:p,durationMs:Date.now()-r}};try{if(!l)c=await _e(t.launch),l=c.cdp;let p=l;if(f.push(p.on("Network.requestWillBeSent",(u)=>{let w=new URL(u.request.url);if(w.origin===n&&o(w,u.request.method)){let b=`${u.request.method} ${w.pathname}${w.search}`;s.push(b),i.push({type:"request",request:b})}})),f.push(p.on("Network.responseReceived",(u)=>{let w=new URL(u.response.url);if(w.origin===n&&u.response.status>=400)a.push(`${u.response.status} ${w.pathname}`)})),f.push(p.on("Page.frameNavigated",(u)=>{if(u.frame?.parentId)return;d=u.frame?.id??d,h=u.frame?.unreachableUrl||`${u.frame?.url??h}${u.frame?.urlFragment??""}`,i.push({type:"navigated",url:h,sameDocument:!1})})),f.push(p.on("Page.navigatedWithinDocument",(u)=>{if(d&&u.frameId!==d)return;h=u.url,i.push({type:"navigated",url:u.url,sameDocument:!0})})),await p.send("Page.enable"),d=(await p.send("Page.getFrameTree").catch(()=>null))?.frameTree?.frame?.id,t.viewport)await p.send("Emulation.setDeviceMetricsOverride",{deviceScaleFactor:1,mobile:!1,...t.viewport});await t.beforeNavigate?.(p);let x=await Be(p,e,t);return await m(x)}catch(p){return await m(!1,[`audit: ${p?.message??String(p)}`])}finally{for(let p of f)p();await c?.close().catch(()=>{})}}function ae(e){try{let t=new URL(e);return`${t.pathname}${t.search}`}catch{return e}}function nt(e){let t=ae(e.url),r=e.finalUrl?ae(e.finalUrl):t,n=Object.entries(e.duplicates).map(([o,s])=>`${s}x ${o}`);return`${t}${r!==t?` -> ${r}`:""}: ${e.apiRequests.length} api${n.length?` | dup ${n.join(", ")}`:""}${e.failed.length?` | failed ${e.failed.join(", ")}`:""}${e.errors.length?` | errors ${e.errors.slice(0,3).join(" ; ")}`:""}${e.settled?"":" | never settled"}`}function ot(e){return e.settled&&!Object.keys(e.duplicates).length&&!e.failed.length&&!e.errors.length}export{le as Cdp,U as ChromeLaunchError,Me as LIVE_RELOAD_PATTERNS,Ie as SHARED_CHROME_LINGER_MS,rt as auditPage,qe as chromeHasUpdater,tt as closeSharedBrowsers,H as connect,He as duplicateRequests,Ee as findChrome,nt as formatPageAudit,_e as launch,We as loadDuplicates,Re as matchesUrlPattern,Be as navigate,ot as pageAuditClean,re as pausedRequestMatches,$e as prepareMacChrome,Le as quietLiveReload,Ne as settle,Ve as splitPageLoads,ce as waitFor};
21
+ `).toString("base64")}).catch(()=>{})})}var ie=new Map;function Le(e){let t=e.chromePath??F.env.CHROME_PATH;if(t)return t;let r=Ee();if(!r)throw Error("No Chrome found: install Google Chrome or Chromium, or set CHROME_PATH");if((e.stableMacChrome??!0)&&F.platform==="darwin"&&qe(r)){let n=ie.get(r);if(n&&[...R.values()].some((s)=>s.chromePath===n&&!s.exited()))return n;let o=Me({source:r}).executable;return ie.set(r,o),o}return r}async function ue(e,t,r,i,n){if(v(n,"process-start"),!te(t))throw Error(`Chrome was not found at ${t}`);let o=e.profile||ve(_(xe(),"stacks-browser-"));k(_(o,"DevToolsActivePort"),{force:!0});let s=_(o,"DevToolsActivePort"),a=(()=>{try{return Bun.spawn([t,...e.headless===!1?[]:["--headless=new"],"--remote-debugging-port=0",`--user-data-dir=${o}`,"--no-first-run","--no-default-browser-check","--disable-background-networking","--disable-component-update","--disable-extensions","--hide-scrollbars","--use-mock-keychain",...e.args??[],"about:blank"],{stdin:"ignore",stdout:"ignore",stderr:"pipe"})}catch(u){let w=null;try{if(!e.profile)k(o,{recursive:!0,force:!0})}catch(b){w=String(b)}throw new H(u,{pid:null,chromePath:t,profile:o,temporaryProfile:!e.profile,elapsedMs:Date.now()-r,phase:n.phase,phaseStartedMs:{...n.phaseStartedMs},exitCodeBeforeCleanup:null,signalCodeBeforeCleanup:null,exitCode:null,signalCode:null,portFileExisted:!1,portFileContents:null,stderrTail:"",stderrTruncated:!1,cleanupError:w})}})(),c="",l=!1,m,g=Promise.resolve(),d,h=()=>d??=(async()=>{let u=!1,w=[];try{if(a.exitCode===null&&a.signalCode===null)try{a.kill("SIGTERM")}catch{}if(!await M(a.exited,2000)){try{a.kill("SIGKILL")}catch{}if(!await M(a.exited,2000))throw Error(`Chrome process ${a.pid} did not exit after SIGKILL`)}u=!0}catch(b){w.push(String(b)),u=a.exitCode!==null||a.signalCode!==null}finally{try{if(!await M(g,100)&&m){if(!await M(m.cancel(),100))throw Error("Chrome stderr cancellation did not finish")}if(!await M(g,100))throw Error("Chrome stderr reader did not finish")}catch(b){w.push(String(b))}finally{if(u&&!e.profile)try{k(o,{recursive:!0,force:!0})}catch(b){w.push(String(b))}}}if(w.length)throw Error(`Chrome cleanup failed: ${w.join("; ")}`)})(),f=()=>a.exitCode!==null||a.signalCode!==null,p=()=>{let u=i-(Date.now()-r);if(u<=0)throw Error(`Timed out after ${i}ms starting Chrome`);if(f())throw Error("Chrome exited during startup");return u},x=async(u,w,b=n)=>{let T=Date.now()-b.started,E=b.phase,A={...b.phaseStartedMs},{exitCode:L,signalCode:I}=a,he=te(s),pe=B(s),V=null;if(w)try{await h()}catch(O){V=O instanceof Error?O.message:String(O)}return new H(u,{pid:a.pid,chromePath:t,profile:o,temporaryProfile:!e.profile,elapsedMs:T,phase:E,phaseStartedMs:A,exitCodeBeforeCleanup:L,signalCodeBeforeCleanup:I,exitCode:a.exitCode,signalCode:a.signalCode,portFileExisted:he,portFileContents:pe,stderrTail:c,stderrTruncated:l,cleanupError:V})};try{m=a.stderr.getReader();let u=m;g=(async()=>{let E=new TextDecoder;for(;;){let{value:A,done:L}=await u.read(),I=E.decode(A,{stream:!L});if(l||=c.length+I.length>16384,c=(c+I).slice(-16384),L)return}})().catch((E)=>{c=`${c}
22
+ [stderr read failed: ${String(E)}]`.slice(-16384)}),v(n,"devtools-port"),await U(()=>(p(),B(s)?.includes(`
23
+ `)),p(),{label:"Chrome to start"});let[w,b=""]=(B(s)??"").split(`
24
+ `),T=Number(w);if(!Number.isInteger(T)||T<1||T>65535)throw Error("Chrome wrote an invalid DevToolsActivePort");return{pid:a.pid,chromePath:t,profile:o,port:T,browserPath:b.trim(),remaining:p,close:h,failure:x,exited:f}}catch(u){throw await x(u,!0)}}async function D(e,t,r){let i;try{return await Promise.race([e,new Promise((n,o)=>{i=setTimeout(()=>o(Error(`Timed out after ${t}ms ${r}`)),t)})])}finally{clearTimeout(i)}}async function de(e,t){if(await Promise.all([e.send("Page.enable"),e.send("Runtime.enable"),e.send("Network.enable"),e.send("Log.enable"),...t.viewport===!1?[]:[e.send("Emulation.setDeviceMetricsOverride",{deviceScaleFactor:1,mobile:!1,width:1440,height:1000,...t.viewport})]]),t.userAgent!==!1){let r=typeof t.userAgent==="string"?t.userAgent:String((await e.send("Browser.getVersion")).userAgent||"").replace("HeadlessChrome","Chrome");if(r)await e.send("Network.setUserAgentOverride",{userAgent:r})}if(t.quietLiveReload!==!1)await Fe(e,t.quietLiveReload);if(t.acceptDialogs!==!1)e.on("Page.javascriptDialogOpening",(r)=>{e.errors.push(`Dialog (${r.type}) on ${r.url}: ${r.message||"(no message)"}`),e.send("Page.handleJavaScriptDialog",{accept:!0}).catch(()=>{})})}var Ie=1500,C=new Map,R=new Map,oe=!1;function ke(){if(oe)return;oe=!0,F.once("exit",()=>{for(let[e,{profile:t}]of R){try{F.kill(e,"SIGKILL")}catch{}try{if(t)k(t,{recursive:!0,force:!0})}catch{}}})}async function rt(){let e=[...C.values()];C.clear(),await Promise.allSettled(e.map(async(t)=>{let r=await t;if(clearTimeout(r.linger),r.browser.disconnect(),await r.chrome.close(),r.chrome.pid!==null)R.delete(r.chrome.pid)}))}async function De(e,t,r,i,n,o){let s=C.get(e);if(s){let c=n-(Date.now()-i);if(c<=0)throw Error(`Timed out after ${n}ms starting Chrome`);let l=await D(s.catch(()=>null),c,"waiting for shared Chrome");if(l&&!l.browser.closed&&!l.chrome.exited())return clearTimeout(l.linger),l.sessions++,l;if(C.get(e)===s)C.delete(e)}let a=(async()=>{let c=await ue(t,r,i,n,o);if(c.pid!==null)R.set(c.pid,{profile:c.profile,chromePath:c.chromePath,exited:c.exited});try{if(!c.browserPath.startsWith("/devtools/browser/"))throw Error("Chrome wrote no browser endpoint in DevToolsActivePort");v(o,"browser-connect");let l=await W(`ws://127.0.0.1:${c.port}${c.browserPath}`,{...t,connectTimeoutMs:Math.min(t.connectTimeoutMs??30000,c.remaining())});return{chrome:c,browser:l,sessions:0}}catch(l){if(c.pid!==null)R.delete(c.pid);throw await c.failure(l,!0)}})();C.set(e,a),ke();try{let c=await a;return c.sessions++,c}catch(c){if(C.get(e)===a)C.delete(e);throw c}}function se(e,t,r=!1){if(t.sessions=Math.max(0,t.sessions-1),t.sessions>0)return Promise.resolve();let i=async()=>{if(t.sessions>0)return;let n=C.get(e);if(n&&await n.catch(()=>null)===t)C.delete(e);if(t.browser.disconnect(),await t.chrome.close(),t.chrome.pid!==null)R.delete(t.chrome.pid)};if(r)return i();return clearTimeout(t.linger),t.linger=setTimeout(()=>{i().catch(()=>{})},Ie),Promise.resolve()}async function Ae(e,t,r,i,n){let o=JSON.stringify([t,e.headless!==!1,e.args??[]]),s=await De(o,e,t,r,i,n),a=()=>{let g=i-(Date.now()-r);if(g<=0)throw Error(`Timed out after ${i}ms starting Chrome`);return g},c,l,m=async()=>{if(c&&!s.browser.closed)await s.browser.send("Target.disposeBrowserContext",{browserContextId:c}).catch(()=>{})};try{v(n,"context-create"),c=(await D(s.browser.send("Target.createBrowserContext",{disposeOnDetach:!1}),a(),"creating a browser context")).browserContextId,v(n,"target-create");let{targetId:g}=await D(s.browser.send("Target.createTarget",{url:"about:blank",browserContextId:c}),a(),"opening a page"),d=`ws://127.0.0.1:${s.chrome.port}/devtools/page/${g}`;v(n,"page-connect");let h=l=await W(d,{...e,connectTimeoutMs:Math.min(e.connectTimeoutMs??30000,a())});v(n,"page-prepare"),await D(de(h,e),a(),"preparing the page"),a();let f;return{cdp:h,close:()=>f??=(async()=>{h.disconnect(),await m(),await se(o,s)})(),target:d,port:s.chrome.port,chromePath:t}}catch(g){l?.disconnect(),await m();let d=s.sessions<=1,h=await s.chrome.failure(g,!1,n);throw await se(o,s,d).catch((f)=>{h.diagnostics.cleanupError=f instanceof Error?f.message:String(f)}),h}}async function Oe(e,t,r,i,n){let o=await ue(e,t,r,i,n),s,a,c=()=>a??=(async()=>{s?.disconnect(),await o.close()})(),l=setTimeout(()=>s?.disconnect(),Math.max(1,i-(Date.now()-r)));try{let m=o.port;v(n,"target-discovery");let g=await U(async()=>{let h=await fetch(`http://127.0.0.1:${m}/json/list`,{signal:AbortSignal.timeout(Math.min(1000,o.remaining()))}).then((f)=>f.json()).catch(()=>[]);return Array.isArray(h)&&h.length?h:null},Math.min(12000,o.remaining()),{label:"a page target"}),d=g.find((h)=>h.type==="page"&&h.url==="about:blank")||g.find((h)=>h.type==="page");if(!d)throw Error("Chrome did not expose a page target");return v(n,"page-connect"),s=await W(d.webSocketDebuggerUrl,{...e,connectTimeoutMs:Math.min(e.connectTimeoutMs??30000,o.remaining())}),o.remaining(),v(n,"page-prepare"),await de(s,e),o.remaining(),clearTimeout(l),{cdp:s,close:c,target:d.webSocketDebuggerUrl,port:m,chromePath:t}}catch(m){throw clearTimeout(l),s?.disconnect(),await o.failure(m,!0)}}async function Ne(e={}){let t=e.startTimeoutMs??30000;if(!Number.isFinite(t)||t<=0)throw RangeError("startTimeoutMs must be positive and finite");let r=Le(e),i=Date.now(),n={started:i,phase:"process-acquire",phaseStartedMs:{"process-acquire":0}};return!e.profile&&e.shared!==!1&&F.env.STACKS_BROWSER_SHARED!=="0"?await Ae(e,r,i,t,n):await Oe(e,r,i,t,n)}async function _e(e,t={}){let r=t.quietMs??1000,i=t.timeoutMs??15000,n=Date.now(),o=0;while(Date.now()-n<i){if(e.closed)throw Error("Chrome closed the connection");if(e.inFlight.size>0||await e.evaluate("document.readyState !== 'complete'").catch(()=>!0))o=0;else if(!o)o=Date.now();else if(Date.now()-o>=r)return!0;await Bun.sleep(100)}return!1}async function Be(e,t,r={}){let i=await e.send("Page.navigate",{url:t});if(i?.errorText)throw Error(`Could not open ${t}: ${i.errorText}`);return await _e(e,r)}var He=(e)=>e.pathname.startsWith("/api/")&&!e.pathname.endsWith("/events");function Ue(e){let t={};for(let r of e)t[r]=(t[r]||0)+1;return Object.fromEntries(Object.entries(t).filter(([,r])=>r>1))}function ae(e){try{let t=new URL(e);return`${t.origin}${t.pathname}`}catch{return e.replace(/[?#].*$/,"")}}function We(e,t,r="navigation"){let i=[{url:e,apiRequests:[]}],n=!1;for(let o of t){let s=i.at(-1);if(o.type==="request"){s.apiRequests.push(o.request);continue}let a=r==="navigation"&&n&&(o.sameDocument?ae(o.url)!==ae(s.url):!0);if(n=!0,a)i.push({url:o.url,apiRequests:[]});else s.url=o.url}return i.map((o)=>({...o,duplicates:Ue(o.apiRequests)}))}function Ve(e){let t={};for(let r of e)for(let[i,n]of Object.entries(r.duplicates))t[i]=Math.max(t[i]??0,n);return t}async function nt(e,t={}){let r=Date.now(),i=new URL(e).origin,n=t.isApiRequest??He,o=[],s=[],a=[],c=null,l=t.cdp,m=[],g=l?.errors.length??0,d,h=e,f=async(p,x=[])=>{let u=We(e,s,t.pageLoads),w=h;if(l&&!l.closed){let b=await l.evaluate("location.href").catch(()=>null);if(b&&b!=="about:blank"&&!b.startsWith("chrome-error:"))w=b}return{url:e,finalUrl:w,apiRequests:o,duplicates:Ve(u),loads:u,failed:a,errors:[...l?.errors.slice(g)??[],...x],settled:p,durationMs:Date.now()-r}};try{if(!l)c=await Ne(t.launch),l=c.cdp;let p=l;if(m.push(p.on("Network.requestWillBeSent",(u)=>{let w=new URL(u.request.url);if(w.origin===i&&n(w,u.request.method)){let b=`${u.request.method} ${w.pathname}${w.search}`;o.push(b),s.push({type:"request",request:b})}})),m.push(p.on("Network.responseReceived",(u)=>{let w=new URL(u.response.url);if(w.origin===i&&u.response.status>=400)a.push(`${u.response.status} ${w.pathname}`)})),m.push(p.on("Page.frameNavigated",(u)=>{if(u.frame?.parentId)return;d=u.frame?.id??d,h=u.frame?.unreachableUrl||`${u.frame?.url??h}${u.frame?.urlFragment??""}`,s.push({type:"navigated",url:h,sameDocument:!1})})),m.push(p.on("Page.navigatedWithinDocument",(u)=>{if(d&&u.frameId!==d)return;h=u.url,s.push({type:"navigated",url:u.url,sameDocument:!0})})),await p.send("Page.enable"),d=(await p.send("Page.getFrameTree").catch(()=>null))?.frameTree?.frame?.id,t.viewport)await p.send("Emulation.setDeviceMetricsOverride",{deviceScaleFactor:1,mobile:!1,...t.viewport});await t.beforeNavigate?.(p);let x=await Be(p,e,t);return await f(x)}catch(p){return await f(!1,[`audit: ${p?.message??String(p)}`])}finally{for(let p of m)p();await c?.close().catch(()=>{})}}function ce(e){try{let t=new URL(e);return`${t.pathname}${t.search}`}catch{return e}}function it(e){let t=ce(e.url),r=e.finalUrl?ce(e.finalUrl):t,i=Object.entries(e.duplicates).map(([n,o])=>`${o}x ${n}`);return`${t}${r!==t?` -> ${r}`:""}: ${e.apiRequests.length} api${i.length?` | dup ${i.join(", ")}`:""}${e.failed.length?` | failed ${e.failed.join(", ")}`:""}${e.errors.length?` | errors ${e.errors.slice(0,3).join(" ; ")}`:""}${e.settled?"":" | never settled"}`}function ot(e){return e.settled&&!Object.keys(e.duplicates).length&&!e.failed.length&&!e.errors.length}export{le as Cdp,H as ChromeLaunchError,$e as LIVE_RELOAD_PATTERNS,Ie as SHARED_CHROME_LINGER_MS,nt as auditPage,qe as chromeHasUpdater,tt as click,rt as closeSharedBrowsers,W as connect,Ue as duplicateRequests,Ee as findChrome,it as formatPageAudit,Ne as launch,Ve as loadDuplicates,Re as matchesUrlPattern,Be as navigate,ot as pageAuditClean,ne as pausedRequestMatches,Me as prepareMacChrome,Fe as quietLiveReload,_e as settle,We as splitPageLoads,U as waitFor};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/testing",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.341",
5
+ "version": "0.75.342",
6
6
  "description": "The Stacks way of testing.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -79,16 +79,16 @@
79
79
  "prepublishOnly": "bun run build"
80
80
  },
81
81
  "dependencies": {
82
- "@stacksjs/auth": "0.75.341",
83
- "@stacksjs/cache": "0.75.341",
84
- "@stacksjs/config": "0.75.341",
85
- "@stacksjs/database": "0.75.341",
86
- "@stacksjs/email": "0.75.341",
87
- "@stacksjs/events": "0.75.341",
88
- "@stacksjs/path": "0.75.341",
89
- "@stacksjs/router": "0.75.341",
90
- "@stacksjs/storage": "0.75.341",
91
- "@stacksjs/strings": "0.75.341",
82
+ "@stacksjs/auth": "0.75.342",
83
+ "@stacksjs/cache": "0.75.342",
84
+ "@stacksjs/config": "0.75.342",
85
+ "@stacksjs/database": "0.75.342",
86
+ "@stacksjs/email": "0.75.342",
87
+ "@stacksjs/events": "0.75.342",
88
+ "@stacksjs/path": "0.75.342",
89
+ "@stacksjs/router": "0.75.342",
90
+ "@stacksjs/storage": "0.75.342",
91
+ "@stacksjs/strings": "0.75.342",
92
92
  "@stacksjs/ts-cloud": "^0.16.69",
93
93
  "very-happy-dom": "^0.4.1"
94
94
  },