@stacksjs/testing 0.75.308 → 0.75.310

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
@@ -14,17 +14,11 @@ export declare function matchesUrlPattern(pattern: string, url: string): boolean
14
14
  export declare function pausedRequestMatches(pattern: FetchPattern, paused: Pick<PausedRequest, 'request' | 'resourceType' | 'responseStatusCode' | 'responseErrorReason'>): boolean;
15
15
  /** Open a session on a DevTools WebSocket URL (`ws://127.0.0.1:9222/devtools/page/...`). */
16
16
  export declare function connect(url: string, options?: CdpOptions): Promise<Cdp>;
17
- /** The Chrome to drive: `CHROME_PATH`, else the first installed one found. Null when there is none. */
17
+ /** Discover Chrome through the same source used by the standalone browser skill. */
18
18
  export declare function findChrome(): string | null;
19
- /** True when this Chrome would be killed by GoogleUpdater (it still carries the updater's keys). */
19
+ /** Whether this executable carries the macOS updater registration. */
20
20
  export declare function chromeHasUpdater(executable: string): boolean;
21
- /**
22
- * A private clone of a macOS Chrome that GoogleUpdater leaves alone (see the
23
- * module docs): `cp -cR`, the `KS*` keys deleted from `Info.plist`, its own
24
- * `CFBundleIdentifier`, `codesign --force --deep --sign -`. Kept in
25
- * `directory` (default a per-version folder in the temp directory) and
26
- * reused while its version matches the source's.
27
- */
21
+ /** Prepare the framework's private automation executable without modifying the installed app. */
28
22
  export declare function prepareMacChrome(options?: { source?: string, directory?: string }): PreparedChrome;
