2024-09-06 03:31:56 +02:00
/ * *
* Copyright ( c ) Microsoft Corporation .
*
* Licensed under the Apache License , Version 2.0 ( the 'License' ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an 'AS IS' BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2025-02-19 15:32:12 +01:00
import os from 'os' ;
import path from 'path' ;
2025-02-07 22:54:01 +01:00
2025-02-12 00:40:41 +01:00
import { assert } from '../../utils' ;
import { wrapInASCIIBox } from '../utils/ascii' ;
2025-02-07 22:54:01 +01:00
import { BrowserReadyState , BrowserType , kNoXServerRunningError } from '../browserType' ;
import { BidiBrowser } from './bidiBrowser' ;
import { kBrowserCloseMessageId } from './bidiConnection' ;
import { createProfile } from './third_party/firefoxPrefs' ;
2024-09-06 03:31:56 +02:00
import type { BrowserOptions } from '../browser' ;
import type { SdkObject } from '../instrumentation' ;
2025-02-12 00:40:41 +01:00
import type { Env } from '../utils/processLauncher' ;
2024-09-06 03:31:56 +02:00
import type { ProtocolError } from '../protocolError' ;
import type { ConnectionTransport } from '../transport' ;
import type * as types from '../types' ;
2025-02-07 22:54:01 +01:00
2024-09-06 03:31:56 +02:00
export class BidiFirefox extends BrowserType {
constructor ( parent : SdkObject ) {
super ( parent , 'bidi' ) ;
this . _useBidi = true ;
}
override async connectToTransport ( transport : ConnectionTransport , options : BrowserOptions ) : Promise < BidiBrowser > {
return BidiBrowser . connect ( this . attribution . playwright , transport , options ) ;
}
override doRewriteStartupLog ( error : ProtocolError ) : ProtocolError {
if ( ! error . logs )
return error ;
// https://github.com/microsoft/playwright/issues/6500
if ( error . logs . includes ( ` as root in a regular user's session is not supported. ` ) )
error . logs = '\n' + wrapInASCIIBox ( ` Firefox is unable to launch if the $ HOME folder isn't owned by the current user. \ nWorkaround: Set the HOME=/root environment variable ${ process . env . GITHUB_ACTION ? ' in your GitHub Actions workflow file' : '' } when running Playwright. ` , 1 ) ;
if ( error . logs . includes ( 'no DISPLAY environment variable specified' ) )
error . logs = '\n' + wrapInASCIIBox ( kNoXServerRunningError , 1 ) ;
return error ;
}
override amendEnvironment ( env : Env , userDataDir : string , executable : string , browserArguments : string [ ] ) : Env {
if ( ! path . isAbsolute ( os . homedir ( ) ) )
throw new Error ( ` Cannot launch Firefox with relative home directory. Did you set ${ os . platform ( ) === 'win32' ? 'USERPROFILE' : 'HOME' } to a relative path? ` ) ;
2024-09-19 01:02:10 +02:00
env = {
. . . env ,
'MOZ_CRASHREPORTER' : '1' ,
'MOZ_CRASHREPORTER_NO_REPORT' : '1' ,
'MOZ_CRASHREPORTER_SHUTDOWN' : '1' ,
} ;
2024-09-06 03:31:56 +02:00
if ( os . platform ( ) === 'linux' ) {
// Always remove SNAP_NAME and SNAP_INSTANCE_NAME env variables since they
// confuse Firefox: in our case, builds never come from SNAP.
// See https://github.com/microsoft/playwright/issues/20555
return { . . . env , SNAP_NAME : undefined , SNAP_INSTANCE_NAME : undefined } ;
}
return env ;
}
override attemptToGracefullyCloseBrowser ( transport : ConnectionTransport ) : void {
transport . send ( { method : 'browser.close' , params : { } , id : kBrowserCloseMessageId } ) ;
}
2024-09-26 03:17:07 +02:00
override async prepareUserDataDir ( options : types.LaunchOptions , userDataDir : string ) : Promise < void > {
await createProfile ( {
path : userDataDir ,
preferences : options.firefoxUserPrefs || { } ,
} ) ;
}
2024-09-06 03:31:56 +02:00
override defaultArgs ( options : types.LaunchOptions , isPersistent : boolean , userDataDir : string ) : string [ ] {
const { args = [ ] , headless } = options ;
const userDataDirArg = args . find ( arg = > arg . startsWith ( '-profile' ) || arg . startsWith ( '--profile' ) ) ;
if ( userDataDirArg )
throw this . _createUserDataDirArgMisuseError ( '--profile' ) ;
const firefoxArguments = [ '--remote-debugging-port=0' ] ;
if ( headless )
firefoxArguments . push ( '--headless' ) ;
else
firefoxArguments . push ( '--foreground' ) ;
firefoxArguments . push ( ` --profile ` , userDataDir ) ;
firefoxArguments . push ( . . . args ) ;
return firefoxArguments ;
}
override readyState ( options : types.LaunchOptions ) : BrowserReadyState | undefined {
assert ( options . useWebSocket ) ;
return new FirefoxReadyState ( ) ;
}
}
class FirefoxReadyState extends BrowserReadyState {
override onBrowserOutput ( message : string ) : void {
// Bidi WebSocket in Firefox.
const match = message . match ( /WebDriver BiDi listening on (ws:\/\/.*)$/ ) ;
if ( match )
this . _wsEndpoint . resolve ( match [ 1 ] + '/session' ) ;
}
}