@@ -130,18 +130,25 @@ function installPuppeteerBrowsersMock(
130130 executablePath : string ;
131131 path ?: string ;
132132 buildId ?: string ;
133+ platform ?: string ;
133134 } > ;
135+ browserPlatform ?: string ;
134136 installedInHfCacheError ?: Error ;
135137 installResult ?: { executablePath : string } ;
136138 installImpl ?: ( ) => Promise < { executablePath : string } > ;
137139 } = { } ,
138140) {
139141 vi . doMock ( "@puppeteer/browsers" , ( ) => ( {
140142 Browser : { CHROMEHEADLESSSHELL : "chrome-headless-shell" } ,
141- detectBrowserPlatform : ( ) => "linux" ,
143+ detectBrowserPlatform : ( ) => opts . browserPlatform ?? "linux" ,
142144 getInstalledBrowsers : opts . installedInHfCacheError
143145 ? vi . fn ( ) . mockRejectedValue ( opts . installedInHfCacheError )
144- : vi . fn ( ) . mockResolvedValue ( opts . installedInHfCache ?? [ ] ) ,
146+ : vi . fn ( ) . mockResolvedValue (
147+ ( opts . installedInHfCache ?? [ ] ) . map ( ( browser ) => ( {
148+ platform : opts . browserPlatform ?? "linux" ,
149+ ...browser ,
150+ } ) ) ,
151+ ) ,
145152 install : vi
146153 . fn ( )
147154 . mockImplementation (
@@ -222,6 +229,41 @@ describe("findBrowser — cache resolution", () => {
222229 expect ( result ) . toEqual ( { executablePath : SYSTEM_CHROME , source : "system" } ) ;
223230 } ) ;
224231
232+ it ( "ignores a current-version HyperFrames cache entry for another platform" , async ( ) => {
233+ Object . defineProperty ( process , "platform" , { value : "darwin" , configurable : true } ) ;
234+ Object . defineProperty ( process , "arch" , { value : "arm64" , configurable : true } ) ;
235+ const macArm64Binary = join (
236+ HF_CACHE ,
237+ "chrome-headless-shell" ,
238+ "mac_arm-131.0.6778.85" ,
239+ "chrome-headless-shell-mac-arm64" ,
240+ "chrome-headless-shell" ,
241+ ) ;
242+ installFsMocks ( { existing : new Set ( [ HF_CACHE , HF_BINARY , macArm64Binary ] ) } ) ;
243+ installPuppeteerBrowsersMock ( {
244+ browserPlatform : "mac_arm" ,
245+ installedInHfCache : [
246+ {
247+ browser : "chrome-headless-shell" ,
248+ executablePath : HF_BINARY ,
249+ buildId : CHROME_VERSION ,
250+ platform : "linux" ,
251+ } ,
252+ {
253+ browser : "chrome-headless-shell" ,
254+ executablePath : macArm64Binary ,
255+ buildId : CHROME_VERSION ,
256+ platform : "mac_arm" ,
257+ } ,
258+ ] ,
259+ } ) ;
260+
261+ const { findBrowser } = await import ( "./manager.js" ) ;
262+ const result = await findBrowser ( ) ;
263+
264+ expect ( result ) . toEqual ( { executablePath : macArm64Binary , source : "cache" } ) ;
265+ } ) ;
266+
225267 it ( "re-downloads when the hyperframes cache manifest points at a missing binary" , async ( ) => {
226268 const redownloadedBinary = join (
227269 HF_CACHE ,
@@ -492,6 +534,91 @@ describe("findBrowser — cache resolution", () => {
492534 expect ( result ) . toEqual ( { executablePath : PUPPETEER_BINARY , source : "cache" } ) ;
493535 } ) ;
494536
537+ it . each ( [
538+ {
539+ hostPlatform : "darwin" ,
540+ hostArch : "arm64" ,
541+ expectedDirectory : "chrome-headless-shell-mac-arm64" ,
542+ expectedExecutable : "chrome-headless-shell" ,
543+ } ,
544+ {
545+ hostPlatform : "darwin" ,
546+ hostArch : "x64" ,
547+ expectedDirectory : "chrome-headless-shell-mac-x64" ,
548+ expectedExecutable : "chrome-headless-shell" ,
549+ } ,
550+ {
551+ hostPlatform : "linux" ,
552+ hostArch : "x64" ,
553+ expectedDirectory : "chrome-headless-shell-linux64" ,
554+ expectedExecutable : "chrome-headless-shell" ,
555+ } ,
556+ {
557+ hostPlatform : "win32" ,
558+ hostArch : "ia32" ,
559+ expectedDirectory : "chrome-headless-shell-win32" ,
560+ expectedExecutable : "chrome-headless-shell.exe" ,
561+ } ,
562+ {
563+ hostPlatform : "win32" ,
564+ hostArch : "x64" ,
565+ expectedDirectory : "chrome-headless-shell-win64" ,
566+ expectedExecutable : "chrome-headless-shell.exe" ,
567+ } ,
568+ ] ) (
569+ "selects only the host-compatible cached shell on $hostPlatform/$hostArch when every platform is present" ,
570+ async ( { hostPlatform, hostArch, expectedDirectory, expectedExecutable } ) => {
571+ Object . defineProperty ( process , "platform" , { value : hostPlatform , configurable : true } ) ;
572+ Object . defineProperty ( process , "arch" , { value : hostArch , configurable : true } ) ;
573+ const version = "host-148.0.7778.97" ;
574+ const candidates = [
575+ [ "chrome-headless-shell-linux64" , "chrome-headless-shell" ] ,
576+ [ "chrome-headless-shell-mac-arm64" , "chrome-headless-shell" ] ,
577+ [ "chrome-headless-shell-mac-x64" , "chrome-headless-shell" ] ,
578+ [ "chrome-headless-shell-win32" , "chrome-headless-shell.exe" ] ,
579+ [ "chrome-headless-shell-win64" , "chrome-headless-shell.exe" ] ,
580+ ] as const ;
581+ const binaries = candidates . map ( ( [ directory , executable ] ) =>
582+ join ( PUPPETEER_CACHE , version , directory , executable ) ,
583+ ) ;
584+ const expectedBinary = join ( PUPPETEER_CACHE , version , expectedDirectory , expectedExecutable ) ;
585+ installFsMocks ( {
586+ existing : new Set ( [ PUPPETEER_CACHE , ...binaries ] ) ,
587+ dirs : { [ PUPPETEER_CACHE ] : [ version ] } ,
588+ } ) ;
589+ installPuppeteerBrowsersMock ( ) ;
590+
591+ const { findBrowser } = await import ( "./manager.js" ) ;
592+ const result = await findBrowser ( ) ;
593+
594+ expect ( result ) . toEqual ( { executablePath : expectedBinary , source : "cache" } ) ;
595+ } ,
596+ ) ;
597+
598+ it . each ( [
599+ { hostPlatform : "linux" , hostArch : "arm64" } ,
600+ { hostPlatform : "win32" , hostArch : "arm64" } ,
601+ ] ) (
602+ "does not select a foreign cached shell on unsupported $hostPlatform/$hostArch" ,
603+ async ( { hostPlatform, hostArch } ) => {
604+ Object . defineProperty ( process , "platform" , { value : hostPlatform , configurable : true } ) ;
605+ Object . defineProperty ( process , "arch" , { value : hostArch , configurable : true } ) ;
606+ const version = "host-148.0.7778.97" ;
607+ const binaries = [
608+ join ( PUPPETEER_CACHE , version , "chrome-headless-shell-linux64" , "chrome-headless-shell" ) ,
609+ join ( PUPPETEER_CACHE , version , "chrome-headless-shell-win64" , "chrome-headless-shell.exe" ) ,
610+ ] ;
611+ installFsMocks ( {
612+ existing : new Set ( [ PUPPETEER_CACHE , ...binaries ] ) ,
613+ dirs : { [ PUPPETEER_CACHE ] : [ version ] } ,
614+ } ) ;
615+ installPuppeteerBrowsersMock ( ) ;
616+
617+ const { findBrowser } = await import ( "./manager.js" ) ;
618+ await expect ( findBrowser ( ) ) . resolves . toBeUndefined ( ) ;
619+ } ,
620+ ) ;
621+
495622 it ( "prefers the puppeteer cache over the hyperframes cache when BOTH are populated" , async ( ) => {
496623 // The HF cache is pinned to `CHROME_VERSION` (131-era) which lags upstream
497624 // by many releases. The engine's `resolveHeadlessShellPath` scans the
0 commit comments