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:
parent
30ff27843a
commit
b09ea69024
|
|
@ -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;
|
|
||||||
```
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 + '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue