diff --git a/docs/src/docker.md b/docs/src/docker.md index 231e908b115e1..f039b321f5f16 100644 --- a/docs/src/docker.md +++ b/docs/src/docker.md @@ -179,14 +179,22 @@ Docker integration usage: npx playwright docker start ``` -1. Run tests inside Docker container. Note that this command accepts all the same arguments - as a regular `npx playwright test` command. +1. Run tests inside Docker container using the `PLAYWRIGHT_DOCKER` environment variable. + You can set this environment variable as a part of your config: - ```bash js - npx playwright docker test + ```ts + // playwright.config.ts + import type { PlaywrightTestConfig } from '@playwright/test'; + + process.env.PLAYWRIGHT_DOCKER = '1'; + + const config: PlaywrightTestConfig = { + /* ... configuration ... */ + }; + export default config; ``` - Note that this command will detect running Docker container, and auto-launch it if needed. + NOTE: Playwright will automatically detect a running Docker container or start it if needed. 1. Finally, stop background Docker container when you're done working with tests: @@ -194,17 +202,3 @@ Docker integration usage: npx playwright docker stop ``` -Playwright Test sets `PLAYWRIGHT_DOCKER` environment variable when it uses Docker integration. -You can use this variable to customize config or tests behavior, for example: - -```ts -// playwright.config.ts -import type { PlaywrightTestConfig } from '@playwright/test'; - -const config: PlaywrightTestConfig = { - // Ignore all snapshot expectations when running outside - // of docker integration. - ignoreSnapshots: !process.env.PLAYWRIGHT_DOCKER, -}; -export default config; -``` diff --git a/packages/playwright-test/src/cli.ts b/packages/playwright-test/src/cli.ts index b5f6ac0c54d04..7e32353179aca 100644 --- a/packages/playwright-test/src/cli.ts +++ b/packages/playwright-test/src/cli.ts @@ -30,7 +30,7 @@ import { baseFullConfig, defaultTimeout, fileIsModule } from './loader'; import type { TraceMode } from './types'; export function addTestCommands(program: Command) { - addTestCommand(program, false /* isDocker */); + addTestCommand(program); addShowReportCommand(program); addListFilesCommand(program); addDockerCommand(program); @@ -38,41 +38,58 @@ export function addTestCommands(program: Command) { function addDockerCommand(program: Command) { const dockerCommand = program.command('docker') - .description(`run tests in Docker (EXPERIMENTAL)`); + .description(`Manage Docker integration (EXPERIMENTAL)`); dockerCommand.command('build') .description('build local docker image') .action(async function(options) { - await docker.buildPlaywrightImage(); + try { + await docker.buildPlaywrightImage(); + } catch (e) { + console.error(e.stack ? e : e.message); + } }); dockerCommand.command('start') .description('start docker container') .action(async function(options) { - await docker.startPlaywrightContainer(); + try { + await docker.startPlaywrightContainer(); + } catch (e) { + console.error(e.stack ? e : e.message); + } }); dockerCommand.command('stop') .description('stop docker container') .action(async function(options) { - await docker.stopAllPlaywrightContainers(); + try { + await docker.stopAllPlaywrightContainers(); + } catch (e) { + console.error(e.stack ? e : e.message); + } }); dockerCommand.command('delete-image', { hidden: true }) .description('delete docker image, if any') .action(async function(options) { - await docker.deletePlaywrightImage(); + try { + await docker.deletePlaywrightImage(); + } catch (e) { + console.error(e.stack ? e : e.message); + } }); - addTestCommand(dockerCommand, true /* isDocker */); + dockerCommand.command('print-status-json', { hidden: true }) + .description('print docker status') + .action(async function(options) { + await docker.printDockerStatus(); + }); } -function addTestCommand(program: Command, isDocker: boolean) { +function addTestCommand(program: Command) { const command = program.command('test [test-filter...]'); - if (isDocker) - command.description('run tests with Playwright Test and browsers inside docker container'); - else - command.description('run tests with Playwright Test'); + command.description('run tests with Playwright Test'); command.option('--browser ', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`); command.option('--headed', `Run tests in headed browsers (default: headless)`); command.option('--debug', `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --maxFailures=1 --headed --workers=1" options`); @@ -100,8 +117,6 @@ function addTestCommand(program: Command, isDocker: boolean) { command.option('-x', `Stop after the first failure`); command.action(async (args, opts) => { try { - if (isDocker) - process.env.PLAYWRIGHT_DOCKER = '1'; await runTests(args, opts); } catch (e) { console.error(e); @@ -113,10 +128,10 @@ Arguments [test-filter...]: Pass arguments to filter test files. Each argument is treated as a regular expression. Examples: - $ npx playwright${isDocker ? ' docker ' : ' '}test my.spec.ts - $ npx playwright${isDocker ? ' docker ' : ' '}test some.spec.ts:42 - $ npx playwright${isDocker ? ' docker ' : ' '}test --headed - $ npx playwright${isDocker ? ' docker ' : ' '}test --browser=webkit`); + $ npx playwright test my.spec.ts + $ npx playwright test some.spec.ts:42 + $ npx playwright test --headed + $ npx playwright test --browser=webkit`); } function addListFilesCommand(program: Command) { diff --git a/packages/playwright-test/src/docker/docker.ts b/packages/playwright-test/src/docker/docker.ts index d7faf9c820010..ada1437bcd0f4 100644 --- a/packages/playwright-test/src/docker/docker.ts +++ b/packages/playwright-test/src/docker/docker.ts @@ -85,7 +85,7 @@ export async function buildPlaywrightImage() { // Use our docker build scripts in development mode! if (!process.env.PWTEST_DOCKER_BASE_IMAGE) { const arch = process.arch === 'arm64' ? '--arm64' : '--amd64'; - console.error(utils.wrapInASCIIBox([ + throw createStacklessError(utils.wrapInASCIIBox([ `You are in DEVELOPMENT mode!`, ``, `1. Build local base image`, @@ -93,7 +93,6 @@ export async function buildPlaywrightImage() { `2. Use the local base to build VRT image:`, ` PWTEST_DOCKER_BASE_IMAGE=playwright:localbuild npx playwright docker build`, ].join('\n'), 1)); - process.exit(1); } baseImageName = process.env.PWTEST_DOCKER_BASE_IMAGE; } else { @@ -168,6 +167,19 @@ interface ContainerInfo { vncSession: string; } +export async function printDockerStatus() { + const isDockerEngine = await dockerApi.checkEngineRunning(); + const imageIsPulled = isDockerEngine && !!(await findDockerImage(VRT_IMAGE_NAME)); + const info = isDockerEngine ? await containerInfo() : undefined; + console.log(JSON.stringify({ + dockerEngineRunning: isDockerEngine, + imageName: VRT_IMAGE_NAME, + imageIsPulled, + containerWSEndpoing: info?.wsEndpoint ?? '', + containerVNCEndpoint: info?.vncSession ?? '', + }, null, 2)); +} + async function containerInfo(): Promise { const allContainers = await dockerApi.listContainers(); const pwDockerImage = await findDockerImage(VRT_IMAGE_NAME); @@ -201,7 +213,7 @@ async function containerInfo(): Promise { async function ensurePlaywrightContainerOrDie(): Promise { const pwImage = await findDockerImage(VRT_IMAGE_NAME); if (!pwImage) { - console.error('\n' + utils.wrapInASCIIBox([ + throw createStacklessError('\n' + utils.wrapInASCIIBox([ `Failed to find local docker image.`, `Please build local docker image with the following command:`, ``, @@ -209,7 +221,6 @@ async function ensurePlaywrightContainerOrDie(): Promise { ``, `<3 Playwright Team`, ].join('\n'), 1)); - process.exit(1); } let info = await containerInfo(); @@ -242,14 +253,13 @@ async function ensurePlaywrightContainerOrDie(): Promise { async function checkDockerEngineIsRunningOrDie() { if (await dockerApi.checkEngineRunning()) return; - console.error(utils.wrapInASCIIBox([ + throw createStacklessError(utils.wrapInASCIIBox([ `Docker is not running!`, `Please install and launch docker:`, ``, ` https://docs.docker.com/get-docker`, ``, ].join('\n'), 1)); - process.exit(1); } async function findDockerImage(imageName: string): Promise { @@ -257,3 +267,8 @@ async function findDockerImage(imageName: string): Promise image.names.includes(imageName)); } +function createStacklessError(message: string) { + const error = new Error(message); + error.stack = ''; + return error; +} diff --git a/tests/installation/docker-integration.spec.ts b/tests/installation/docker-integration.spec.ts index 7b0b08fec3771..8989e06295f0d 100755 --- a/tests/installation/docker-integration.spec.ts +++ b/tests/installation/docker-integration.spec.ts @@ -30,8 +30,9 @@ test.beforeAll(async ({ exec }) => { test('make sure it tells to run `npx playwright docker build` when image is not instaleld', async ({ exec }) => { await exec('npm i --foreground-scripts @playwright/test'); - const result = await exec('npx playwright docker test docker.spec.js', { + const result = await exec('npx playwright test docker.spec.js', { expectToExitWithError: true, + env: { PLAYWRIGHT_DOCKER: '1' }, }); expect(result).toContain('npx playwright docker build'); }); @@ -52,7 +53,9 @@ test.describe('installed image', () => { test('make sure it auto-starts container', async ({ exec }) => { await exec('npm i --foreground-scripts @playwright/test'); await exec('npx playwright docker stop'); - const result = await exec('npx playwright docker test docker.spec.js --grep platform'); + const result = await exec('npx playwright test docker.spec.js --grep platform', { + env: { PLAYWRIGHT_DOCKER: '1' }, + }); expect(result).toContain('@chromium Linux'); }); @@ -71,7 +74,9 @@ test.describe('installed image', () => { test('all browsers work headless', async ({ exec }) => { await exec('npm i --foreground-scripts @playwright/test'); - const result = await exec('npx playwright docker test docker.spec.js --grep platform --browser all'); + const result = await exec('npx playwright test docker.spec.js --grep platform --browser all', { + env: { PLAYWRIGHT_DOCKER: '1' }, + }); expect(result).toContain('@chromium Linux'); expect(result).toContain('@webkit Linux'); expect(result).toContain('@firefox Linux'); @@ -92,18 +97,24 @@ test.describe('installed image', () => { test('all browsers work headed', async ({ exec }) => { await exec('npm i --foreground-scripts @playwright/test'); { - const result = await exec(`npx playwright docker test docker.spec.js --headed --grep userAgent --browser chromium`); + const result = await exec(`npx playwright test docker.spec.js --headed --grep userAgent --browser chromium`, { + env: { PLAYWRIGHT_DOCKER: '1' }, + }); expect(result).toContain('@chromium'); expect(result).not.toContain('Headless'); expect(result).toContain(' Chrome/'); } { - const result = await exec(`npx playwright docker test docker.spec.js --headed --grep userAgent --browser webkit`); + const result = await exec(`npx playwright test docker.spec.js --headed --grep userAgent --browser webkit`, { + env: { PLAYWRIGHT_DOCKER: '1' }, + }); expect(result).toContain('@webkit'); expect(result).toContain(' Version/'); } { - const result = await exec(`npx playwright docker test docker.spec.js --headed --grep userAgent --browser firefox`); + const result = await exec(`npx playwright test docker.spec.js --headed --grep userAgent --browser firefox`, { + env: { PLAYWRIGHT_DOCKER: '1' }, + }); expect(result).toContain('@firefox'); expect(result).toContain(' Firefox/'); } @@ -111,8 +122,9 @@ test.describe('installed image', () => { test('screenshots should use __screenshots__ folder', async ({ exec, tmpWorkspace }) => { await exec('npm i --foreground-scripts @playwright/test'); - await exec('npx playwright docker test docker.spec.js --grep screenshot --browser all', { + await exec('npx playwright test docker.spec.js --grep screenshot --browser all', { expectToExitWithError: true, + env: { PLAYWRIGHT_DOCKER: '1' }, }); await expect(path.join(tmpWorkspace, '__screenshots__', 'firefox', 'docker.spec.js', 'img.png')).toExistOnFS(); await expect(path.join(tmpWorkspace, '__screenshots__', 'chromium', 'docker.spec.js', 'img.png')).toExistOnFS(); @@ -126,8 +138,9 @@ test.describe('installed image', () => { server.setRoute('/', (request, response) => { response.end('Hello from host'); }); - const result = await exec('npx playwright docker test docker.spec.js --grep localhost --browser all', { + const result = await exec('npx playwright test docker.spec.js --grep localhost --browser all', { env: { + PLAYWRIGHT_DOCKER: '1', TEST_PORT: TEST_PORT + '', }, });