chore: build a client bundle (#34847)

This commit is contained in:
Pavel Feldman 2025-02-19 15:27:00 -08:00 committed by GitHub
parent 1f1e2acf9b
commit d5adeb3cf4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 23190 additions and 41 deletions

15
package-lock.json generated
View File

@ -1576,6 +1576,10 @@
"resolved": "packages/playwright-browser-webkit",
"link": true
},
"node_modules/@playwright/client": {
"resolved": "packages/playwright-client",
"link": true
},
"node_modules/@playwright/experimental-ct-core": {
"resolved": "packages/playwright-ct-core",
"link": true
@ -8932,6 +8936,17 @@
"node": ">=18"
}
},
"packages/playwright-client": {
"name": "@playwright/client",
"version": "1.51.0-next",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.51.0-next"
},
"engines": {
"node": ">=18"
}
},
"packages/playwright-core": {
"version": "1.51.0-next",
"license": "Apache-2.0",

View File

@ -1,7 +1,7 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the 'License");
* 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
*
@ -14,12 +14,12 @@
* limitations under the License.
*/
import { Connection } from './connection';
import { setPlatformForSelectors } from './selectors';
import type { Browser } from './types/types';
import type { Platform } from './platform';
export * from './types/types';
export function createConnectionFactory(platform: Platform): () => Connection {
setPlatformForSelectors(platform);
return () => new Connection(platform);
}
export type Options = {
headless?: boolean;
};
export const connect: (wsEndpoint: string, browserName: string, options: Options) => Promise<Browser>;

View File

@ -0,0 +1,17 @@
/**
* 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.
*/
module.exports = require('./lib/index');

View File

@ -0,0 +1,17 @@
/**
* 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.
*/
export { connect } from './index.js';

View File

@ -0,0 +1,34 @@
{
"name": "@playwright/client",
"private": true,
"version": "1.51.0-next",
"description": "A thin client for Playwright",
"repository": {
"type": "git",
"url": "git+https://github.com/microsoft/playwright.git"
},
"homepage": "https://playwright.dev",
"engines": {
"node": ">=18"
},
"author": {
"name": "Microsoft Corporation"
},
"license": "Apache-2.0",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.mjs",
"require": "./index.js",
"default": "./index.js"
},
"./package.json": "./package.json"
},
"scripts": {
"build": "esbuild ./src/index.ts --outdir=lib --format=cjs --bundle --platform=node --target=ES2019",
"watch": "esbuild ./src/index.ts --outdir=lib --format=cjs --bundle --platform=node --target=ES2019 --watch"
},
"dependencies": {
"playwright-core": "1.51.0-next"
}
}

View File

@ -0,0 +1,40 @@
/**
* 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 { Connection } from '../../playwright-core/src/client/connection';
import { webPlatform } from './webPlatform';
import type { Browser } from '../../playwright-core/src/client/browser';
export type Options = {
headless?: boolean;
};
export async function connect(wsEndpoint: string, browserName: string, options: Options): Promise<Browser> {
const ws = new WebSocket(`${wsEndpoint}?browser=${browserName}&launch-options=${JSON.stringify(options)}`);
await new Promise((f, r) => {
ws.addEventListener('open', f);
ws.addEventListener('error', r);
});
const connection = new Connection(webPlatform);
connection.onmessage = message => ws.send(JSON.stringify(message));
ws.addEventListener('message', message => connection.dispatch(JSON.parse(message.data)));
ws.addEventListener('close', () => connection.close());
const playwright = await connection.initializePlaywright();
return playwright._preLaunchedBrowser();
}

View File

@ -0,0 +1,48 @@
/* eslint-disable no-console */
/**
* 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 { emptyPlatform } from '../../playwright-core/src/client/platform';
import type { Platform } from '../../playwright-core/src/client/platform';
export const webPlatform: Platform = {
...emptyPlatform,
name: 'web',
boxedStackPrefixes: () => [],
calculateSha1: async (text: string) => {
const bytes = new TextEncoder().encode(text);
const hashBuffer = await window.crypto.subtle.digest('SHA-1', bytes);
return Array.from(new Uint8Array(hashBuffer), b => b.toString(16).padStart(2, '0')).join('');
},
createGuid: () => {
return Array.from(window.crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join('');
},
isLogEnabled(name: 'api' | 'channel') {
return true;
},
log(name: 'api' | 'channel', message: string | Error | object) {
console.debug(name, message);
},
showInternalStackFrames: () => true,
};

22974
packages/playwright-client/types/types.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { webColors, noColors } from '../utils/isomorphic/colors';
import { webColors } from '../utils/isomorphic/colors';
import type * as fs from 'fs';
import type * as path from 'path';
@ -71,7 +71,7 @@ export const emptyPlatform: Platform = {
throw new Error('Not implemented');
},
colors: noColors,
colors: webColors,
createGuid: () => {
throw new Error('Not implemented');
@ -121,23 +121,3 @@ export const emptyPlatform: Platform = {
zones: { empty: noopZone, current: () => noopZone },
};
export const webPlatform: Platform = {
...emptyPlatform,
name: 'web',
boxedStackPrefixes: () => [],
calculateSha1: async (text: string) => {
const bytes = new TextEncoder().encode(text);
const hashBuffer = await window.crypto.subtle.digest('SHA-1', bytes);
return Array.from(new Uint8Array(hashBuffer), b => b.toString(16).padStart(2, '0')).join('');
},
colors: webColors,
createGuid: () => {
return Array.from(window.crypto.getRandomValues(new Uint8Array(16)), b => b.toString(16).padStart(2, '0')).join('');
},
};

View File

@ -15,6 +15,7 @@
*/
import { Android } from './android';
import { Browser } from './browser';
import { BrowserType } from './browserType';
import { ChannelOwner } from './channelOwner';
import { Electron } from './electron';
@ -86,6 +87,12 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
return [this.chromium, this.firefox, this.webkit, this._bidiChromium, this._bidiFirefox];
}
_preLaunchedBrowser(): Browser {
const browser = Browser.from(this._initializer.preLaunchedBrowser!);
browser._browserType = this[browser._name as 'chromium' | 'firefox' | 'webkit'];
return browser;
}
_allContexts() {
return this._browserTypes().flatMap(type => [...type._contexts]);
}