29
23
  /**
30
24
  * Answer live-reload streams matching `patterns` (default stx's) with one that
package/dist/browser.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // @bun
2
- import{closeSync as oe,existsSync as E,mkdirSync as ie,mkdtempSync as J,openSync as ae,readSync as ce,renameSync as le,rmSync as R}from"fs";import{tmpdir as z}from"os";import{join as v}from"path";import P from"process";async function Y(t,e=12000,r={}){let n=Date.now(),s=await t();while(!s&&Date.now()-n<e)await Bun.sleep(r.intervalMs??100),s=await t();if(!s)throw Error(`Timed out after ${e}ms${r.label?` waiting for ${r.label}`:""}`);return s}var ue=new Set(["Fetch.continueRequest","Fetch.fulfillRequest","Fetch.failRequest","Fetch.continueResponse"]);function de(t,e){let r="";for(let n=0;n<t.length;n++){let s=t[n];if(s==="\\"&&n+1<t.length)r+=U(t[++n]);else if(s==="*")r+="[\\s\\S]*";else if(s==="?")r+="[\\s\\S]";else r+=U(s)}return new RegExp(`^${r}$`).test(e)}function U(t){return t.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&")}function H(t,e){let r=e.responseStatusCode!=null||e.responseErrorReason!=null?"Response":"Request";if((t.requestStage??"Request")!==r)return!1;if(t.resourceType&&t.resourceType!==e.resourceType)return!1;return de(t.urlPattern??"*",e.request?.url??"")}class Q{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(t,e={}){this.socket=t;this.commandTimeoutMs=e.commandTimeoutMs??30000,t.addEventListener("close",()=>this.markClosed()),t.addEventListener("message",(r)=>this.receive(String(r.data)))}markClosed(){this.closed=!0,this.frameLoaders.clear(),this.requestLoaders.clear();for(let[t,e]of this.pending)clearTimeout(e.timer),e.reject(Error("Chrome closed the connection")),this.pending.delete(t)}receive(t){let e;try{e=JSON.parse(t)}catch{return}if(e.id){let r=this.pending.get(e.id);if(!r)return;if(this.pending.delete(e.id),clearTimeout(r.timer),e.error)r.reject(Error(e.error.message));else r.resolve(e.result);return}if(this.track(e.method,e.params??{}),e.method==="Fetch.requestPaused"&&!this.routePaused(e.params??{}))return;this.dispatch(e.method,e.params??{})}dispatch(t,e){for(let r of[...this.listeners.get(t)??[]])try{r(e)}catch(n){this.errors.push(`Listener for ${t} threw: ${n?.message??String(n)}`)}}routePaused(t){this.remember(t.requestId,!1);let e=this.interceptors.filter((s)=>s.patterns.some((o)=>H(o,t)));for(let s of e)try{let o=s.handler(t,this);if(o&&typeof o.catch==="function")o.catch((a)=>this.errors.push(`Fetch handler threw: ${a?.message??String(a)}`))}catch(o){this.errors.push(`Fetch handler threw: ${o?.message??String(o)}`)}let r=(this.listeners.get("Fetch.requestPaused")?.size??0)>0,n=this.ownFetch?this.ownFetch.patterns.some((s)=>H(s,t)):!1;if(!e.length&&!n&&!r)this.send("Fetch.continueRequest",{requestId:t.requestId}).catch(()=>{});return r&&(n||!e.length)}remember(t,e){if(this.answered.delete(t),this.answered.set(t,e),this.answered.size>2000){for(let[r]of this.answered)if(this.answered.delete(r),this.answered.size<=1000)break}}async applyFetch(){let t=[...this.interceptors.flatMap((e)=>e.patterns),...this.ownFetch?.patterns??[]];if(!t.length)return await this.command("Fetch.disable");return await this.command("Fetch.enable",{...this.ownFetch?.params,patterns:t})}async intercept(t,e){if(!t.length)throw TypeError("intercept needs at least one pattern");let r={patterns:t.map((s)=>({...s})),handler:e};this.interceptors.push(r);try{await this.applyFetch()}catch(s){throw this.interceptors=this.interceptors.filter((o)=>o!==r),s}let n=!1;return async()=>{if(n)return;if(n=!0,this.interceptors=this.interceptors.filter((s)=>s!==r),!this.closed)await this.applyFetch()}}finishRequest(t){this.inFlight.delete(t),this.requestLoaders.delete(t)}retireFrameRequests(t,e){for(let[r,n]of this.requestLoaders)if(n.frameId===t&&(e===void 0||n.loaderId===e))this.finishRequest(r)}track(t,e){if(t==="Network.requestWillBeSent"){let{requestId:r,frameId:n,loaderId:s}=e;if(this.requestLoaders.delete(r),n&&s)this.requestLoaders.set(r,{frameId:n,loaderId:s});this.inFlight.set(e.requestId,`${e.request?.method} ${e.request?.url}`)}else if(t==="Network.loadingFinished"||t==="Network.loadingFailed")this.finishRequest(e.requestId);else if(t==="Network.responseReceived"&&e.response?.mimeType==="text/event-stream")this.finishRequest(e.requestId);else if(t==="Page.frameNavigated"&&e.frame?.id&&e.frame?.loaderId){let{id:r,loaderId:n}=e.frame,s=this.frameLoaders.get(r);if(s&&s!==n)this.retireFrameRequests(r,s);this.frameLoaders.set(r,n)}else if(t==="Page.frameDetached"&&e.reason==="remove")this.frameLoaders.delete(e.frameId),this.retireFrameRequests(e.frameId);if(t==="Runtime.exceptionThrown")this.errors.push(e.exceptionDetails?.exception?.description||e.exceptionDetails?.text||"Runtime exception");else if(t==="Runtime.consoleAPICalled"&&e.type==="error")this.errors.push((e.args||[]).map((r)=>r.value??r.description??"").join(" "));else if(t==="Log.entryAdded"&&e.entry?.level==="error"&&!String(e.entry.text).includes("favicon"))this.errors.push(e.entry.text);else if(t==="Network.loadingFailed"&&!e.canceled)this.errors.push(`Network: ${e.errorText||"request failed"}`)}on(t,e){let r=this.listeners.get(t);if(!r)this.listeners.set(t,r=new Set);return r.add(e),()=>this.off(t,e)}off(t,e){this.listeners.get(t)?.delete(e)}once(t,e=()=>!0,r=this.commandTimeoutMs){return new Promise((n,s)=>{if(this.closed)return s(Error("Chrome closed the connection"));let o=setTimeout(()=>{i(),clearInterval(a),s(Error(`Timed out after ${r}ms waiting for ${t}`))},r),a=setInterval(()=>{if(!this.closed)return;clearTimeout(o),clearInterval(a),i(),s(Error("Chrome closed the connection"))},100),i=this.on(t,(c)=>{if(!e(c))return;clearTimeout(o),clearInterval(a),i(),n(c)})})}send(t,e={}){if(t==="Fetch.enable"){let{patterns:r,...n}=e;return this.ownFetch={patterns:r?.length?r.map((s)=>({...s})):[{urlPattern:"*"}],params:n},this.closed?this.command(t,e):this.applyFetch()}if(t==="Fetch.disable")return this.ownFetch=null,this.closed?this.command(t,e):this.applyFetch();if(ue.has(t)&&typeof e.requestId==="string"){if(this.answered.get(e.requestId)===!0)return Promise.resolve({});if(this.answered.has(e.requestId))this.remember(e.requestId,!0)}return this.command(t,e)}command(t,e={}){let r=++this.id;return new Promise((n,s)=>{if(this.closed)return s(Error("Chrome closed the connection"));let o=setTimeout(()=>{this.pending.delete(r),s(Error(`Chrome command timed out: ${t}`))},this.commandTimeoutMs);this.pending.set(r,{resolve:n,reject:s,timer:o}),this.socket.send(JSON.stringify({id:r,method:t,params:e}))})}async evaluate(t){let e=await this.send("Runtime.evaluate",{expression:t,awaitPromise:!0,returnByValue:!0});if(e.exceptionDetails)throw Error(e.exceptionDetails.exception?.description||e.exceptionDetails.text);return e.result?.value}disconnect(){this.markClosed();try{this.socket.close()}catch{}}}async function B(t,e={}){let r=new WebSocket(t);return await new Promise((n,s)=>{let o=(u)=>{if(clearTimeout(c),r.removeEventListener("open",a),r.removeEventListener("error",i),r.removeEventListener("close",i),u){try{r.close()}catch{}s(u)}else n()},a=()=>o(),i=()=>o(Error(`Could not connect to Chrome at ${t}`)),c=setTimeout(()=>o(Error(`Timed out connecting to Chrome at ${t}`)),e.connectTimeoutMs??30000);r.addEventListener("open",a,{once:!0}),r.addEventListener("error",i,{once:!0}),r.addEventListener("close",i,{once:!0})}),new Q(r,e)}var X=["/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"],he=["google-chrome","google-chrome-stable","chromium","chromium-browser","chrome"],Z=["KSProductID","KSUpdateURL","KSChannelID","KSVersion"],j="dev.stacks.testing.chrome";function pe(){if(P.env.CHROME_PATH)return P.env.CHROME_PATH;if(P.platform==="darwin"){let t=X.find((e)=>E(e));if(t)return t}for(let t of he){let e=Bun.which(t);if(e)return e}return null}function ee(t){let e=/^(.*?\.app)\/Contents\/MacOS\/[^/]+$/.exec(t);return e?e[1]:null}function x(t,e){let r=Bun.spawnSync(["/usr/libexec/PlistBuddy","-c",e,t],{stdout:"pipe",stderr:"pipe"});return{ok:r.exitCode===0,out:r.stdout.toString().trim()}}function fe(t){let e=ee(t);if(!e||P.platform!=="darwin")return!1;let r=v(e,"Contents/Info.plist");return E(r)&&Z.some((n)=>x(r,`Print :${n}`).ok)}function me(t={}){if(P.platform!=="darwin")throw Error("prepareMacChrome is for macOS; elsewhere launch Chrome as installed");let e=t.source??X[0],r=ee(e);if(!r||!E(r))throw Error(`No Chrome app bundle at ${e}`);let n=v(r,"Contents/Info.plist"),s=x(n,"Print :CFBundleShortVersionString").out||"unknown",o=x(n,"Print :CFBundleExecutable").out||"Google Chrome",a=t.directory??v(z(),`stacks-chrome-${s.replace(/[^\w.-]/g,"_")}`),i=v(a,"Chrome.app"),c=v(i,"Contents/MacOS",o),u=v(i,"Contents/Info.plist");if(E(c)&&x(u,"Print :CFBundleIdentifier").out===j&&x(u,"Print :CFBundleShortVersionString").out===s)return{executable:c,app:i,reused:!0};ie(a,{recursive:!0});let f=J(v(a,"staging-")),g=v(f,"Chrome.app"),w=(d,b)=>{let h=Bun.spawnSync(d,{stdout:"pipe",stderr:"pipe"});if(h.exitCode!==0)throw Error(`${b} failed: ${h.stderr.toString().trim()}`)};try{w(["/bin/cp","-cR",r,g],"Cloning Chrome");let d=v(g,"Contents/Info.plist");for(let b of Z)x(d,`Delete :${b}`);w(["/usr/libexec/PlistBuddy","-c",`Set :CFBundleIdentifier ${j}`,d],"Renaming the Chrome bundle"),w(["codesign","--force","--deep","--sign","-",g],"Re-signing Chrome"),R(i,{recursive:!0,force:!0}),le(g,i)}finally{R(f,{recursive:!0,force:!0})}return{executable:c,app:i,reused:!1}}function C(t,e){t.phase=e,t.phaseStartedMs[e]=Date.now()-t.started}class _ extends Error{diagnostics;constructor(t,e){super(`${t instanceof Error?t.message:String(t)}
3
- Chrome startup: ${JSON.stringify(e)}`,{cause:t});this.diagnostics=e;this.name="ChromeLaunchError"}}async function L(t,e){let r;try{return await Promise.race([t.then(()=>!0),new Promise((n)=>{r=setTimeout(()=>n(!1),e)})])}finally{clearTimeout(r)}}function O(t){let e;try{e=ae(t,"r");let r=Buffer.alloc(1024);return r.subarray(0,ce(e,r,0,r.length,0)).toString("utf8")}catch{return null}finally{if(e!==void 0)try{oe(e)}catch{}}}var ge=["*/_stx/hmr*"];async function we(t,e=ge){if(!e.length)return async()=>{};return await t.intercept(e.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 fe,existsSync as Q,mkdtempSync as ge,openSync as we,readSync as be,rmSync as D}from"fs";import{tmpdir as Ce}from"os";import{join as _}from"path";import q from"process";import{existsSync as I,mkdirSync as ue,mkdtempSync as de,renameSync as he,rmSync as j}from"fs";import{tmpdir as pe}from"os";import{join as y}from"path";import R from"process";var W=["/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"],me=["google-chrome","google-chrome-stable","chromium","chromium-browser","chrome"],V=["KSProductID","KSUpdateURL","KSChannelID","KSVersion"],G="dev.stacks.testing.chrome";function K(){if(R.env.CHROME_PATH)return R.env.CHROME_PATH;if(R.platform==="darwin"){let e=W.find((t)=>I(t));if(e)return e}for(let e of me){let t=Bun.which(e);if(t)return t}return null}function J(e){let t=/^(.*?\.app)\/Contents\/MacOS\/[^/]+$/.exec(e);return t?t[1]:null}function x(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 z(e){let t=J(e);if(!t||R.platform!=="darwin")return!1;let r=y(t,"Contents/Info.plist");return I(r)&&V.some((n)=>x(r,`Print :${n}`).ok)}function Y(e={}){if(R.platform!=="darwin")throw Error("prepareMacChrome is for macOS; elsewhere launch Chrome as installed");let t=e.source??W[0],r=J(t);if(!r||!I(r))throw Error(`No Chrome app bundle at ${t}`);let n=y(r,"Contents/Info.plist"),o=x(n,"Print :CFBundleShortVersionString").out||"unknown",s=x(n,"Print :CFBundleExecutable").out||"Google Chrome",a=e.directory??y(pe(),`stacks-chrome-${o.replace(/[^\w.-]/g,"_")}`),i=y(a,"Chrome.app"),c=y(i,"Contents/MacOS",s),u=y(i,"Contents/Info.plist");if(I(c)&&x(u,"Print :CFBundleIdentifier").out===G&&x(u,"Print :CFBundleShortVersionString").out===o)return{executable:c,app:i,reused:!0};ue(a,{recursive:!0});let m=de(y(a,"staging-")),g=y(m,"Chrome.app"),w=(d,b)=>{let h=Bun.spawnSync(d,{stdout:"pipe",stderr:"pipe"});if(h.exitCode!==0)throw Error(`${b} failed: ${h.stderr.toString().trim()}`)};try{w(["/bin/cp","-cR",r,g],"Cloning Chrome");let d=y(g,"Contents/Info.plist");for(let b of V)x(d,`Delete :${b}`);w(["/usr/libexec/PlistBuddy","-c",`Set :CFBundleIdentifier ${G}`,d],"Renaming the Chrome bundle"),w(["codesign","--force","--deep","--sign","-",g],"Re-signing Chrome"),j(i,{recursive:!0,force:!0}),he(g,i)}finally{j(m,{recursive:!0,force:!0})}return{executable:c,app:i,reused:!1}}async function oe(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 ve=new Set(["Fetch.continueRequest","Fetch.fulfillRequest","Fetch.failRequest","Fetch.continueResponse"]);function ye(e,t){let r="";for(let n=0;n<e.length;n++){let o=e[n];if(o==="\\"&&n+1<e.length)r+=X(e[++n]);else if(o==="*")r+="[\\s\\S]*";else if(o==="?")r+="[\\s\\S]";else r+=X(o)}return new RegExp(`^${r}$`).test(t)}function X(e){return e.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&")}function Z(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 ye(e.urlPattern??"*",t.request?.url??"")}class se{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)=>Z(s,e)));for(let o of t)try{let s=o.handler(e,this);if(s&&typeof s.catch==="function")s.catch((a)=>this.errors.push(`Fetch handler threw: ${a?.message??String(a)}`))}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)=>Z(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(()=>{i(),clearInterval(a),o(Error(`Timed out after ${r}ms waiting for ${e}`))},r),a=setInterval(()=>{if(!this.closed)return;clearTimeout(s),clearInterval(a),i(),o(Error("Chrome closed the connection"))},100),i=this.on(e,(c)=>{if(!t(c))return;clearTimeout(s),clearInterval(a),i(),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(ve.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 U(e,t={}){let r=new WebSocket(e);return await new Promise((n,o)=>{let s=(u)=>{if(clearTimeout(c),r.removeEventListener("open",a),r.removeEventListener("error",i),r.removeEventListener("close",i),u){try{r.close()}catch{}o(u)}else n()},a=()=>s(),i=()=>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",a,{once:!0}),r.addEventListener("error",i,{once:!0}),r.addEventListener("close",i,{once:!0})}),new se(r,t)}function Pe(){return K()}function Se(e){return z(e)}function xe(e={}){return Y(e)}function v(e,t){e.phase=t,e.phaseStartedMs[t]=Date.now()-e.started}class N 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 E(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=we(e,"r");let r=Buffer.alloc(1024);return r.subarray(0,be(t,r,0,r.length,0)).toString("utf8")}catch{return null}finally{if(t!==void 0)try{fe(t)}catch{}}}var Te=["*/_stx/hmr*"];async function Re(e,t=Te){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
4
4
 
5
- `).toString("base64")}).catch(()=>{})})}function be(t){let e=t.chromePath??P.env.CHROME_PATH;if(e)return e;let r=pe();if(!r)throw Error("No Chrome found: install Google Chrome or Chromium, or set CHROME_PATH");if((t.stableMacChrome??!0)&&P.platform==="darwin"&&fe(r))return me({source:r}).executable;return r}async function te(t,e,r,n,s){if(C(s,"process-start"),!E(e))throw Error(`Chrome was not found at ${e}`);let o=t.profile||J(v(z(),"stacks-browser-"));R(v(o,"DevToolsActivePort"),{force:!0});let a=v(o,"DevToolsActivePort"),i=(()=>{try{return Bun.spawn([e,...t.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",...t.args??[],"about:blank"],{stdin:"ignore",stdout:"ignore",stderr:"pipe"})}catch(l){let p=null;try{if(!t.profile)R(o,{recursive:!0,force:!0})}catch(m){p=String(m)}throw new _(l,{pid:null,chromePath:e,profile:o,temporaryProfile:!t.profile,elapsedMs:Date.now()-r,phase:s.phase,phaseStartedMs:{...s.phaseStartedMs},exitCodeBeforeCleanup:null,signalCodeBeforeCleanup:null,exitCode:null,signalCode:null,portFileExisted:!1,portFileContents:null,stderrTail:"",stderrTruncated:!1,cleanupError:p})}})(),c="",u=!1,f,g=Promise.resolve(),w,d=()=>w??=(async()=>{let l=!1,p=[];try{if(i.exitCode===null&&i.signalCode===null)try{i.kill("SIGTERM")}catch{}if(!await L(i.exited,2000)){try{i.kill("SIGKILL")}catch{}if(!await L(i.exited,2000))throw Error(`Chrome process ${i.pid} did not exit after SIGKILL`)}l=!0}catch(m){p.push(String(m)),l=i.exitCode!==null||i.signalCode!==null}finally{try{if(!await L(g,100)&&f){if(!await L(f.cancel(),100))throw Error("Chrome stderr cancellation did not finish")}if(!await L(g,100))throw Error("Chrome stderr reader did not finish")}catch(m){p.push(String(m))}finally{if(l&&!t.profile)try{R(o,{recursive:!0,force:!0})}catch(m){p.push(String(m))}}}if(p.length)throw Error(`Chrome cleanup failed: ${p.join("; ")}`)})(),b=()=>i.exitCode!==null||i.signalCode!==null,h=()=>{let l=n-(Date.now()-r);if(l<=0)throw Error(`Timed out after ${n}ms starting Chrome`);if(b())throw Error("Chrome exited during startup");return l},S=async(l,p,m=s)=>{let T=Date.now()-m.started,q=m.phase,A={...m.phaseStartedMs},{exitCode:F,signalCode:I}=i,ne=E(a),se=O(a),N=null;if(p)try{await d()}catch(k){N=k instanceof Error?k.message:String(k)}return new _(l,{pid:i.pid,chromePath:e,profile:o,temporaryProfile:!t.profile,elapsedMs:T,phase:q,phaseStartedMs:A,exitCodeBeforeCleanup:F,signalCodeBeforeCleanup:I,exitCode:i.exitCode,signalCode:i.signalCode,portFileExisted:ne,portFileContents:se,stderrTail:c,stderrTruncated:u,cleanupError:N})};try{f=i.stderr.getReader();let l=f;g=(async()=>{let q=new TextDecoder;for(;;){let{value:A,done:F}=await l.read(),I=q.decode(A,{stream:!F});if(u||=c.length+I.length>16384,c=(c+I).slice(-16384),F)return}})().catch((q)=>{c=`${c}
6
- [stderr read failed: ${String(q)}]`.slice(-16384)}),C(s,"devtools-port"),await Y(()=>(h(),O(a)?.includes(`
7
- `)),h(),{label:"Chrome to start"});let[p,m=""]=(O(a)??"").split(`
8
- `),T=Number(p);if(!Number.isInteger(T)||T<1||T>65535)throw Error("Chrome wrote an invalid DevToolsActivePort");return{pid:i.pid,chromePath:e,profile:o,port:T,browserPath:m.trim(),remaining:h,close:d,failure:S,exited:b}}catch(l){throw await S(l,!0)}}async function D(t,e,r){let n;try{return await Promise.race([t,new Promise((s,o)=>{n=setTimeout(()=>o(Error(`Timed out after ${e}ms ${r}`)),e)})])}finally{clearTimeout(n)}}async function re(t,e){if(await Promise.all([t.send("Page.enable"),t.send("Runtime.enable"),t.send("Network.enable"),t.send("Log.enable"),...e.viewport===!1?[]:[t.send("Emulation.setDeviceMetricsOverride",{deviceScaleFactor:1,mobile:!1,width:1440,height:1000,...e.viewport})]]),e.userAgent!==!1){let r=typeof e.userAgent==="string"?e.userAgent:String((await t.send("Browser.getVersion")).userAgent||"").replace("HeadlessChrome","Chrome");if(r)await t.send("Network.setUserAgentOverride",{userAgent:r})}if(e.quietLiveReload!==!1)await we(t,e.quietLiveReload);if(e.acceptDialogs!==!1)t.on("Page.javascriptDialogOpening",(r)=>{t.errors.push(`Dialog (${r.type}) on ${r.url}: ${r.message||"(no message)"}`),t.send("Page.handleJavaScriptDialog",{accept:!0}).catch(()=>{})})}var ve=1500,y=new Map,M=new Map,G=!1;function ye(){if(G)return;G=!0,P.once("exit",()=>{for(let[t,e]of M){try{P.kill(t,"SIGKILL")}catch{}try{if(e)R(e,{recursive:!0,force:!0})}catch{}}})}async function Ae(){let t=[...y.values()];y.clear(),await Promise.allSettled(t.map(async(e)=>{let r=await e;if(clearTimeout(r.linger),r.browser.disconnect(),await r.chrome.close(),r.chrome.pid!==null)M.delete(r.chrome.pid)}))}async function Ce(t,e,r,n,s,o){let a=y.get(t);if(a){let c=s-(Date.now()-n);if(c<=0)throw Error(`Timed out after ${s}ms starting Chrome`);let u=await D(a.catch(()=>null),c,"waiting for shared Chrome");if(u&&!u.browser.closed&&!u.chrome.exited())return clearTimeout(u.linger),u.sessions++,u;if(y.get(t)===a)y.delete(t)}let i=(async()=>{let c=await te(e,r,n,s,o);if(c.pid!==null)M.set(c.pid,c.profile);try{if(!c.browserPath.startsWith("/devtools/browser/"))throw Error("Chrome wrote no browser endpoint in DevToolsActivePort");C(o,"browser-connect");let u=await B(`ws://127.0.0.1:${c.port}${c.browserPath}`,{...e,connectTimeoutMs:Math.min(e.connectTimeoutMs??30000,c.remaining())});return{chrome:c,browser:u,sessions:0}}catch(u){if(c.pid!==null)M.delete(c.pid);throw await c.failure(u,!0)}})();y.set(t,i),ye();try{let c=await i;return c.sessions++,c}catch(c){if(y.get(t)===i)y.delete(t);throw c}}function W(t,e,r=!1){if(e.sessions=Math.max(0,e.sessions-1),e.sessions>0)return Promise.resolve();let n=async()=>{if(e.sessions>0)return;let s=y.get(t);if(s&&await s.catch(()=>null)===e)y.delete(t);if(e.browser.disconnect(),await e.chrome.close(),e.chrome.pid!==null)M.delete(e.chrome.pid)};if(r)return n();return clearTimeout(e.linger),e.linger=setTimeout(()=>{n().catch(()=>{})},ve),Promise.resolve()}async function Pe(t,e,r,n,s){let o=JSON.stringify([e,t.headless!==!1,t.args??[]]),a=await Ce(o,t,e,r,n,s),i=()=>{let g=n-(Date.now()-r);if(g<=0)throw Error(`Timed out after ${n}ms starting Chrome`);return g},c,u,f=async()=>{if(c&&!a.browser.closed)await a.browser.send("Target.disposeBrowserContext",{browserContextId:c}).catch(()=>{})};try{C(s,"context-create"),c=(await D(a.browser.send("Target.createBrowserContext",{disposeOnDetach:!1}),i(),"creating a browser context")).browserContextId,C(s,"target-create");let{targetId:g}=await D(a.browser.send("Target.createTarget",{url:"about:blank",browserContextId:c}),i(),"opening a page"),w=`ws://127.0.0.1:${a.chrome.port}/devtools/page/${g}`;C(s,"page-connect");let d=u=await B(w,{...t,connectTimeoutMs:Math.min(t.connectTimeoutMs??30000,i())});C(s,"page-prepare"),await D(re(d,t),i(),"preparing the page"),i();let b;return{cdp:d,close:()=>b??=(async()=>{d.disconnect(),await f(),await W(o,a)})(),target:w,port:a.chrome.port,chromePath:e}}catch(g){u?.disconnect(),await f();let w=a.sessions<=1,d=await a.chrome.failure(g,!1,s);throw await W(o,a,w).catch((b)=>{d.diagnostics.cleanupError=b instanceof Error?b.message:String(b)}),d}}async function Se(t,e,r,n,s){let o=await te(t,e,r,n,s),a,i,c=()=>i??=(async()=>{a?.disconnect(),await o.close()})(),u=setTimeout(()=>a?.disconnect(),Math.max(1,n-(Date.now()-r)));try{let f=o.port;C(s,"target-discovery");let g=await Y(async()=>{let d=await fetch(`http://127.0.0.1:${f}/json/list`,{signal:AbortSignal.timeout(Math.min(1000,o.remaining()))}).then((b)=>b.json()).catch(()=>[]);return Array.isArray(d)&&d.length?d:null},Math.min(12000,o.remaining()),{label:"a page target"}),w=g.find((d)=>d.type==="page"&&d.url==="about:blank")||g.find((d)=>d.type==="page");if(!w)throw Error("Chrome did not expose a page target");return C(s,"page-connect"),a=await B(w.webSocketDebuggerUrl,{...t,connectTimeoutMs:Math.min(t.connectTimeoutMs??30000,o.remaining())}),o.remaining(),C(s,"page-prepare"),await re(a,t),o.remaining(),clearTimeout(u),{cdp:a,close:c,target:w.webSocketDebuggerUrl,port:f,chromePath:e}}catch(f){throw clearTimeout(u),a?.disconnect(),await o.failure(f,!0)}}async function Te(t={}){let e=t.startTimeoutMs??30000;if(!Number.isFinite(e)||e<=0)throw RangeError("startTimeoutMs must be positive and finite");let r=be(t),n=Date.now(),s={started:n,phase:"process-acquire",phaseStartedMs:{"process-acquire":0}};return!t.profile&&t.shared!==!1&&P.env.STACKS_BROWSER_SHARED!=="0"?await Pe(t,r,n,e,s):await Se(t,r,n,e,s)}async function xe(t,e={}){let r=e.quietMs??1000,n=e.timeoutMs??15000,s=Date.now(),o=0;while(Date.now()-s<n){if(t.closed)throw Error("Chrome closed the connection");if(t.inFlight.size>0||await t.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 Re(t,e,r={}){let n=await t.send("Page.navigate",{url:e});if(n?.errorText)throw Error(`Could not open ${e}: ${n.errorText}`);return await xe(t,r)}var Ee=(t)=>t.pathname.startsWith("/api/")&&!t.pathname.endsWith("/events");function qe(t){let e={};for(let r of t)e[r]=(e[r]||0)+1;return Object.fromEntries(Object.entries(e).filter(([,r])=>r>1))}function V(t){try{let e=new URL(t);return`${e.origin}${e.pathname}`}catch{return t.replace(/[?#].*$/,"")}}function $e(t,e,r="navigation"){let n=[{url:t,apiRequests:[]}],s=!1;for(let o of e){let a=n.at(-1);if(o.type==="request"){a.apiRequests.push(o.request);continue}let i=r==="navigation"&&s&&(o.sameDocument?V(o.url)!==V(a.url):!0);if(s=!0,i)n.push({url:o.url,apiRequests:[]});else a.url=o.url}return n.map((o)=>({...o,duplicates:qe(o.apiRequests)}))}function Le(t){let e={};for(let r of t)for(let[n,s]of Object.entries(r.duplicates))e[n]=Math.max(e[n]??0,s);return e}async function ke(t,e={}){let r=Date.now(),n=new URL(t).origin,s=e.isApiRequest??Ee,o=[],a=[],i=[],c=null,u=e.cdp,f=[],g=u?.errors.length??0,w,d=t,b=async(h,S=[])=>{let l=$e(t,a,e.pageLoads),p=d;if(u&&!u.closed){let m=await u.evaluate("location.href").catch(()=>null);if(m&&m!=="about:blank"&&!m.startsWith("chrome-error:"))p=m}return{url:t,finalUrl:p,apiRequests:o,duplicates:Le(l),loads:l,failed:i,errors:[...u?.errors.slice(g)??[],...S],settled:h,durationMs:Date.now()-r}};try{if(!u)c=await Te(e.launch),u=c.cdp;let h=u;if(f.push(h.on("Network.requestWillBeSent",(l)=>{let p=new URL(l.request.url);if(p.origin===n&&s(p,l.request.method)){let m=`${l.request.method} ${p.pathname}${p.search}`;o.push(m),a.push({type:"request",request:m})}})),f.push(h.on("Network.responseReceived",(l)=>{let p=new URL(l.response.url);if(p.origin===n&&l.response.status>=400)i.push(`${l.response.status} ${p.pathname}`)})),f.push(h.on("Page.frameNavigated",(l)=>{if(l.frame?.parentId)return;w=l.frame?.id??w,d=l.frame?.unreachableUrl||`${l.frame?.url??d}${l.frame?.urlFragment??""}`,a.push({type:"navigated",url:d,sameDocument:!1})})),f.push(h.on("Page.navigatedWithinDocument",(l)=>{if(w&&l.frameId!==w)return;d=l.url,a.push({type:"navigated",url:l.url,sameDocument:!0})})),await h.send("Page.enable"),w=(await h.send("Page.getFrameTree").catch(()=>null))?.frameTree?.frame?.id,e.viewport)await h.send("Emulation.setDeviceMetricsOverride",{deviceScaleFactor:1,mobile:!1,...e.viewport});await e.beforeNavigate?.(h);let S=await Re(h,t,e);return await b(S)}catch(h){return await b(!1,[`audit: ${h?.message??String(h)}`])}finally{for(let h of f)h();await c?.close().catch(()=>{})}}function K(t){try{let e=new URL(t);return`${e.pathname}${e.search}`}catch{return t}}function Oe(t){let e=K(t.url),r=t.finalUrl?K(t.finalUrl):e,n=Object.entries(t.duplicates).map(([s,o])=>`${o}x ${s}`);return`${e}${r!==e?` -> ${r}`:""}: ${t.apiRequests.length} api${n.length?` | dup ${n.join(", ")}`:""}${t.failed.length?` | failed ${t.failed.join(", ")}`:""}${t.errors.length?` | errors ${t.errors.slice(0,3).join(" ; ")}`:""}${t.settled?"":" | never settled"}`}function _e(t){return t.settled&&!Object.keys(t.duplicates).length&&!t.failed.length&&!t.errors.length}export{Q as Cdp,_ as ChromeLaunchError,ge as LIVE_RELOAD_PATTERNS,ve as SHARED_CHROME_LINGER_MS,ke as auditPage,fe as chromeHasUpdater,Ae as closeSharedBrowsers,B as connect,qe as duplicateRequests,pe as findChrome,Oe as formatPageAudit,Te as launch,Le as loadDuplicates,de as matchesUrlPattern,Re as navigate,_e as pageAuditClean,H as pausedRequestMatches,me as prepareMacChrome,we as quietLiveReload,xe as settle,$e as splitPageLoads,Y as waitFor};
5
+ `).toString("base64")}).catch(()=>{})})}function Ee(e){let t=e.chromePath??q.env.CHROME_PATH;if(t)return t;let r=Pe();if(!r)throw Error("No Chrome found: install Google Chrome or Chromium, or set CHROME_PATH");if((e.stableMacChrome??!0)&&q.platform==="darwin"&&Se(r))return xe({source:r}).executable;return r}async function ie(e,t,r,n,o){if(v(o,"process-start"),!Q(t))throw Error(`Chrome was not found at ${t}`);let s=e.profile||ge(_(Ce(),"stacks-browser-"));D(_(s,"DevToolsActivePort"),{force:!0});let a=_(s,"DevToolsActivePort"),i=(()=>{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(l){let p=null;try{if(!e.profile)D(s,{recursive:!0,force:!0})}catch(f){p=String(f)}throw new N(l,{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:p})}})(),c="",u=!1,m,g=Promise.resolve(),w,d=()=>w??=(async()=>{let l=!1,p=[];try{if(i.exitCode===null&&i.signalCode===null)try{i.kill("SIGTERM")}catch{}if(!await E(i.exited,2000)){try{i.kill("SIGKILL")}catch{}if(!await E(i.exited,2000))throw Error(`Chrome process ${i.pid} did not exit after SIGKILL`)}l=!0}catch(f){p.push(String(f)),l=i.exitCode!==null||i.signalCode!==null}finally{try{if(!await E(g,100)&&m){if(!await E(m.cancel(),100))throw Error("Chrome stderr cancellation did not finish")}if(!await E(g,100))throw Error("Chrome stderr reader did not finish")}catch(f){p.push(String(f))}finally{if(l&&!e.profile)try{D(s,{recursive:!0,force:!0})}catch(f){p.push(String(f))}}}if(p.length)throw Error(`Chrome cleanup failed: ${p.join("; ")}`)})(),b=()=>i.exitCode!==null||i.signalCode!==null,h=()=>{let l=n-(Date.now()-r);if(l<=0)throw Error(`Timed out after ${n}ms starting Chrome`);if(b())throw Error("Chrome exited during startup");return l},P=async(l,p,f=o)=>{let S=Date.now()-f.started,T=f.phase,k={...f.phaseStartedMs},{exitCode:L,signalCode:F}=i,ce=Q(a),le=B(a),H=null;if(p)try{await d()}catch(O){H=O instanceof Error?O.message:String(O)}return new N(l,{pid:i.pid,chromePath:t,profile:s,temporaryProfile:!e.profile,elapsedMs:S,phase:T,phaseStartedMs:k,exitCodeBeforeCleanup:L,signalCodeBeforeCleanup:F,exitCode:i.exitCode,signalCode:i.signalCode,portFileExisted:ce,portFileContents:le,stderrTail:c,stderrTruncated:u,cleanupError:H})};try{m=i.stderr.getReader();let l=m;g=(async()=>{let T=new TextDecoder;for(;;){let{value:k,done:L}=await l.read(),F=T.decode(k,{stream:!L});if(u||=c.length+F.length>16384,c=(c+F).slice(-16384),L)return}})().catch((T)=>{c=`${c}
6
+ [stderr read failed: ${String(T)}]`.slice(-16384)}),v(o,"devtools-port"),await oe(()=>(h(),B(a)?.includes(`
7
+ `)),h(),{label:"Chrome to start"});let[p,f=""]=(B(a)??"").split(`
8
+ `),S=Number(p);if(!Number.isInteger(S)||S<1||S>65535)throw Error("Chrome wrote an invalid DevToolsActivePort");return{pid:i.pid,chromePath:t,profile:s,port:S,browserPath:f.trim(),remaining:h,close:d,failure:P,exited:b}}catch(l){throw await P(l,!0)}}async function A(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 ae(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 Re(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 qe=1500,C=new Map,M=new Map,ee=!1;function $e(){if(ee)return;ee=!0,q.once("exit",()=>{for(let[e,t]of M){try{q.kill(e,"SIGKILL")}catch{}try{if(t)D(t,{recursive:!0,force:!0})}catch{}}})}async function Ye(){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)M.delete(r.chrome.pid)}))}async function Me(e,t,r,n,o,s){let a=C.get(e);if(a){let c=o-(Date.now()-n);if(c<=0)throw Error(`Timed out after ${o}ms starting Chrome`);let u=await A(a.catch(()=>null),c,"waiting for shared Chrome");if(u&&!u.browser.closed&&!u.chrome.exited())return clearTimeout(u.linger),u.sessions++,u;if(C.get(e)===a)C.delete(e)}let i=(async()=>{let c=await ie(t,r,n,o,s);if(c.pid!==null)M.set(c.pid,c.profile);try{if(!c.browserPath.startsWith("/devtools/browser/"))throw Error("Chrome wrote no browser endpoint in DevToolsActivePort");v(s,"browser-connect");let u=await U(`ws://127.0.0.1:${c.port}${c.browserPath}`,{...t,connectTimeoutMs:Math.min(t.connectTimeoutMs??30000,c.remaining())});return{chrome:c,browser:u,sessions:0}}catch(u){if(c.pid!==null)M.delete(c.pid);throw await c.failure(u,!0)}})();C.set(e,i),$e();try{let c=await i;return c.sessions++,c}catch(c){if(C.get(e)===i)C.delete(e);throw c}}function te(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=C.get(e);if(o&&await o.catch(()=>null)===t)C.delete(e);if(t.browser.disconnect(),await t.chrome.close(),t.chrome.pid!==null)M.delete(t.chrome.pid)};if(r)return n();return clearTimeout(t.linger),t.linger=setTimeout(()=>{n().catch(()=>{})},qe),Promise.resolve()}async function Le(e,t,r,n,o){let s=JSON.stringify([t,e.headless!==!1,e.args??[]]),a=await Me(s,e,t,r,n,o),i=()=>{let g=n-(Date.now()-r);if(g<=0)throw Error(`Timed out after ${n}ms starting Chrome`);return g},c,u,m=async()=>{if(c&&!a.browser.closed)await a.browser.send("Target.disposeBrowserContext",{browserContextId:c}).catch(()=>{})};try{v(o,"context-create"),c=(await A(a.browser.send("Target.createBrowserContext",{disposeOnDetach:!1}),i(),"creating a browser context")).browserContextId,v(o,"target-create");let{targetId:g}=await A(a.browser.send("Target.createTarget",{url:"about:blank",browserContextId:c}),i(),"opening a page"),w=`ws://127.0.0.1:${a.chrome.port}/devtools/page/${g}`;v(o,"page-connect");let d=u=await U(w,{...e,connectTimeoutMs:Math.min(e.connectTimeoutMs??30000,i())});v(o,"page-prepare"),await A(ae(d,e),i(),"preparing the page"),i();let b;return{cdp:d,close:()=>b??=(async()=>{d.disconnect(),await m(),await te(s,a)})(),target:w,port:a.chrome.port,chromePath:t}}catch(g){u?.disconnect(),await m();let w=a.sessions<=1,d=await a.chrome.failure(g,!1,o);throw await te(s,a,w).catch((b)=>{d.diagnostics.cleanupError=b instanceof Error?b.message:String(b)}),d}}async function Fe(e,t,r,n,o){let s=await ie(e,t,r,n,o),a,i,c=()=>i??=(async()=>{a?.disconnect(),await s.close()})(),u=setTimeout(()=>a?.disconnect(),Math.max(1,n-(Date.now()-r)));try{let m=s.port;v(o,"target-discovery");let g=await oe(async()=>{let d=await fetch(`http://127.0.0.1:${m}/json/list`,{signal:AbortSignal.timeout(Math.min(1000,s.remaining()))}).then((b)=>b.json()).catch(()=>[]);return Array.isArray(d)&&d.length?d:null},Math.min(12000,s.remaining()),{label:"a page target"}),w=g.find((d)=>d.type==="page"&&d.url==="about:blank")||g.find((d)=>d.type==="page");if(!w)throw Error("Chrome did not expose a page target");return v(o,"page-connect"),a=await U(w.webSocketDebuggerUrl,{...e,connectTimeoutMs:Math.min(e.connectTimeoutMs??30000,s.remaining())}),s.remaining(),v(o,"page-prepare"),await ae(a,e),s.remaining(),clearTimeout(u),{cdp:a,close:c,target:w.webSocketDebuggerUrl,port:m,chromePath:t}}catch(m){throw clearTimeout(u),a?.disconnect(),await s.failure(m,!0)}}async function Ie(e={}){let t=e.startTimeoutMs??30000;if(!Number.isFinite(t)||t<=0)throw RangeError("startTimeoutMs must be positive and finite");let r=Ee(e),n=Date.now(),o={started:n,phase:"process-acquire",phaseStartedMs:{"process-acquire":0}};return!e.profile&&e.shared!==!1&&q.env.STACKS_BROWSER_SHARED!=="0"?await Le(e,r,n,t,o):await Fe(e,r,n,t,o)}async function De(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 Ae(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 De(e,r)}var ke=(e)=>e.pathname.startsWith("/api/")&&!e.pathname.endsWith("/events");function Oe(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 re(e){try{let t=new URL(e);return`${t.origin}${t.pathname}`}catch{return e.replace(/[?#].*$/,"")}}function _e(e,t,r="navigation"){let n=[{url:e,apiRequests:[]}],o=!1;for(let s of t){let a=n.at(-1);if(s.type==="request"){a.apiRequests.push(s.request);continue}let i=r==="navigation"&&o&&(s.sameDocument?re(s.url)!==re(a.url):!0);if(o=!0,i)n.push({url:s.url,apiRequests:[]});else a.url=s.url}return n.map((s)=>({...s,duplicates:Oe(s.apiRequests)}))}function Be(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 Qe(e,t={}){let r=Date.now(),n=new URL(e).origin,o=t.isApiRequest??ke,s=[],a=[],i=[],c=null,u=t.cdp,m=[],g=u?.errors.length??0,w,d=e,b=async(h,P=[])=>{let l=_e(e,a,t.pageLoads),p=d;if(u&&!u.closed){let f=await u.evaluate("location.href").catch(()=>null);if(f&&f!=="about:blank"&&!f.startsWith("chrome-error:"))p=f}return{url:e,finalUrl:p,apiRequests:s,duplicates:Be(l),loads:l,failed:i,errors:[...u?.errors.slice(g)??[],...P],settled:h,durationMs:Date.now()-r}};try{if(!u)c=await Ie(t.launch),u=c.cdp;let h=u;if(m.push(h.on("Network.requestWillBeSent",(l)=>{let p=new URL(l.request.url);if(p.origin===n&&o(p,l.request.method)){let f=`${l.request.method} ${p.pathname}${p.search}`;s.push(f),a.push({type:"request",request:f})}})),m.push(h.on("Network.responseReceived",(l)=>{let p=new URL(l.response.url);if(p.origin===n&&l.response.status>=400)i.push(`${l.response.status} ${p.pathname}`)})),m.push(h.on("Page.frameNavigated",(l)=>{if(l.frame?.parentId)return;w=l.frame?.id??w,d=l.frame?.unreachableUrl||`${l.frame?.url??d}${l.frame?.urlFragment??""}`,a.push({type:"navigated",url:d,sameDocument:!1})})),m.push(h.on("Page.navigatedWithinDocument",(l)=>{if(w&&l.frameId!==w)return;d=l.url,a.push({type:"navigated",url:l.url,sameDocument:!0})})),await h.send("Page.enable"),w=(await h.send("Page.getFrameTree").catch(()=>null))?.frameTree?.frame?.id,t.viewport)await h.send("Emulation.setDeviceMetricsOverride",{deviceScaleFactor:1,mobile:!1,...t.viewport});await t.beforeNavigate?.(h);let P=await Ae(h,e,t);return await b(P)}catch(h){return await b(!1,[`audit: ${h?.message??String(h)}`])}finally{for(let h of m)h();await c?.close().catch(()=>{})}}function ne(e){try{let t=new URL(e);return`${t.pathname}${t.search}`}catch{return e}}function Xe(e){let t=ne(e.url),r=e.finalUrl?ne(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 Ze(e){return e.settled&&!Object.keys(e.duplicates).length&&!e.failed.length&&!e.errors.length}export{se as Cdp,N as ChromeLaunchError,Te as LIVE_RELOAD_PATTERNS,qe as SHARED_CHROME_LINGER_MS,Qe as auditPage,Se as chromeHasUpdater,Ye as closeSharedBrowsers,U as connect,Oe as duplicateRequests,Pe as findChrome,Xe as formatPageAudit,Ie as launch,Be as loadDuplicates,ye as matchesUrlPattern,Ae as navigate,Ze as pageAuditClean,Z as pausedRequestMatches,xe as prepareMacChrome,Re as quietLiveReload,De as settle,_e as splitPageLoads,oe 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.308",
5
+ "version": "0.75.310",
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.308",
83
- "@stacksjs/cache": "0.75.308",
84
- "@stacksjs/config": "0.75.308",
85
- "@stacksjs/database": "0.75.308",
86
- "@stacksjs/email": "0.75.308",
87
- "@stacksjs/events": "0.75.308",
88
- "@stacksjs/path": "0.75.308",
89
- "@stacksjs/router": "0.75.308",
90
- "@stacksjs/storage": "0.75.308",
91
- "@stacksjs/strings": "0.75.308",
82
+ "@stacksjs/auth": "0.75.310",
83
+ "@stacksjs/cache": "0.75.310",
84
+ "@stacksjs/config": "0.75.310",
85
+ "@stacksjs/database": "0.75.310",
86
+ "@stacksjs/email": "0.75.310",
87
+ "@stacksjs/events": "0.75.310",
88
+ "@stacksjs/path": "0.75.310",
89
+ "@stacksjs/router": "0.75.310",
90
+ "@stacksjs/storage": "0.75.310",
91
+ "@stacksjs/strings": "0.75.310",
92
92
  "@stacksjs/ts-cloud": "^0.16.69",
93
93
  "very-happy-dom": "^0.3.5"
94
94
  },