From c910a9be19c730c51868e5eed3cfc7899ce23a55 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Tue, 9 Aug 2022 18:12:43 +0300 Subject: [PATCH 01/15] feat: introduce docker integration This patch introduces the following commands: - `npx playwright docker build` that builds a VRT docker image locally that is based off the `mcr.microsoft.com/playwright:jammy` - `npx playwright docker start` that launches a docker container with browsers. - `npx playwright docker stop` that stops given docker container. - `npx playwright docker test` that runs all the tests inside a launched docker container. --- docs/src/docker.md | 42 ++ packages/playwright-core/package.json | 1 + packages/playwright-core/src/cli/docker.ts | 389 ++++++++++++++++++ packages/playwright-test/src/cli.ts | 91 +++- packages/playwright-test/src/index.ts | 12 +- tests/installation/docker-integration.spec.ts | 128 ++++++ .../fixture-scripts/docker.spec.js | 19 + tests/installation/globalSetup.ts | 63 +-- utils/docker/Dockerfile.jammy | 5 +- 9 files changed, 715 insertions(+), 35 deletions(-) create mode 100644 packages/playwright-core/src/cli/docker.ts create mode 100755 tests/installation/docker-integration.spec.ts create mode 100644 tests/installation/fixture-scripts/docker.spec.js diff --git a/docs/src/docker.md b/docs/src/docker.md index dee119bcb43a6..77ea348e48014 100644 --- a/docs/src/docker.md +++ b/docs/src/docker.md @@ -145,3 +145,45 @@ The image will be tagged as `playwright:localbuild-focal` and could be run as: ``` docker run --rm -it playwright:localbuild /bin/bash ``` + +## (Experimental) Playwright Test Docker Integration +* langs: js + +Playwright Test now ships an **experimental** docker integration. +With this integration, **only** browser binaries are running inside a docker container, +while all the code is still running on the host operating system. + +Docker container provides a consistent environment, eliminating browser rendering +differences across platforms. Playwright Test will automatically proxy host network +into the container, so browsers can access servers running on the host. + +:::note +Docker integration requires Docker installed & running on your computer. +See https://docs.docker.com/get-docker/ +::: + +Docker integration usage: + +1. Build a local docker image that will be used to run containers. This step + should be done only once. + ```bash js + npx playwright docker build + ``` + +2. Run docker container in the background. + ```bash js + npx playwright docker start + ``` + +3. Run tests inside docker container. Note that this command accepts all the same arguments + as a regular `npx playwright test` command. + ```bash js + npx playwright docker test + ``` + + Note that this command will detect running docker container, and auto-launch it if needed. + +4. Finally, stop docker container when it is no longer needed. + ```bash js + npx playwright docker stop + ``` diff --git a/packages/playwright-core/package.json b/packages/playwright-core/package.json index 9e1a6f4d90de9..816bff1b2f7a6 100644 --- a/packages/playwright-core/package.json +++ b/packages/playwright-core/package.json @@ -20,6 +20,7 @@ }, "./cli": "./cli.js", "./package.json": "./package.json", + "./lib/cli/docker": "./lib/cli/docker.js", "./lib/grid/gridServer": "./lib/grid/gridServer.js", "./lib/outofprocess": "./lib/outofprocess.js", "./lib/utils": "./lib/utils/index.js", diff --git a/packages/playwright-core/src/cli/docker.ts b/packages/playwright-core/src/cli/docker.ts new file mode 100644 index 0000000000000..41287068f3928 --- /dev/null +++ b/packages/playwright-core/src/cli/docker.ts @@ -0,0 +1,389 @@ +/** + * 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. + */ +/* eslint-disable no-console */ + +import http from 'http'; +import * as utils from '../utils'; +import { spawnAsync } from '../utils/spawnAsync'; +import { getPlaywrightVersion } from '../common/userAgent'; + +interface DockerImage { + Containers: number; + Created: number; + Id: string; + Labels: null | Record; + ParentId: string; + RepoDigests: null | string[]; + RepoTags: null | string[]; + SharedSize: number; + Size: number; + VirtualSize: number; +} + +const VRT_IMAGE_DISTRO = 'jammy'; +const VRT_IMAGE_NAME = `playwright:local-${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}`; +const VRT_CONTAINER_NAME = `playwright-${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}`; + +const GENERATE_FLUXBOX_BROWSERS_MENU = ` + const { chromium, firefox, webkit } = require('playwright-core'); + + console.log(\` + [begin] (fluxbox) + [submenu] (Browsers) {} + [exec] (Chromium) { $\{chromium.executablePath()} --no-sandbox --test-type= } <> + [exec] (Firefox) { $\{firefox.executablePath()} } <> + [exec] (WebKit) { $\{webkit.executablePath()} } <> + [end] + [include] (/etc/X11/fluxbox/fluxbox-menu) + [end] + \`); +`; + +const CONTAINER_ENTRY_POINT = `#!/bin/bash + set -e + SCREEN_WIDTH=1360 + SCREEN_HEIGHT=1020 + SCREEN_DEPTH=24 + SCREEN_DPI=96 + GEOMETRY="$SCREEN_WIDTH""x""$SCREEN_HEIGHT""x""$SCREEN_DEPTH" + + nohup /usr/bin/xvfb-run --server-num=$DISPLAY_NUM \ + --listen-tcp \ + --server-args="-screen 0 "$GEOMETRY" -fbdir /var/tmp -dpi "$SCREEN_DPI" -listen tcp -noreset -ac +extension RANDR" \ + /usr/bin/fluxbox -display "$DISPLAY" >/dev/null 2>&1 & + + for i in $(seq 1 50) + do + if xdpyinfo -display $DISPLAY >/dev/null 2>&1; then + break + fi + echo "Waiting for Xvfb..." + sleep 0.2 + done + + + nohup x11vnc -forever -shared -rfbport 5900 -rfbportv6 5900 -display "$DISPLAY" >/dev/null 2>&1 & + nohup /opt/bin/noVNC/utils/novnc_proxy --listen 7900 --vnc localhost:5900 >/dev/null 2>&1 & + cd /ms-playwright-agent + NOVNC_UUID=$(cat /proc/sys/kernel/random/uuid) + echo "novnc is listening on http://127.0.0.1:7900?path=$NOVNC_UUID&resize=scale" + PW_UUID=$(cat /proc/sys/kernel/random/uuid) + npx playwright run-server --port=5400 --path=/$PW_UUID +`; + +const NOVNC_REF = '1.3.0'; +const WEBSOCKIFY_REF = '0.10.0'; +const CONTAINER_BUILD_SCRIPT = ` + # Generate entry point script + cat <<'EOF' >/start.sh + ${CONTAINER_ENTRY_POINT} + EOF + chmod 755 /start.sh + + export DEBIAN_FRONTEND=noninteractive + + # Install FluxBox, VNC & noVNC + mkdir -p /opt/bin && chmod +x /dev/shm \ + && apt-get update && apt-get install -y unzip fluxbox x11vnc \ + && curl -L -o noVNC.zip "https://github.com/novnc/noVNC/archive/v${NOVNC_REF}.zip" \ + && unzip -x noVNC.zip \ + && rm -rf noVNC-${NOVNC_REF}/{docs,tests} \ + && mv noVNC-${NOVNC_REF} /opt/bin/noVNC \ + && cp /opt/bin/noVNC/vnc.html /opt/bin/noVNC/index.html \ + && rm noVNC.zip \ + && curl -L -o websockify.zip "https://github.com/novnc/websockify/archive/v${WEBSOCKIFY_REF}.zip" \ + && unzip -x websockify.zip \ + && rm websockify.zip \ + && rm -rf websockify-${WEBSOCKIFY_REF}/{docs,tests} \ + && mv websockify-${WEBSOCKIFY_REF} /opt/bin/noVNC/utils/websockify + + # Configure FluxBox menus + cd /ms-playwright-agent + cat <<'EOF' | node > configuration.txt + ${GENERATE_FLUXBOX_BROWSERS_MENU} + EOF + mkdir /root/.fluxbox && mv /ms-playwright-agent/configuration.txt /root/.fluxbox/menu +`.split('\n').map(line => line.substring(2)).join('\n'); + +export async function deleteImage() { + const dockerImage = await findDockerImage(VRT_IMAGE_NAME); + if (!dockerImage) + return; + + if (await containerInfo()) + await stopContainer(); + await callDockerAPI('delete', `/images/${dockerImage.Id}`); +} + +export async function buildImage() { + const isDevelopmentMode = getPlaywrightVersion().includes('next'); + let baseImageName = `mcr.microsoft.com/playwright:v${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}`; + // 1. Build or pull base image. + if (isDevelopmentMode) { + // 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([ + `You are in DEVELOPMENT mode!`, + ``, + `1. Build local base image`, + ` ./utils/docker/build.sh ${arch} ${VRT_IMAGE_DISTRO} playwright:localbuild`, + `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 { + const { code } = await spawnAsync('docker', ['pull', baseImageName], { stdio: 'inherit' }); + if (code !== 0) + throw new Error('Failed to pull docker image!'); + } + // 2. Find pulled docker image + const dockerImage = await findDockerImage(baseImageName); + if (!dockerImage) + throw new Error(`Failed to pull ${baseImageName}`); + // 3. Launch container and install VNC in it + console.log(`Building ${VRT_IMAGE_NAME}...`); + const containerId = await launchContainer({ + image: dockerImage, + autoRemove: false, + command: ['/bin/bash', '-c', CONTAINER_BUILD_SCRIPT], + }); + await postJSON(`/containers/${containerId}/wait`); + + // 4. Commit a new image based on the launched container with installed VNC & noVNC. + const [vrtRepo, vrtTag] = VRT_IMAGE_NAME.split(':'); + await postJSON(`/commit?container=${containerId}&repo=${vrtRepo}&tag=${vrtTag}`, { + Entrypoint: ['/start.sh'], + Env: [ + 'DISPLAY_NUM=99', + 'DISPLAY=:99', + ], + }); + await Promise.all([ + // Make sure to wait for the container to be removed. + postJSON(`/containers/${containerId}/wait?condition=removed`), + callDockerAPI('delete', `/containers/${containerId}`), + ]); + console.log(`Done!`); +} + +interface ContainerInfo { + wsEndpoint: string; + vncSession: string; +} + +export async function containerInfo(): Promise { + const containerId = await findRunningDockerContainerId(); + if (!containerId) + return undefined; + const rawLogs = await callDockerAPI('get', `/containers/${containerId}/logs?stdout=true&stderr=true`).catch(e => ''); + if (!rawLogs) + return undefined; + // Docker might prefix every log line with 8 characters. Stip them out. + // See https://github.com/moby/moby/issues/7375 + // This doesn't happen if the containers is launched manually with attached terminal. + const logLines = rawLogs.split('\n').map(line => { + if ([0, 1, 2].includes(line.charCodeAt(0))) + return line.substring(8); + return line; + }); + const WS_LINE_PREFIX = 'Listening on ws://'; + const webSocketLine = logLines.find(line => line.startsWith(WS_LINE_PREFIX)); + const NOVNC_LINE_PREFIX = 'novnc is listening on '; + const novncLine = logLines.find(line => line.startsWith(NOVNC_LINE_PREFIX)); + return novncLine && webSocketLine ? { + wsEndpoint: 'ws://' + webSocketLine.substring(WS_LINE_PREFIX.length), + vncSession: novncLine.substring(NOVNC_LINE_PREFIX.length), + } : undefined; +} + +export async function ensureContainerOrDie(): Promise { + const pwImage = await findDockerImage(VRT_IMAGE_NAME); + if (!pwImage) { + console.error('\n' + utils.wrapInASCIIBox([ + `Failed to find local docker image.`, + `Please build local docker image with the following command:`, + ``, + ` npx playwright docker build`, + ``, + `<3 Playwright Team`, + ].join('\n'), 1)); + process.exit(1); + } + + let info = await containerInfo(); + if (info) + return info; + + await launchContainer({ + image: pwImage, + name: VRT_CONTAINER_NAME, + autoRemove: true, + ports: [5400, 7900], + }); + + // Wait for the service to become available. + const startTime = Date.now(); + const timeouts = [0, 100, 100, 200, 500, 1000]; + do { + await new Promise(x => setTimeout(x, timeouts.shift() ?? 1000)); + info = await containerInfo(); + } while (!info && Date.now() < startTime + 60000); + + if (!info) + throw new Error('Failed to launch docker container!'); + return info; +} + +export async function stopContainer() { + const containerId = await findRunningDockerContainerId(); + if (!containerId) { + console.log(`Container is not running.`); + return undefined; + } + await Promise.all([ + // Make sure to wait for the container to be removed. + postJSON(`/containers/${containerId}/wait?condition=removed`), + postJSON(`/containers/${containerId}/kill`), + ]); +} + +export async function ensureDockerEngineIsRunningOrDie() { + try { + await callDockerAPI('get', '/info'); + } catch (e) { + console.error(utils.wrapInASCIIBox([ + `Docker is not running!`, + `Please install if necessary and launch docker first:`, + ``, + ` https://docs.docker.com/get-docker`, + ``, + ].join('\n'), 1)); + process.exit(1); + } +} + +async function findDockerImage(imageName: string): Promise { + const images: DockerImage[] | null = await getJSON('/images/json'); + return images ? images.find(image => image.RepoTags?.includes(imageName)) : undefined; +} + +interface Container { + ImageID: string; + State: string; + Names: [string]; + Id: string; +} + +async function findRunningDockerContainerId(): Promise { + const containers: (Container[]|undefined) = await getJSON('/containers/json'); + if (!containers) + return undefined; + // 1. Try findind a container with our name. This happens if the container is launched + // automatically with `npx playwright docker start`. + let container = containers.find((container: Container) => container.Names.some(name => name.includes(VRT_CONTAINER_NAME))); + // 2. Alternatively, the container might be launched manually with a direct docker command. + // In this case, we should look for a container with a proper base image. + if (!container) { + const dockerImage = await findDockerImage(VRT_IMAGE_NAME); + container = dockerImage ? containers.find((container: Container) => container.ImageID === dockerImage.Id) : undefined; + } + return container?.State === 'running' ? container.Id : undefined; +} + +interface ContainerOptions { + image: DockerImage; + autoRemove: boolean; + command?: string[]; + ports?: Number[]; + name?: string; +} + +async function launchContainer(options: ContainerOptions): Promise { + const ExposedPorts: any = {}; + const PortBindings: any = {}; + for (const port of (options.ports ?? [])) { + ExposedPorts[`${port}/tcp`] = {}; + PortBindings[`${port}/tcp`] = [{ HostPort: port + '' }]; + } + const container = await postJSON(`/containers/create` + (options.name ? '?name=' + options.name : ''), { + Cmd: options.command, + AttachStdout: true, + AttachStderr: true, + Image: options.image.Id, + ExposedPorts, + HostConfig: { + Init: true, + AutoRemove: options.autoRemove, + ShmSize: 2 * 1024 * 1024 * 1024, + PortBindings, + }, + }); + await postJSON(`/containers/${container.Id}/start`); + return container.Id; +} + +async function getJSON(url: string): Promise { + const result = await callDockerAPI('get', url); + if (!result) + return result; + return JSON.parse(result); +} + +async function postJSON(url: string, json: any = undefined) { + const result = await callDockerAPI('post', url, json ? JSON.stringify(json) : undefined); + if (!result) + return result; + return JSON.parse(result); +} + +const DOCKER_API_VERSION = '1.41'; + +function callDockerAPI(method: 'post'|'get'|'delete', url: string, body: Buffer|string|undefined = undefined): Promise { + const dockerSocket = process.platform === 'win32' ? '\\\\.\\pipe\\docker_engine' : '/var/run/docker.sock'; + return new Promise((resolve, reject) => { + const request = http.request({ + socketPath: dockerSocket, + path: `/v${DOCKER_API_VERSION}${url}`, + method, + }, (response: http.IncomingMessage) => { + let body = ''; + response.on('data', function(chunk){ + body += chunk; + }); + response.on('end', function(){ + if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 300) + reject(new Error(`${method} ${url} FAILED with statusCode ${response.statusCode} and body\n${body}`)); + else + resolve(body); + }); + }); + request.on('error', function(e){ + reject(e); + }); + if (body) { + request.setHeader('Content-Type', 'application/json'); + request.setHeader('Content-Length', body.length); + request.write(body); + } else { + request.setHeader('Content-Type', 'text/plain'); + } + request.end(); + }); +} + diff --git a/packages/playwright-test/src/cli.ts b/packages/playwright-test/src/cli.ts index a2cbcdf959641..8cfdf60458701 100644 --- a/packages/playwright-test/src/cli.ts +++ b/packages/playwright-test/src/cli.ts @@ -17,9 +17,11 @@ /* eslint-disable no-console */ import type { Command } from 'playwright-core/lib/utilsBundle'; +import * as docker from 'playwright-core/lib/cli/docker'; import fs from 'fs'; import url from 'url'; import path from 'path'; +import { colors } from 'playwright-core/lib/utilsBundle'; import { Runner, builtInReporters, kDefaultConfigFiles } from './runner'; import type { ConfigCLIOverrides } from './runner'; import { stopProfiling, startProfiling } from './profiler'; @@ -29,14 +31,68 @@ import { baseFullConfig, defaultTimeout, fileIsModule } from './loader'; import type { TraceMode } from './types'; export function addTestCommands(program: Command) { - addTestCommand(program); + addTestCommand(program, false /* isDocker */); addShowReportCommand(program); addListFilesCommand(program); + addDockerCommand(program); } -function addTestCommand(program: Command) { +function addDockerCommand(program: Command) { + const dockerCommand = program.command('docker') + .description('EXPERIMENTAL (might change in future releases) docker integration.'); + + dockerCommand.command('build') + .description('Build local docker image') + .action(async function(options) { + await docker.ensureDockerEngineIsRunningOrDie(); + await docker.buildImage(); + }); + + dockerCommand.command('start') + .description('Start docker container') + .action(async function(options) { + await docker.ensureDockerEngineIsRunningOrDie(); + if (await docker.containerInfo()) { + console.log('Container is already running.'); + return; + } + process.stdout.write(`Launching docker container... `); + const time = Date.now(); + const info = await docker.ensureContainerOrDie(); + const deltaMs = (Date.now() - time); + console.log('Done in ' + (deltaMs / 1000).toFixed(1) + 's'); + console.log([ + `- VNC session: ${info.vncSession}`, + `- Run tests with browsers inside container:`, + ` npx playwright docker test`, + `- Stop container *manually* when it is no longer needed:`, + ` npx playwright docker stop`, + ].join('\n')); + }); + + dockerCommand.command('delete-image', { hidden: true }) + .description('Delete docker image, if any') + .action(async function(options) { + await docker.ensureDockerEngineIsRunningOrDie(); + await docker.deleteImage(); + }); + + dockerCommand.command('stop') + .description('Stop docker container') + .action(async function(options) { + await docker.ensureDockerEngineIsRunningOrDie(); + await docker.stopContainer(); + }); + + addTestCommand(dockerCommand, true /* isDocker */); +} + +function addTestCommand(program: Command, isDocker: boolean) { const command = program.command('test [test-filter...]'); - command.description('Run tests with Playwright Test'); + if (isDocker) + command.description('Run tests with Playwright Test and browsers inside docker container'); + else + 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`); @@ -64,6 +120,27 @@ function addTestCommand(program: Command) { command.option('-x', `Stop after the first failure`); command.action(async (args, opts) => { try { + if (isDocker && !process.env.PW_TS_ESM_ON) { + await docker.ensureDockerEngineIsRunningOrDie(); + let info = await docker.containerInfo(); + if (info) { + console.log(colors.yellow('NOTE: Using docker container to run browsers')); + console.log(colors.yellow(` VNC: ${info.vncSession}`)); + } else { + process.stdout.write(colors.dim(`Launching docker container... `)); + const time = Date.now(); + info = await docker.ensureContainerOrDie(); + const deltaMs = (Date.now() - time); + console.log(colors.dim('Done in ' + (deltaMs / 1000).toFixed(1) + 's')); + console.log(colors.yellow('NOTE: Using docker container to run browsers')); + console.log(colors.yellow(` VNC: ${info.vncSession}`)); + } + process.env.PW_TEST_CONNECT_WS_ENDPOINT = info.wsEndpoint; + process.env.PW_TEST_CONNECT_HEADERS = JSON.stringify({ + 'x-playwright-proxy': '*', + }); + process.env.PW_TEST_IS_DOCKER = '1'; + } await runTests(args, opts); } catch (e) { console.error(e); @@ -75,10 +152,10 @@ Arguments [test-filter...]: Pass arguments to filter test files. Each argument is treated as a regular expression. Examples: - $ npx playwright test my.spec.ts - $ npx playwright test some.spec.ts:42 - $ npx playwright test --headed - $ npx playwright test --browser=webkit`); + $ 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`); } function addListFilesCommand(program: Command) { diff --git a/packages/playwright-test/src/index.ts b/packages/playwright-test/src/index.ts index aadc12f9a6360..29e52f759c5e9 100644 --- a/packages/playwright-test/src/index.ts +++ b/packages/playwright-test/src/index.ts @@ -51,6 +51,7 @@ type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _browserOptions: LaunchOptions; _artifactsDir: () => string; _snapshotSuffix: string; + _isDocker: boolean; }; export const test = _baseTest.extend({ @@ -72,7 +73,10 @@ export const test = _baseTest.extend({ headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: 'worker', option: true }], channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: 'worker', option: true }], launchOptions: [{}, { scope: 'worker', option: true }], - connectOptions: [process.env.PW_TEST_CONNECT_WS_ENDPOINT ? { wsEndpoint: process.env.PW_TEST_CONNECT_WS_ENDPOINT } : undefined, { scope: 'worker', option: true }], + connectOptions: [process.env.PW_TEST_CONNECT_WS_ENDPOINT ? { + wsEndpoint: process.env.PW_TEST_CONNECT_WS_ENDPOINT, + headers: process.env.PW_TEST_CONNECT_HEADERS ? JSON.parse(process.env.PW_TEST_CONNECT_HEADERS) : undefined, + } : undefined, { scope: 'worker', option: true }], screenshot: ['off', { scope: 'worker', option: true }], video: ['off', { scope: 'worker', option: true }], trace: ['off', { scope: 'worker', option: true }], @@ -220,7 +224,11 @@ export const test = _baseTest.extend({ }); }, - _snapshotSuffix: [process.platform, { scope: 'worker' }], + _isDocker: [!!process.env.PW_TEST_IS_DOCKER, { scope: 'worker' }], + + _snapshotSuffix: [async ({ _isDocker }, use, testInfo) => { + await use(_isDocker ? 'docker' : process.platform); + }, { scope: 'worker' }], _setupContextOptionsAndArtifacts: [async ({ playwright, _snapshotSuffix, _combinedContextOptions, _browserOptions, _artifactsDir, trace, screenshot, actionTimeout, navigationTimeout }, use, testInfo) => { testInfo.snapshotSuffix = _snapshotSuffix; diff --git a/tests/installation/docker-integration.spec.ts b/tests/installation/docker-integration.spec.ts new file mode 100755 index 0000000000000..2a38b50de351b --- /dev/null +++ b/tests/installation/docker-integration.spec.ts @@ -0,0 +1,128 @@ +/** + * 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. + */ +import { test, expect } from './npmTest'; +import * as path from 'path'; +import * as fs from 'fs'; +import { TestServer } from '../../utils/testserver'; + +// Skipping docker tests on CI on non-linux since GHA does not have +// Docker engine installed on macOS and Windows. +test.skip(() => process.env.CI && process.platform !== 'linux'); + +test.beforeAll(async ({ exec }) => { + // Delete any previous docker image to ensure clean run. + await exec('npx playwright docker delete-image', { + cwd: path.join(__dirname, '..', '..'), + }); +}); + +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', { + expectToExitWithError: true, + }); + expect(result).toContain('npx playwright docker build'); +}); + +test.describe('installed image', () => { + test.beforeAll(async ({ exec }) => { + await exec('npx playwright docker build', { + env: { PWTEST_DOCKER_BASE_IMAGE: 'playwright:installation-tests-jammy' }, + cwd: path.join(__dirname, '..', '..'), + }); + }); + test.afterAll(async ({ exec }) => { + await exec('npx playwright docker delete-image', { + cwd: path.join(__dirname, '..', '..'), + }); + }); + + test('make sure it tells to run `npx playwright docker start`', async ({ exec }) => { + await exec('npm i --foreground-scripts @playwright/test'); + await exec('npx playwright docker test docker.spec.js --grep userAgent'); + }); + + test.describe('running container', () => { + test.beforeAll(async ({ exec }) => { + await exec('npx playwright docker start', { + cwd: path.join(__dirname, '..', '..'), + }); + }); + + test.afterAll(async ({ exec }) => { + await exec('npx playwright docker stop', { + cwd: path.join(__dirname, '..', '..'), + }); + }); + + 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'); + expect(result).toContain('@chromium Linux'); + expect(result).toContain('@webkit Linux'); + expect(result).toContain('@firefox Linux'); + }); + + 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`); + 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`); + expect(result).toContain('@webkit'); + expect(result).toContain(' Version/'); + } + { + const result = await exec(`npx playwright docker test docker.spec.js --headed --grep userAgent --browser firefox`); + expect(result).toContain('@firefox'); + expect(result).toContain(' Firefox/'); + } + }); + + test('screenshots have docker suffix', async ({ exec, tmpWorkspace }) => { + await exec('npm i --foreground-scripts @playwright/test'); + await exec('npx playwright docker test docker.spec.js --grep screenshot --browser all', { + expectToExitWithError: true, + }); + const files = await fs.promises.readdir(path.join(tmpWorkspace, 'docker.spec.js-snapshots')); + expect(files).toContain('img-chromium-docker.png'); + expect(files).toContain('img-firefox-docker.png'); + expect(files).toContain('img-webkit-docker.png'); + }); + + test('port forwarding works', async ({ exec, tmpWorkspace }) => { + await exec('npm i --foreground-scripts @playwright/test'); + const TEST_PORT = 8425; + const server = await TestServer.create(tmpWorkspace, TEST_PORT); + server.setRoute('/', (request, response) => { + response.end('Hello from host'); + }); + const result = await exec('npx playwright docker test docker.spec.js --grep localhost --browser all', { + env: { + TEST_PORT: TEST_PORT + '', + }, + }); + expect(result).toContain('@chromium Hello from host'); + expect(result).toContain('@webkit Hello from host'); + expect(result).toContain('@firefox Hello from host'); + }); + }); +}); + diff --git a/tests/installation/fixture-scripts/docker.spec.js b/tests/installation/fixture-scripts/docker.spec.js new file mode 100644 index 0000000000000..edc16f1133a51 --- /dev/null +++ b/tests/installation/fixture-scripts/docker.spec.js @@ -0,0 +1,19 @@ +const { test, expect } = require('@playwright/test'); + +test('platform', async ({ page }) => { + console.log('@' + page.context().browser().browserType().name(), await page.evaluate(() => navigator.platform)); +}); + +test('userAgent', async ({ page }) => { + console.log('@' + page.context().browser().browserType().name(), await page.evaluate(() => navigator.userAgent)); +}); + +test('screenshot', async ({ page }) => { + await expect(page).toHaveScreenshot('img.png'); +}); + +test('localhost', async ({ page }) => { + expect(process.env.TEST_PORT).toBeTruthy(); + await page.goto('http://localhost:' + process.env.TEST_PORT); + console.log('@' + page.context().browser().browserType().name(), await page.textContent('body')); +}); diff --git a/tests/installation/globalSetup.ts b/tests/installation/globalSetup.ts index 936ecb0b13202..fba3e998b19d1 100644 --- a/tests/installation/globalSetup.ts +++ b/tests/installation/globalSetup.ts @@ -21,40 +21,55 @@ import fs from 'fs'; import { TMP_WORKSPACES } from './npmTest'; const PACKAGE_BUILDER_SCRIPT = path.join(__dirname, '..', '..', 'utils', 'pack_package.js'); +const DOCKER_BUILDER_SCRIPT = path.join(__dirname, '..', '..', 'utils', 'docker', 'build.sh'); async function globalSetup() { await promisify(rimraf)(TMP_WORKSPACES); console.log(`Temporary workspaces will be created in ${TMP_WORKSPACES}. They will not be removed at the end. Set DEBUG=itest to determine which sub-dir a specific test is using.`); await fs.promises.mkdir(TMP_WORKSPACES, { recursive: true }); + if (process.env.PWTEST_INSTALLATION_TEST_SKIP_PACKAGE_BUILDS) { console.log('Skipped building packages. Unset PWTEST_INSTALLATION_TEST_SKIP_PACKAGE_BUILDS to build packages.'); - return; - } + } else { + console.log('Building packages. Set PWTEST_INSTALLATION_TEST_SKIP_PACKAGE_BUILDS to skip.'); + const outputDir = path.join(__dirname, 'output'); + await promisify(rimraf)(outputDir); + await fs.promises.mkdir(outputDir, { recursive: true }); + + const build = async (buildTarget: string, pkgNameOverride?: string) => { + const outPath = path.resolve(path.join(outputDir, `${buildTarget}.tgz`)); + const { code, stderr, stdout } = await spawnAsync('node', [PACKAGE_BUILDER_SCRIPT, buildTarget, outPath]); + if (!!code) + throw new Error(`Failed to build: ${buildTarget}:\n${stderr}\n${stdout}`); + console.log('Built:', pkgNameOverride || buildTarget); + return [pkgNameOverride || buildTarget, outPath]; + }; - console.log('Building packages. Set PWTEST_INSTALLATION_TEST_SKIP_PACKAGE_BUILDS to skip.'); - const outputDir = path.join(__dirname, 'output'); - await promisify(rimraf)(outputDir); - await fs.promises.mkdir(outputDir, { recursive: true }); + const builds = await Promise.all([ + build('playwright-core'), + build('playwright-test', '@playwright/test'), + build('playwright'), + build('playwright-chromium'), + build('playwright-firefox'), + build('playwright-webkit'), + ]); - const build = async (buildTarget: string, pkgNameOverride?: string) => { - const outPath = path.resolve(path.join(outputDir, `${buildTarget}.tgz`)); - const { code, stderr, stdout } = await spawnAsync('node', [PACKAGE_BUILDER_SCRIPT, buildTarget, outPath]); + await fs.promises.writeFile(path.join(__dirname, '.registry.json'), JSON.stringify(Object.fromEntries(builds))); + } + + if (process.env.CI && process.platform !== 'linux') { + console.log('Skipped building docker: docker tests are not supported on Windows and macOS Github Actions.'); + } else if (process.env.PWTEST_INSTALLATION_TEST_SKIP_DOCKER_BUILD) { + console.log('Skipped building docker. Unset PWTEST_INSTALLATION_TEST_SKIP_DOCKER_BUILD to build docker.'); + } else { + console.log('Building docker. Set PWTEST_INSTALLATION_TEST_SKIP_DOCKER_BUILD to skip.'); + const DOCKER_IMAGE_NAME = 'playwright:installation-tests-jammy'; + const arch = process.arch === 'arm64' ? '--arm64' : '--amd64'; + const { code, stderr, stdout } = await spawnAsync('bash', [DOCKER_BUILDER_SCRIPT, arch, 'jammy', DOCKER_IMAGE_NAME]); if (!!code) - throw new Error(`Failed to build: ${buildTarget}:\n${stderr}\n${stdout}`); - console.log('Built:', pkgNameOverride || buildTarget); - return [pkgNameOverride || buildTarget, outPath]; - }; - - const builds = await Promise.all([ - build('playwright-core'), - build('playwright-test', '@playwright/test'), - build('playwright'), - build('playwright-chromium'), - build('playwright-firefox'), - build('playwright-webkit'), - ]); - - await fs.promises.writeFile(path.join(__dirname, '.registry.json'), JSON.stringify(Object.fromEntries(builds))); + throw new Error(`Failed to build docker:\n${stderr}\n${stdout}`); + console.log('Built: docker image ', DOCKER_IMAGE_NAME); + } } export default globalSetup; diff --git a/utils/docker/Dockerfile.jammy b/utils/docker/Dockerfile.jammy index 63868afbdd5fd..683d80bf31576 100644 --- a/utils/docker/Dockerfile.jammy +++ b/utils/docker/Dockerfile.jammy @@ -27,7 +27,9 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright # The package should be built beforehand from tip-of-tree Playwright. COPY ./playwright-core.tar.gz /tmp/playwright-core.tar.gz -# 2. Bake in browsers & deps. +# 2. Bake in Playwright Agent. +# Playwright Agent is used to bake in browsers and browser dependencies, +# and run docker server later on. # Browsers will be downloaded in `/ms-playwright`. # Note: make sure to set 777 to the registry so that any user can access # registry. @@ -38,5 +40,4 @@ RUN mkdir /ms-playwright && \ npx playwright mark-docker-image "${DOCKER_IMAGE_NAME_TEMPLATE}" && \ npx playwright install --with-deps && rm -rf /var/lib/apt/lists/* && \ rm /tmp/playwright-core.tar.gz && \ - rm -rf /ms-playwright-agent && \ chmod -R 777 /ms-playwright From 7f20c790e474eba280dc535622e0de29b6bf5af7 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 8 Sep 2022 18:43:18 +0200 Subject: [PATCH 02/15] jammy -> focal --- packages/playwright-core/src/cli/docker.ts | 2 +- tests/installation/docker-integration.spec.ts | 2 +- tests/installation/globalSetup.ts | 4 ++-- utils/docker/Dockerfile.focal | 5 +++-- utils/docker/Dockerfile.jammy | 7 +++---- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/playwright-core/src/cli/docker.ts b/packages/playwright-core/src/cli/docker.ts index 41287068f3928..539c1727edb52 100644 --- a/packages/playwright-core/src/cli/docker.ts +++ b/packages/playwright-core/src/cli/docker.ts @@ -33,7 +33,7 @@ interface DockerImage { VirtualSize: number; } -const VRT_IMAGE_DISTRO = 'jammy'; +const VRT_IMAGE_DISTRO = 'focal'; const VRT_IMAGE_NAME = `playwright:local-${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}`; const VRT_CONTAINER_NAME = `playwright-${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}`; diff --git a/tests/installation/docker-integration.spec.ts b/tests/installation/docker-integration.spec.ts index 2a38b50de351b..c067e8985dc2e 100755 --- a/tests/installation/docker-integration.spec.ts +++ b/tests/installation/docker-integration.spec.ts @@ -40,7 +40,7 @@ test('make sure it tells to run `npx playwright docker build` when image is not test.describe('installed image', () => { test.beforeAll(async ({ exec }) => { await exec('npx playwright docker build', { - env: { PWTEST_DOCKER_BASE_IMAGE: 'playwright:installation-tests-jammy' }, + env: { PWTEST_DOCKER_BASE_IMAGE: 'playwright:installation-tests-focal' }, cwd: path.join(__dirname, '..', '..'), }); }); diff --git a/tests/installation/globalSetup.ts b/tests/installation/globalSetup.ts index fba3e998b19d1..0518517e65c41 100644 --- a/tests/installation/globalSetup.ts +++ b/tests/installation/globalSetup.ts @@ -63,9 +63,9 @@ async function globalSetup() { console.log('Skipped building docker. Unset PWTEST_INSTALLATION_TEST_SKIP_DOCKER_BUILD to build docker.'); } else { console.log('Building docker. Set PWTEST_INSTALLATION_TEST_SKIP_DOCKER_BUILD to skip.'); - const DOCKER_IMAGE_NAME = 'playwright:installation-tests-jammy'; + const DOCKER_IMAGE_NAME = 'playwright:installation-tests-focal'; const arch = process.arch === 'arm64' ? '--arm64' : '--amd64'; - const { code, stderr, stdout } = await spawnAsync('bash', [DOCKER_BUILDER_SCRIPT, arch, 'jammy', DOCKER_IMAGE_NAME]); + const { code, stderr, stdout } = await spawnAsync('bash', [DOCKER_BUILDER_SCRIPT, arch, 'focal', DOCKER_IMAGE_NAME]); if (!!code) throw new Error(`Failed to build docker:\n${stderr}\n${stdout}`); console.log('Built: docker image ', DOCKER_IMAGE_NAME); diff --git a/utils/docker/Dockerfile.focal b/utils/docker/Dockerfile.focal index 81bac7369c90c..f6db13a34a2fb 100644 --- a/utils/docker/Dockerfile.focal +++ b/utils/docker/Dockerfile.focal @@ -27,7 +27,9 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright # The package should be built beforehand from tip-of-tree Playwright. COPY ./playwright-core.tar.gz /tmp/playwright-core.tar.gz -# 2. Bake in browsers & deps. +# 2. Bake in Playwright Agent. +# Playwright Agent is used to bake in browsers and browser dependencies, +# and run docker server later on. # Browsers will be downloaded in `/ms-playwright`. # Note: make sure to set 777 to the registry so that any user can access # registry. @@ -38,5 +40,4 @@ RUN mkdir /ms-playwright && \ npx playwright mark-docker-image "${DOCKER_IMAGE_NAME_TEMPLATE}" && \ npx playwright install --with-deps && rm -rf /var/lib/apt/lists/* && \ rm /tmp/playwright-core.tar.gz && \ - rm -rf /ms-playwright-agent && \ chmod -R 777 /ms-playwright diff --git a/utils/docker/Dockerfile.jammy b/utils/docker/Dockerfile.jammy index 683d80bf31576..5dedca7d51d8b 100644 --- a/utils/docker/Dockerfile.jammy +++ b/utils/docker/Dockerfile.jammy @@ -25,11 +25,9 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright # 1. Add tip-of-tree Playwright package to install its browsers. # The package should be built beforehand from tip-of-tree Playwright. -COPY ./playwright-core.tar.gz /tmp/playwright-core.tar.gz -# 2. Bake in Playwright Agent. -# Playwright Agent is used to bake in browsers and browser dependencies, -# and run docker server later on. +COPY ./playwright-core.tar.gz /tmp/playwright-core.tar.gz +# 2. Bake in browsers & deps. # Browsers will be downloaded in `/ms-playwright`. # Note: make sure to set 777 to the registry so that any user can access # registry. @@ -40,4 +38,5 @@ RUN mkdir /ms-playwright && \ npx playwright mark-docker-image "${DOCKER_IMAGE_NAME_TEMPLATE}" && \ npx playwright install --with-deps && rm -rf /var/lib/apt/lists/* && \ rm /tmp/playwright-core.tar.gz && \ + rm -rf /ms-playwright-agent && \ chmod -R 777 /ms-playwright From c18738bdaa9bda3c36f7451e0b705f2452a3a22c Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Thu, 8 Sep 2022 13:38:43 -0400 Subject: [PATCH 03/15] tests --- tests/installation/docker-integration.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/installation/docker-integration.spec.ts b/tests/installation/docker-integration.spec.ts index c067e8985dc2e..3cffac2ac24d9 100755 --- a/tests/installation/docker-integration.spec.ts +++ b/tests/installation/docker-integration.spec.ts @@ -50,9 +50,11 @@ test.describe('installed image', () => { }); }); - test('make sure it tells to run `npx playwright docker start`', async ({ exec }) => { + test('make sure it auto-starts container', async ({ exec }) => { await exec('npm i --foreground-scripts @playwright/test'); - await exec('npx playwright docker test docker.spec.js --grep userAgent'); + await exec('npx playwright docker stop'); + const result = await exec('npx playwright docker test docker.spec.js --grep platform'); + expect(result).toContain('@chromium Linux'); }); test.describe('running container', () => { From 9009277784eb28ef454b911fffb67b8f90aa5bd3 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 9 Sep 2022 08:06:08 -0400 Subject: [PATCH 04/15] address initial batch of comments --- packages/playwright-core/src/cli/docker.ts | 21 ++++++--------------- packages/playwright-test/src/cli.ts | 17 ++++++++--------- packages/playwright-test/src/index.ts | 7 +------ 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/packages/playwright-core/src/cli/docker.ts b/packages/playwright-core/src/cli/docker.ts index 539c1727edb52..21136d4ba8cf8 100644 --- a/packages/playwright-core/src/cli/docker.ts +++ b/packages/playwright-core/src/cli/docker.ts @@ -65,7 +65,7 @@ const CONTAINER_ENTRY_POINT = `#!/bin/bash --server-args="-screen 0 "$GEOMETRY" -fbdir /var/tmp -dpi "$SCREEN_DPI" -listen tcp -noreset -ac +extension RANDR" \ /usr/bin/fluxbox -display "$DISPLAY" >/dev/null 2>&1 & - for i in $(seq 1 50) + for i in $(seq 1 500) do if xdpyinfo -display $DISPLAY >/dev/null 2>&1; then break @@ -252,10 +252,8 @@ export async function ensureContainerOrDie(): Promise { export async function stopContainer() { const containerId = await findRunningDockerContainerId(); - if (!containerId) { - console.log(`Container is not running.`); - return undefined; - } + if (!containerId) + return; await Promise.all([ // Make sure to wait for the container to be removed. postJSON(`/containers/${containerId}/wait?condition=removed`), @@ -269,7 +267,7 @@ export async function ensureDockerEngineIsRunningOrDie() { } catch (e) { console.error(utils.wrapInASCIIBox([ `Docker is not running!`, - `Please install if necessary and launch docker first:`, + `Please install and launch docker:`, ``, ` https://docs.docker.com/get-docker`, ``, @@ -294,15 +292,8 @@ async function findRunningDockerContainerId(): Promise { const containers: (Container[]|undefined) = await getJSON('/containers/json'); if (!containers) return undefined; - // 1. Try findind a container with our name. This happens if the container is launched - // automatically with `npx playwright docker start`. - let container = containers.find((container: Container) => container.Names.some(name => name.includes(VRT_CONTAINER_NAME))); - // 2. Alternatively, the container might be launched manually with a direct docker command. - // In this case, we should look for a container with a proper base image. - if (!container) { - const dockerImage = await findDockerImage(VRT_IMAGE_NAME); - container = dockerImage ? containers.find((container: Container) => container.ImageID === dockerImage.Id) : undefined; - } + const dockerImage = await findDockerImage(VRT_IMAGE_NAME); + const container = dockerImage ? containers.find((container: Container) => container.ImageID === dockerImage.Id) : undefined; return container?.State === 'running' ? container.Id : undefined; } diff --git a/packages/playwright-test/src/cli.ts b/packages/playwright-test/src/cli.ts index 8cfdf60458701..e3160229b8476 100644 --- a/packages/playwright-test/src/cli.ts +++ b/packages/playwright-test/src/cli.ts @@ -52,15 +52,14 @@ function addDockerCommand(program: Command) { .description('Start docker container') .action(async function(options) { await docker.ensureDockerEngineIsRunningOrDie(); - if (await docker.containerInfo()) { - console.log('Container is already running.'); - return; + let info = await docker.containerInfo(); + if (!info) { + process.stdout.write(`Launching docker container... `); + const time = Date.now(); + info = await docker.ensureContainerOrDie(); + const deltaMs = (Date.now() - time); + console.log('Done in ' + (deltaMs / 1000).toFixed(1) + 's'); } - process.stdout.write(`Launching docker container... `); - const time = Date.now(); - const info = await docker.ensureContainerOrDie(); - const deltaMs = (Date.now() - time); - console.log('Done in ' + (deltaMs / 1000).toFixed(1) + 's'); console.log([ `- VNC session: ${info.vncSession}`, `- Run tests with browsers inside container:`, @@ -139,7 +138,7 @@ function addTestCommand(program: Command, isDocker: boolean) { process.env.PW_TEST_CONNECT_HEADERS = JSON.stringify({ 'x-playwright-proxy': '*', }); - process.env.PW_TEST_IS_DOCKER = '1'; + process.env.PW_TEST_SNAPSHOT_SUFFIX = 'docker'; } await runTests(args, opts); } catch (e) { diff --git a/packages/playwright-test/src/index.ts b/packages/playwright-test/src/index.ts index 29e52f759c5e9..f139aaaf8b609 100644 --- a/packages/playwright-test/src/index.ts +++ b/packages/playwright-test/src/index.ts @@ -51,7 +51,6 @@ type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & { _browserOptions: LaunchOptions; _artifactsDir: () => string; _snapshotSuffix: string; - _isDocker: boolean; }; export const test = _baseTest.extend({ @@ -224,11 +223,7 @@ export const test = _baseTest.extend({ }); }, - _isDocker: [!!process.env.PW_TEST_IS_DOCKER, { scope: 'worker' }], - - _snapshotSuffix: [async ({ _isDocker }, use, testInfo) => { - await use(_isDocker ? 'docker' : process.platform); - }, { scope: 'worker' }], + _snapshotSuffix: [process.env.PW_TEST_SNAPSHOT_SUFFIX ?? process.platform, { scope: 'worker' }], _setupContextOptionsAndArtifacts: [async ({ playwright, _snapshotSuffix, _combinedContextOptions, _browserOptions, _artifactsDir, trace, screenshot, actionTimeout, navigationTimeout }, use, testInfo) => { testInfo.snapshotSuffix = _snapshotSuffix; From dfeeef739c59d58f6f221696a065d9f5b99e903b Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 9 Sep 2022 13:31:57 -0400 Subject: [PATCH 05/15] move docker --- packages/playwright-core/package.json | 2 +- packages/playwright-test/src/cli.ts | 2 +- .../src/cli => playwright-test/src/docker}/docker.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename packages/{playwright-core/src/cli => playwright-test/src/docker}/docker.ts (98%) diff --git a/packages/playwright-core/package.json b/packages/playwright-core/package.json index 816bff1b2f7a6..425bed4effda9 100644 --- a/packages/playwright-core/package.json +++ b/packages/playwright-core/package.json @@ -20,10 +20,10 @@ }, "./cli": "./cli.js", "./package.json": "./package.json", - "./lib/cli/docker": "./lib/cli/docker.js", "./lib/grid/gridServer": "./lib/grid/gridServer.js", "./lib/outofprocess": "./lib/outofprocess.js", "./lib/utils": "./lib/utils/index.js", + "./lib/common/userAgent": "./lib/common/userAgent.js", "./lib/utils/comparators": "./lib/utils/comparators.js", "./lib/utils/eventsHelper": "./lib/utils/eventsHelper.js", "./lib/utils/fileUtils": "./lib/utils/fileUtils.js", diff --git a/packages/playwright-test/src/cli.ts b/packages/playwright-test/src/cli.ts index e3160229b8476..0aabcf6da9096 100644 --- a/packages/playwright-test/src/cli.ts +++ b/packages/playwright-test/src/cli.ts @@ -17,7 +17,7 @@ /* eslint-disable no-console */ import type { Command } from 'playwright-core/lib/utilsBundle'; -import * as docker from 'playwright-core/lib/cli/docker'; +import * as docker from './docker/docker'; import fs from 'fs'; import url from 'url'; import path from 'path'; diff --git a/packages/playwright-core/src/cli/docker.ts b/packages/playwright-test/src/docker/docker.ts similarity index 98% rename from packages/playwright-core/src/cli/docker.ts rename to packages/playwright-test/src/docker/docker.ts index 21136d4ba8cf8..ab283043d8fdb 100644 --- a/packages/playwright-core/src/cli/docker.ts +++ b/packages/playwright-test/src/docker/docker.ts @@ -16,9 +16,9 @@ /* eslint-disable no-console */ import http from 'http'; -import * as utils from '../utils'; -import { spawnAsync } from '../utils/spawnAsync'; -import { getPlaywrightVersion } from '../common/userAgent'; +import { spawnAsync } from 'playwright-core/lib/utils/spawnAsync'; +import * as utils from 'playwright-core/lib/utils'; +import { getPlaywrightVersion } from 'playwright-core/lib/common/userAgent'; interface DockerImage { Containers: number; From 0cacf877f93680ed18347ee846b879eee8833ffb Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 9 Sep 2022 14:35:42 -0400 Subject: [PATCH 06/15] split out build script --- packages/playwright-test/src/docker/build.sh | 72 +++++++++++++++ packages/playwright-test/src/docker/docker.ts | 88 ++----------------- utils/build/build.js | 2 +- 3 files changed, 78 insertions(+), 84 deletions(-) create mode 100644 packages/playwright-test/src/docker/build.sh diff --git a/packages/playwright-test/src/docker/build.sh b/packages/playwright-test/src/docker/build.sh new file mode 100644 index 0000000000000..1997cc4f99edb --- /dev/null +++ b/packages/playwright-test/src/docker/build.sh @@ -0,0 +1,72 @@ +export NOVNC_REF = '1.3.0'; +export WEBSOCKIFY_REF = '0.10.0'; +export DEBIAN_FRONTEND=noninteractive + +# Install FluxBox, VNC & noVNC +mkdir -p /opt/bin && chmod +x /dev/shm \ + && apt-get update && apt-get install -y unzip fluxbox x11vnc \ + && curl -L -o noVNC.zip "https://github.com/novnc/noVNC/archive/v${NOVNC_REF}.zip" \ + && unzip -x noVNC.zip \ + && rm -rf noVNC-${NOVNC_REF}/{docs,tests} \ + && mv noVNC-${NOVNC_REF} /opt/bin/noVNC \ + && cp /opt/bin/noVNC/vnc.html /opt/bin/noVNC/index.html \ + && rm noVNC.zip \ + && curl -L -o websockify.zip "https://github.com/novnc/websockify/archive/v${WEBSOCKIFY_REF}.zip" \ + && unzip -x websockify.zip \ + && rm websockify.zip \ + && rm -rf websockify-${WEBSOCKIFY_REF}/{docs,tests} \ + && mv websockify-${WEBSOCKIFY_REF} /opt/bin/noVNC/utils/websockify + +# Configure FluxBox menus +mkdir /root/.fluxbox +cd /ms-playwright-agent +cat <<'EOF' | node > /root/.fluxbox/menu + const { chromium, firefox, webkit } = require('playwright-core'); + + console.log(\` + [begin] (fluxbox) + [submenu] (Browsers) {} + [exec] (Chromium) { $\{chromium.executablePath()} --no-sandbox --test-type= } <> + [exec] (Firefox) { $\{firefox.executablePath()} } <> + [exec] (WebKit) { $\{webkit.executablePath()} } <> + [end] + [include] (/etc/X11/fluxbox/fluxbox-menu) + [end] + \`); +EOF + +# Create entrypoint.sh +cat <<'EOF' > /entrypoint.sh +#!/bin/bash +set -e +SCREEN_WIDTH=1360 +SCREEN_HEIGHT=1020 +SCREEN_DEPTH=24 +SCREEN_DPI=96 +GEOMETRY="$SCREEN_WIDTH""x""$SCREEN_HEIGHT""x""$SCREEN_DEPTH" + +nohup /usr/bin/xvfb-run --server-num=$DISPLAY_NUM \ + --listen-tcp \ + --server-args="-screen 0 "$GEOMETRY" -fbdir /var/tmp -dpi "$SCREEN_DPI" -listen tcp -noreset -ac +extension RANDR" \ + /usr/bin/fluxbox -display "$DISPLAY" >/dev/null 2>&1 & + +for i in $(seq 1 500); do + if xdpyinfo -display $DISPLAY >/dev/null 2>&1; then + break + fi + echo "Waiting for Xvfb..." + sleep 0.2 +done + +nohup x11vnc -forever -shared -rfbport 5900 -rfbportv6 5900 -display "$DISPLAY" >/dev/null 2>&1 & +nohup /opt/bin/noVNC/utils/novnc_proxy --listen 7900 --vnc localhost:5900 >/dev/null 2>&1 & + +cd /ms-playwright-agent + +NOVNC_UUID=$(cat /proc/sys/kernel/random/uuid) +echo "novnc is listening on http://127.0.0.1:7900?path=$NOVNC_UUID&resize=scale" + +PW_UUID=$(cat /proc/sys/kernel/random/uuid) +npx playwright run-server --port=5400 --path=/$PW_UUID +EOF +chmod 755 /entrypoint.sh diff --git a/packages/playwright-test/src/docker/docker.ts b/packages/playwright-test/src/docker/docker.ts index ab283043d8fdb..f4442f74c830e 100644 --- a/packages/playwright-test/src/docker/docker.ts +++ b/packages/playwright-test/src/docker/docker.ts @@ -16,6 +16,8 @@ /* eslint-disable no-console */ import http from 'http'; +import path from 'path'; +import fs from 'fs'; import { spawnAsync } from 'playwright-core/lib/utils/spawnAsync'; import * as utils from 'playwright-core/lib/utils'; import { getPlaywrightVersion } from 'playwright-core/lib/common/userAgent'; @@ -37,87 +39,6 @@ const VRT_IMAGE_DISTRO = 'focal'; const VRT_IMAGE_NAME = `playwright:local-${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}`; const VRT_CONTAINER_NAME = `playwright-${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}`; -const GENERATE_FLUXBOX_BROWSERS_MENU = ` - const { chromium, firefox, webkit } = require('playwright-core'); - - console.log(\` - [begin] (fluxbox) - [submenu] (Browsers) {} - [exec] (Chromium) { $\{chromium.executablePath()} --no-sandbox --test-type= } <> - [exec] (Firefox) { $\{firefox.executablePath()} } <> - [exec] (WebKit) { $\{webkit.executablePath()} } <> - [end] - [include] (/etc/X11/fluxbox/fluxbox-menu) - [end] - \`); -`; - -const CONTAINER_ENTRY_POINT = `#!/bin/bash - set -e - SCREEN_WIDTH=1360 - SCREEN_HEIGHT=1020 - SCREEN_DEPTH=24 - SCREEN_DPI=96 - GEOMETRY="$SCREEN_WIDTH""x""$SCREEN_HEIGHT""x""$SCREEN_DEPTH" - - nohup /usr/bin/xvfb-run --server-num=$DISPLAY_NUM \ - --listen-tcp \ - --server-args="-screen 0 "$GEOMETRY" -fbdir /var/tmp -dpi "$SCREEN_DPI" -listen tcp -noreset -ac +extension RANDR" \ - /usr/bin/fluxbox -display "$DISPLAY" >/dev/null 2>&1 & - - for i in $(seq 1 500) - do - if xdpyinfo -display $DISPLAY >/dev/null 2>&1; then - break - fi - echo "Waiting for Xvfb..." - sleep 0.2 - done - - - nohup x11vnc -forever -shared -rfbport 5900 -rfbportv6 5900 -display "$DISPLAY" >/dev/null 2>&1 & - nohup /opt/bin/noVNC/utils/novnc_proxy --listen 7900 --vnc localhost:5900 >/dev/null 2>&1 & - cd /ms-playwright-agent - NOVNC_UUID=$(cat /proc/sys/kernel/random/uuid) - echo "novnc is listening on http://127.0.0.1:7900?path=$NOVNC_UUID&resize=scale" - PW_UUID=$(cat /proc/sys/kernel/random/uuid) - npx playwright run-server --port=5400 --path=/$PW_UUID -`; - -const NOVNC_REF = '1.3.0'; -const WEBSOCKIFY_REF = '0.10.0'; -const CONTAINER_BUILD_SCRIPT = ` - # Generate entry point script - cat <<'EOF' >/start.sh - ${CONTAINER_ENTRY_POINT} - EOF - chmod 755 /start.sh - - export DEBIAN_FRONTEND=noninteractive - - # Install FluxBox, VNC & noVNC - mkdir -p /opt/bin && chmod +x /dev/shm \ - && apt-get update && apt-get install -y unzip fluxbox x11vnc \ - && curl -L -o noVNC.zip "https://github.com/novnc/noVNC/archive/v${NOVNC_REF}.zip" \ - && unzip -x noVNC.zip \ - && rm -rf noVNC-${NOVNC_REF}/{docs,tests} \ - && mv noVNC-${NOVNC_REF} /opt/bin/noVNC \ - && cp /opt/bin/noVNC/vnc.html /opt/bin/noVNC/index.html \ - && rm noVNC.zip \ - && curl -L -o websockify.zip "https://github.com/novnc/websockify/archive/v${WEBSOCKIFY_REF}.zip" \ - && unzip -x websockify.zip \ - && rm websockify.zip \ - && rm -rf websockify-${WEBSOCKIFY_REF}/{docs,tests} \ - && mv websockify-${WEBSOCKIFY_REF} /opt/bin/noVNC/utils/websockify - - # Configure FluxBox menus - cd /ms-playwright-agent - cat <<'EOF' | node > configuration.txt - ${GENERATE_FLUXBOX_BROWSERS_MENU} - EOF - mkdir /root/.fluxbox && mv /ms-playwright-agent/configuration.txt /root/.fluxbox/menu -`.split('\n').map(line => line.substring(2)).join('\n'); - export async function deleteImage() { const dockerImage = await findDockerImage(VRT_IMAGE_NAME); if (!dockerImage) @@ -158,17 +79,18 @@ export async function buildImage() { throw new Error(`Failed to pull ${baseImageName}`); // 3. Launch container and install VNC in it console.log(`Building ${VRT_IMAGE_NAME}...`); + const buildScriptText = await fs.promises.readFile(path.join(__dirname, 'build.sh'), 'utf8'); const containerId = await launchContainer({ image: dockerImage, autoRemove: false, - command: ['/bin/bash', '-c', CONTAINER_BUILD_SCRIPT], + command: ['/bin/bash', '-c', buildScriptText], }); await postJSON(`/containers/${containerId}/wait`); // 4. Commit a new image based on the launched container with installed VNC & noVNC. const [vrtRepo, vrtTag] = VRT_IMAGE_NAME.split(':'); await postJSON(`/commit?container=${containerId}&repo=${vrtRepo}&tag=${vrtTag}`, { - Entrypoint: ['/start.sh'], + Entrypoint: ['/entrypoint.sh'], Env: [ 'DISPLAY_NUM=99', 'DISPLAY=:99', diff --git a/utils/build/build.js b/utils/build/build.js index fa64cc871d1bb..0a07a7e69601b 100644 --- a/utils/build/build.js +++ b/utils/build/build.js @@ -308,7 +308,7 @@ copyFiles.push({ }); copyFiles.push({ - files: 'packages/playwright-test/src/**/*.js', + files: 'packages/playwright-test/src/**/*.(js|sh)', from: 'packages/playwright-test/src', to: 'packages/playwright-test/lib', ignored: ['**/.eslintrc.js'] From 5da9e46c74c3e93e59fb4ed7e25682e98b7c5cce Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 9 Sep 2022 14:38:58 -0400 Subject: [PATCH 07/15] add deps --- packages/playwright-test/src/DEPS.list | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/playwright-test/src/DEPS.list b/packages/playwright-test/src/DEPS.list index c6fa7ed79c648..fa00ac451af31 100644 --- a/packages/playwright-test/src/DEPS.list +++ b/packages/playwright-test/src/DEPS.list @@ -1,5 +1,6 @@ [*] ./utilsBundle.ts +docker/ matchers/ reporters/ third_party/ From ea1164d6141f9089d533129664431ee164ae7e98 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 9 Sep 2022 15:59:02 -0400 Subject: [PATCH 08/15] add note on docker desktop --- docs/src/docker.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/docker.md b/docs/src/docker.md index 77ea348e48014..45ab6ae37c25c 100644 --- a/docs/src/docker.md +++ b/docs/src/docker.md @@ -162,6 +162,11 @@ Docker integration requires Docker installed & running on your computer. See https://docs.docker.com/get-docker/ ::: +:::note +If you use [Docker Desktop](https://www.docker.com/products/docker-desktop/), make sure to increase +default CPU and mem limit for better performance. +::: + Docker integration usage: 1. Build a local docker image that will be used to run containers. This step From c93f11884d5b3da842f4907eac4fe4a5d985d5e5 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 9 Sep 2022 16:19:20 -0400 Subject: [PATCH 09/15] wip --- packages/playwright-core/src/cli/cli.ts | 2 +- packages/playwright-test/src/cli.ts | 30 ++++++++++++------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/playwright-core/src/cli/cli.ts b/packages/playwright-core/src/cli/cli.ts index fe7486bc55aae..ed315e4506e4f 100755 --- a/packages/playwright-core/src/cli/cli.ts +++ b/packages/playwright-core/src/cli/cli.ts @@ -291,7 +291,7 @@ program program .command('show-trace [trace...]') .option('-b, --browser ', 'browser to use, one of cr, chromium, ff, firefox, wk, webkit', 'chromium') - .description('Show trace viewer') + .description('show trace viewer') .action(function(traces, options) { if (options.browser === 'cr') options.browser = 'chromium'; diff --git a/packages/playwright-test/src/cli.ts b/packages/playwright-test/src/cli.ts index 0aabcf6da9096..eb32666e1d6a7 100644 --- a/packages/playwright-test/src/cli.ts +++ b/packages/playwright-test/src/cli.ts @@ -39,22 +39,22 @@ export function addTestCommands(program: Command) { function addDockerCommand(program: Command) { const dockerCommand = program.command('docker') - .description('EXPERIMENTAL (might change in future releases) docker integration.'); + .description(`run tests in Docker (EXPERIMENTAL)`); dockerCommand.command('build') - .description('Build local docker image') + .description('build local docker image') .action(async function(options) { await docker.ensureDockerEngineIsRunningOrDie(); await docker.buildImage(); }); dockerCommand.command('start') - .description('Start docker container') + .description('start docker container') .action(async function(options) { await docker.ensureDockerEngineIsRunningOrDie(); let info = await docker.containerInfo(); if (!info) { - process.stdout.write(`Launching docker container... `); + process.stdout.write(`Starting docker container... `); const time = Date.now(); info = await docker.ensureContainerOrDie(); const deltaMs = (Date.now() - time); @@ -70,14 +70,14 @@ function addDockerCommand(program: Command) { }); dockerCommand.command('delete-image', { hidden: true }) - .description('Delete docker image, if any') + .description('delete docker image, if any') .action(async function(options) { await docker.ensureDockerEngineIsRunningOrDie(); await docker.deleteImage(); }); dockerCommand.command('stop') - .description('Stop docker container') + .description('stop docker container') .action(async function(options) { await docker.ensureDockerEngineIsRunningOrDie(); await docker.stopContainer(); @@ -89,9 +89,9 @@ function addDockerCommand(program: Command) { function addTestCommand(program: Command, isDocker: boolean) { const command = program.command('test [test-filter...]'); if (isDocker) - command.description('Run tests with Playwright Test and browsers inside docker container'); + 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`); @@ -120,20 +120,20 @@ function addTestCommand(program: Command, isDocker: boolean) { command.action(async (args, opts) => { try { if (isDocker && !process.env.PW_TS_ESM_ON) { + console.log(colors.dim('Using docker container to run browsers.')); await docker.ensureDockerEngineIsRunningOrDie(); let info = await docker.containerInfo(); - if (info) { - console.log(colors.yellow('NOTE: Using docker container to run browsers')); - console.log(colors.yellow(` VNC: ${info.vncSession}`)); - } else { - process.stdout.write(colors.dim(`Launching docker container... `)); + if (!info) { + process.stdout.write(colors.dim(`Starting docker container... `)); const time = Date.now(); info = await docker.ensureContainerOrDie(); const deltaMs = (Date.now() - time); console.log(colors.dim('Done in ' + (deltaMs / 1000).toFixed(1) + 's')); - console.log(colors.yellow('NOTE: Using docker container to run browsers')); - console.log(colors.yellow(` VNC: ${info.vncSession}`)); + console.log(colors.dim('The Docker container will keep running after tests finished.')); + console.log(colors.dim('Stop manually using:')); + console.log(colors.dim(' npx playwright docker stop')); } + console.log(colors.dim(`View screen: ${info.vncSession}`)); process.env.PW_TEST_CONNECT_WS_ENDPOINT = info.wsEndpoint; process.env.PW_TEST_CONNECT_HEADERS = JSON.stringify({ 'x-playwright-proxy': '*', From 6f4c1ffb2c4e4b7587f6697a3326df2385070da3 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 9 Sep 2022 16:24:56 -0400 Subject: [PATCH 10/15] fix docker --- packages/playwright-test/src/docker/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/playwright-test/src/docker/build.sh b/packages/playwright-test/src/docker/build.sh index 1997cc4f99edb..335eed96426e7 100644 --- a/packages/playwright-test/src/docker/build.sh +++ b/packages/playwright-test/src/docker/build.sh @@ -1,5 +1,5 @@ -export NOVNC_REF = '1.3.0'; -export WEBSOCKIFY_REF = '0.10.0'; +export NOVNC_REF='1.3.0' +export WEBSOCKIFY_REF='0.10.0' export DEBIAN_FRONTEND=noninteractive # Install FluxBox, VNC & noVNC From d86fb3c2c4e531a396e867ff4e91aabbe7f67cde Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 9 Sep 2022 16:30:13 -0400 Subject: [PATCH 11/15] fix docker --- packages/playwright-test/src/docker/build.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/playwright-test/src/docker/build.sh b/packages/playwright-test/src/docker/build.sh index 335eed96426e7..1459fd2849c4e 100644 --- a/packages/playwright-test/src/docker/build.sh +++ b/packages/playwright-test/src/docker/build.sh @@ -23,16 +23,16 @@ cd /ms-playwright-agent cat <<'EOF' | node > /root/.fluxbox/menu const { chromium, firefox, webkit } = require('playwright-core'); - console.log(\` + console.log(` [begin] (fluxbox) [submenu] (Browsers) {} - [exec] (Chromium) { $\{chromium.executablePath()} --no-sandbox --test-type= } <> - [exec] (Firefox) { $\{firefox.executablePath()} } <> - [exec] (WebKit) { $\{webkit.executablePath()} } <> + [exec] (Chromium) { ${chromium.executablePath()} --no-sandbox --test-type= } <> + [exec] (Firefox) { ${firefox.executablePath()} } <> + [exec] (WebKit) { ${webkit.executablePath()} } <> [end] [include] (/etc/X11/fluxbox/fluxbox-menu) [end] - \`); + `); EOF # Create entrypoint.sh From 0c4b5c42be4d74a5db28682deaf1db2c319ebddd Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 9 Sep 2022 17:20:54 -0400 Subject: [PATCH 12/15] nicer docker container --- packages/playwright-test/src/docker/build.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/playwright-test/src/docker/build.sh b/packages/playwright-test/src/docker/build.sh index 1459fd2849c4e..b59267599588f 100644 --- a/packages/playwright-test/src/docker/build.sh +++ b/packages/playwright-test/src/docker/build.sh @@ -63,8 +63,10 @@ nohup /opt/bin/noVNC/utils/novnc_proxy --listen 7900 --vnc localhost:5900 >/dev/ cd /ms-playwright-agent +fbsetbg -c /ms-playwright-agent/node_modules/playwright-core/lib/server/chromium/appIcon.png + NOVNC_UUID=$(cat /proc/sys/kernel/random/uuid) -echo "novnc is listening on http://127.0.0.1:7900?path=$NOVNC_UUID&resize=scale" +echo "novnc is listening on http://127.0.0.1:7900?path=$NOVNC_UUID&resize=scale&autoconnect=1" PW_UUID=$(cat /proc/sys/kernel/random/uuid) npx playwright run-server --port=5400 --path=/$PW_UUID From 533da87c9b78cdd2217dfb8dbcf1bb9969db1624 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 9 Sep 2022 17:54:26 -0400 Subject: [PATCH 13/15] wip --- utils/docker/Dockerfile.jammy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/docker/Dockerfile.jammy b/utils/docker/Dockerfile.jammy index 5dedca7d51d8b..63868afbdd5fd 100644 --- a/utils/docker/Dockerfile.jammy +++ b/utils/docker/Dockerfile.jammy @@ -25,8 +25,8 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright # 1. Add tip-of-tree Playwright package to install its browsers. # The package should be built beforehand from tip-of-tree Playwright. - COPY ./playwright-core.tar.gz /tmp/playwright-core.tar.gz + # 2. Bake in browsers & deps. # Browsers will be downloaded in `/ms-playwright`. # Note: make sure to set 777 to the registry so that any user can access From d629655f7aa90367784ee8d11aa52ec6bc4b9207 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 9 Sep 2022 18:02:54 -0400 Subject: [PATCH 14/15] address max comments --- docs/src/docker.md | 18 +++++++++--------- packages/playwright-test/src/docker/docker.ts | 1 + 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/src/docker.md b/docs/src/docker.md index 45ab6ae37c25c..7d4a4a43b45c2 100644 --- a/docs/src/docker.md +++ b/docs/src/docker.md @@ -149,12 +149,12 @@ docker run --rm -it playwright:localbuild /bin/bash ## (Experimental) Playwright Test Docker Integration * langs: js -Playwright Test now ships an **experimental** docker integration. -With this integration, **only** browser binaries are running inside a docker container, +Playwright Test now ships an **experimental** Docker integration. +With this integration, **only** browser binaries are running inside a Docker container, while all the code is still running on the host operating system. Docker container provides a consistent environment, eliminating browser rendering -differences across platforms. Playwright Test will automatically proxy host network +differences across platforms. Playwright Test will automatically proxy host network traffic into the container, so browsers can access servers running on the host. :::note @@ -169,26 +169,26 @@ default CPU and mem limit for better performance. Docker integration usage: -1. Build a local docker image that will be used to run containers. This step - should be done only once. +1. Build a local Docker image that will be used to run containers. This step + needs to be done only once. ```bash js npx playwright docker build ``` -2. Run docker container in the background. +2. Run Docker container in the background. ```bash js npx playwright docker start ``` -3. Run tests inside docker container. Note that this command accepts all the same arguments +3. Run tests inside Docker container. Note that this command accepts all the same arguments as a regular `npx playwright test` command. ```bash js npx playwright docker test ``` - Note that this command will detect running docker container, and auto-launch it if needed. + Note that this command will detect running Docker container, and auto-launch it if needed. -4. Finally, stop docker container when it is no longer needed. +4. Finally, stop Docker container when it is no longer needed. ```bash js npx playwright docker stop ``` diff --git a/packages/playwright-test/src/docker/docker.ts b/packages/playwright-test/src/docker/docker.ts index f4442f74c830e..041d0c76306c2 100644 --- a/packages/playwright-test/src/docker/docker.ts +++ b/packages/playwright-test/src/docker/docker.ts @@ -273,6 +273,7 @@ function callDockerAPI(method: 'post'|'get'|'delete', url: string, body: Buffer| const request = http.request({ socketPath: dockerSocket, path: `/v${DOCKER_API_VERSION}${url}`, + timeout: 30000, method, }, (response: http.IncomingMessage) => { let body = ''; From e01a3ea6dfb3fa513b5d858a298219d9d2bc4948 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Fri, 9 Sep 2022 18:03:40 -0400 Subject: [PATCH 15/15] rename build.sh into build_docker_image.sh --- .../src/docker/{build.sh => build_docker_image.sh} | 0 packages/playwright-test/src/docker/docker.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/playwright-test/src/docker/{build.sh => build_docker_image.sh} (100%) diff --git a/packages/playwright-test/src/docker/build.sh b/packages/playwright-test/src/docker/build_docker_image.sh similarity index 100% rename from packages/playwright-test/src/docker/build.sh rename to packages/playwright-test/src/docker/build_docker_image.sh diff --git a/packages/playwright-test/src/docker/docker.ts b/packages/playwright-test/src/docker/docker.ts index 041d0c76306c2..12eda03c2b307 100644 --- a/packages/playwright-test/src/docker/docker.ts +++ b/packages/playwright-test/src/docker/docker.ts @@ -79,7 +79,7 @@ export async function buildImage() { throw new Error(`Failed to pull ${baseImageName}`); // 3. Launch container and install VNC in it console.log(`Building ${VRT_IMAGE_NAME}...`); - const buildScriptText = await fs.promises.readFile(path.join(__dirname, 'build.sh'), 'utf8'); + const buildScriptText = await fs.promises.readFile(path.join(__dirname, 'build_docker_image.sh'), 'utf8'); const containerId = await launchContainer({ image: dockerImage, autoRemove: false,