View File

@ -16,19 +16,18 @@
import { AndroidServerLauncherImpl } from './androidServerImpl';
import { BrowserServerLauncherImpl } from './browserServerImpl';
import { createConnectionFactory } from './client/clientBundle';
import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from './server';
import { nodePlatform } from './server/utils/nodePlatform';
import { Connection } from './client/connection';
import { setPlatformForSelectors } from './client/selectors';
import type { Playwright as PlaywrightAPI } from './client/playwright';
import type { Language } from './utils';
const connectionFactory = createConnectionFactory(nodePlatform);
export function createInProcessPlaywright(): PlaywrightAPI {
const playwright = createPlaywright({ sdkLanguage: (process.env.PW_LANG_NAME as Language | undefined) || 'javascript' });
const clientConnection = connectionFactory();
setPlatformForSelectors(nodePlatform);
const clientConnection = new Connection(nodePlatform);
clientConnection.useRawBuffers();
const dispatcherConnection = new DispatcherConnection(true /* local */);

View File

@ -17,16 +17,17 @@
import * as childProcess from 'child_process';
import path from 'path';
import { createConnectionFactory } from './client/clientBundle';
import { Connection } from './client/connection';
import { PipeTransport } from './server/utils/pipeTransport';
import { ManualPromise } from './utils/isomorphic/manualPromise';
import { nodePlatform } from './server/utils/nodePlatform';
import { setPlatformForSelectors } from './client/selectors';
import type { Playwright } from './client/playwright';
const connectionFactory = createConnectionFactory(nodePlatform);
export async function start(env: any = {}): Promise<{ playwright: Playwright, stop: () => Promise<void> }> {
setPlatformForSelectors(nodePlatform);
const client = new PlaywrightClient(env);
const playwright = await client._playwright;
(playwright as any).driverProcess = client._driverProcess;
@ -50,7 +51,7 @@ class PlaywrightClient {
this._driverProcess.unref();
this._driverProcess.stderr!.on('data', data => process.stderr.write(data));
const connection = connectionFactory();
const connection = new Connection(nodePlatform);
const transport = new PipeTransport(this._driverProcess.stdin!, this._driverProcess.stdout!);
connection.onmessage = message => transport.send(JSON.stringify(message));
transport.onmessage = message => connection.dispatch(JSON.parse(message));

View File

@ -15,7 +15,6 @@
* limitations under the License.
*/
import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
import { Readable } from 'stream';
import { ReadStream } from 'fs';
import { Protocol } from './protocol';

View File

@ -275,6 +275,19 @@ for (const bundle of bundles) {
});
}
// Build/watch playwright-client.
steps.push({
command: 'npm',
args: [
'run',
watchMode ? 'watch' : 'build',
...(withSourceMaps ? ['--', '--sourcemap'] : [])
],
shell: true,
cwd: path.join(__dirname, '..', '..', 'packages', 'playwright-client'),
concurrent: true,
});
// Build/watch trace viewer service worker.
steps.push({
command: 'npx',

View File

@ -622,13 +622,16 @@ class TypesGenerator {
}
const coreTypesDir = path.join(PROJECT_DIR, 'packages', 'playwright-core', 'types');
const clientTypesDir = path.join(PROJECT_DIR, 'packages', 'playwright-client', 'types');
if (!fs.existsSync(coreTypesDir))
fs.mkdirSync(coreTypesDir)
const playwrightTypesDir = path.join(PROJECT_DIR, 'packages', 'playwright', 'types');
if (!fs.existsSync(playwrightTypesDir))
fs.mkdirSync(playwrightTypesDir)
writeFile(path.join(coreTypesDir, 'protocol.d.ts'), fs.readFileSync(path.join(PROJECT_DIR, 'packages', 'playwright-core', 'src', 'server', 'chromium', 'protocol.d.ts'), 'utf8'), false);
writeFile(path.join(coreTypesDir, 'types.d.ts'), await generateCoreTypes(), true);
const coreTypes = await generateCoreTypes();
writeFile(path.join(coreTypesDir, 'types.d.ts'), coreTypes, true);
writeFile(path.join(clientTypesDir, 'types.d.ts'), coreTypes, true);
writeFile(path.join(playwrightTypesDir, 'test.d.ts'), await generateTestTypes(), true);
writeFile(path.join(playwrightTypesDir, 'testReporter.d.ts'), await generateReporterTypes(), true);
process.exit(0);

View File

@ -14,7 +14,6 @@
* limitations under the License.
*/
import { ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
import { Readable } from 'stream';
import { ReadStream } from 'fs';
import { Protocol } from './protocol';

3
utils/kill_watch.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
ps ax | grep playwright | grep "vite\|tsc\|babel\|esbuild" | sed 's|pts/.*||' | xargs kill