diff --git a/packages/playwright-core/package.json b/packages/playwright-core/package.json index 7617cd054e..65fe71ee28 100644 --- a/packages/playwright-core/package.json +++ b/packages/playwright-core/package.json @@ -24,6 +24,7 @@ "./lib/outofprocess": "./lib/outofprocess.js", "./lib/utils": "./lib/utils/index.js", "./lib/common/userAgent": "./lib/common/userAgent.js", + "./lib/containers/docker": "./lib/containers/docker.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-core/src/cli/DEPS.list b/packages/playwright-core/src/cli/DEPS.list index 872049bbe0..5ab1f1c5e7 100644 --- a/packages/playwright-core/src/cli/DEPS.list +++ b/packages/playwright-core/src/cli/DEPS.list @@ -13,6 +13,7 @@ [cli.ts] ../server/trace/viewer/traceViewer.ts ../server/ +../containers/ [driver.ts] ../** diff --git a/packages/playwright-core/src/cli/cli.ts b/packages/playwright-core/src/cli/cli.ts index ed315e4506..1bf3984777 100755 --- a/packages/playwright-core/src/cli/cli.ts +++ b/packages/playwright-core/src/cli/cli.ts @@ -38,6 +38,7 @@ import type { GridFactory } from '../grid/gridServer'; import { GridServer } from '../grid/gridServer'; import type { Executable } from '../server'; import { registry, writeDockerVersion } from '../server'; +import { addDockerCLI } from '../containers/docker'; const packageJSON = require('../../package.json'); @@ -114,6 +115,7 @@ function checkBrowsersToInstall(args: string[]): Executable[] { return executables; } + program .command('install [browser...]') .description('ensure browsers necessary for this version of Playwright are installed') @@ -305,6 +307,8 @@ Examples: $ show-trace https://example.com/trace.zip`); +addDockerCLI(program); + if (!process.env.PW_LANG_NAME) { let playwrightTestPackagePath = null; const resolvePwTestPaths = [__dirname, process.cwd()]; diff --git a/packages/playwright-core/src/containers/DEPS.list b/packages/playwright-core/src/containers/DEPS.list new file mode 100644 index 0000000000..c89b15d593 --- /dev/null +++ b/packages/playwright-core/src/containers/DEPS.list @@ -0,0 +1,4 @@ +[*] +../utils/ +../utilsBundle.ts +../common/ diff --git a/packages/playwright-test/src/docker/build_docker_image.sh b/packages/playwright-core/src/containers/build_docker_image.sh similarity index 100% rename from packages/playwright-test/src/docker/build_docker_image.sh rename to packages/playwright-core/src/containers/build_docker_image.sh diff --git a/packages/playwright-test/src/docker/docker.ts b/packages/playwright-core/src/containers/docker.ts similarity index 82% rename from packages/playwright-test/src/docker/docker.ts rename to packages/playwright-core/src/containers/docker.ts index f7b470469b..580fdac0fd 100644 --- a/packages/playwright-test/src/docker/docker.ts +++ b/packages/playwright-core/src/containers/docker.ts @@ -17,13 +17,11 @@ import path from 'path'; import fs from 'fs'; -import { colors } from 'playwright-core/lib/utilsBundle'; -import { spawnAsync } from 'playwright-core/lib/utils/spawnAsync'; -import * as utils from 'playwright-core/lib/utils'; -import { getPlaywrightVersion } from 'playwright-core/lib/common/userAgent'; +import { spawnAsync } from '../utils/spawnAsync'; +import * as utils from '../utils'; +import { getPlaywrightVersion } from '../common/userAgent'; import * as dockerApi from './dockerApi'; -import type { TestRunnerPlugin } from '../plugins'; -import type { FullConfig, Reporter, Suite } from '../../types/testReporter'; +import type { Command } from '../utilsBundle'; const VRT_IMAGE_DISTRO = 'focal'; const VRT_IMAGE_NAME = `playwright:local-${getPlaywrightVersion()}-${VRT_IMAGE_DISTRO}`; @@ -31,7 +29,7 @@ const VRT_CONTAINER_NAME = `playwright-${getPlaywrightVersion()}-${VRT_IMAGE_DIS const VRT_CONTAINER_LABEL_NAME = 'dev.playwright.vrt-service.version'; const VRT_CONTAINER_LABEL_VALUE = '1'; -export async function startPlaywrightContainer() { +async function startPlaywrightContainer() { await checkDockerEngineIsRunningOrDie(); let info = await containerInfo(); @@ -52,7 +50,7 @@ export async function startPlaywrightContainer() { ].join('\n')); } -export async function stopAllPlaywrightContainers() { +async function stopAllPlaywrightContainers() { await checkDockerEngineIsRunningOrDie(); const allContainers = await dockerApi.listContainers(); @@ -63,7 +61,7 @@ export async function stopAllPlaywrightContainers() { }))); } -export async function deletePlaywrightImage() { +async function deletePlaywrightImage() { await checkDockerEngineIsRunningOrDie(); const dockerImage = await findDockerImage(VRT_IMAGE_NAME); @@ -75,7 +73,7 @@ export async function deletePlaywrightImage() { await dockerApi.removeImage(dockerImage.imageId); } -export async function buildPlaywrightImage() { +async function buildPlaywrightImage() { await checkDockerEngineIsRunningOrDie(); const isDevelopmentMode = getPlaywrightVersion().includes('next'); @@ -130,44 +128,12 @@ export async function buildPlaywrightImage() { console.log(`Done!`); } -export const dockerPlugin: TestRunnerPlugin = { - name: 'playwright:docker', - - async setup(config: FullConfig, configDir: string, rootSuite: Suite, reporter: Reporter) { - if (!process.env.PLAYWRIGHT_DOCKER) - return; - - const print = (text: string) => reporter.onStdOut?.(text); - const println = (text: string) => reporter.onStdOut?.(text + '\n'); - - println(colors.dim('Using docker container to run browsers.')); - await checkDockerEngineIsRunningOrDie(); - let info = await containerInfo(); - if (!info) { - print(colors.dim(`Starting docker container... `)); - const time = Date.now(); - info = await ensurePlaywrightContainerOrDie(); - const deltaMs = (Date.now() - time); - println(colors.dim('Done in ' + (deltaMs / 1000).toFixed(1) + 's')); - println(colors.dim('The Docker container will keep running after tests finished.')); - println(colors.dim('Stop manually using:')); - println(colors.dim(' npx playwright docker stop')); - } - println(colors.dim(`View screen: ${info.vncSession}`)); - println(''); - process.env.PW_TEST_CONNECT_WS_ENDPOINT = info.wsEndpoint; - process.env.PW_TEST_CONNECT_HEADERS = JSON.stringify({ - 'x-playwright-proxy': '*', - }); - }, -}; - interface ContainerInfo { wsEndpoint: string; vncSession: string; } -export async function printDockerStatus() { +async function printDockerStatus() { const isDockerEngine = await dockerApi.checkEngineRunning(); const imageIsPulled = isDockerEngine && !!(await findDockerImage(VRT_IMAGE_NAME)); const info = isDockerEngine ? await containerInfo() : undefined; @@ -180,7 +146,7 @@ export async function printDockerStatus() { }, null, 2)); } -async function containerInfo(): Promise { +export async function containerInfo(): Promise { const allContainers = await dockerApi.listContainers(); const pwDockerImage = await findDockerImage(VRT_IMAGE_NAME); const container = allContainers.find(container => container.imageId === pwDockerImage?.imageId && container.state === 'running'); @@ -210,7 +176,7 @@ async function containerInfo(): Promise { return wsEndpoint && vncSession ? { wsEndpoint, vncSession } : undefined; } -async function ensurePlaywrightContainerOrDie(): Promise { +export async function ensurePlaywrightContainerOrDie(): Promise { const pwImage = await findDockerImage(VRT_IMAGE_NAME); if (!pwImage) { throw createStacklessError('\n' + utils.wrapInASCIIBox([ @@ -284,7 +250,7 @@ async function ensurePlaywrightContainerOrDie(): Promise { return info; } -async function checkDockerEngineIsRunningOrDie() { +export async function checkDockerEngineIsRunningOrDie() { if (await dockerApi.checkEngineRunning()) return; throw createStacklessError(utils.wrapInASCIIBox([ @@ -306,3 +272,54 @@ function createStacklessError(message: string) { error.stack = ''; return error; } + +export function addDockerCLI(program: Command) { + const dockerCommand = program.command('docker') + .description(`Manage Docker integration (EXPERIMENTAL)`); + + dockerCommand.command('build') + .description('build local docker image') + .action(async function(options) { + try { + await buildPlaywrightImage(); + } catch (e) { + console.error(e.stack ? e : e.message); + } + }); + + dockerCommand.command('start') + .description('start docker container') + .action(async function(options) { + try { + await startPlaywrightContainer(); + } catch (e) { + console.error(e.stack ? e : e.message); + } + }); + + dockerCommand.command('stop') + .description('stop docker container') + .action(async function(options) { + try { + await stopAllPlaywrightContainers(); + } catch (e) { + console.error(e.stack ? e : e.message); + } + }); + + dockerCommand.command('delete-image', { hidden: true }) + .description('delete docker image, if any') + .action(async function(options) { + try { + await deletePlaywrightImage(); + } catch (e) { + console.error(e.stack ? e : e.message); + } + }); + + dockerCommand.command('print-status-json', { hidden: true }) + .description('print docker status') + .action(async function(options) { + await printDockerStatus(); + }); +} diff --git a/packages/playwright-test/src/docker/dockerApi.ts b/packages/playwright-core/src/containers/dockerApi.ts similarity index 100% rename from packages/playwright-test/src/docker/dockerApi.ts rename to packages/playwright-core/src/containers/dockerApi.ts diff --git a/packages/playwright-test/src/DEPS.list b/packages/playwright-test/src/DEPS.list index fa00ac451a..c6fa7ed79c 100644 --- a/packages/playwright-test/src/DEPS.list +++ b/packages/playwright-test/src/DEPS.list @@ -1,6 +1,5 @@ [*] ./utilsBundle.ts -docker/ matchers/ reporters/ third_party/ diff --git a/packages/playwright-test/src/cli.ts b/packages/playwright-test/src/cli.ts index 4d8a7832fa..ca44de2a78 100644 --- a/packages/playwright-test/src/cli.ts +++ b/packages/playwright-test/src/cli.ts @@ -17,7 +17,6 @@ /* eslint-disable no-console */ import type { Command } from 'playwright-core/lib/utilsBundle'; -import * as docker from './docker/docker'; import fs from 'fs'; import url from 'url'; import path from 'path'; @@ -33,58 +32,6 @@ export function addTestCommands(program: Command) { addTestCommand(program); addShowReportCommand(program); addListFilesCommand(program); - addDockerCommand(program); -} - -function addDockerCommand(program: Command) { - const dockerCommand = program.command('docker') - .description(`Manage Docker integration (EXPERIMENTAL)`); - - dockerCommand.command('build') - .description('build local docker image') - .action(async function(options) { - try { - await docker.buildPlaywrightImage(); - } catch (e) { - console.error(e.stack ? e : e.message); - } - }); - - dockerCommand.command('start') - .description('start docker container') - .action(async function(options) { - try { - await docker.startPlaywrightContainer(); - } catch (e) { - console.error(e.stack ? e : e.message); - } - }); - - dockerCommand.command('stop') - .description('stop docker container') - .action(async function(options) { - try { - await docker.stopAllPlaywrightContainers(); - } catch (e) { - console.error(e.stack ? e : e.message); - } - }); - - dockerCommand.command('delete-image', { hidden: true }) - .description('delete docker image, if any') - .action(async function(options) { - try { - await docker.deletePlaywrightImage(); - } catch (e) { - console.error(e.stack ? e : e.message); - } - }); - - dockerCommand.command('print-status-json', { hidden: true }) - .description('print docker status') - .action(async function(options) { - await docker.printDockerStatus(); - }); } function addTestCommand(program: Command) { diff --git a/packages/playwright-test/src/plugins/dockerPlugin.ts b/packages/playwright-test/src/plugins/dockerPlugin.ts new file mode 100644 index 0000000000..c9e6a463e5 --- /dev/null +++ b/packages/playwright-test/src/plugins/dockerPlugin.ts @@ -0,0 +1,54 @@ +/** + * 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 type { TestRunnerPlugin } from '.'; +import type { FullConfig, Reporter, Suite } from '../../types/testReporter'; +import { colors } from 'playwright-core/lib/utilsBundle'; +import { checkDockerEngineIsRunningOrDie, containerInfo, ensurePlaywrightContainerOrDie } from 'playwright-core/lib/containers/docker'; + +export const dockerPlugin: TestRunnerPlugin = { + name: 'playwright:docker', + + async setup(config: FullConfig, configDir: string, rootSuite: Suite, reporter: Reporter) { + if (!process.env.PLAYWRIGHT_DOCKER) + return; + + const print = (text: string) => reporter.onStdOut?.(text); + const println = (text: string) => reporter.onStdOut?.(text + '\n'); + + println(colors.dim('Using docker container to run browsers.')); + await checkDockerEngineIsRunningOrDie(); + let info = await containerInfo(); + if (!info) { + print(colors.dim(`Starting docker container... `)); + const time = Date.now(); + info = await ensurePlaywrightContainerOrDie(); + const deltaMs = (Date.now() - time); + println(colors.dim('Done in ' + (deltaMs / 1000).toFixed(1) + 's')); + println(colors.dim('The Docker container will keep running after tests finished.')); + println(colors.dim('Stop manually using:')); + println(colors.dim(' npx playwright docker stop')); + } + println(colors.dim(`View screen: ${info.vncSession}`)); + println(''); + process.env.PW_TEST_CONNECT_WS_ENDPOINT = info.wsEndpoint; + process.env.PW_TEST_CONNECT_HEADERS = JSON.stringify({ + 'x-playwright-proxy': '*', + }); + }, +}; + + diff --git a/packages/playwright-test/src/runner.ts b/packages/playwright-test/src/runner.ts index 777855d949..6ea084162f 100644 --- a/packages/playwright-test/src/runner.ts +++ b/packages/playwright-test/src/runner.ts @@ -45,7 +45,7 @@ import { SigIntWatcher } from './sigIntWatcher'; import type { TestRunnerPlugin } from './plugins'; import { setRunnerToAddPluginsTo } from './plugins'; import { webServerPluginsForConfig } from './plugins/webServerPlugin'; -import { dockerPlugin } from './docker/docker'; +import { dockerPlugin } from './plugins/dockerPlugin'; import { MultiMap } from 'playwright-core/lib/utils/multimap'; const removeFolderAsync = promisify(rimraf); diff --git a/utils/build/build.js b/utils/build/build.js index 42f0076793..6e4632264e 100644 --- a/utils/build/build.js +++ b/utils/build/build.js @@ -301,14 +301,14 @@ copyFiles.push({ // Babel doesn't touch JS files, so copy them manually. // For example: diff_match_patch.js copyFiles.push({ - files: 'packages/playwright-core/src/**/*.js', + files: 'packages/playwright-core/src/**/*.(js|sh)', from: 'packages/playwright-core/src', to: 'packages/playwright-core/lib', ignored: ['**/.eslintrc.js', '**/webpack*.config.js', '**/injected/**/*'] }); copyFiles.push({ - files: 'packages/playwright-test/src/**/*.(js|sh)', + files: 'packages/playwright-test/src/**/*.sh', from: 'packages/playwright-test/src', to: 'packages/playwright-test/lib', ignored: ['**/.eslintrc.js']