chore(docker): address docker offline comments (#17377)

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"
  }
  ```
This commit is contained in:
Andrey Lushnikov 2022-09-15 15:48:12 -07:00 committed by GitHub
parent 30ff27843a
commit b09ea69024
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 88 additions and 51 deletions

View file

@ -179,14 +179,22 @@ Docker integration usage:
npx playwright docker start npx playwright docker start
``` ```
1. Run tests inside Docker container. Note that this command accepts all the same arguments 1. Run tests inside Docker container using the `PLAYWRIGHT_DOCKER` environment variable.
as a regular `npx playwright test` command. You can set this environment variable as a part of your config:
```bash js ```ts
npx playwright docker test // 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: 1. Finally, stop background Docker container when you're done working with tests:
@ -194,17 +202,3 @@ Docker integration usage:
npx playwright docker stop 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;
```

View file

@ -30,7 +30,7 @@ import { baseFullConfig, defaultTimeout, fileIsModule } from './loader';
import type { TraceMode } from './types'; import type { TraceMode } from './types';
export function addTestCommands(program: Command) { export function addTestCommands(program: Command) {
addTestCommand(program, false /* isDocker */); addTestCommand(program);
addShowReportCommand(program); addShowReportCommand(program);
addListFilesCommand(program); addListFilesCommand(program);
addDockerCommand(program); addDockerCommand(program);
@ -38,41 +38,58 @@ export function addTestCommands(program: Command) {
function addDockerCommand(program: Command) { function addDockerCommand(program: Command) {
const dockerCommand = program.command('docker') const dockerCommand = program.command('docker')
.description(`run tests in Docker (EXPERIMENTAL)`); .description(`Manage Docker integration (EXPERIMENTAL)`);
dockerCommand.command('build') dockerCommand.command('build')
.description('build local docker image') .description('build local docker image')
.action(async function(options) { .action(async function(options) {
await docker.buildPlaywrightImage(); try {
await docker.buildPlaywrightImage();
} catch (e) {
console.error(e.stack ? e : e.message);
}
}); });
dockerCommand.command('start') dockerCommand.command('start')
.description('start docker container') .description('start docker container')
.action(async function(options) { .action(async function(options) {
await docker.startPlaywrightContainer(); try {
await docker.startPlaywrightContainer();
} catch (e) {
console.error(e.stack ? e : e.message);
}
}); });
dockerCommand.command('stop') dockerCommand.command('stop')
.description('stop docker container') .description('stop docker container')
.action(async function(options) { .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 }) dockerCommand.command('delete-image', { hidden: true })
.description('delete docker image, if any') .description('delete docker image, if any')
.action(async function(options) { .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...]'); const command = program.command('test [test-filter...]');
if (isDocker) command.description('run tests with Playwright Test');
command.description('run tests with Playwright Test and browsers inside docker container');
else
command.description('run tests with Playwright Test');
command.option('--browser <browser>', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`); command.option('--browser <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('--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`); 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.option('-x', `Stop after the first failure`);
command.action(async (args, opts) => { command.action(async (args, opts) => {
try { try {
if (isDocker)
process.env.PLAYWRIGHT_DOCKER = '1';
await runTests(args, opts); await runTests(args, opts);
} catch (e) { } catch (e) {
console.error(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. Pass arguments to filter test files. Each argument is treated as a regular expression.
Examples: Examples:
$ npx playwright${isDocker ? ' docker ' : ' '}test my.spec.ts $ npx playwright test my.spec.ts
$ npx playwright${isDocker ? ' docker ' : ' '}test some.spec.ts:42 $ npx playwright test some.spec.ts:42
$ npx playwright${isDocker ? ' docker ' : ' '}test --headed $ npx playwright test --headed
$ npx playwright${isDocker ? ' docker ' : ' '}test --browser=webkit`); $ npx playwright test --browser=webkit`);
} }
function addListFilesCommand(program: Command) { function addListFilesCommand(program: Command) {

View file

@ -85,7 +85,7 @@ export async function buildPlaywrightImage() {
// Use our docker build scripts in development mode! // Use our docker build scripts in development mode!
if (!process.env.PWTEST_DOCKER_BASE_IMAGE) { if (!process.env.PWTEST_DOCKER_BASE_IMAGE) {
const arch = process.arch === 'arm64' ? '--arm64' : '--amd64'; const arch = process.arch === 'arm64' ? '--arm64' : '--amd64';
console.error(utils.wrapInASCIIBox([ throw createStacklessError(utils.wrapInASCIIBox([
`You are in DEVELOPMENT mode!`, `You are in DEVELOPMENT mode!`,
``, ``,
`1. Build local base image`, `1. Build local base image`,
@ -93,7 +93,6 @@ export async function buildPlaywrightImage() {
`2. Use the local base to build VRT image:`, `2. Use the local base to build VRT image:`,
` PWTEST_DOCKER_BASE_IMAGE=playwright:localbuild npx playwright docker build`, ` PWTEST_DOCKER_BASE_IMAGE=playwright:localbuild npx playwright docker build`,
].join('\n'), 1)); ].join('\n'), 1));
process.exit(1);
} }
baseImageName = process.env.PWTEST_DOCKER_BASE_IMAGE; baseImageName = process.env.PWTEST_DOCKER_BASE_IMAGE;
} else { } else {
@ -168,6 +167,19 @@ interface ContainerInfo {
vncSession: string; 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<ContainerInfo|undefined> { async function containerInfo(): Promise<ContainerInfo|undefined> {
const allContainers = await dockerApi.listContainers(); const allContainers = await dockerApi.listContainers();
const pwDockerImage = await findDockerImage(VRT_IMAGE_NAME); const pwDockerImage = await findDockerImage(VRT_IMAGE_NAME);
@ -201,7 +213,7 @@ async function containerInfo(): Promise<ContainerInfo|undefined> {
async function ensurePlaywrightContainerOrDie(): Promise<ContainerInfo> { async function ensurePlaywrightContainerOrDie(): Promise<ContainerInfo> {
const pwImage = await findDockerImage(VRT_IMAGE_NAME); const pwImage = await findDockerImage(VRT_IMAGE_NAME);
if (!pwImage) { if (!pwImage) {
console.error('\n' + utils.wrapInASCIIBox([ throw createStacklessError('\n' + utils.wrapInASCIIBox([
`Failed to find local docker image.`, `Failed to find local docker image.`,
`Please build local docker image with the following command:`, `Please build local docker image with the following command:`,
``, ``,
@ -209,7 +221,6 @@ async function ensurePlaywrightContainerOrDie(): Promise<ContainerInfo> {
``, ``,
`<3 Playwright Team`, `<3 Playwright Team`,
].join('\n'), 1)); ].join('\n'), 1));
process.exit(1);
} }
let info = await containerInfo(); let info = await containerInfo();
@ -242,14 +253,13 @@ async function ensurePlaywrightContainerOrDie(): Promise<ContainerInfo> {
async function checkDockerEngineIsRunningOrDie() { async function checkDockerEngineIsRunningOrDie() {
if (await dockerApi.checkEngineRunning()) if (await dockerApi.checkEngineRunning())
return; return;
console.error(utils.wrapInASCIIBox([ throw createStacklessError(utils.wrapInASCIIBox([
`Docker is not running!`, `Docker is not running!`,
`Please install and launch docker:`, `Please install and launch docker:`,
``, ``,
` https://docs.docker.com/get-docker`, ` https://docs.docker.com/get-docker`,
``, ``,
].join('\n'), 1)); ].join('\n'), 1));
process.exit(1);
} }
async function findDockerImage(imageName: string): Promise<dockerApi.DockerImage|undefined> { async function findDockerImage(imageName: string): Promise<dockerApi.DockerImage|undefined> {
@ -257,3 +267,8 @@ async function findDockerImage(imageName: string): Promise<dockerApi.DockerImage
return images.find(image => image.names.includes(imageName)); return images.find(image => image.names.includes(imageName));
} }
function createStacklessError(message: string) {
const error = new Error(message);
error.stack = '';
return error;
}

View file

@ -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 }) => { 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'); 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, expectToExitWithError: true,
env: { PLAYWRIGHT_DOCKER: '1' },
}); });
expect(result).toContain('npx playwright docker build'); expect(result).toContain('npx playwright docker build');
}); });
@ -52,7 +53,9 @@ test.describe('installed image', () => {
test('make sure it auto-starts container', async ({ exec }) => { test('make sure it auto-starts container', async ({ exec }) => {
await exec('npm i --foreground-scripts @playwright/test'); await exec('npm i --foreground-scripts @playwright/test');
await exec('npx playwright docker stop'); 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'); expect(result).toContain('@chromium Linux');
}); });
@ -71,7 +74,9 @@ test.describe('installed image', () => {
test('all browsers work headless', async ({ exec }) => { test('all browsers work headless', async ({ exec }) => {
await exec('npm i --foreground-scripts @playwright/test'); 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('@chromium Linux');
expect(result).toContain('@webkit Linux'); expect(result).toContain('@webkit Linux');
expect(result).toContain('@firefox Linux'); expect(result).toContain('@firefox Linux');
@ -92,18 +97,24 @@ test.describe('installed image', () => {
test('all browsers work headed', async ({ exec }) => { test('all browsers work headed', async ({ exec }) => {
await exec('npm i --foreground-scripts @playwright/test'); 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).toContain('@chromium');
expect(result).not.toContain('Headless'); expect(result).not.toContain('Headless');
expect(result).toContain(' Chrome/'); 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('@webkit');
expect(result).toContain(' Version/'); 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');
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 }) => { test('screenshots should use __screenshots__ folder', async ({ exec, tmpWorkspace }) => {
await exec('npm i --foreground-scripts @playwright/test'); 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, 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__', 'firefox', 'docker.spec.js', 'img.png')).toExistOnFS();
await expect(path.join(tmpWorkspace, '__screenshots__', 'chromium', '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) => { server.setRoute('/', (request, response) => {
response.end('Hello from host'); 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: { env: {
PLAYWRIGHT_DOCKER: '1',
TEST_PORT: TEST_PORT + '', TEST_PORT: TEST_PORT + '',
}, },
}); });