From 9abdff1313e429e612da68fb2a0131ee422baae4 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Thu, 15 Sep 2022 13:54:01 -0400 Subject: [PATCH 1/3] chore(docker): address docker offline comments This patch: - Removes all `process.exit(1)` from `docker.ts` and instead throws errors. - Drops the `npx playwright docker test` command. We agreed to engage docker when `PLAYWRIGHT_DOCKER` environment variable is set. - Introduces hidden `npx playwright docker status` command that dumps a JSON with docker status: ```sh aslushnikov:~/prog/playwright$ npx playwright docker status { "dockerEngineRunning": true, "imageName": "playwright:local-1.27.0-next-focal", "imageIsPulled": true, "containerWSEndpoing": "ws://127.0.0.1:55077/eafeb84c-571b-4d12-ac51-f6a2b43e9155", "containerVNCEndpoint": "http://127.0.0.1:55076/?path=fb6d4add-9adf-4c3c-b335-893bdc235cd7&resize=scale&autoconnect=1" } ``` --- docs/src/docker.md | 35 +++++++------ packages/playwright-test/src/cli.ts | 51 ++++++++++++------- packages/playwright-test/src/docker/docker.ts | 27 +++++++--- tests/installation/docker-integration.spec.ts | 29 ++++++++--- 4 files changed, 92 insertions(+), 50 deletions(-) diff --git a/docs/src/docker.md b/docs/src/docker.md index 231e908b115e1..a452caaf56f05 100644 --- a/docs/src/docker.md +++ b/docs/src/docker.md @@ -179,14 +179,27 @@ 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: ```bash js - npx playwright docker test + PLAYWRIGHT_DOCKER=1 npx playwright test ``` - Note that this command will detect running Docker container, and auto-launch it if needed. + You can set this environment variable as a part of your config: + + ```ts + // playwright.config.ts + import type { PlaywrightTestConfig } from '@playwright/test'; + + process.env.PLAYWRIGHT_DOCKER = '1'; + + const config: PlaywrightTestConfig = { + /* ... configuration ... */ + }; + export default config; + ``` + + NOTE: this command will detect running Docker container, and auto-launch it if needed. 1. Finally, stop background Docker container when you're done working with tests: @@ -194,17 +207,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..6e8d3143461a2 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('status', { 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 + '', }, }); From 691d7885a07a894a4e5e31df4a0aeffa00666d58 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Thu, 15 Sep 2022 18:45:07 -0400 Subject: [PATCH 2/3] address comments --- docs/src/docker.md | 10 +++------- packages/playwright-test/src/cli.ts | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/src/docker.md b/docs/src/docker.md index a452caaf56f05..091322ed01902 100644 --- a/docs/src/docker.md +++ b/docs/src/docker.md @@ -179,12 +179,7 @@ Docker integration usage: npx playwright docker start ``` -1. Run tests inside Docker container using the `PLAYWRIGHT_DOCKER` environment variable: - - ```bash js - PLAYWRIGHT_DOCKER=1 npx playwright test - ``` - +1. Run tests inside Docker container using the `PLAYWRIGHT_DOCKER` environment variable. You can set this environment variable as a part of your config: ```ts @@ -199,7 +194,8 @@ Docker integration usage: export default config; ``` - NOTE: this command will detect running Docker container, and auto-launch it if needed. + NOTE: Running tests with `PLAYWRIGHT_DOCKER` environment variable will detect pre-launched + Docker container, and auto-launch it if there's no running one. 1. Finally, stop background Docker container when you're done working with tests: diff --git a/packages/playwright-test/src/cli.ts b/packages/playwright-test/src/cli.ts index 6e8d3143461a2..7e32353179aca 100644 --- a/packages/playwright-test/src/cli.ts +++ b/packages/playwright-test/src/cli.ts @@ -80,7 +80,7 @@ function addDockerCommand(program: Command) { } }); - dockerCommand.command('status', { hidden: true }) + dockerCommand.command('print-status-json', { hidden: true }) .description('print docker status') .action(async function(options) { await docker.printDockerStatus(); From 569f3972eb63be21a5cf4d9fc4fe299d85b7a263 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Thu, 15 Sep 2022 18:45:50 -0400 Subject: [PATCH 3/3] better docs --- docs/src/docker.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/src/docker.md b/docs/src/docker.md index 091322ed01902..f039b321f5f16 100644 --- a/docs/src/docker.md +++ b/docs/src/docker.md @@ -194,8 +194,7 @@ Docker integration usage: export default config; ``` - NOTE: Running tests with `PLAYWRIGHT_DOCKER` environment variable will detect pre-launched - Docker container, and auto-launch it if there's no running one. + 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: