@@ -59,7 +59,6 @@ type ConnectOptions = {
59
59
logger ?: Logger ,
60
60
timeout ?: number ,
61
61
} ;
62
- export type LaunchType = 'local' | 'server' | 'persistent' ;
63
62
export type LaunchOptions = LaunchOptionsBase & { slowMo ?: number } ;
64
63
type LaunchServerOptions = LaunchOptionsBase & { port ?: number } ;
65
64
@@ -73,6 +72,7 @@ export interface BrowserType {
73
72
}
74
73
75
74
const mkdtempAsync = util . promisify ( fs . mkdtemp ) ;
75
+ const DOWNLOADS_FOLDER = path . join ( os . tmpdir ( ) , 'playwright_downloads-' ) ;
76
76
77
77
export abstract class BrowserTypeBase implements BrowserType {
78
78
private _name : string ;
@@ -100,24 +100,37 @@ export abstract class BrowserTypeBase implements BrowserType {
100
100
101
101
async launch ( options : LaunchOptions = { } ) : Promise < Browser > {
102
102
assert ( ! ( options as any ) . userDataDir , 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead' ) ;
103
- return this . _innerLaunch ( 'local' , options , undefined ) ;
103
+ assert ( ! ( options as any ) . port , 'Cannot specify a port without launching as a server.' ) ;
104
+ return this . _innerLaunch ( options , undefined ) ;
104
105
}
105
106
106
107
async launchPersistentContext ( userDataDir : string , options : LaunchOptions & PersistentContextOptions = { } ) : Promise < BrowserContext > {
108
+ assert ( ! ( options as any ) . port , 'Cannot specify a port without launching as a server.' ) ;
107
109
const persistent = validatePersistentContextOptions ( options ) ;
108
- const browser = await this . _innerLaunch ( 'persistent' , options , persistent , userDataDir ) ;
110
+ const browser = await this . _innerLaunch ( options , persistent , userDataDir ) ;
109
111
return browser . _defaultContext ! ;
110
112
}
111
113
112
- async _innerLaunch ( launchType : LaunchType , options : LaunchOptions , persistent : PersistentContextOptions | undefined , userDataDir ?: string ) : Promise < BrowserBase > {
114
+ async _innerLaunch ( options : LaunchOptions , persistent : PersistentContextOptions | undefined , userDataDir ?: string ) : Promise < BrowserBase > {
113
115
const deadline = TimeoutSettings . computeDeadline ( options . timeout , 30000 ) ;
114
116
const logger = new RootLogger ( options . logger ) ;
115
117
logger . startLaunchRecording ( ) ;
116
118
117
119
let browserServer : BrowserServer | undefined ;
118
120
try {
119
- browserServer = await this . _launchServer ( options , launchType , logger , deadline , userDataDir ) ;
120
- const promise = this . _innerLaunchPromise ( browserServer , options , persistent ) ;
121
+ const launched = await this . _launchServer ( options , ! ! persistent , logger , deadline , userDataDir ) ;
122
+ browserServer = launched . browserServer ;
123
+ const browserOptions : BrowserOptions = {
124
+ slowMo : options . slowMo ,
125
+ persistent,
126
+ headful : ! processBrowserArgOptions ( options ) . headless ,
127
+ logger,
128
+ downloadsPath : launched . downloadsPath ,
129
+ ownedServer : browserServer ,
130
+ } ;
131
+ copyTestHooks ( options , browserOptions ) ;
132
+ const hasCustomArguments = ! ! options . ignoreDefaultArgs && ! Array . isArray ( options . ignoreDefaultArgs ) ;
133
+ const promise = this . _innerCreateBrowser ( launched . transport , browserOptions , hasCustomArguments ) ;
121
134
const browser = await helper . waitWithDeadline ( promise , 'the browser to launch' , deadline , 'pw:browser*' ) ;
122
135
return browser ;
123
136
} catch ( e ) {
@@ -132,34 +145,23 @@ export abstract class BrowserTypeBase implements BrowserType {
132
145
}
133
146
}
134
147
135
- async _innerLaunchPromise ( browserServer : BrowserServer , options : LaunchOptions , persistent : PersistentContextOptions | undefined ) : Promise < BrowserBase > {
136
- if ( ( options as any ) . __testHookBeforeCreateBrowser )
137
- await ( options as any ) . __testHookBeforeCreateBrowser ( ) ;
138
-
139
- const browserOptions : BrowserOptions = {
140
- slowMo : options . slowMo ,
141
- persistent,
142
- headful : browserServer . _headful ,
143
- logger : browserServer . _logger ,
144
- downloadsPath : browserServer . _downloadsPath ,
145
- ownedServer : browserServer ,
146
- } ;
147
- for ( const [ key , value ] of Object . entries ( options ) ) {
148
- if ( key . startsWith ( '__testHook' ) )
149
- ( browserOptions as any ) [ key ] = value ;
150
- }
151
-
152
- const browser = await this . _connectToTransport ( browserServer . _transport , browserOptions ) ;
153
- if ( persistent && ( ! options . ignoreDefaultArgs || Array . isArray ( options . ignoreDefaultArgs ) ) ) {
154
- const context = browser . _defaultContext ! ;
155
- await context . _loadDefaultContext ( ) ;
156
- }
148
+ async _innerCreateBrowser ( transport : ConnectionTransport , browserOptions : BrowserOptions , hasCustomArguments : boolean ) : Promise < BrowserBase > {
149
+ if ( ( browserOptions as any ) . __testHookBeforeCreateBrowser )
150
+ await ( browserOptions as any ) . __testHookBeforeCreateBrowser ( ) ;
151
+ const browser = await this . _connectToTransport ( transport , browserOptions ) ;
152
+ // We assume no control when using custom arguments, and do not prepare the default context in that case.
153
+ if ( browserOptions . persistent && ! hasCustomArguments )
154
+ await browser . _defaultContext ! . _loadDefaultContext ( ) ;
157
155
return browser ;
158
156
}
159
157
160
158
async launchServer ( options : LaunchServerOptions = { } ) : Promise < BrowserServer > {
159
+ assert ( ! ( options as any ) . userDataDir , 'userDataDir option is not supported in `browserType.launchServer`. Use `browserType.launchPersistentContext` instead' ) ;
160
+ const { port = 0 } = options ;
161
161
const logger = new RootLogger ( options . logger ) ;
162
- return this . _launchServer ( options , 'server' , logger , TimeoutSettings . computeDeadline ( options . timeout , 30000 ) ) ;
162
+ const { browserServer, transport } = await this . _launchServer ( options , false , logger , TimeoutSettings . computeDeadline ( options . timeout , 30000 ) ) ;
163
+ browserServer . _webSocketWrapper = this . _wrapTransportWithWebSocket ( transport , logger , port ) ;
164
+ return browserServer ;
163
165
}
164
166
165
167
async connect ( options : ConnectOptions ) : Promise < Browser > {
@@ -170,7 +172,12 @@ export abstract class BrowserTypeBase implements BrowserType {
170
172
let transport : ConnectionTransport | undefined ;
171
173
try {
172
174
transport = await WebSocketTransport . connect ( options . wsEndpoint , logger , deadline ) ;
173
- const promise = this . _innerConnectPromise ( transport , options , logger ) ;
175
+ const browserOptions : BrowserOptions = {
176
+ slowMo : options . slowMo ,
177
+ logger,
178
+ } ;
179
+ copyTestHooks ( options , browserOptions ) ;
180
+ const promise = this . _innerCreateBrowser ( transport , browserOptions , false ) ;
174
181
const browser = await helper . waitWithDeadline ( promise , 'connect to browser' , deadline , 'pw:browser*' ) ;
175
182
logger . stopLaunchRecording ( ) ;
176
183
return browser ;
@@ -189,13 +196,7 @@ export abstract class BrowserTypeBase implements BrowserType {
189
196
}
190
197
}
191
198
192
- async _innerConnectPromise ( transport : ConnectionTransport , options : ConnectOptions , logger : RootLogger ) : Promise < Browser > {
193
- if ( ( options as any ) . __testHookBeforeCreateBrowser )
194
- await ( options as any ) . __testHookBeforeCreateBrowser ( ) ;
195
- return this . _connectToTransport ( transport , { slowMo : options . slowMo , logger } ) ;
196
- }
197
-
198
- private async _launchServer ( options : LaunchServerOptions , launchType : LaunchType , logger : RootLogger , deadline : number , userDataDir ?: string ) : Promise < BrowserServer > {
199
+ private async _launchServer ( options : LaunchServerOptions , isPersistent : boolean , logger : RootLogger , deadline : number , userDataDir ?: string ) : Promise < { browserServer : BrowserServer , downloadsPath : string , transport : ConnectionTransport } > {
199
200
const {
200
201
ignoreDefaultArgs = false ,
201
202
args = [ ] ,
@@ -204,21 +205,20 @@ export abstract class BrowserTypeBase implements BrowserType {
204
205
handleSIGINT = true ,
205
206
handleSIGTERM = true ,
206
207
handleSIGHUP = true ,
207
- port = 0 ,
208
208
} = options ;
209
- assert ( ! port || launchType === 'server' , 'Cannot specify a port without launching as a server.' ) ;
210
209
211
- let temporaryUserDataDir : string | null = null ;
210
+ const downloadsPath = await mkdtempAsync ( DOWNLOADS_FOLDER ) ;
211
+ const tempDirectories = [ downloadsPath ] ;
212
212
if ( ! userDataDir ) {
213
213
userDataDir = await mkdtempAsync ( path . join ( os . tmpdir ( ) , `playwright_${ this . _name } dev_profile-` ) ) ;
214
- temporaryUserDataDir = userDataDir ;
214
+ tempDirectories . push ( userDataDir ) ;
215
215
}
216
216
217
217
const browserArguments = [ ] ;
218
218
if ( ! ignoreDefaultArgs )
219
- browserArguments . push ( ...this . _defaultArgs ( options , launchType , userDataDir ) ) ;
219
+ browserArguments . push ( ...this . _defaultArgs ( options , isPersistent , userDataDir ) ) ;
220
220
else if ( Array . isArray ( ignoreDefaultArgs ) )
221
- browserArguments . push ( ...this . _defaultArgs ( options , launchType , userDataDir ) . filter ( arg => ignoreDefaultArgs . indexOf ( arg ) === - 1 ) ) ;
221
+ browserArguments . push ( ...this . _defaultArgs ( options , isPersistent , userDataDir ) . filter ( arg => ignoreDefaultArgs . indexOf ( arg ) === - 1 ) ) ;
222
222
else
223
223
browserArguments . push ( ...args ) ;
224
224
@@ -230,7 +230,7 @@ export abstract class BrowserTypeBase implements BrowserType {
230
230
// "Cannot access 'browserServer' before initialization" if something went wrong.
231
231
let transport : ConnectionTransport | undefined = undefined ;
232
232
let browserServer : BrowserServer | undefined = undefined ;
233
- const { launchedProcess, gracefullyClose, downloadsPath } = await launchProcess ( {
233
+ const { launchedProcess, gracefullyClose } = await launchProcess ( {
234
234
executablePath : executable ,
235
235
args : browserArguments ,
236
236
env : this . _amendEnvironment ( env , userDataDir , executable , browserArguments ) ,
@@ -239,7 +239,7 @@ export abstract class BrowserTypeBase implements BrowserType {
239
239
handleSIGHUP,
240
240
logger,
241
241
pipe : ! this . _webSocketRegexNotPipe ,
242
- tempDir : temporaryUserDataDir || undefined ,
242
+ tempDirectories ,
243
243
attemptToGracefullyClose : async ( ) => {
244
244
if ( ( options as any ) . __testHookGracefullyClose )
245
245
await ( options as any ) . __testHookGracefullyClose ( ) ;
@@ -269,14 +269,20 @@ export abstract class BrowserTypeBase implements BrowserType {
269
269
helper . killProcess ( launchedProcess ) ;
270
270
throw e ;
271
271
}
272
- browserServer = new BrowserServer ( options , launchedProcess , gracefullyClose , transport , downloadsPath ,
273
- launchType === 'server' ? this . _wrapTransportWithWebSocket ( transport , logger , port ) : null ) ;
274
- return browserServer ;
272
+ browserServer = new BrowserServer ( launchedProcess , gracefullyClose ) ;
273
+ return { browserServer, downloadsPath, transport } ;
275
274
}
276
275
277
- abstract _defaultArgs ( options : BrowserArgOptions , launchType : LaunchType , userDataDir : string ) : string [ ] ;
276
+ abstract _defaultArgs ( options : BrowserArgOptions , isPersistent : boolean , userDataDir : string ) : string [ ] ;
278
277
abstract _connectToTransport ( transport : ConnectionTransport , options : BrowserOptions ) : Promise < BrowserBase > ;
279
278
abstract _wrapTransportWithWebSocket ( transport : ConnectionTransport , logger : InnerLogger , port : number ) : WebSocketWrapper ;
280
279
abstract _amendEnvironment ( env : Env , userDataDir : string , executable : string , browserArguments : string [ ] ) : Env ;
281
280
abstract _attemptToGracefullyCloseBrowser ( transport : ConnectionTransport ) : void ;
282
281
}
282
+
283
+ function copyTestHooks ( from : object , to : object ) {
284
+ for ( const [ key , value ] of Object . entries ( from ) ) {
285
+ if ( key . startsWith ( '__testHook' ) )
286
+ ( to as any ) [ key ] = value ;
287
+ }
288
+ }
0 commit comments