diff --git a/packages/playwright-core/package.json b/packages/playwright-core/package.json index 8176ffca81..c4e9cc05a4 100644 --- a/packages/playwright-core/package.json +++ b/packages/playwright-core/package.json @@ -19,8 +19,6 @@ "./cli": "./cli.js", "./package.json": "./package.json", "./lib/grid/gridServer": "./lib/grid/gridServer.js", - "./lib/grid/gridClient": "./lib/grid/gridClient.js", - "./lib/grid/dockerGridFactory": "./lib/grid/dockerGridFactory.js", "./lib/outofprocess": "./lib/outofprocess.js", "./lib/utils": "./lib/utils/index.js", "./lib/utils/comparators": "./lib/utils/comparators.js", @@ -35,7 +33,6 @@ "./lib/utils/stackTrace": "./lib/utils/stackTrace.js", "./lib/utils/timeoutRunner": "./lib/utils/timeoutRunner.js", "./lib/remote/playwrightServer": "./lib/remote/playwrightServer.js", - "./lib/remote/playwrightClient": "./lib/remote/playwrightClient.js", "./lib/server": "./lib/server/index.js", "./types/protocol": "./types/protocol.d.ts", "./types/structs": "./types/structs.d.ts" diff --git a/packages/playwright-core/src/client/playwright.ts b/packages/playwright-core/src/client/playwright.ts index 3d510f185d..41a0a92c27 100644 --- a/packages/playwright-core/src/client/playwright.ts +++ b/packages/playwright-core/src/client/playwright.ts @@ -16,7 +16,7 @@ import type * as channels from '../protocol/channels'; import { TimeoutError } from '../common/errors'; -import * as socks from '../common/socksProxy'; +import type * as socks from '../common/socksProxy'; import { Android } from './android'; import { BrowserType } from './browserType'; import { ChannelOwner } from './channelOwner'; @@ -87,23 +87,6 @@ export class Playwright extends ChannelOwner { this.selectors._addChannel(selectorsOwner); } - // TODO: remove this methods together with PlaywrightClient. - _enablePortForwarding(redirectPortForTest?: number) { - const socksSupport = this._initializer.socksSupport; - if (!socksSupport) - return; - const handler = new socks.SocksProxyHandler(redirectPortForTest); - this._socksProxyHandler = handler; - handler.on(socks.SocksProxyHandler.Events.SocksConnected, (payload: socks.SocksSocketConnectedPayload) => socksSupport.socksConnected(payload).catch(() => {})); - handler.on(socks.SocksProxyHandler.Events.SocksData, (payload: socks.SocksSocketDataPayload) => socksSupport.socksData({ uid: payload.uid, data: payload.data.toString('base64') }).catch(() => {})); - handler.on(socks.SocksProxyHandler.Events.SocksError, (payload: socks.SocksSocketErrorPayload) => socksSupport.socksError(payload).catch(() => {})); - handler.on(socks.SocksProxyHandler.Events.SocksFailed, (payload: socks.SocksSocketFailedPayload) => socksSupport.socksFailed(payload).catch(() => {})); - handler.on(socks.SocksProxyHandler.Events.SocksEnd, (payload: socks.SocksSocketEndPayload) => socksSupport.socksEnd(payload).catch(() => {})); - socksSupport.on('socksRequested', payload => handler.socketRequested(payload)); - socksSupport.on('socksClosed', payload => handler.socketClosed(payload)); - socksSupport.on('socksData', payload => handler.sendSocketData({ uid: payload.uid, data: Buffer.from(payload.data, 'base64') })); - } - static from(channel: channels.PlaywrightChannel): Playwright { return (channel as any)._object; } diff --git a/packages/playwright-core/src/grid/dockerGridFactory.ts b/packages/playwright-core/src/grid/dockerGridFactory.ts deleted file mode 100644 index 7926d38db1..0000000000 --- a/packages/playwright-core/src/grid/dockerGridFactory.ts +++ /dev/null @@ -1,165 +0,0 @@ -/** - * 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 http from 'http'; -import os from 'os'; -import type { GridAgentLaunchOptions, GridFactory } from './gridServer'; -import * as utils from '../utils'; - -const dockerFactory: GridFactory = { - name: 'Agents launched inside Docker container', - capacity: Infinity, - launchTimeout: 30000, - retireTimeout: Infinity, - launch: async (options: GridAgentLaunchOptions) => { - const { vncUrl } = await launchDockerGridAgent(options.agentId, options.gridURL); - /* eslint-disable no-console */ - console.log(``); - console.log(`✨ Running browsers inside docker container: ${vncUrl} ✨`); - } -}; - -export default dockerFactory; - -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; -} - -async function launchDockerGridAgent(agentId: string, gridURL: string): Promise<{vncUrl: string }> { - const gridPort = new URL(gridURL).port || '80'; - const images: DockerImage[] | null = await getJSON('/images/json'); - - if (!images) { - throw new Error(`\n` + utils.wrapInASCIIBox([ - `Failed to list docker images`, - `Please ensure docker is running.`, - ``, - `<3 Playwright Team`, - ].join('\n'), 1)); - } - - const imageName = process.env.PWTEST_IMAGE_NAME ?? `mcr.microsoft.com/playwright:v${require('../../package.json').version}-focal`; - const pwImage = images.find(image => image.RepoTags?.includes(imageName)); - - if (!pwImage) { - throw new Error(`\n` + utils.wrapInASCIIBox([ - `Failed to find ${imageName} docker image.`, - `Please pull docker image with the following command:`, - ``, - ` npx playwright install docker-image`, - ``, - `<3 Playwright Team`, - ].join('\n'), 1)); - } - const Env = [ - 'PW_SOCKS_PROXY_PORT=1', // Enable port forwarding over PlaywrightClient - ]; - const forwardIfDefined = (envName: string) => { - if (process.env[envName]) - Env.push(`CI=${process.env[envName]}`); - }; - forwardIfDefined('CI'); - forwardIfDefined('PWDEBUG'); - forwardIfDefined('DEBUG'); - forwardIfDefined('DEBUG_FILE'); - forwardIfDefined('SELENIUM_REMOTE_URL'); - - const container = await postJSON('/containers/create', { - Env, - WorkingDir: '/ms-playwright-agent', - Cmd: [ 'bash', 'start_agent.sh', agentId, `http://host.docker.internal:${gridPort}` ], - AttachStdout: true, - AttachStderr: true, - Image: pwImage.Id, - ExposedPorts: { - '7900/tcp': { } - }, - HostConfig: { - Init: true, - AutoRemove: true, - ShmSize: 2 * 1024 * 1024 * 1024, - ExtraHosts: process.platform === 'linux' ? [ - 'host.docker.internal:host-gateway', // Enable host.docker.internal on Linux. - ] : [], - PortBindings: { - '7900/tcp': [{ HostPort: '0' }] - }, - }, - }); - await postJSON(`/containers/${container.Id}/start`); - const info = await getJSON(`/containers/${container.Id}/json`); - const vncPort = info?.NetworkSettings?.Ports['7900/tcp']; - return { - vncUrl: `http://localhost:${vncPort[0].HostPort}`, - }; -} - -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); -} - -function callDockerAPI(method: 'post'|'get', url: string, body: Buffer|string|undefined = undefined): Promise { - const dockerSocket = os.platform() === 'win32' ? '\\\\.\\pipe\\docker_engine' : '/var/run/docker.sock'; - return new Promise((resolve, reject) => { - const request = http.request({ - socketPath: dockerSocket, - path: 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) { - console.error(`ERROR ${method} ${url}`, response.statusCode, body); - resolve(null); - } else { - resolve(body); - } - }); - }); - request.on('error', function(e){ - console.error('Error fetching json: ' + e); - resolve(null); - }); - if (body) { - request.setHeader('Content-Type', 'application/json'); - request.setHeader('Content-Length', body.length); - request.write(body); - } - request.end(); - }); -} diff --git a/packages/playwright-core/src/grid/gridAgent.ts b/packages/playwright-core/src/grid/gridAgent.ts index 53c02b5144..3cc8c06534 100644 --- a/packages/playwright-core/src/grid/gridAgent.ts +++ b/packages/playwright-core/src/grid/gridAgent.ts @@ -31,10 +31,15 @@ export function launchGridAgent(agentId: string, gridURL: string, runId: string ws.on('message', (message: string) => { log('worker requested ' + message); const { workerId, browserAlias } = JSON.parse(message); - if (browserAlias) - fork(require.resolve('./gridBrowserWorker.js'), [gridURL, agentId, workerId, browserAlias], { detached: true }); - else - fork(require.resolve('./gridWorker.js'), [gridURL, agentId, workerId], { detached: true }); + if (!workerId) { + log('workerId not specified'); + return; + } + if (!browserAlias) { + log('browserAlias not specified'); + return; + } + fork(require.resolve('./gridBrowserWorker.js'), [gridURL, agentId, workerId, browserAlias], { detached: true }); }); ws.on('close', () => process.exit(0)); } diff --git a/packages/playwright-core/src/grid/gridWorker.ts b/packages/playwright-core/src/grid/gridWorker.ts deleted file mode 100644 index 5201d9dbf5..0000000000 --- a/packages/playwright-core/src/grid/gridWorker.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * 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 WebSocket from 'ws'; -import debug from 'debug'; -import { createPlaywright, PlaywrightDispatcher, DispatcherConnection, Root } from '../server'; -import { gracefullyCloseAll } from '../utils/processLauncher'; -import { SocksProxy } from '../common/socksProxy'; - -function launchGridWorker(gridURL: string, agentId: string, workerId: string) { - const log = debug(`pw:grid:worker:${workerId}`); - log('created'); - const ws = new WebSocket(gridURL.replace('http://', 'ws://') + `/registerWorker?agentId=${agentId}&workerId=${workerId}`); - const dispatcherConnection = new DispatcherConnection(); - dispatcherConnection.onmessage = message => ws.send(JSON.stringify(message)); - ws.once('open', () => { - new Root(dispatcherConnection, async rootScope => { - const playwright = createPlaywright('javascript'); - const socksProxy = new SocksProxy(); - playwright.options.socksProxyPort = await socksProxy.listen(0); - return new PlaywrightDispatcher(rootScope, playwright, socksProxy); - }); - }); - ws.on('message', message => dispatcherConnection.dispatch(JSON.parse(message.toString()))); - ws.on('close', async () => { - // Drop any messages during shutdown on the floor. - dispatcherConnection.onmessage = () => {}; - setTimeout(() => process.exit(0), 30000); - // Meanwhile, try to gracefully close all browsers. - await gracefullyCloseAll(); - process.exit(0); - }); -} - -launchGridWorker(process.argv[2], process.argv[3], process.argv[4]); diff --git a/packages/playwright-core/src/remote/playwrightClient.ts b/packages/playwright-core/src/remote/playwrightClient.ts deleted file mode 100644 index 571ed28fcd..0000000000 --- a/packages/playwright-core/src/remote/playwrightClient.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * 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 WebSocket from 'ws'; -import { Connection } from '../client/connection'; -import type { Playwright } from '../client/playwright'; -import { makeWaitForNextTask } from '../utils'; - -// TODO: this file should be removed because it uses the old protocol. - -export type PlaywrightClientConnectOptions = { - wsEndpoint: string; - timeout?: number; - followRedirects?: boolean; -}; - -export class PlaywrightClient { - private _playwright: Playwright; - private _ws: WebSocket; - private _closePromise: Promise; - - static async connect(options: PlaywrightClientConnectOptions): Promise { - const { wsEndpoint, timeout = 30000, followRedirects = true } = options; - const connection = new Connection(); - connection.markAsRemote(); - const ws = new WebSocket(wsEndpoint, { followRedirects }); - const waitForNextTask = makeWaitForNextTask(); - connection.onmessage = message => { - if (ws.readyState === 2 /** CLOSING */ || ws.readyState === 3 /** CLOSED */) - throw new Error('PlaywrightClient: writing to closed WebSocket connection'); - ws.send(JSON.stringify(message)); - }; - ws.on('message', message => waitForNextTask(() => connection.dispatch(JSON.parse(message.toString())))); - const errorPromise = new Promise((_, reject) => ws.on('error', error => reject(error))); - const closePromise = new Promise((_, reject) => ws.on('close', () => reject(new Error('Connection closed')))); - const playwrightClientPromise = new Promise((resolve, reject) => { - let playwright: Playwright; - ws.on('open', async () => { - playwright = await connection.initializePlaywright(); - resolve(new PlaywrightClient(playwright, ws)); - }); - ws.on('close', (code, reason) => connection.close(reason.toString())); - }); - let timer: NodeJS.Timeout; - try { - await Promise.race([ - playwrightClientPromise, - errorPromise, - closePromise, - new Promise((_, reject) => timer = setTimeout(() => reject(`Timeout of ${timeout}ms exceeded while connecting.`), timeout)) - ]); - return await playwrightClientPromise; - } finally { - clearTimeout(timer!); - } - } - - constructor(playwright: Playwright, ws: WebSocket) { - this._playwright = playwright; - this._ws = ws; - this._closePromise = new Promise(f => ws.on('close', f)); - } - - playwright(): Playwright { - return this._playwright; - } - - async close() { - this._ws.close(); - await this._closePromise; - } -} diff --git a/packages/playwright-test/src/cli.ts b/packages/playwright-test/src/cli.ts index c03fab10e9..e98ef0743e 100644 --- a/packages/playwright-test/src/cli.ts +++ b/packages/playwright-test/src/cli.ts @@ -27,9 +27,6 @@ import { Runner, builtInReporters, kDefaultConfigFiles } from './runner'; import { stopProfiling, startProfiling } from './profiler'; import type { FilePatternFilter } from './util'; import { showHTMLReport } from './reporters/html'; -import { GridServer } from 'playwright-core/lib/grid/gridServer'; -import dockerFactory from 'playwright-core/lib/grid/dockerGridFactory'; -import { createGuid } from 'playwright-core/lib/utils'; import { hostPlatform } from 'playwright-core/lib/utils/hostPlatform'; import { fileIsModule } from './loader'; @@ -166,8 +163,6 @@ async function runTests(args: string[], opts: { [key: string]: any }) { }; }); - if (process.env.PLAYWRIGHT_DOCKER) - runner.addInternalGlobalSetup(launchDockerContainer); const result = await runner.runAllTests({ listOnly: !!opts.list, filePatternFilter, @@ -235,16 +230,6 @@ function resolveReporter(id: string) { return require.resolve(id, { paths: [ process.cwd() ] }); } -async function launchDockerContainer(): Promise<() => Promise> { - const gridServer = new GridServer(dockerFactory, createGuid()); - await gridServer.start(); - // Start docker container in advance. - const { error } = await gridServer.createAgent(); - if (error) - throw error; - return async () => await gridServer.stop(); -} - function restartWithExperimentalTsEsm(configFile: string | null): boolean { const nodeVersion = +process.versions.node.split('.')[0]; // New experimental loader is only supported on Node 16+. diff --git a/packages/playwright-test/src/index.ts b/packages/playwright-test/src/index.ts index 758c9eabd6..4431a4cb6e 100644 --- a/packages/playwright-test/src/index.ts +++ b/packages/playwright-test/src/index.ts @@ -230,7 +230,7 @@ export const test = _baseTest.extend({ }); }, - _snapshotSuffix: [process.env.PLAYWRIGHT_DOCKER ? 'docker' : process.platform, { scope: 'worker' }], + _snapshotSuffix: [process.platform, { scope: 'worker' }], _setupContextOptionsAndArtifacts: [async ({ playwright, _snapshotSuffix, _combinedContextOptions, _browserOptions, _artifactsDir, trace, screenshot, actionTimeout, navigationTimeout }, use, testInfo) => { testInfo.snapshotSuffix = _snapshotSuffix; diff --git a/tests/library/playwright.config.ts b/tests/library/playwright.config.ts index f7d6d6e518..7dfbd05bb7 100644 --- a/tests/library/playwright.config.ts +++ b/tests/library/playwright.config.ts @@ -83,7 +83,7 @@ if (mode === 'service') { config.projects = [{ name: 'Chromium page tests', testMatch: /page\/.*spec.ts$/, - testIgnore: 'screenshot', + testIgnore: '**/*screenshot*', use: { browserName: 'chromium', mode diff --git a/tests/library/port-forwarding-server.spec.ts b/tests/library/port-forwarding-server.spec.ts index aef6d3082b..eaebc2f5c4 100644 --- a/tests/library/port-forwarding-server.spec.ts +++ b/tests/library/port-forwarding-server.spec.ts @@ -31,8 +31,7 @@ class OutOfProcessPlaywrightServer { stdio: 'pipe', detached: true, env: { - ...process.env, - PW_SOCKS_PROXY_PORT: '1' + ...process.env } }); this._driverProcess.unref(); diff --git a/tests/page/locator-misc-2.spec.ts b/tests/page/locator-misc-2.spec.ts index 1e95974100..5d5bb3cabe 100644 --- a/tests/page/locator-misc-2.spec.ts +++ b/tests/page/locator-misc-2.spec.ts @@ -61,7 +61,8 @@ it('should type', async ({ page }) => { expect(await page.$eval('input', input => input.value)).toBe('hello'); }); -it('should take screenshot', async ({ page, server, browserName, headless, isAndroid }) => { +it('should take screenshot', async ({ page, server, browserName, headless, isAndroid, mode }) => { + it.skip(mode === 'service'); it.skip(browserName === 'firefox' && !headless); it.skip(isAndroid, 'Different dpr. Remove after using 1x scale for screenshots.'); await page.setViewportSize({ width: 500, height: 500 }); diff --git a/tests/page/page-request-fulfill.spec.ts b/tests/page/page-request-fulfill.spec.ts index cc93da6010..558db8ef6d 100644 --- a/tests/page/page-request-fulfill.spec.ts +++ b/tests/page/page-request-fulfill.spec.ts @@ -76,7 +76,8 @@ it('should work with status code 422', async ({ page, server }) => { expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); }); -it('should allow mocking binary responses', async ({ page, server, browserName, headless, asset, isAndroid }) => { +it('should allow mocking binary responses', async ({ page, server, browserName, headless, asset, isAndroid, mode }) => { + it.skip(mode === 'service'); it.skip(browserName === 'firefox' && !headless, 'Firefox headed produces a different image.'); it.skip(isAndroid); @@ -97,7 +98,8 @@ it('should allow mocking binary responses', async ({ page, server, browserName, expect(await img.screenshot()).toMatchSnapshot('mock-binary-response.png'); }); -it('should allow mocking svg with charset', async ({ page, server, browserName, headless, isAndroid }) => { +it('should allow mocking svg with charset', async ({ page, server, browserName, headless, isAndroid, mode }) => { + it.skip(mode === 'service'); it.skip(browserName === 'firefox' && !headless, 'Firefox headed produces a different image.'); it.skip(isAndroid); @@ -117,7 +119,8 @@ it('should allow mocking svg with charset', async ({ page, server, browserName, expect(await img.screenshot()).toMatchSnapshot('mock-svg.png'); }); -it('should work with file path', async ({ page, server, asset, isAndroid }) => { +it('should work with file path', async ({ page, server, asset, isAndroid, mode }) => { + it.skip(mode === 'service'); it.skip(isAndroid); await page.route('**/*', route => route.fulfill({ contentType: 'shouldBeIgnored', path: asset('pptr.png') })); diff --git a/tests/playwright-test/playwright-test-fixtures.ts b/tests/playwright-test/playwright-test-fixtures.ts index cc1b07e1e8..9d1b55e718 100644 --- a/tests/playwright-test/playwright-test-fixtures.ts +++ b/tests/playwright-test/playwright-test-fixtures.ts @@ -141,7 +141,6 @@ async function runPlaywrightTest(childProcess: CommonFixtures['childProcess'], b GITHUB_SHA: undefined, // END: Reserved CI PW_TEST_HTML_REPORT_OPEN: undefined, - PLAYWRIGHT_DOCKER: undefined, PW_TEST_REPORTER: undefined, PW_TEST_REPORTER_WS_ENDPOINT: undefined, PW_TEST_SOURCE_TRANSFORM: undefined,