2020-12-22 23:54:13 +01:00
# ! / u s r / b i n / e n v n o d e
/ * *
* 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 .
* /
2022-02-26 00:43:58 +01:00
2022-03-01 21:56:26 +01:00
/* eslint-disable no-console */
2020-12-22 23:54:13 +01:00
2022-03-01 21:56:26 +01:00
import fs from 'fs' ;
import os from 'os' ;
import path from 'path' ;
2022-04-19 02:50:25 +02:00
import type { Command } from '../utilsBundle' ;
import { program } from '../utilsBundle' ;
2022-03-01 21:56:26 +01:00
import { runDriver , runServer , printApiJson , launchBrowserServer } from './driver' ;
import { showTraceViewer } from '../server/trace/viewer/traceViewer' ;
import * as playwright from '../..' ;
2022-04-06 23:57:14 +02:00
import type { BrowserContext } from '../client/browserContext' ;
import type { Browser } from '../client/browser' ;
import type { Page } from '../client/page' ;
import type { BrowserType } from '../client/browserType' ;
import type { BrowserContextOptions , LaunchOptions } from '../client/types' ;
2022-03-01 21:56:26 +01:00
import { spawn } from 'child_process' ;
2022-04-12 19:10:34 +02:00
import { wrapInASCIIBox , isLikelyNpxGlobal , assert } from '../utils' ;
2022-03-01 21:56:26 +01:00
import { launchGridAgent } from '../grid/gridAgent' ;
2022-04-06 23:57:14 +02:00
import type { GridFactory } from '../grid/gridServer' ;
import { GridServer } from '../grid/gridServer' ;
2022-04-07 07:21:27 +02:00
import type { Executable } from '../server' ;
import { registry , writeDockerVersion } from '../server' ;
2022-03-01 21:56:26 +01:00
const packageJSON = require ( '../../package.json' ) ;
program
. version ( 'Version ' + ( process . env . PW_CLI_DISPLAY_VERSION || packageJSON . version ) )
. name ( buildBasePlaywrightCLICommand ( process . env . PW_LANG_NAME ) ) ;
2022-03-25 22:45:53 +01:00
program
2022-04-12 19:10:34 +02:00
. command ( 'mark-docker-image [dockerImageNameTemplate]' , { hidden : true } )
2022-03-25 22:45:53 +01:00
. description ( 'mark docker image' )
. allowUnknownOption ( true )
. action ( function ( dockerImageNameTemplate ) {
2022-04-12 19:10:34 +02:00
assert ( dockerImageNameTemplate , 'dockerImageNameTemplate is required' ) ;
writeDockerVersion ( dockerImageNameTemplate ) . catch ( logErrorAndExit ) ;
2022-03-25 22:45:53 +01:00
} ) ;
2022-03-01 21:56:26 +01:00
commandWithOpenOptions ( 'open [url]' , 'open page in browser specified via -b, --browser' , [ ] )
. action ( function ( url , options ) {
2022-10-25 18:55:20 +02:00
open ( options , url , codegenId ( ) ) . catch ( logErrorAndExit ) ;
2022-03-01 21:56:26 +01:00
} )
. addHelpText ( 'afterAll' , `
Examples :
2022-07-25 18:16:20 +02:00
$ open
$ open - b webkit https : //example.com`);
2022-03-01 21:56:26 +01:00
commandWithOpenOptions ( 'codegen [url]' , 'open page and generate code for user actions' ,
[
[ '-o, --output <file name>' , 'saves the generated script to a file' ] ,
2022-10-25 18:55:20 +02:00
[ '--target <language>' , ` language to generate, one of javascript, playwright-test, python, python-async, python-pytest, csharp, csharp-mstest, csharp-nunit, java ` , codegenId ( ) ] ,
2022-07-25 18:16:20 +02:00
[ '--save-trace <filename>' , 'record a trace for the session and save it to a file' ] ,
2022-03-01 21:56:26 +01:00
] ) . action ( function ( url , options ) {
codegen ( options , url , options . target , options . output ) . catch ( logErrorAndExit ) ;
} ) . addHelpText ( 'afterAll' , `
Examples :
$ codegen
$ codegen -- target = python
$ codegen - b webkit https : //example.com`);
program
. command ( 'debug <app> [args...]' , { hidden : true } )
. description ( 'run command in debug mode: disable timeout, open inspector' )
. allowUnknownOption ( true )
. action ( function ( app , options ) {
spawn ( app , options , {
env : { . . . process . env , PWDEBUG : '1' } ,
stdio : 'inherit'
} ) ;
} ) . addHelpText ( 'afterAll' , `
Examples :
$ debug node test . js
$ debug npm run test ` );
function suggestedBrowsersToInstall() {
return registry . executables ( ) . filter ( e = > e . installType !== 'none' && e . type !== 'tool' ) . map ( e = > e . name ) . join ( ', ' ) ;
}
function checkBrowsersToInstall ( args : string [ ] ) : Executable [ ] {
const faultyArguments : string [ ] = [ ] ;
const executables : Executable [ ] = [ ] ;
for ( const arg of args ) {
const executable = registry . findExecutable ( arg ) ;
if ( ! executable || executable . installType === 'none' )
faultyArguments . push ( arg ) ;
else
executables . push ( executable ) ;
}
if ( faultyArguments . length ) {
console . log ( ` Invalid installation targets: ${ faultyArguments . map ( name = > ` ' ${ name } ' ` ) . join ( ', ' ) } . Expecting one of: ${ suggestedBrowsersToInstall ( ) } ` ) ;
process . exit ( 1 ) ;
}
return executables ;
}
2022-09-21 21:45:43 +02:00
2022-03-01 21:56:26 +01:00
program
. command ( 'install [browser...]' )
. description ( 'ensure browsers necessary for this version of Playwright are installed' )
. option ( '--with-deps' , 'install system dependencies for browsers' )
2022-09-08 18:05:09 +02:00
. option ( '--dry-run' , 'do not execute installation, only print information' )
2022-04-01 19:05:53 +02:00
. option ( '--force' , 'force reinstall of stable browser channels' )
2022-09-08 18:05:09 +02:00
. action ( async function ( args : string [ ] , options : { withDeps? : boolean , force? : boolean , dryRun? : boolean } ) {
2022-04-08 19:46:24 +02:00
if ( isLikelyNpxGlobal ( ) ) {
2022-04-04 05:26:23 +02:00
console . error ( wrapInASCIIBox ( [
` WARNING: It looks like you are running 'npx playwright install' without first ` ,
` installing your project's dependencies. ` ,
` ` ,
` To avoid unexpected behavior, please install your dependencies first, and ` ,
` then run Playwright's install command: ` ,
` ` ,
` npm install ` ,
` npx playwright install ` ,
` ` ,
` If your project does not yet depend on Playwright, first install the ` ,
` applicable npm package (most commonly @playwright/test), and ` ,
` then run Playwright's install command to download the browsers: ` ,
` ` ,
` npm install @playwright/test ` ,
` npx playwright install ` ,
` ` ,
] . join ( '\n' ) , 1 ) ) ;
}
2022-03-01 21:56:26 +01:00
try {
2022-09-08 18:05:09 +02:00
const hasNoArguments = ! args . length ;
const executables = hasNoArguments ? registry . defaultExecutables ( ) : checkBrowsersToInstall ( args ) ;
if ( options . withDeps )
await registry . installDeps ( executables , ! ! options . dryRun ) ;
if ( options . dryRun ) {
for ( const executable of executables ) {
const version = executable . browserVersion ? ` version ` + executable . browserVersion : '' ;
console . log ( ` browser: ${ executable . name } ${ version ? ' ' + version : '' } ` ) ;
console . log ( ` Install location: ${ executable . directory ? ? '<system>' } ` ) ;
if ( executable . downloadURLs ? . length ) {
const [ url , . . . fallbacks ] = executable . downloadURLs ;
console . log ( ` Download url: ${ url } ` ) ;
for ( let i = 0 ; i < fallbacks . length ; ++ i )
console . log ( ` Download fallback ${ i + 1 } : ${ fallbacks [ i ] } ` ) ;
2022-03-01 21:56:26 +01:00
}
2022-09-08 18:05:09 +02:00
console . log ( ` ` ) ;
2022-03-01 21:56:26 +01:00
}
2022-09-08 18:05:09 +02:00
} else {
const forceReinstall = hasNoArguments ? false : ! ! options . force ;
await registry . install ( executables , forceReinstall ) ;
2022-03-01 21:56:26 +01:00
}
} catch ( e ) {
console . log ( ` Failed to install browsers \ n ${ e } ` ) ;
process . exit ( 1 ) ;
}
} ) . addHelpText ( 'afterAll' , `
Examples :
- $ install
Install default browsers .
- $ install chrome firefox
Install custom browsers , supports $ { suggestedBrowsersToInstall ( ) } . ` );
program
. command ( 'install-deps [browser...]' )
. description ( 'install dependencies necessary to run browsers (will ask for sudo permissions)' )
. option ( '--dry-run' , 'Do not execute installation commands, only print them' )
. action ( async function ( args : string [ ] , options : { dryRun? : boolean } ) {
try {
if ( ! args . length )
await registry . installDeps ( registry . defaultExecutables ( ) , ! ! options . dryRun ) ;
else
await registry . installDeps ( checkBrowsersToInstall ( args ) , ! ! options . dryRun ) ;
} catch ( e ) {
console . log ( ` Failed to install browser dependencies \ n ${ e } ` ) ;
process . exit ( 1 ) ;
}
} ) . addHelpText ( 'afterAll' , `
Examples :
- $ install - deps
Install dependencies for default browsers .
- $ install - deps chrome firefox
Install dependencies for specific browsers , supports $ { suggestedBrowsersToInstall ( ) } . ` );
const browsers = [
{ alias : 'cr' , name : 'Chromium' , type : 'chromium' } ,
{ alias : 'ff' , name : 'Firefox' , type : 'firefox' } ,
{ alias : 'wk' , name : 'WebKit' , type : 'webkit' } ,
] ;
for ( const { alias , name , type } of browsers ) {
commandWithOpenOptions ( ` ${ alias } [url] ` , ` open page in ${ name } ` , [ ] )
. action ( function ( url , options ) {
open ( { . . . options , browser : type } , url , options . target ) . catch ( logErrorAndExit ) ;
} ) . addHelpText ( 'afterAll' , `
Examples :
$ $ { alias } https : //example.com`);
}
commandWithOpenOptions ( 'screenshot <url> <filename>' , 'capture a page screenshot' ,
[
[ '--wait-for-selector <selector>' , 'wait for selector before taking a screenshot' ] ,
[ '--wait-for-timeout <timeout>' , 'wait for timeout in milliseconds before taking a screenshot' ] ,
[ '--full-page' , 'whether to take a full page screenshot (entire scrollable area)' ] ,
] ) . action ( function ( url , filename , command ) {
screenshot ( command , command , url , filename ) . catch ( logErrorAndExit ) ;
} ) . addHelpText ( 'afterAll' , `
Examples :
$ screenshot - b webkit https : //example.com example.png`);
commandWithOpenOptions ( 'pdf <url> <filename>' , 'save page as pdf' ,
[
[ '--wait-for-selector <selector>' , 'wait for given selector before saving as pdf' ] ,
[ '--wait-for-timeout <timeout>' , 'wait for given timeout in milliseconds before saving as pdf' ] ,
] ) . action ( function ( url , filename , options ) {
pdf ( options , options , url , filename ) . catch ( logErrorAndExit ) ;
} ) . addHelpText ( 'afterAll' , `
Examples :
$ pdf https : //example.com example.pdf`);
program
. command ( 'experimental-grid-server' , { hidden : true } )
. option ( '--port <port>' , 'grid port; defaults to 3333' )
2022-04-08 02:22:52 +02:00
. option ( '--address <address>' , 'address of the server' )
2022-03-01 21:56:26 +01:00
. option ( '--agent-factory <factory>' , 'path to grid agent factory or npm package' )
. option ( '--auth-token <authToken>' , 'optional authentication token' )
. action ( function ( options ) {
2022-04-08 02:22:52 +02:00
launchGridServer ( options . agentFactory , options . port || 3333 , options . address , options . authToken ) ;
2022-03-01 21:56:26 +01:00
} ) ;
program
. command ( 'experimental-grid-agent' , { hidden : true } )
. requiredOption ( '--agent-id <agentId>' , 'agent ID' )
. requiredOption ( '--grid-url <gridURL>' , 'grid URL' )
2022-04-13 21:13:35 +02:00
. option ( '--run-id <github run_id>' , 'Workflow run_id' )
2022-03-01 21:56:26 +01:00
. action ( function ( options ) {
2022-04-13 21:13:35 +02:00
launchGridAgent ( options . agentId , options . gridUrl , options . runId ) ;
2022-03-01 21:56:26 +01:00
} ) ;
program
. command ( 'run-driver' , { hidden : true } )
. action ( function ( options ) {
runDriver ( ) ;
} ) ;
program
. command ( 'run-server' , { hidden : true } )
. option ( '--port <port>' , 'Server port' )
2022-03-07 18:15:46 +01:00
. option ( '--path <path>' , 'Endpoint Path' , '/' )
. option ( '--max-clients <maxClients>' , 'Maximum clients' )
2022-03-01 21:56:26 +01:00
. action ( function ( options ) {
2022-11-03 21:47:51 +01:00
runServer ( {
port : options.port ? + options.port : undefined ,
path : options.path ,
maxConnections : options.maxClients ? + options.maxClients : Infinity ,
} ) . catch ( logErrorAndExit ) ;
2022-03-01 21:56:26 +01:00
} ) ;
program
. command ( 'print-api-json' , { hidden : true } )
. action ( function ( options ) {
printApiJson ( ) ;
} ) ;
program
. command ( 'launch-server' , { hidden : true } )
. requiredOption ( '--browser <browserName>' , 'Browser name, one of "chromium", "firefox" or "webkit"' )
. option ( '--config <path-to-config-file>' , 'JSON file with launchServer options' )
. action ( function ( options ) {
2022-03-03 17:17:23 +01:00
launchBrowserServer ( options . browser , options . config ) ;
2022-03-01 21:56:26 +01:00
} ) ;
program
. command ( 'show-trace [trace...]' )
. option ( '-b, --browser <browserType>' , 'browser to use, one of cr, chromium, ff, firefox, wk, webkit' , 'chromium' )
2023-01-27 23:20:25 +01:00
. option ( '-h, --host <host>' , 'Host to serve trace on' , 'localhost' )
. option ( '-p, --port <port>' , 'Port to serve trace on' , '9322' )
2022-09-10 00:25:42 +02:00
. description ( 'show trace viewer' )
2022-03-01 21:56:26 +01:00
. action ( function ( traces , options ) {
if ( options . browser === 'cr' )
options . browser = 'chromium' ;
if ( options . browser === 'ff' )
options . browser = 'firefox' ;
if ( options . browser === 'wk' )
options . browser = 'webkit' ;
2023-01-27 23:20:25 +01:00
showTraceViewer ( traces , options . browser , { headless : false , host : options.host , port : + options . port } ) . catch ( logErrorAndExit ) ;
2022-03-01 21:56:26 +01:00
} ) . addHelpText ( 'afterAll' , `
Examples :
$ show - trace https : //example.com/trace.zip`);
if ( ! process . env . PW_LANG_NAME ) {
let playwrightTestPackagePath = null ;
2022-08-20 12:20:31 +02:00
const resolvePwTestPaths = [ __dirname , process . cwd ( ) ] ;
2022-03-01 21:56:26 +01:00
try {
playwrightTestPackagePath = require . resolve ( '@playwright/test/lib/cli' , {
2022-08-20 12:20:31 +02:00
paths : resolvePwTestPaths ,
2022-03-01 21:56:26 +01:00
} ) ;
} catch { }
if ( playwrightTestPackagePath ) {
2022-08-20 12:20:31 +02:00
const pwTestVersion = require ( require . resolve ( '@playwright/test/package.json' , {
paths : resolvePwTestPaths ,
} ) ) . version ;
const pwCoreVersion = require ( path . join ( __dirname , '../../package.json' ) ) . version ;
if ( pwTestVersion !== pwCoreVersion ) {
let hasPlaywrightPackage = false ;
try {
require ( 'playwright' ) ;
hasPlaywrightPackage = true ;
} catch { }
2022-10-10 21:34:25 +02:00
const strayPackage = hasPlaywrightPackage ? 'playwright' : 'playwright-core' ;
2022-08-20 12:20:31 +02:00
console . error ( wrapInASCIIBox ( [
2022-10-10 21:34:25 +02:00
` Playwright Test integrity check failed: ` ,
` You have @playwright/test version ' ${ pwTestVersion } ' and ' ${ strayPackage } ' version ' ${ pwCoreVersion } ' installed! ` ,
` You probably added ' ${ strayPackage } ' into your package.json by accident, remove it and re-run 'npm install' ` ,
2022-08-20 12:20:31 +02:00
] . join ( '\n' ) , 1 ) ) ;
process . exit ( 1 ) ;
}
2022-07-29 20:40:33 +02:00
require ( playwrightTestPackagePath ) . addTestCommands ( program ) ;
2022-03-01 21:56:26 +01:00
} else {
{
const command = program . command ( 'test' ) . allowUnknownOption ( true ) ;
command . description ( 'Run tests with Playwright Test. Available in @playwright/test package.' ) ;
command . action ( async ( ) = > {
console . error ( 'Please install @playwright/test package to use Playwright Test.' ) ;
console . error ( ' npm install -D @playwright/test' ) ;
process . exit ( 1 ) ;
} ) ;
}
{
const command = program . command ( 'show-report' ) . allowUnknownOption ( true ) ;
command . description ( 'Show Playwright Test HTML report. Available in @playwright/test package.' ) ;
command . action ( async ( ) = > {
console . error ( 'Please install @playwright/test package to use Playwright Test.' ) ;
console . error ( ' npm install -D @playwright/test' ) ;
process . exit ( 1 ) ;
} ) ;
}
}
}
program . parse ( process . argv ) ;
type Options = {
browser : string ;
channel? : string ;
colorScheme? : string ;
device? : string ;
geolocation? : string ;
ignoreHttpsErrors? : boolean ;
lang? : string ;
loadStorage? : string ;
proxyServer? : string ;
proxyBypass? : string ;
2022-06-17 01:03:35 +02:00
blockServiceWorkers? : boolean ;
2022-06-08 19:59:50 +02:00
saveHar? : string ;
saveHarGlob? : string ;
2022-03-01 21:56:26 +01:00
saveStorage? : string ;
saveTrace? : string ;
timeout : string ;
timezone? : string ;
viewportSize? : string ;
userAgent? : string ;
} ;
type CaptureOptions = {
waitForSelector? : string ;
waitForTimeout? : string ;
fullPage : boolean ;
} ;
async function launchContext ( options : Options , headless : boolean , executablePath? : string ) : Promise < { browser : Browser , browserName : string , launchOptions : LaunchOptions , contextOptions : BrowserContextOptions , context : BrowserContext } > {
validateOptions ( options ) ;
const browserType = lookupBrowserType ( options ) ;
const launchOptions : LaunchOptions = { headless , executablePath } ;
if ( options . channel )
launchOptions . channel = options . channel as any ;
2022-08-09 00:13:38 +02:00
launchOptions . handleSIGINT = false ;
2022-03-01 21:56:26 +01:00
const contextOptions : BrowserContextOptions =
// Copy the device descriptor since we have to compare and modify the options.
options . device ? { . . . playwright . devices [ options . device ] } : { } ;
// In headful mode, use host device scale factor for things to look nice.
// In headless, keep things the way it works in Playwright by default.
// Assume high-dpi on MacOS. TODO: this is not perfect.
if ( ! headless )
contextOptions . deviceScaleFactor = os . platform ( ) === 'darwin' ? 2 : 1 ;
// Work around the WebKit GTK scrolling issue.
if ( browserType . name ( ) === 'webkit' && process . platform === 'linux' ) {
delete contextOptions . hasTouch ;
delete contextOptions . isMobile ;
}
if ( contextOptions . isMobile && browserType . name ( ) === 'firefox' )
contextOptions . isMobile = undefined ;
2022-06-17 01:03:35 +02:00
if ( options . blockServiceWorkers )
contextOptions . serviceWorkers = 'block' ;
2022-03-01 21:56:26 +01:00
// Proxy
if ( options . proxyServer ) {
launchOptions . proxy = {
server : options.proxyServer
} ;
if ( options . proxyBypass )
launchOptions . proxy . bypass = options . proxyBypass ;
}
const browser = await browserType . launch ( launchOptions ) ;
2022-07-21 00:32:57 +02:00
if ( process . env . PWTEST_CLI_EXIT ) {
const logs : string [ ] = [ ] ;
require ( 'playwright-core/lib/utilsBundle' ) . debug . log = ( . . . args : any [ ] ) = > {
const line = require ( 'util' ) . format ( . . . args ) + '\n' ;
logs . push ( line ) ;
process . stderr . write ( line ) ;
} ;
browser . on ( 'disconnected' , ( ) = > {
const hasCrashLine = logs . some ( line = > line . includes ( 'process did exit:' ) && ! line . includes ( 'process did exit: exitCode=0, signal=null' ) ) ;
if ( hasCrashLine ) {
process . stderr . write ( 'Detected browser crash.\n' ) ;
// Make sure we exit abnormally when browser crashes.
process . exit ( 1 ) ;
}
} ) ;
}
2022-03-01 21:56:26 +01:00
// Viewport size
if ( options . viewportSize ) {
try {
2022-08-18 20:12:33 +02:00
const [ width , height ] = options . viewportSize . split ( ',' ) . map ( n = > parseInt ( n , 10 ) ) ;
2022-03-01 21:56:26 +01:00
contextOptions . viewport = { width , height } ;
} catch ( e ) {
console . log ( 'Invalid window size format: use "width, height", for example --window-size=800,600' ) ;
process . exit ( 0 ) ;
}
}
// Geolocation
if ( options . geolocation ) {
try {
const [ latitude , longitude ] = options . geolocation . split ( ',' ) . map ( n = > parseFloat ( n . trim ( ) ) ) ;
contextOptions . geolocation = {
latitude ,
longitude
} ;
} catch ( e ) {
console . log ( 'Invalid geolocation format: user lat, long, for example --geolocation="37.819722,-122.478611"' ) ;
process . exit ( 0 ) ;
}
contextOptions . permissions = [ 'geolocation' ] ;
}
// User agent
if ( options . userAgent )
contextOptions . userAgent = options . userAgent ;
// Lang
if ( options . lang )
contextOptions . locale = options . lang ;
// Color scheme
if ( options . colorScheme )
contextOptions . colorScheme = options . colorScheme as 'dark' | 'light' ;
// Timezone
if ( options . timezone )
contextOptions . timezoneId = options . timezone ;
// Storage
if ( options . loadStorage )
contextOptions . storageState = options . loadStorage ;
if ( options . ignoreHttpsErrors )
contextOptions . ignoreHTTPSErrors = true ;
2022-06-08 19:59:50 +02:00
// HAR
if ( options . saveHar ) {
2022-06-22 23:44:12 +02:00
contextOptions . recordHar = { path : path.resolve ( process . cwd ( ) , options . saveHar ) , mode : 'minimal' } ;
2022-06-08 19:59:50 +02:00
if ( options . saveHarGlob )
contextOptions . recordHar . urlFilter = options . saveHarGlob ;
2022-06-20 23:14:40 +02:00
contextOptions . serviceWorkers = 'block' ;
2022-06-08 19:59:50 +02:00
}
2022-03-01 21:56:26 +01:00
// Close app when the last window closes.
const context = await browser . newContext ( contextOptions ) ;
let closingBrowser = false ;
async function closeBrowser() {
// We can come here multiple times. For example, saving storage creates
// a temporary page and we call closeBrowser again when that page closes.
if ( closingBrowser )
return ;
closingBrowser = true ;
if ( options . saveTrace )
await context . tracing . stop ( { path : options.saveTrace } ) ;
if ( options . saveStorage )
await context . storageState ( { path : options.saveStorage } ) . catch ( e = > null ) ;
2022-06-08 19:59:50 +02:00
if ( options . saveHar )
await context . close ( ) ;
2022-03-01 21:56:26 +01:00
await browser . close ( ) ;
}
context . on ( 'page' , page = > {
page . on ( 'dialog' , ( ) = > { } ) ; // Prevent dialogs from being automatically dismissed.
page . on ( 'close' , ( ) = > {
const hasPage = browser . contexts ( ) . some ( context = > context . pages ( ) . length > 0 ) ;
if ( hasPage )
return ;
// Avoid the error when the last page is closed because the browser has been closed.
closeBrowser ( ) . catch ( e = > null ) ;
} ) ;
} ) ;
2022-08-09 00:13:38 +02:00
process . on ( 'SIGINT' , async ( ) = > {
await closeBrowser ( ) ;
process . exit ( 130 ) ;
} ) ;
2022-06-10 06:07:57 +02:00
const timeout = options . timeout ? parseInt ( options . timeout , 10 ) : 0 ;
context . setDefaultTimeout ( timeout ) ;
context . setDefaultNavigationTimeout ( timeout ) ;
2022-03-01 21:56:26 +01:00
if ( options . saveTrace )
await context . tracing . start ( { screenshots : true , snapshots : true } ) ;
// Omit options that we add automatically for presentation purpose.
delete launchOptions . headless ;
delete launchOptions . executablePath ;
2022-08-09 00:13:38 +02:00
delete launchOptions . handleSIGINT ;
2022-03-01 21:56:26 +01:00
delete contextOptions . deviceScaleFactor ;
return { browser , browserName : browserType.name ( ) , context , contextOptions , launchOptions } ;
}
async function openPage ( context : BrowserContext , url : string | undefined ) : Promise < Page > {
const page = await context . newPage ( ) ;
if ( url ) {
if ( fs . existsSync ( url ) )
url = 'file://' + path . resolve ( url ) ;
else if ( ! url . startsWith ( 'http' ) && ! url . startsWith ( 'file://' ) && ! url . startsWith ( 'about:' ) && ! url . startsWith ( 'data:' ) )
url = 'http://' + url ;
await page . goto ( url ) ;
}
return page ;
}
async function open ( options : Options , url : string | undefined , language : string ) {
const { context , launchOptions , contextOptions } = await launchContext ( options , ! ! process . env . PWTEST_CLI_HEADLESS , process . env . PWTEST_CLI_EXECUTABLE_PATH ) ;
await context . _enableRecorder ( {
language ,
launchOptions ,
contextOptions ,
device : options.device ,
saveStorage : options.saveStorage ,
2021-01-26 04:01:04 +01:00
} ) ;
2022-03-01 21:56:26 +01:00
await openPage ( context , url ) ;
if ( process . env . PWTEST_CLI_EXIT )
await Promise . all ( context . pages ( ) . map ( p = > p . close ( ) ) ) ;
}
2022-02-15 22:10:35 +01:00
2022-03-01 21:56:26 +01:00
async function codegen ( options : Options , url : string | undefined , language : string , outputFile? : string ) {
const { context , launchOptions , contextOptions } = await launchContext ( options , ! ! process . env . PWTEST_CLI_HEADLESS , process . env . PWTEST_CLI_EXECUTABLE_PATH ) ;
await context . _enableRecorder ( {
language ,
launchOptions ,
contextOptions ,
device : options.device ,
saveStorage : options.saveStorage ,
2022-08-06 04:34:57 +02:00
mode : 'recording' ,
2022-08-09 00:13:38 +02:00
outputFile : outputFile ? path . resolve ( outputFile ) : undefined ,
handleSIGINT : false ,
2022-02-15 22:10:35 +01:00
} ) ;
2022-03-01 21:56:26 +01:00
await openPage ( context , url ) ;
if ( process . env . PWTEST_CLI_EXIT )
await Promise . all ( context . pages ( ) . map ( p = > p . close ( ) ) ) ;
}
async function waitForPage ( page : Page , captureOptions : CaptureOptions ) {
if ( captureOptions . waitForSelector ) {
console . log ( ` Waiting for selector ${ captureOptions . waitForSelector } ... ` ) ;
await page . waitForSelector ( captureOptions . waitForSelector ) ;
}
if ( captureOptions . waitForTimeout ) {
console . log ( ` Waiting for timeout ${ captureOptions . waitForTimeout } ... ` ) ;
await page . waitForTimeout ( parseInt ( captureOptions . waitForTimeout , 10 ) ) ;
}
}
async function screenshot ( options : Options , captureOptions : CaptureOptions , url : string , path : string ) {
const { browser , context } = await launchContext ( options , true ) ;
console . log ( 'Navigating to ' + url ) ;
const page = await openPage ( context , url ) ;
await waitForPage ( page , captureOptions ) ;
console . log ( 'Capturing screenshot into ' + path ) ;
await page . screenshot ( { path , fullPage : ! ! captureOptions . fullPage } ) ;
await browser . close ( ) ;
}
async function pdf ( options : Options , captureOptions : CaptureOptions , url : string , path : string ) {
if ( options . browser !== 'chromium' ) {
console . error ( 'PDF creation is only working with Chromium' ) ;
process . exit ( 1 ) ;
}
const { browser , context } = await launchContext ( { . . . options , browser : 'chromium' } , true ) ;
console . log ( 'Navigating to ' + url ) ;
const page = await openPage ( context , url ) ;
await waitForPage ( page , captureOptions ) ;
console . log ( 'Saving as pdf into ' + path ) ;
await page . pdf ! ( { path } ) ;
await browser . close ( ) ;
}
function lookupBrowserType ( options : Options ) : BrowserType {
let name = options . browser ;
if ( options . device ) {
const device = playwright . devices [ options . device ] ;
name = device . defaultBrowserType ;
}
let browserType : any ;
switch ( name ) {
case 'chromium' : browserType = playwright . chromium ; break ;
case 'webkit' : browserType = playwright . webkit ; break ;
case 'firefox' : browserType = playwright . firefox ; break ;
case 'cr' : browserType = playwright . chromium ; break ;
case 'wk' : browserType = playwright . webkit ; break ;
case 'ff' : browserType = playwright . firefox ; break ;
}
if ( browserType )
return browserType ;
program . help ( ) ;
2022-04-19 02:50:25 +02:00
process . exit ( 1 ) ;
2022-03-01 21:56:26 +01:00
}
function validateOptions ( options : Options ) {
if ( options . device && ! ( options . device in playwright . devices ) ) {
console . log ( ` Device descriptor not found: ' ${ options . device } ', available devices are: ` ) ;
for ( const name in playwright . devices )
console . log ( ` " ${ name } " ` ) ;
process . exit ( 0 ) ;
}
if ( options . colorScheme && ! [ 'light' , 'dark' ] . includes ( options . colorScheme ) ) {
console . log ( 'Invalid color scheme, should be one of "light", "dark"' ) ;
process . exit ( 0 ) ;
}
}
function logErrorAndExit ( e : Error ) {
console . error ( e ) ;
process . exit ( 1 ) ;
}
2022-10-25 18:55:20 +02:00
function codegenId ( ) : string {
return process . env . PW_LANG_NAME || 'playwright-test' ;
2022-03-01 21:56:26 +01:00
}
function commandWithOpenOptions ( command : string , description : string , options : any [ ] [ ] ) : Command {
let result = program . command ( command ) . description ( description ) ;
for ( const option of options )
result = result . option ( option [ 0 ] , . . . option . slice ( 1 ) ) ;
return result
. option ( '-b, --browser <browserType>' , 'browser to use, one of cr, chromium, ff, firefox, wk, webkit' , 'chromium' )
2022-06-17 01:03:35 +02:00
. option ( '--block-service-workers' , 'block service workers' )
2022-03-01 21:56:26 +01:00
. option ( '--channel <channel>' , 'Chromium distribution channel, "chrome", "chrome-beta", "msedge-dev", etc' )
. option ( '--color-scheme <scheme>' , 'emulate preferred color scheme, "light" or "dark"' )
. option ( '--device <deviceName>' , 'emulate device, for example "iPhone 11"' )
. option ( '--geolocation <coordinates>' , 'specify geolocation coordinates, for example "37.819722,-122.478611"' )
. option ( '--ignore-https-errors' , 'ignore https errors' )
. option ( '--load-storage <filename>' , 'load context storage state from the file, previously saved with --save-storage' )
. option ( '--lang <language>' , 'specify language / locale, for example "en-GB"' )
. option ( '--proxy-server <proxy>' , 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"' )
. option ( '--proxy-bypass <bypass>' , 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"' )
2022-06-08 19:59:50 +02:00
. option ( '--save-har <filename>' , 'save HAR file with all network activity at the end' )
. option ( '--save-har-glob <glob pattern>' , 'filter entries in the HAR by matching url against this glob pattern' )
2022-03-01 21:56:26 +01:00
. option ( '--save-storage <filename>' , 'save context storage state at the end, for later use with --load-storage' )
. option ( '--timezone <time zone>' , 'time zone to emulate, for example "Europe/Rome"' )
2022-06-10 06:07:57 +02:00
. option ( '--timeout <timeout>' , 'timeout for Playwright actions in milliseconds, no timeout by default' )
2022-03-01 21:56:26 +01:00
. option ( '--user-agent <ua string>' , 'specify user agent string' )
. option ( '--viewport-size <size>' , 'specify browser viewport size in pixels, for example "1280, 720"' ) ;
}
2022-04-08 02:22:52 +02:00
async function launchGridServer ( factoryPathOrPackageName : string , port : number , address : string | undefined , authToken : string | undefined ) : Promise < void > {
2022-03-01 21:56:26 +01:00
if ( ! factoryPathOrPackageName )
factoryPathOrPackageName = path . join ( '..' , 'grid' , 'simpleGridFactory' ) ;
let factory ;
try {
factory = require ( path . resolve ( factoryPathOrPackageName ) ) ;
} catch ( e ) {
factory = require ( factoryPathOrPackageName ) ;
}
if ( factory && typeof factory === 'object' && ( 'default' in factory ) )
factory = factory [ 'default' ] ;
if ( ! factory || ! factory . launch || typeof factory . launch !== 'function' )
throw new Error ( 'factory does not export `launch` method' ) ;
factory . name = factory . name || factoryPathOrPackageName ;
2022-04-08 02:22:52 +02:00
const gridServer = new GridServer ( factory as GridFactory , authToken , address ) ;
2022-03-01 21:56:26 +01:00
await gridServer . start ( port ) ;
2022-04-08 23:57:43 +02:00
console . log ( 'Grid server is running at ' + gridServer . gridURL ( ) ) ;
2022-03-01 21:56:26 +01:00
}
function buildBasePlaywrightCLICommand ( cliTargetLang : string | undefined ) : string {
switch ( cliTargetLang ) {
case 'python' :
return ` playwright ` ;
case 'java' :
2023-01-05 19:55:07 +01:00
return ` mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="...options.." ` ;
2022-03-01 21:56:26 +01:00
case 'csharp' :
2022-08-14 20:01:00 +02:00
return ` pwsh bin/Debug/netX/playwright.ps1 ` ;
2022-03-01 21:56:26 +01:00
default :
return ` npx playwright ` ;
}
2021-11-22 18:34:22 +01:00
}