diff --git a/packages/isomorphic/protocolMetainfo.ts b/packages/isomorphic/protocolMetainfo.ts
index b0e779d1b061a..e99ac29133016 100644
--- a/packages/isomorphic/protocolMetainfo.ts
+++ b/packages/isomorphic/protocolMetainfo.ts
@@ -115,6 +115,7 @@ export const methodMetainfo = new Map<string, MethodMetainfo>([
   ['BrowserType.launch', { title: 'Launch browser', }],
   ['BrowserType.launchPersistentContext', { title: 'Launch persistent context', }],
   ['BrowserType.connectOverCDP', { title: 'Connect over CDP', }],
+  ['BrowserType.connectOverCDPTransport', { title: 'Connect over CDP transport', }],
   ['BrowserType.connectToWorker', { title: 'Connect to worker', }],
   ['Disposable.dispose', { internal: true, potentiallyClosesScope: true, }],
   ['Electron.launch', { title: 'Launch electron', }],
diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts
index 7a032da7ad9a6..85617e01e86a5 100644
--- a/packages/playwright-client/types/types.d.ts
+++ b/packages/playwright-client/types/types.d.ts
@@ -15414,6 +15414,32 @@ export interface BrowserType<Unused = {}> {
    * @param options
    */
   connectOverCDP(options: ConnectOverCDPOptions & { wsEndpoint?: string }): Promise<Browser>;
+  /**
+   * This method attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
+   *
+   * The default browser context is accessible via
+   * [browser.contexts()](https://playwright.dev/docs/api/class-browser#browser-contexts).
+   *
+   * **NOTE** Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
+   *
+   * **NOTE** This connection is significantly lower fidelity than the Playwright protocol connection via
+   * [browserType.connect(endpoint[, options])](https://playwright.dev/docs/api/class-browsertype#browser-type-connect).
+   * If you are experiencing issues or attempting to use advanced functionality, you probably want to use
+   * [browserType.connect(endpoint[, options])](https://playwright.dev/docs/api/class-browsertype#browser-type-connect).
+   *
+   * **Usage**
+   *
+   * ```js
+   * const browser = await playwright.chromium.connectOverCDP('http://localhost:9222');
+   * const defaultContext = browser.contexts()[0];
+   * const page = defaultContext.pages()[0];
+   * ```
+   *
+   * @param endpointURL A CDP websocket endpoint or http url to connect to. For example `http://localhost:9222/` or
+   * `ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4`.
+   * @param options
+   */
+  connectOverCDP(transport: ConnectionTransport): Promise<Browser>;
   /**
    * This method attaches Playwright to an existing browser instance created via `BrowserType.launchServer` in Node.js.
    *
@@ -16194,6 +16220,13 @@ export interface BrowserType<Unused = {}> {
   name(): string;
 }
 
+export interface ConnectionTransport {
+  send(message: object): void;
+  close(): void;
+  onmessage?: (message: object) => void;
+  onclose?: (reason?: string) => void;
+}
+
 /**
  * The `CDPSession` instances are used to talk raw Chrome Devtools Protocol:
  * - protocol methods can be called with `session.send` method.
diff --git a/packages/playwright-core/src/client/browserType.ts b/packages/playwright-core/src/client/browserType.ts
index 3a98bb8120099..5cbc931d710fc 100644
--- a/packages/playwright-core/src/client/browserType.ts
+++ b/packages/playwright-core/src/client/browserType.ts
@@ -138,12 +138,15 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
 
   async connectOverCDP(options: api.ConnectOverCDPOptions  & { wsEndpoint?: string }): Promise<api.Browser>;
   async connectOverCDP(endpointURL: string, options?: api.ConnectOverCDPOptions): Promise<api.Browser>;
-  async connectOverCDP(endpointURLOrOptions: (api.ConnectOverCDPOptions & { wsEndpoint?: string })|string, options?: api.ConnectOverCDPOptions) {
+  async connectOverCDP(transport: api.ConnectionTransport): Promise<api.Browser>;
+  async connectOverCDP(endpointURLOrOptions: (api.ConnectOverCDPOptions & { wsEndpoint?: string })|string|api.ConnectionTransport, options?: api.ConnectOverCDPOptions) {
     if (typeof endpointURLOrOptions === 'string')
       return await this._connectOverCDP(endpointURLOrOptions, options);
+    if (isConnectionTransport(endpointURLOrOptions))
+      return await this._connectOverCDPTransport(endpointURLOrOptions);
     const endpointURL = 'endpointURL' in endpointURLOrOptions ? endpointURLOrOptions.endpointURL : endpointURLOrOptions.wsEndpoint;
     assert(endpointURL, 'Cannot connect over CDP without wsEndpoint.');
-    return await this.connectOverCDP(endpointURL, endpointURLOrOptions);
+    return await this._connectOverCDP(endpointURL, endpointURLOrOptions);
   }
 
   async _connectOverCDP(endpointURL: string, params: api.ConnectOverCDPOptions = {}): Promise<Browser>  {
@@ -159,6 +162,17 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
       noDefaults: params.noDefaults,
       artifactsDir: params.artifactsDir,
     });
+    return await this._browserFromConnectResult(result);
+  }
+
+  async _connectOverCDPTransport(transport: api.ConnectionTransport): Promise<Browser> {
+    if (this.name() !== 'chromium')
+      throw new Error('Connecting over CDP is only supported in Chromium.');
+    const result = await this._channel.connectOverCDPTransport({ transport: transport as any });
+    return await this._browserFromConnectResult(result);
+  }
+
+  private async _browserFromConnectResult(result: { browser: channels.BrowserChannel, defaultContext?: channels.BrowserContextChannel }): Promise<Browser> {
     const browser = Browser.from(result.browser);
     browser._connectToBrowserType(this, {}, undefined);
     if (result.defaultContext)
@@ -175,5 +189,8 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
     });
     return Worker.from(result.worker);
   }
+}
 
+function isConnectionTransport(value: any): value is api.ConnectionTransport {
+  return !!value && typeof value === 'object' && typeof value.send === 'function' && typeof value.close === 'function';
 }
diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts
index af1c8ecc2b90b..1e39b28725483 100644
--- a/packages/playwright-core/src/protocol/validator.ts
+++ b/packages/playwright-core/src/protocol/validator.ts
@@ -1099,6 +1099,13 @@ scheme.BrowserTypeConnectOverCDPResult = tObject({
   browser: tChannel(['Browser']),
   defaultContext: tOptional(tChannel(['BrowserContext'])),
 });
+scheme.BrowserTypeConnectOverCDPTransportParams = tObject({
+  transport: tBinary,
+});
+scheme.BrowserTypeConnectOverCDPTransportResult = tObject({
+  browser: tChannel(['Browser']),
+  defaultContext: tOptional(tChannel(['BrowserContext'])),
+});
 scheme.BrowserTypeConnectToWorkerParams = tObject({
   endpoint: tString,
   timeout: tFloat,
diff --git a/packages/playwright-core/src/server/browserType.ts b/packages/playwright-core/src/server/browserType.ts
index 8096c9eedab47..d9a1e34f524d4 100644
--- a/packages/playwright-core/src/server/browserType.ts
+++ b/packages/playwright-core/src/server/browserType.ts
@@ -290,6 +290,10 @@ export abstract class BrowserType extends SdkObject {
     throw new Error('CDP connections are only supported by Chromium');
   }
 
+  async connectOverCDPTransport(progress: Progress, transport: ConnectionTransport): Promise<Browser> {
+    throw new Error('CDP connections are only supported by Chromium');
+  }
+
   async connectToWorker(progress: Progress, endpoint: string): Promise<Worker> {
     throw new Error('CDP connections are only supported by Chromium');
   }
diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts
index e0b4d362590d8..063701c82b144 100644
--- a/packages/playwright-core/src/server/chromium/chromium.ts
+++ b/packages/playwright-core/src/server/chromium/chromium.ts
@@ -166,6 +166,11 @@ export class Chromium extends BrowserType {
     }
   }
 
+  override async connectOverCDPTransport(progress: Progress, transport: ConnectionTransport) {
+    const closeAndWait = async () => transport.close();
+    return this._connectOverCDPImpl(progress, transport, closeAndWait, { isLocal: true });
+  }
+
   override async connectToWorker(progress: Progress, endpoint: string) {
     const wsEndpoint = await urlToWSEndpoint(progress, endpoint, {});
     const transport = await WebSocketTransport.connect(progress, wsEndpoint);
diff --git a/packages/playwright-core/src/server/dispatchers/browserTypeDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserTypeDispatcher.ts
index bd5c7a3fe0d87..40633088d5d1e 100644
--- a/packages/playwright-core/src/server/dispatchers/browserTypeDispatcher.ts
+++ b/packages/playwright-core/src/server/dispatchers/browserTypeDispatcher.ts
@@ -65,6 +65,15 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.Brow
     };
   }
 
+  async connectOverCDPTransport(params: channels.BrowserTypeConnectOverCDPTransportParams, progress: Progress): Promise<channels.BrowserTypeConnectOverCDPTransportResult> {
+    if (this._denyLaunch)
+      throw new Error(`Launching more browsers is not allowed.`);
+
+    const browser = await this._object.connectOverCDPTransport(progress, params.transport as any);
+    const browserDispatcher = new BrowserDispatcher(this, browser);
+    return { browser: browserDispatcher, defaultContext: browser._defaultContext ? BrowserContextDispatcher.from(browserDispatcher, browser._defaultContext) : undefined };
+  }
+
   async connectToWorker(params: channels.BrowserTypeConnectToWorkerParams, progress: Progress): Promise<channels.BrowserTypeConnectToWorkerResult> {
     if (this._denyLaunch)
       throw new Error(`Launching more browsers is not allowed.`);
diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts
index 7a032da7ad9a6..85617e01e86a5 100644
--- a/packages/playwright-core/types/types.d.ts
+++ b/packages/playwright-core/types/types.d.ts
@@ -15414,6 +15414,32 @@ export interface BrowserType<Unused = {}> {
    * @param options
    */
   connectOverCDP(options: ConnectOverCDPOptions & { wsEndpoint?: string }): Promise<Browser>;
+  /**
+   * This method attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
+   *
+   * The default browser context is accessible via
+   * [browser.contexts()](https://playwright.dev/docs/api/class-browser#browser-contexts).
+   *
+   * **NOTE** Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
+   *
+   * **NOTE** This connection is significantly lower fidelity than the Playwright protocol connection via
+   * [browserType.connect(endpoint[, options])](https://playwright.dev/docs/api/class-browsertype#browser-type-connect).
+   * If you are experiencing issues or attempting to use advanced functionality, you probably want to use
+   * [browserType.connect(endpoint[, options])](https://playwright.dev/docs/api/class-browsertype#browser-type-connect).
+   *
+   * **Usage**
+   *
+   * ```js
+   * const browser = await playwright.chromium.connectOverCDP('http://localhost:9222');
+   * const defaultContext = browser.contexts()[0];
+   * const page = defaultContext.pages()[0];
+   * ```
+   *
+   * @param endpointURL A CDP websocket endpoint or http url to connect to. For example `http://localhost:9222/` or
+   * `ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4`.
+   * @param options
+   */
+  connectOverCDP(transport: ConnectionTransport): Promise<Browser>;
   /**
    * This method attaches Playwright to an existing browser instance created via `BrowserType.launchServer` in Node.js.
    *
@@ -16194,6 +16220,13 @@ export interface BrowserType<Unused = {}> {
   name(): string;
 }
 
+export interface ConnectionTransport {
+  send(message: object): void;
+  close(): void;
+  onmessage?: (message: object) => void;
+  onclose?: (reason?: string) => void;
+}
+
 /**
  * The `CDPSession` instances are used to talk raw Chrome Devtools Protocol:
  * - protocol methods can be called with `session.send` method.
diff --git a/packages/protocol/spec/browserType.yml b/packages/protocol/spec/browserType.yml
index b86f06b7cfde9..022ed15aa3123 100644
--- a/packages/protocol/spec/browserType.yml
+++ b/packages/protocol/spec/browserType.yml
@@ -56,6 +56,14 @@ BrowserType:
         browser: Browser
         defaultContext: BrowserContext?
 
+    connectOverCDPTransport:
+      title: Connect over CDP transport
+      parameters:
+        transport: binary
+      returns:
+        browser: Browser
+        defaultContext: BrowserContext?
+
     connectToWorker:
       title: Connect to worker
       parameters:
diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts
index c2f9cea282714..a4dd1e2bd47d9 100644
--- a/packages/protocol/src/channels.d.ts
+++ b/packages/protocol/src/channels.d.ts
@@ -1822,6 +1822,7 @@ export interface BrowserTypeChannel extends BrowserTypeEventTarget, Channel {
   launch(params: BrowserTypeLaunchParams, progress?: Progress): Promise<BrowserTypeLaunchResult>;
   launchPersistentContext(params: BrowserTypeLaunchPersistentContextParams, progress?: Progress): Promise<BrowserTypeLaunchPersistentContextResult>;
   connectOverCDP(params: BrowserTypeConnectOverCDPParams, progress?: Progress): Promise<BrowserTypeConnectOverCDPResult>;
+  connectOverCDPTransport(params: BrowserTypeConnectOverCDPTransportParams, progress?: Progress): Promise<BrowserTypeConnectOverCDPTransportResult>;
   connectToWorker(params: BrowserTypeConnectToWorkerParams, progress?: Progress): Promise<BrowserTypeConnectToWorkerResult>;
 }
 export type BrowserTypeLaunchParams = {
@@ -2078,6 +2079,16 @@ export type BrowserTypeConnectOverCDPResult = {
   browser: BrowserChannel,
   defaultContext?: BrowserContextChannel,
 };
+export type BrowserTypeConnectOverCDPTransportParams = {
+  transport: Binary,
+};
+export type BrowserTypeConnectOverCDPTransportOptions = {
+
+};
+export type BrowserTypeConnectOverCDPTransportResult = {
+  browser: BrowserChannel,
+  defaultContext?: BrowserContextChannel,
+};
 export type BrowserTypeConnectToWorkerParams = {
   endpoint: string,
   timeout: number,
diff --git a/tests/library/chromium/connect-over-cdp.spec.ts b/tests/library/chromium/connect-over-cdp.spec.ts
index 52397b0d02030..29c6e99f5d87c 100644
--- a/tests/library/chromium/connect-over-cdp.spec.ts
+++ b/tests/library/chromium/connect-over-cdp.spec.ts
@@ -22,7 +22,7 @@ import path from 'path';
 import { getUserAgent, server as coreServer } from '../../../packages/playwright-core/lib/coreBundle';
 import { suppressCertificateWarning } from '../../config/utils';
 
-const { nullProgress } = coreServer;
+const { WebSocketTransport, nullProgress } = coreServer;
 type Frame = coreServer.Frame;
 
 test('should connect to an existing cdp session', async ({ browserType, mode }, testInfo) => {
@@ -750,3 +750,32 @@ test('noDefaults should not affect new contexts', async ({ browserType, mode, se
     await browserServer.close();
   }
 });
+
+test('should connect over CDP using a ConnectionTransport', async ({ browserType, mode, server }, testInfo) => {
+  test.skip(mode !== 'default', 'Passing a transport to connectOverCDP is only available in-process');
+
+  const port = 9339 + testInfo.workerIndex;
+  const browserServer = await browserType.launch({
+    args: ['--remote-debugging-port=' + port]
+  });
+  try {
+    const json = await new Promise<string>((resolve, reject) => {
+      http.get(`http://127.0.0.1:${port}/json/version/`, resp => {
+        let data = '';
+        resp.on('data', chunk => data += chunk);
+        resp.on('end', () => resolve(data));
+      }).on('error', reject);
+    });
+    const wsEndpoint = JSON.parse(json).webSocketDebuggerUrl;
+    const transport = await WebSocketTransport.connect(undefined, wsEndpoint);
+    const cdpBrowser = await browserType.connectOverCDP(transport);
+    const contexts = cdpBrowser.contexts();
+    expect(contexts.length).toBe(1);
+    const page = await contexts[0].newPage();
+    await page.goto(server.EMPTY_PAGE);
+    expect(page.url()).toBe(server.EMPTY_PAGE);
+    await cdpBrowser.close();
+  } finally {
+    await browserServer.close();
+  }
+});
diff --git a/utils/generate_types/index.js b/utils/generate_types/index.js
index 2274c79577616..09ca058974f55 100644
--- a/utils/generate_types/index.js
+++ b/utils/generate_types/index.js
@@ -521,6 +521,9 @@ class TypesGenerator {
       doNotGenerate: new Set([
         ...assertionClasses,
       ]),
+      ignoreMissing: new Set([
+        'ConnectionTransport',
+      ]),
     });
     let types = await generator.generateTypes(path.join(__dirname, 'overrides.d.ts'));
     const namedDevices = Object.keys(devices).map(name => `  ${JSON.stringify(name)}: DeviceDescriptor;`).join('\n');
diff --git a/utils/generate_types/overrides.d.ts b/utils/generate_types/overrides.d.ts
index 03ca88049c18c..687f620e36984 100644
--- a/utils/generate_types/overrides.d.ts
+++ b/utils/generate_types/overrides.d.ts
@@ -216,6 +216,7 @@ export interface BrowserType<Unused = {}> {
    * @deprecated
    */
   connectOverCDP(options: ConnectOverCDPOptions & { wsEndpoint?: string }): Promise<Browser>;
+  connectOverCDP(transport: ConnectionTransport): Promise<Browser>;
   connect(wsEndpoint: string, options?: ConnectOptions): Promise<Browser>;
   /**
    * wsEndpoint in options is deprecated. Instead use `wsEndpoint`.
@@ -226,6 +227,13 @@ export interface BrowserType<Unused = {}> {
   connect(options: ConnectOptions & { wsEndpoint?: string }): Promise<Browser>;
 }
 
+export interface ConnectionTransport {
+  send(message: object): void;
+  close(): void;
+  onmessage?: (message: object) => void;
+  onclose?: (reason?: string) => void;
+}
+
 export interface CDPSession {
   on<T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void): this;
   addListener<T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void): this;
