2020-01-24 14:49:47 -08: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 .
* /
2020-05-22 07:03:42 -07:00
import * as fs from 'fs' ;
import * as os from 'os' ;
import * as path from 'path' ;
import * as util from 'util' ;
2020-06-25 08:30:56 -07:00
import { BrowserContext , verifyProxySettings , validateBrowserContextOptions } from '../browserContext' ;
2020-06-10 16:33:27 -07:00
import { BrowserServer } from './browserServer' ;
2020-04-28 17:06:01 -07:00
import * as browserPaths from '../install/browserPaths' ;
2020-06-16 17:11:19 -07:00
import { Loggers , Logger } from '../logger' ;
2020-05-20 16:30:04 -07:00
import { ConnectionTransport , WebSocketTransport } from '../transport' ;
import { BrowserBase , BrowserOptions , Browser } from '../browser' ;
2020-06-11 18:18:33 -07:00
import { assert , helper } from '../helper' ;
2020-05-22 07:03:42 -07:00
import { launchProcess , Env , waitForLine } from './processLauncher' ;
import { Events } from '../events' ;
import { PipeTransport } from './pipeTransport' ;
2020-06-04 16:43:48 -07:00
import { Progress , runAbortableTask } from '../progress' ;
2020-06-16 17:11:19 -07:00
import * as types from '../types' ;
2020-06-05 15:53:30 -07:00
import { TimeoutSettings } from '../timeoutSettings' ;
2020-06-10 16:33:27 -07:00
import { WebSocketServer } from './webSocketServer' ;
2020-06-23 14:51:06 -07:00
import { LoggerSink } from '../loggerSink' ;
2020-07-24 16:14:14 -07:00
import { validateHostRequirements } from './validateDependencies' ;
2020-01-24 14:49:47 -08:00
2020-06-25 08:30:56 -07:00
type FirefoxPrefsOptions = { firefoxUserPrefs ? : { [ key : string ] : string | number | boolean } } ;
type LaunchOptions = types . LaunchOptions & { logger? : LoggerSink } ;
type ConnectOptions = types . ConnectOptions & { logger? : LoggerSink } ;
2020-05-31 09:28:57 -07:00
2020-01-24 14:49:47 -08:00
2020-06-25 08:30:56 -07:00
export type LaunchNonPersistentOptions = LaunchOptions & FirefoxPrefsOptions ;
type LaunchPersistentOptions = LaunchOptions & types . BrowserContextOptions ;
type LaunchServerOptions = types . LaunchServerOptions & { logger? : LoggerSink } & FirefoxPrefsOptions ;
2020-05-20 16:30:04 -07:00
export interface BrowserType {
2020-01-24 14:49:47 -08:00
executablePath ( ) : string ;
2020-01-28 18:09:07 -08:00
name ( ) : string ;
2020-06-25 08:30:56 -07:00
launch ( options? : LaunchNonPersistentOptions ) : Promise < Browser > ;
launchServer ( options? : LaunchServerOptions ) : Promise < BrowserServer > ;
launchPersistentContext ( userDataDir : string , options? : LaunchPersistentOptions ) : Promise < BrowserContext > ;
2020-02-04 19:41:38 -08:00
connect ( options : ConnectOptions ) : Promise < Browser > ;
2020-01-24 14:49:47 -08:00
}
2020-04-28 17:06:01 -07:00
2020-06-08 21:45:35 -07:00
const mkdirAsync = util . promisify ( fs . mkdir ) ;
2020-05-22 07:03:42 -07:00
const mkdtempAsync = util . promisify ( fs . mkdtemp ) ;
2020-07-29 23:16:24 -07:00
const existsAsync = ( path : string ) : Promise < boolean > = > new Promise ( resolve = > fs . stat ( path , err = > resolve ( ! err ) ) ) ;
2020-05-22 16:06:00 -07:00
const DOWNLOADS_FOLDER = path . join ( os . tmpdir ( ) , 'playwright_downloads-' ) ;
2020-05-22 07:03:42 -07:00
2020-06-04 16:40:07 -07:00
type WebSocketNotPipe = { webSocketRegex : RegExp , stream : 'stdout' | 'stderr' } ;
2020-05-20 16:30:04 -07:00
export abstract class BrowserTypeBase implements BrowserType {
2020-04-28 17:06:01 -07:00
private _name : string ;
2020-04-29 17:19:21 -07:00
private _executablePath : string | undefined ;
2020-06-04 16:40:07 -07:00
private _webSocketNotPipe : WebSocketNotPipe | null ;
2020-07-15 15:24:38 -07:00
private _browserDescriptor : browserPaths.BrowserDescriptor ;
2020-05-19 14:55:11 -07:00
readonly _browserPath : string ;
2020-04-28 17:06:01 -07:00
2020-06-04 16:40:07 -07:00
constructor ( packagePath : string , browser : browserPaths.BrowserDescriptor , webSocketOrPipe : WebSocketNotPipe | null ) {
2020-04-28 17:06:01 -07:00
this . _name = browser . name ;
2020-04-29 17:19:21 -07:00
const browsersPath = browserPaths . browsersPath ( packagePath ) ;
2020-07-15 15:24:38 -07:00
this . _browserDescriptor = browser ;
2020-05-19 14:55:11 -07:00
this . _browserPath = browserPaths . browserDirectory ( browsersPath , browser ) ;
this . _executablePath = browserPaths . executablePath ( this . _browserPath , browser ) ;
2020-06-04 16:40:07 -07:00
this . _webSocketNotPipe = webSocketOrPipe ;
2020-04-28 17:06:01 -07:00
}
executablePath ( ) : string {
2020-04-29 17:19:21 -07:00
if ( ! this . _executablePath )
throw new Error ( 'Browser is not supported on current platform' ) ;
2020-04-28 17:06:01 -07:00
return this . _executablePath ;
}
name ( ) : string {
return this . _name ;
}
2020-06-25 08:30:56 -07:00
async launch ( options : LaunchNonPersistentOptions = { } ) : Promise < Browser > {
2020-05-20 16:30:04 -07:00
assert ( ! ( options as any ) . userDataDir , 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead' ) ;
2020-05-22 16:06:00 -07:00
assert ( ! ( options as any ) . port , 'Cannot specify a port without launching as a server.' ) ;
2020-06-10 20:48:54 -07:00
options = validateLaunchOptions ( options ) ;
2020-06-16 17:11:19 -07:00
const loggers = new Loggers ( options . logger ) ;
fix: re-write Chromium startup error with clear instructions (#3070)
This patch detects Chromium crash with a sandboxing error and re-writes
the error to surface information nicely.
#### Error Before:
```sh
pwuser@23592d09b3bd:~/tmp$ node a.js
(node:324) UnhandledPromiseRejectionWarning: browserType.launch: Protocol error (Browser.getVersion): Target closed.
=========================== logs ===========================
[browser] <launching> /home/pwuser/.cache/ms-playwright/chromium-790602/chrome-linux/chrome --disable-background-networking --enable-features=NetworkService,NetworkServiceInProcess --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-breakpad --disab
le-client-side-phishing-detection --disable-component-extensions-with-background-pages --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=TranslateUI,BlinkGenPropertyTrees,ImprovedCookieControls,SameSiteByDefaultCookies --disable-hang-monitor --disab
le-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --disable-sync --force-color-profile=srgb --metrics-recording-only --no-first-run --enable-automation --password-store=basic --use-mock-keychain --user-data-dir=/tmp/playwrig
ht_chromiumdev_profile-mjSfr2 --remote-debugging-pipe --headless --hide-scrollbars --mute-audio --no-startup-window
[browser] <launched> pid=401
[browser] [0722/170825.030020:FATAL:zygote_host_impl_linux.cc(117)] No usable sandbox! Update your kernel or see https://chromium.googlesource.com/chromium/src/+/master/docs/linux/suid_sandbox_development.md for more information on developing with the SUID sandbox. If you want to live
dangerously and need an immediate workaround, you can try using --no-sandbox.
[browser] #0 0x55ac4f8c7be9 base::debug::CollectStackTrace()
[browser] #1 0x55ac4f841c13 base::debug::StackTrace::StackTrace()
[browser] #2 0x55ac4f853680 logging::LogMessage::~LogMessage()
[browser] #3 0x55ac4df2307e content::ZygoteHostImpl::Init()
[browser] #4 0x55ac4f40dd47 content::ContentMainRunnerImpl::Initialize()
[browser] #5 0x55ac4f45c9fa service_manager::Main()
[browser] #6 0x55ac4f40c361 content::ContentMain()
[browser] #7 0x55ac4f45b5bd headless::(anonymous namespace)::RunContentMain()
[browser] #8 0x55ac4f45b2bc headless::HeadlessShellMain()
[browser] #9 0x55ac4ccc22e7 ChromeMain
[browser] #10 0x7f0f3d736b97 __libc_start_main
[browser] #11 0x55ac4ccc212a _start
[browser]
[browser] Received signal 6
[browser] #0 0x55ac4f8c7be9 base::debug::CollectStackTrace()
[browser] #1 0x55ac4f841c13 base::debug::StackTrace::StackTrace()
[browser] #2 0x55ac4f8c7785 base::debug::(anonymous namespace)::StackDumpSignalHandler()
[browser] #3 0x7f0f437b3890 (/lib/x86_64-linux-gnu/libpthread-2.27.so+0x1288f)
[browser] #4 0x7f0f3d753e97 gsignal
[browser] #5 0x7f0f3d755801 abort
[browser] #6 0x55ac4f8c66e5 base::debug::BreakDebugger()
[browser] #7 0x55ac4f853aeb logging::LogMessage::~LogMessage()
[browser] #8 0x55ac4df2307e content::ZygoteHostImpl::Init()
[browser] #9 0x55ac4f40dd47 content::ContentMainRunnerImpl::Initialize()
[browser] #10 0x55ac4f45c9fa service_manager::Main()
[browser] #11 0x55ac4f40c361 content::ContentMain()
[browser] #12 0x55ac4f45b5bd headless::(anonymous namespace)::RunContentMain()
[browser] #13 0x55ac4f45b2bc headless::HeadlessShellMain()
[browser] #14 0x55ac4ccc22e7 ChromeMain
[browser] #15 0x7f0f3d736b97 __libc_start_main
[browser] #16 0x55ac4ccc212a _start
[browser] r8: 0000000000000000 r9: 00007ffd38a863b0 r10: 0000000000000008 r11: 0000000000000246
[browser] r12: 00007ffd38a87680 r13: 00007ffd38a86610 r14: 00007ffd38a87690 r15: aaaaaaaaaaaaaaaa
[browser] di: 0000000000000002 si: 00007ffd38a863b0 bp: 00007ffd38a86600 bx: 00007ffd38a86e44
[browser] dx: 0000000000000000 ax: 0000000000000000 cx: 00007f0f3d753e97 sp: 00007ffd38a863b0
[browser] ip: 00007f0f3d753e97 efl: 0000000000000246 cgf: 002b000000000033 erf: 0000000000000000
[browser] trp: 0000000000000000 msk: 0000000000000000 cr2: 0000000000000000
[browser] [end of stack trace]
[browser] Calling _exit(1). Core file will not be generated.
============================================================
Note: use DEBUG=pw:api environment variable and rerun to capture Playwright logs.Error
at /home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:131:63
at new Promise (<anonymous>)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:130:16)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/helper.js:78:31)
at Function.connect (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crBrowser.js:54:39)
at Chromium._connectToTransport (/home/pwuser/tmp/node_modules/playwright/lib/server/chromium.js:52:38)
at Chromium._innerLaunch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:87:36)
at async ProgressController.run (/home/pwuser/tmp/node_modules/playwright/lib/progress.js:75:28)
at async Chromium.launch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:60:25)
at async /home/pwuser/tmp/a.js:4:19
(node:324) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise reject
ion, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:324) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
```
#### Error After:
```sh
pwuser@23592d09b3bd:~/tmp$ node a.js
(node:222) UnhandledPromiseRejectionWarning: browserType.launch: Chromium sandboxing failed!
================================
To workaround sandboxing issues, do either of the following:
- (preferred): Configure environment to support sandboxing: https://github.com/microsoft/playwright/blob/master/docs/troubleshooting.md
- (alternative): Launch Chromium without sandbox using 'chromiumSandbox: false' option
================================
Error
at /home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:131:63
at new Promise (<anonymous>)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:130:16)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/helper.js:78:31)
at Function.connect (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crBrowser.js:54:27)
at Chromium._connectToTransport (/home/pwuser/tmp/node_modules/playwright/lib/server/chromium.js:53:38)
at Chromium._innerLaunch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:89:36)
at async ProgressController.run (/home/pwuser/tmp/node_modules/playwright/lib/progress.js:75:28)
at async Chromium.launch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:61:25)
at async /home/pwuser/tmp/a.js:4:19
(node:222) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise reject
ion, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:222) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
```
References #2745
2020-07-22 10:57:58 -07:00
const label = 'browserType.launch' ;
const browser = await runAbortableTask ( progress = > this . _innerLaunch ( progress , options , loggers , undefined ) , loggers . browser , TimeoutSettings . timeout ( options ) , label ) . catch ( e = > { throw this . _rewriteStartupError ( e , label ) ; } ) ;
2020-05-29 14:39:34 -07:00
return browser ;
2020-05-20 16:30:04 -07:00
}
2020-06-25 08:30:56 -07:00
async launchPersistentContext ( userDataDir : string , options : LaunchPersistentOptions = { } ) : Promise < BrowserContext > {
2020-05-22 16:06:00 -07:00
assert ( ! ( options as any ) . port , 'Cannot specify a port without launching as a server.' ) ;
2020-06-10 20:48:54 -07:00
options = validateLaunchOptions ( options ) ;
2020-06-08 21:45:35 -07:00
const persistent = validateBrowserContextOptions ( options ) ;
2020-06-16 17:11:19 -07:00
const loggers = new Loggers ( options . logger ) ;
fix: re-write Chromium startup error with clear instructions (#3070)
This patch detects Chromium crash with a sandboxing error and re-writes
the error to surface information nicely.
#### Error Before:
```sh
pwuser@23592d09b3bd:~/tmp$ node a.js
(node:324) UnhandledPromiseRejectionWarning: browserType.launch: Protocol error (Browser.getVersion): Target closed.
=========================== logs ===========================
[browser] <launching> /home/pwuser/.cache/ms-playwright/chromium-790602/chrome-linux/chrome --disable-background-networking --enable-features=NetworkService,NetworkServiceInProcess --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-breakpad --disab
le-client-side-phishing-detection --disable-component-extensions-with-background-pages --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=TranslateUI,BlinkGenPropertyTrees,ImprovedCookieControls,SameSiteByDefaultCookies --disable-hang-monitor --disab
le-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --disable-sync --force-color-profile=srgb --metrics-recording-only --no-first-run --enable-automation --password-store=basic --use-mock-keychain --user-data-dir=/tmp/playwrig
ht_chromiumdev_profile-mjSfr2 --remote-debugging-pipe --headless --hide-scrollbars --mute-audio --no-startup-window
[browser] <launched> pid=401
[browser] [0722/170825.030020:FATAL:zygote_host_impl_linux.cc(117)] No usable sandbox! Update your kernel or see https://chromium.googlesource.com/chromium/src/+/master/docs/linux/suid_sandbox_development.md for more information on developing with the SUID sandbox. If you want to live
dangerously and need an immediate workaround, you can try using --no-sandbox.
[browser] #0 0x55ac4f8c7be9 base::debug::CollectStackTrace()
[browser] #1 0x55ac4f841c13 base::debug::StackTrace::StackTrace()
[browser] #2 0x55ac4f853680 logging::LogMessage::~LogMessage()
[browser] #3 0x55ac4df2307e content::ZygoteHostImpl::Init()
[browser] #4 0x55ac4f40dd47 content::ContentMainRunnerImpl::Initialize()
[browser] #5 0x55ac4f45c9fa service_manager::Main()
[browser] #6 0x55ac4f40c361 content::ContentMain()
[browser] #7 0x55ac4f45b5bd headless::(anonymous namespace)::RunContentMain()
[browser] #8 0x55ac4f45b2bc headless::HeadlessShellMain()
[browser] #9 0x55ac4ccc22e7 ChromeMain
[browser] #10 0x7f0f3d736b97 __libc_start_main
[browser] #11 0x55ac4ccc212a _start
[browser]
[browser] Received signal 6
[browser] #0 0x55ac4f8c7be9 base::debug::CollectStackTrace()
[browser] #1 0x55ac4f841c13 base::debug::StackTrace::StackTrace()
[browser] #2 0x55ac4f8c7785 base::debug::(anonymous namespace)::StackDumpSignalHandler()
[browser] #3 0x7f0f437b3890 (/lib/x86_64-linux-gnu/libpthread-2.27.so+0x1288f)
[browser] #4 0x7f0f3d753e97 gsignal
[browser] #5 0x7f0f3d755801 abort
[browser] #6 0x55ac4f8c66e5 base::debug::BreakDebugger()
[browser] #7 0x55ac4f853aeb logging::LogMessage::~LogMessage()
[browser] #8 0x55ac4df2307e content::ZygoteHostImpl::Init()
[browser] #9 0x55ac4f40dd47 content::ContentMainRunnerImpl::Initialize()
[browser] #10 0x55ac4f45c9fa service_manager::Main()
[browser] #11 0x55ac4f40c361 content::ContentMain()
[browser] #12 0x55ac4f45b5bd headless::(anonymous namespace)::RunContentMain()
[browser] #13 0x55ac4f45b2bc headless::HeadlessShellMain()
[browser] #14 0x55ac4ccc22e7 ChromeMain
[browser] #15 0x7f0f3d736b97 __libc_start_main
[browser] #16 0x55ac4ccc212a _start
[browser] r8: 0000000000000000 r9: 00007ffd38a863b0 r10: 0000000000000008 r11: 0000000000000246
[browser] r12: 00007ffd38a87680 r13: 00007ffd38a86610 r14: 00007ffd38a87690 r15: aaaaaaaaaaaaaaaa
[browser] di: 0000000000000002 si: 00007ffd38a863b0 bp: 00007ffd38a86600 bx: 00007ffd38a86e44
[browser] dx: 0000000000000000 ax: 0000000000000000 cx: 00007f0f3d753e97 sp: 00007ffd38a863b0
[browser] ip: 00007f0f3d753e97 efl: 0000000000000246 cgf: 002b000000000033 erf: 0000000000000000
[browser] trp: 0000000000000000 msk: 0000000000000000 cr2: 0000000000000000
[browser] [end of stack trace]
[browser] Calling _exit(1). Core file will not be generated.
============================================================
Note: use DEBUG=pw:api environment variable and rerun to capture Playwright logs.Error
at /home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:131:63
at new Promise (<anonymous>)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:130:16)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/helper.js:78:31)
at Function.connect (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crBrowser.js:54:39)
at Chromium._connectToTransport (/home/pwuser/tmp/node_modules/playwright/lib/server/chromium.js:52:38)
at Chromium._innerLaunch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:87:36)
at async ProgressController.run (/home/pwuser/tmp/node_modules/playwright/lib/progress.js:75:28)
at async Chromium.launch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:60:25)
at async /home/pwuser/tmp/a.js:4:19
(node:324) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise reject
ion, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:324) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
```
#### Error After:
```sh
pwuser@23592d09b3bd:~/tmp$ node a.js
(node:222) UnhandledPromiseRejectionWarning: browserType.launch: Chromium sandboxing failed!
================================
To workaround sandboxing issues, do either of the following:
- (preferred): Configure environment to support sandboxing: https://github.com/microsoft/playwright/blob/master/docs/troubleshooting.md
- (alternative): Launch Chromium without sandbox using 'chromiumSandbox: false' option
================================
Error
at /home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:131:63
at new Promise (<anonymous>)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:130:16)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/helper.js:78:31)
at Function.connect (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crBrowser.js:54:27)
at Chromium._connectToTransport (/home/pwuser/tmp/node_modules/playwright/lib/server/chromium.js:53:38)
at Chromium._innerLaunch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:89:36)
at async ProgressController.run (/home/pwuser/tmp/node_modules/playwright/lib/progress.js:75:28)
at async Chromium.launch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:61:25)
at async /home/pwuser/tmp/a.js:4:19
(node:222) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise reject
ion, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:222) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
```
References #2745
2020-07-22 10:57:58 -07:00
const label = 'browserType.launchPersistentContext' ;
const browser = await runAbortableTask ( progress = > this . _innerLaunch ( progress , options , loggers , persistent , userDataDir ) , loggers . browser , TimeoutSettings . timeout ( options ) , label ) . catch ( e = > { throw this . _rewriteStartupError ( e , label ) ; } ) ;
2020-05-29 14:39:34 -07:00
return browser . _defaultContext ! ;
2020-05-21 09:43:10 -07:00
}
2020-05-20 16:30:04 -07:00
2020-06-25 08:30:56 -07:00
async _innerLaunch ( progress : Progress , options : LaunchOptions , logger : Loggers , persistent : types.BrowserContextOptions | undefined , userDataDir? : string ) : Promise < BrowserBase > {
2020-06-05 13:50:15 -07:00
options . proxy = options . proxy ? verifyProxySettings ( options . proxy ) : undefined ;
2020-05-29 14:39:34 -07:00
const { browserServer , downloadsPath , transport } = await this . _launchServer ( progress , options , ! ! persistent , logger , userDataDir ) ;
if ( ( options as any ) . __testHookBeforeCreateBrowser )
await ( options as any ) . __testHookBeforeCreateBrowser ( ) ;
const browserOptions : BrowserOptions = {
2020-07-08 21:36:03 -07:00
name : this._name ,
2020-05-29 14:39:34 -07:00
slowMo : options.slowMo ,
persistent ,
2020-06-10 20:48:54 -07:00
headful : ! options . headless ,
2020-06-16 17:11:19 -07:00
loggers : logger ,
2020-05-29 14:39:34 -07:00
downloadsPath ,
ownedServer : browserServer ,
2020-06-05 13:50:15 -07:00
proxy : options.proxy ,
2020-05-29 14:39:34 -07:00
} ;
copyTestHooks ( options , browserOptions ) ;
2020-05-22 16:06:00 -07:00
const browser = await this . _connectToTransport ( transport , browserOptions ) ;
// We assume no control when using custom arguments, and do not prepare the default context in that case.
2020-05-29 14:39:34 -07:00
const hasCustomArguments = ! ! options . ignoreDefaultArgs && ! Array . isArray ( options . ignoreDefaultArgs ) ;
if ( persistent && ! hasCustomArguments )
2020-05-22 16:06:00 -07:00
await browser . _defaultContext ! . _loadDefaultContext ( ) ;
2020-05-21 09:43:10 -07:00
return browser ;
2020-05-20 16:30:04 -07:00
}
async launchServer ( options : LaunchServerOptions = { } ) : Promise < BrowserServer > {
2020-05-22 16:06:00 -07:00
assert ( ! ( options as any ) . userDataDir , 'userDataDir option is not supported in `browserType.launchServer`. Use `browserType.launchPersistentContext` instead' ) ;
2020-06-10 20:48:54 -07:00
options = validateLaunchOptions ( options ) ;
2020-06-16 17:11:19 -07:00
const loggers = new Loggers ( options . logger ) ;
2020-06-10 20:48:54 -07:00
const { port = 0 } = options ;
2020-06-04 16:43:48 -07:00
return runAbortableTask ( async progress = > {
2020-06-16 17:11:19 -07:00
const { browserServer , transport } = await this . _launchServer ( progress , options , false , loggers ) ;
browserServer . _webSocketServer = this . _startWebSocketServer ( transport , loggers . browser , port ) ;
2020-05-29 14:39:34 -07:00
return browserServer ;
2020-06-16 17:11:19 -07:00
} , loggers . browser , TimeoutSettings . timeout ( options ) , 'browserType.launchServer' ) ;
2020-05-20 16:30:04 -07:00
}
async connect ( options : ConnectOptions ) : Promise < Browser > {
2020-06-16 17:11:19 -07:00
const loggers = new Loggers ( options . logger ) ;
2020-06-04 16:43:48 -07:00
return runAbortableTask ( async progress = > {
2020-05-29 14:39:34 -07:00
const transport = await WebSocketTransport . connect ( progress , options . wsEndpoint ) ;
2020-06-04 16:43:48 -07:00
progress . cleanupWhenAborted ( ( ) = > transport . closeAndWait ( ) ) ;
2020-05-29 14:39:34 -07:00
if ( ( options as any ) . __testHookBeforeCreateBrowser )
await ( options as any ) . __testHookBeforeCreateBrowser ( ) ;
2020-07-08 21:36:03 -07:00
const browser = await this . _connectToTransport ( transport , { name : this._name , slowMo : options.slowMo , loggers } ) ;
2020-05-21 09:43:10 -07:00
return browser ;
2020-06-16 17:11:19 -07:00
} , loggers . browser , TimeoutSettings . timeout ( options ) , 'browserType.connect' ) ;
2020-05-21 09:43:10 -07:00
}
2020-06-16 17:11:19 -07:00
private async _launchServer ( progress : Progress , options : LaunchServerOptions , isPersistent : boolean , loggers : Loggers , userDataDir? : string ) : Promise < { browserServer : BrowserServer , downloadsPath : string , transport : ConnectionTransport } > {
2020-05-22 07:03:42 -07:00
const {
ignoreDefaultArgs = false ,
args = [ ] ,
executablePath = null ,
env = process . env ,
handleSIGINT = true ,
handleSIGTERM = true ,
handleSIGHUP = true ,
} = options ;
2020-06-08 21:45:35 -07:00
const tempDirectories = [ ] ;
let downloadsPath : string ;
if ( options . downloadsPath ) {
downloadsPath = options . downloadsPath ;
await mkdirAsync ( options . downloadsPath , { recursive : true } ) ;
} else {
downloadsPath = await mkdtempAsync ( DOWNLOADS_FOLDER ) ;
tempDirectories . push ( downloadsPath ) ;
}
2020-05-22 07:03:42 -07:00
if ( ! userDataDir ) {
userDataDir = await mkdtempAsync ( path . join ( os . tmpdir ( ) , ` playwright_ ${ this . _name } dev_profile- ` ) ) ;
2020-05-22 16:06:00 -07:00
tempDirectories . push ( userDataDir ) ;
2020-05-22 07:03:42 -07:00
}
const browserArguments = [ ] ;
if ( ! ignoreDefaultArgs )
2020-05-22 16:06:00 -07:00
browserArguments . push ( . . . this . _defaultArgs ( options , isPersistent , userDataDir ) ) ;
2020-05-22 07:03:42 -07:00
else if ( Array . isArray ( ignoreDefaultArgs ) )
2020-05-22 16:06:00 -07:00
browserArguments . push ( . . . this . _defaultArgs ( options , isPersistent , userDataDir ) . filter ( arg = > ignoreDefaultArgs . indexOf ( arg ) === - 1 ) ) ;
2020-05-22 07:03:42 -07:00
else
browserArguments . push ( . . . args ) ;
const executable = executablePath || this . executablePath ( ) ;
if ( ! executable )
throw new Error ( ` No executable path is specified. Pass "executablePath" option directly. ` ) ;
2020-07-29 23:16:24 -07:00
if ( ! ( await existsAsync ( executable ) ) ) {
const errorMessageLines = [ ` Failed to launch ${ this . _name } because executable doesn't exist at ${ executable } ` ] ;
// If we tried using stock downloaded browser, suggest re-installing playwright.
if ( ! executablePath )
errorMessageLines . push ( ` Try re-installing playwright with "npm install playwright" ` ) ;
throw new Error ( errorMessageLines . join ( '\n' ) ) ;
}
2020-05-22 07:03:42 -07:00
2020-07-15 15:24:38 -07:00
if ( ! executablePath ) {
// We can only validate dependencies for bundled browsers.
2020-07-24 16:14:14 -07:00
await validateHostRequirements ( this . _browserPath , this . _browserDescriptor ) ;
2020-07-15 15:24:38 -07:00
}
2020-05-22 07:03:42 -07:00
// Note: it is important to define these variables before launchProcess, so that we don't get
// "Cannot access 'browserServer' before initialization" if something went wrong.
let transport : ConnectionTransport | undefined = undefined ;
let browserServer : BrowserServer | undefined = undefined ;
2020-05-27 19:59:03 -07:00
const { launchedProcess , gracefullyClose , kill } = await launchProcess ( {
2020-05-22 07:03:42 -07:00
executablePath : executable ,
2020-07-21 13:21:42 -07:00
args : this._amendArguments ( browserArguments ) ,
2020-05-22 07:03:42 -07:00
env : this._amendEnvironment ( env , userDataDir , executable , browserArguments ) ,
handleSIGINT ,
handleSIGTERM ,
handleSIGHUP ,
2020-05-29 14:39:34 -07:00
progress ,
2020-06-04 16:40:07 -07:00
pipe : ! this . _webSocketNotPipe ,
2020-05-22 16:06:00 -07:00
tempDirectories ,
2020-05-22 07:03:42 -07:00
attemptToGracefullyClose : async ( ) = > {
if ( ( options as any ) . __testHookGracefullyClose )
await ( options as any ) . __testHookGracefullyClose ( ) ;
// We try to gracefully close to prevent crash reporting and core dumps.
// Note that it's fine to reuse the pipe transport, since
// our connection ignores kBrowserCloseMessageId.
this . _attemptToGracefullyCloseBrowser ( transport ! ) ;
} ,
2020-05-27 19:59:03 -07:00
onExit : ( exitCode , signal ) = > {
2020-05-22 07:03:42 -07:00
if ( browserServer )
browserServer . emit ( Events . BrowserServer . Close , exitCode , signal ) ;
} ,
} ) ;
2020-05-27 19:59:03 -07:00
browserServer = new BrowserServer ( launchedProcess , gracefullyClose , kill ) ;
2020-06-05 15:53:30 -07:00
progress . cleanupWhenAborted ( ( ) = > browserServer && browserServer . _closeOrKill ( progress . timeUntilDeadline ( ) ) ) ;
2020-05-29 14:39:34 -07:00
2020-06-04 16:40:07 -07:00
if ( this . _webSocketNotPipe ) {
const match = await waitForLine ( progress , launchedProcess , this . _webSocketNotPipe . stream === 'stdout' ? launchedProcess.stdout : launchedProcess.stderr , this . _webSocketNotPipe . webSocketRegex ) ;
2020-05-29 14:39:34 -07:00
const innerEndpoint = match [ 1 ] ;
transport = await WebSocketTransport . connect ( progress , innerEndpoint ) ;
} else {
const stdio = launchedProcess . stdio as unknown as [ NodeJS . ReadableStream , NodeJS . WritableStream , NodeJS . WritableStream , NodeJS . WritableStream , NodeJS . ReadableStream ] ;
2020-06-16 17:11:19 -07:00
transport = new PipeTransport ( stdio [ 3 ] , stdio [ 4 ] , loggers . browser ) ;
2020-05-29 14:39:34 -07:00
}
2020-05-22 16:06:00 -07:00
return { browserServer , downloadsPath , transport } ;
2020-05-22 07:03:42 -07:00
}
2020-06-25 08:30:56 -07:00
abstract _defaultArgs ( options : types.LaunchOptionsBase , isPersistent : boolean , userDataDir : string ) : string [ ] ;
2020-05-20 16:30:04 -07:00
abstract _connectToTransport ( transport : ConnectionTransport , options : BrowserOptions ) : Promise < BrowserBase > ;
2020-06-16 17:11:19 -07:00
abstract _startWebSocketServer ( transport : ConnectionTransport , logger : Logger , port : number ) : WebSocketServer ;
2020-05-22 07:03:42 -07:00
abstract _amendEnvironment ( env : Env , userDataDir : string , executable : string , browserArguments : string [ ] ) : Env ;
2020-07-21 13:21:42 -07:00
abstract _amendArguments ( browserArguments : string [ ] ) : string [ ] ;
fix: re-write Chromium startup error with clear instructions (#3070)
This patch detects Chromium crash with a sandboxing error and re-writes
the error to surface information nicely.
#### Error Before:
```sh
pwuser@23592d09b3bd:~/tmp$ node a.js
(node:324) UnhandledPromiseRejectionWarning: browserType.launch: Protocol error (Browser.getVersion): Target closed.
=========================== logs ===========================
[browser] <launching> /home/pwuser/.cache/ms-playwright/chromium-790602/chrome-linux/chrome --disable-background-networking --enable-features=NetworkService,NetworkServiceInProcess --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-breakpad --disab
le-client-side-phishing-detection --disable-component-extensions-with-background-pages --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=TranslateUI,BlinkGenPropertyTrees,ImprovedCookieControls,SameSiteByDefaultCookies --disable-hang-monitor --disab
le-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --disable-sync --force-color-profile=srgb --metrics-recording-only --no-first-run --enable-automation --password-store=basic --use-mock-keychain --user-data-dir=/tmp/playwrig
ht_chromiumdev_profile-mjSfr2 --remote-debugging-pipe --headless --hide-scrollbars --mute-audio --no-startup-window
[browser] <launched> pid=401
[browser] [0722/170825.030020:FATAL:zygote_host_impl_linux.cc(117)] No usable sandbox! Update your kernel or see https://chromium.googlesource.com/chromium/src/+/master/docs/linux/suid_sandbox_development.md for more information on developing with the SUID sandbox. If you want to live
dangerously and need an immediate workaround, you can try using --no-sandbox.
[browser] #0 0x55ac4f8c7be9 base::debug::CollectStackTrace()
[browser] #1 0x55ac4f841c13 base::debug::StackTrace::StackTrace()
[browser] #2 0x55ac4f853680 logging::LogMessage::~LogMessage()
[browser] #3 0x55ac4df2307e content::ZygoteHostImpl::Init()
[browser] #4 0x55ac4f40dd47 content::ContentMainRunnerImpl::Initialize()
[browser] #5 0x55ac4f45c9fa service_manager::Main()
[browser] #6 0x55ac4f40c361 content::ContentMain()
[browser] #7 0x55ac4f45b5bd headless::(anonymous namespace)::RunContentMain()
[browser] #8 0x55ac4f45b2bc headless::HeadlessShellMain()
[browser] #9 0x55ac4ccc22e7 ChromeMain
[browser] #10 0x7f0f3d736b97 __libc_start_main
[browser] #11 0x55ac4ccc212a _start
[browser]
[browser] Received signal 6
[browser] #0 0x55ac4f8c7be9 base::debug::CollectStackTrace()
[browser] #1 0x55ac4f841c13 base::debug::StackTrace::StackTrace()
[browser] #2 0x55ac4f8c7785 base::debug::(anonymous namespace)::StackDumpSignalHandler()
[browser] #3 0x7f0f437b3890 (/lib/x86_64-linux-gnu/libpthread-2.27.so+0x1288f)
[browser] #4 0x7f0f3d753e97 gsignal
[browser] #5 0x7f0f3d755801 abort
[browser] #6 0x55ac4f8c66e5 base::debug::BreakDebugger()
[browser] #7 0x55ac4f853aeb logging::LogMessage::~LogMessage()
[browser] #8 0x55ac4df2307e content::ZygoteHostImpl::Init()
[browser] #9 0x55ac4f40dd47 content::ContentMainRunnerImpl::Initialize()
[browser] #10 0x55ac4f45c9fa service_manager::Main()
[browser] #11 0x55ac4f40c361 content::ContentMain()
[browser] #12 0x55ac4f45b5bd headless::(anonymous namespace)::RunContentMain()
[browser] #13 0x55ac4f45b2bc headless::HeadlessShellMain()
[browser] #14 0x55ac4ccc22e7 ChromeMain
[browser] #15 0x7f0f3d736b97 __libc_start_main
[browser] #16 0x55ac4ccc212a _start
[browser] r8: 0000000000000000 r9: 00007ffd38a863b0 r10: 0000000000000008 r11: 0000000000000246
[browser] r12: 00007ffd38a87680 r13: 00007ffd38a86610 r14: 00007ffd38a87690 r15: aaaaaaaaaaaaaaaa
[browser] di: 0000000000000002 si: 00007ffd38a863b0 bp: 00007ffd38a86600 bx: 00007ffd38a86e44
[browser] dx: 0000000000000000 ax: 0000000000000000 cx: 00007f0f3d753e97 sp: 00007ffd38a863b0
[browser] ip: 00007f0f3d753e97 efl: 0000000000000246 cgf: 002b000000000033 erf: 0000000000000000
[browser] trp: 0000000000000000 msk: 0000000000000000 cr2: 0000000000000000
[browser] [end of stack trace]
[browser] Calling _exit(1). Core file will not be generated.
============================================================
Note: use DEBUG=pw:api environment variable and rerun to capture Playwright logs.Error
at /home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:131:63
at new Promise (<anonymous>)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:130:16)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/helper.js:78:31)
at Function.connect (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crBrowser.js:54:39)
at Chromium._connectToTransport (/home/pwuser/tmp/node_modules/playwright/lib/server/chromium.js:52:38)
at Chromium._innerLaunch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:87:36)
at async ProgressController.run (/home/pwuser/tmp/node_modules/playwright/lib/progress.js:75:28)
at async Chromium.launch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:60:25)
at async /home/pwuser/tmp/a.js:4:19
(node:324) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise reject
ion, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:324) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
```
#### Error After:
```sh
pwuser@23592d09b3bd:~/tmp$ node a.js
(node:222) UnhandledPromiseRejectionWarning: browserType.launch: Chromium sandboxing failed!
================================
To workaround sandboxing issues, do either of the following:
- (preferred): Configure environment to support sandboxing: https://github.com/microsoft/playwright/blob/master/docs/troubleshooting.md
- (alternative): Launch Chromium without sandbox using 'chromiumSandbox: false' option
================================
Error
at /home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:131:63
at new Promise (<anonymous>)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:130:16)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/helper.js:78:31)
at Function.connect (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crBrowser.js:54:27)
at Chromium._connectToTransport (/home/pwuser/tmp/node_modules/playwright/lib/server/chromium.js:53:38)
at Chromium._innerLaunch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:89:36)
at async ProgressController.run (/home/pwuser/tmp/node_modules/playwright/lib/progress.js:75:28)
at async Chromium.launch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:61:25)
at async /home/pwuser/tmp/a.js:4:19
(node:222) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise reject
ion, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:222) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
```
References #2745
2020-07-22 10:57:58 -07:00
abstract _rewriteStartupError ( error : Error , prefix : string ) : Error ;
2020-05-22 07:03:42 -07:00
abstract _attemptToGracefullyCloseBrowser ( transport : ConnectionTransport ) : void ;
}
2020-05-22 16:06:00 -07:00
function copyTestHooks ( from : object , to : object ) {
for ( const [ key , value ] of Object . entries ( from ) ) {
if ( key . startsWith ( '__testHook' ) )
( to as any ) [ key ] = value ;
}
}
2020-06-10 20:48:54 -07:00
2020-06-25 08:30:56 -07:00
function validateLaunchOptions < Options extends types.LaunchOptionsBase > ( options : Options ) : Options {
2020-06-11 18:18:33 -07:00
const { devtools = false , headless = ! helper . isDebugMode ( ) && ! devtools } = options ;
2020-06-10 20:48:54 -07:00
return { . . . options , devtools , headless } ;
}