mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(cli): share console api between cli and debug mode (#4807)
This commit is contained in:
parent
f709e2300c
commit
225e65e076
@ -23,6 +23,7 @@ import * as program from 'commander';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import { installBrowsersWithProgressBar } from '../install/installer';
|
||||
import * as consoleApiSource from '../generated/consoleApiSource';
|
||||
|
||||
// TODO: we can import from '../..' instead, but that requires generating types
|
||||
// before build, and currently type generator depends on the build.
|
||||
@ -284,6 +285,7 @@ async function openPage(context: BrowserContext, url: string | undefined): Promi
|
||||
|
||||
async function open(options: Options, url: string | undefined) {
|
||||
const { context } = await launchContext(options, false);
|
||||
context._extendInjectedScript(consoleApiSource.source);
|
||||
await openPage(context, url);
|
||||
if (process.env.PWCLI_EXIT_FOR_TEST)
|
||||
await Promise.all(context.pages().map(p => p.close()));
|
||||
|
||||
@ -29,6 +29,7 @@ import { Waiter } from './waiter';
|
||||
import { URLMatch, Headers, WaitForEventOptions, BrowserContextOptions, StorageState } from './types';
|
||||
import { isUnderTest, headersObjectToArray, mkdirIfNeeded } from '../utils/utils';
|
||||
import { isSafeCloseError } from '../utils/errors';
|
||||
import { serializeArgument } from './jsHandle';
|
||||
|
||||
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
|
||||
const fsReadFileAsync = util.promisify(fs.readFile.bind(fs));
|
||||
@ -253,6 +254,10 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async _extendInjectedScript<Arg>(source: string, arg?: Arg) {
|
||||
await this._channel.extendInjectedScript({ source, arg: serializeArgument(arg) });
|
||||
}
|
||||
}
|
||||
|
||||
export async function prepareBrowserContextOptions(options: BrowserContextOptions): Promise<channels.BrowserNewContextOptions> {
|
||||
|
||||
@ -435,6 +435,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: remove once playwright-cli does not use this one anymore.
|
||||
async _extendInjectedScript<Arg>(source: string, arg?: Arg): Promise<JSHandle> {
|
||||
const result = await this._channel.extendInjectedScript({ source, arg: serializeArgument(arg) });
|
||||
return JSHandle.from(result.handle);
|
||||
|
||||
@ -15,10 +15,8 @@
|
||||
*/
|
||||
|
||||
import { BrowserContext, ContextListener, contextListeners } from '../server/browserContext';
|
||||
import * as frames from '../server/frames';
|
||||
import { Page } from '../server/page';
|
||||
import { isDebugMode } from '../utils/utils';
|
||||
import * as debugScriptSource from '../generated/debugScriptSource';
|
||||
import * as consoleApiSource from '../generated/consoleApiSource';
|
||||
|
||||
export function installDebugController() {
|
||||
contextListeners.add(new DebugController());
|
||||
@ -27,25 +25,8 @@ export function installDebugController() {
|
||||
class DebugController implements ContextListener {
|
||||
async onContextCreated(context: BrowserContext): Promise<void> {
|
||||
if (isDebugMode())
|
||||
installDebugControllerInContext(context);
|
||||
context.extendInjectedScript(consoleApiSource.source);
|
||||
}
|
||||
async onContextWillDestroy(context: BrowserContext): Promise<void> {}
|
||||
async onContextDidDestroy(context: BrowserContext): Promise<void> {}
|
||||
}
|
||||
|
||||
async function ensureInstalledInFrame(frame: frames.Frame) {
|
||||
try {
|
||||
await frame.extendInjectedScript(debugScriptSource.source);
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
async function installInPage(page: Page) {
|
||||
page.on(Page.Events.FrameNavigated, ensureInstalledInFrame);
|
||||
await Promise.all(page.frames().map(ensureInstalledInFrame));
|
||||
}
|
||||
|
||||
export async function installDebugControllerInContext(context: BrowserContext) {
|
||||
context.on(BrowserContext.Events.Page, installInPage);
|
||||
await Promise.all(context.pages().map(installInPage));
|
||||
}
|
||||
|
||||
@ -22,6 +22,8 @@ export class ConsoleAPI {
|
||||
|
||||
constructor(injectedScript: InjectedScript) {
|
||||
this._injectedScript = injectedScript;
|
||||
if ((window as any).playwright)
|
||||
return;
|
||||
(window as any).playwright = {
|
||||
$: (selector: string) => this._querySelector(selector),
|
||||
$$: (selector: string) => this._querySelectorAll(selector),
|
||||
@ -58,3 +60,5 @@ export class ConsoleAPI {
|
||||
return generateSelector(this._injectedScript, element).selector;
|
||||
}
|
||||
}
|
||||
|
||||
export default ConsoleAPI;
|
||||
|
||||
@ -18,7 +18,7 @@ const path = require('path');
|
||||
const InlineSource = require('../../server/injected/webpack-inline-source-plugin');
|
||||
|
||||
module.exports = {
|
||||
entry: path.join(__dirname, 'debugScript.ts'),
|
||||
entry: path.join(__dirname, 'consoleApi.ts'),
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
@ -37,10 +37,10 @@ module.exports = {
|
||||
},
|
||||
output: {
|
||||
libraryTarget: 'var',
|
||||
filename: 'debugScriptSource.js',
|
||||
filename: 'consoleApiSource.js',
|
||||
path: path.resolve(__dirname, '../../../lib/server/injected/packed')
|
||||
},
|
||||
plugins: [
|
||||
new InlineSource(path.join(__dirname, '..', '..', 'generated', 'debugScriptSource.ts')),
|
||||
new InlineSource(path.join(__dirname, '..', '..', 'generated', 'consoleApiSource.ts')),
|
||||
]
|
||||
};
|
||||
@ -1,27 +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 { ConsoleAPI } from './consoleApi';
|
||||
import type InjectedScript from '../../server/injected/injectedScript';
|
||||
|
||||
export default class DebugScript {
|
||||
consoleAPI: ConsoleAPI | undefined;
|
||||
constructor(injectedScript: InjectedScript) {
|
||||
if ((window as any).playwright)
|
||||
return;
|
||||
this.consoleAPI = new ConsoleAPI(injectedScript);
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,7 @@ import * as channels from '../protocol/channels';
|
||||
import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
|
||||
import { CRBrowserContext } from '../server/chromium/crBrowser';
|
||||
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
||||
import { parseArgument } from './jsHandleDispatcher';
|
||||
|
||||
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextInitializer> implements channels.BrowserContextChannel {
|
||||
private _context: BrowserContext;
|
||||
@ -125,6 +126,10 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||
await this._context.close();
|
||||
}
|
||||
|
||||
async extendInjectedScript(params: channels.BrowserContextExtendInjectedScriptParams): Promise<void> {
|
||||
await this._context.extendInjectedScript(params.source, parseArgument(params.arg));
|
||||
}
|
||||
|
||||
async crNewCDPSession(params: channels.BrowserContextCrNewCDPSessionParams): Promise<channels.BrowserContextCrNewCDPSessionResult> {
|
||||
if (this._object._browser._options.name !== 'chromium')
|
||||
throw new Error(`CDP session is only available in Chromium`);
|
||||
|
||||
@ -556,6 +556,7 @@ export interface BrowserContextChannel extends Channel {
|
||||
setNetworkInterceptionEnabled(params: BrowserContextSetNetworkInterceptionEnabledParams, metadata?: Metadata): Promise<BrowserContextSetNetworkInterceptionEnabledResult>;
|
||||
setOffline(params: BrowserContextSetOfflineParams, metadata?: Metadata): Promise<BrowserContextSetOfflineResult>;
|
||||
storageState(params?: BrowserContextStorageStateParams, metadata?: Metadata): Promise<BrowserContextStorageStateResult>;
|
||||
extendInjectedScript(params: BrowserContextExtendInjectedScriptParams, metadata?: Metadata): Promise<BrowserContextExtendInjectedScriptResult>;
|
||||
crNewCDPSession(params: BrowserContextCrNewCDPSessionParams, metadata?: Metadata): Promise<BrowserContextCrNewCDPSessionResult>;
|
||||
}
|
||||
export type BrowserContextBindingCallEvent = {
|
||||
@ -697,6 +698,14 @@ export type BrowserContextStorageStateResult = {
|
||||
cookies: NetworkCookie[],
|
||||
origins: OriginStorage[],
|
||||
};
|
||||
export type BrowserContextExtendInjectedScriptParams = {
|
||||
source: string,
|
||||
arg: SerializedArgument,
|
||||
};
|
||||
export type BrowserContextExtendInjectedScriptOptions = {
|
||||
|
||||
};
|
||||
export type BrowserContextExtendInjectedScriptResult = void;
|
||||
export type BrowserContextCrNewCDPSessionParams = {
|
||||
page: PageChannel,
|
||||
};
|
||||
|
||||
@ -601,6 +601,12 @@ BrowserContext:
|
||||
type: array
|
||||
items: OriginStorage
|
||||
|
||||
extendInjectedScript:
|
||||
experimental: True
|
||||
parameters:
|
||||
source: string
|
||||
arg: SerializedArgument
|
||||
|
||||
crNewCDPSession:
|
||||
parameters:
|
||||
page: Page
|
||||
|
||||
@ -337,6 +337,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
offline: tBoolean,
|
||||
});
|
||||
scheme.BrowserContextStorageStateParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextExtendInjectedScriptParams = tObject({
|
||||
source: tString,
|
||||
arg: tType('SerializedArgument'),
|
||||
});
|
||||
scheme.BrowserContextCrNewCDPSessionParams = tObject({
|
||||
page: tChannel('Page'),
|
||||
});
|
||||
|
||||
@ -378,6 +378,16 @@ export abstract class BrowserContext extends EventEmitter {
|
||||
await page.close();
|
||||
}
|
||||
}
|
||||
|
||||
async extendInjectedScript(source: string, arg?: any) {
|
||||
const installInFrame = (frame: frames.Frame) => frame.extendInjectedScript(source, arg).catch(e => {});
|
||||
const installInPage = (page: Page) => {
|
||||
page.on(Page.Events.FrameNavigated, installInFrame);
|
||||
return Promise.all(page.frames().map(installInFrame));
|
||||
};
|
||||
this.on(BrowserContext.Events.Page, installInPage);
|
||||
return Promise.all(this.pages().map(installInPage));
|
||||
}
|
||||
}
|
||||
|
||||
export function assertBrowserContextIsNotOwned(context: BrowserContext) {
|
||||
|
||||
@ -18,11 +18,11 @@ import { folio } from './fixtures';
|
||||
import * as path from 'path';
|
||||
import type { Page, Frame } from '..';
|
||||
|
||||
const { installDebugControllerInContext } = require(path.join(__dirname, '..', 'lib', 'debug', 'debugController'));
|
||||
const { source } = require(path.join(__dirname, '..', 'lib', 'generated', 'consoleApiSource'));
|
||||
|
||||
const fixtures = folio.extend();
|
||||
fixtures.context.override(async ({ context, toImpl }, run) => {
|
||||
await installDebugControllerInContext(toImpl(context));
|
||||
fixtures.context.override(async ({ context }, run) => {
|
||||
await (context as any)._extendInjectedScript(source);
|
||||
await run(context);
|
||||
});
|
||||
const { describe, it, expect } = fixtures.build();
|
||||
|
||||
@ -136,7 +136,7 @@ DEPS['src/remote/'] = ['src/client/', 'src/debug/', 'src/dispatchers/', 'src/ser
|
||||
DEPS['src/service.ts'] = ['src/remote/'];
|
||||
|
||||
// CLI should only use client-side features.
|
||||
DEPS['src/cli/'] = ['src/client/**', 'src/install/**'];
|
||||
DEPS['src/cli/'] = ['src/client/**', 'src/install/**', 'src/generated/'];
|
||||
|
||||
checkDeps().catch(e => {
|
||||
console.error(e && e.stack ? e.stack : e);
|
||||
|
||||
@ -20,7 +20,7 @@ const path = require('path');
|
||||
const files = [
|
||||
path.join('src', 'server', 'injected', 'injectedScript.webpack.config.js'),
|
||||
path.join('src', 'server', 'injected', 'utilityScript.webpack.config.js'),
|
||||
path.join('src', 'debug', 'injected', 'debugScript.webpack.config.js'),
|
||||
path.join('src', 'debug', 'injected', 'consoleApi.webpack.config.js'),
|
||||
];
|
||||
|
||||
function runOne(runner, file) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user