mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: enable debug controller testing (#18270)
This commit is contained in:
parent
9a684d39ab
commit
d3948d1308
@ -100,7 +100,7 @@ class ProtocolHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async navigate(params: { url: string }) {
|
async navigate(params: { url: string }) {
|
||||||
await this._controller.navigateAll(params.url);
|
await this._controller.navigate(params.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setMode(params: { mode: Mode, language?: string, file?: string }) {
|
async setMode(params: { mode: Mode, language?: string, file?: string }) {
|
||||||
@ -112,11 +112,11 @@ class ProtocolHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async highlight(params: { selector: string }) {
|
async highlight(params: { selector: string }) {
|
||||||
await this._controller.highlightAll(params.selector);
|
await this._controller.highlight(params.selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
async hideHighlight() {
|
async hideHighlight() {
|
||||||
await this._controller.hideHighlightAll();
|
await this._controller.hideHighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
async closeAllBrowsers() {
|
async closeAllBrowsers() {
|
||||||
|
|||||||
@ -73,10 +73,6 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
|
|||||||
(global as any)._playwrightInstance = this;
|
(global as any)._playwrightInstance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _hideHighlight() {
|
|
||||||
await this._channel.hideHighlight();
|
|
||||||
}
|
|
||||||
|
|
||||||
_setSelectors(selectors: Selectors) {
|
_setSelectors(selectors: Selectors) {
|
||||||
const selectorsOwner = SelectorsOwner.from(this._initializer.selectors);
|
const selectorsOwner = SelectorsOwner.from(this._initializer.selectors);
|
||||||
this.selectors._removeChannel(selectorsOwner);
|
this.selectors._removeChannel(selectorsOwner);
|
||||||
|
|||||||
@ -316,8 +316,6 @@ scheme.PlaywrightNewRequestParams = tObject({
|
|||||||
scheme.PlaywrightNewRequestResult = tObject({
|
scheme.PlaywrightNewRequestResult = tObject({
|
||||||
request: tChannel(['APIRequestContext']),
|
request: tChannel(['APIRequestContext']),
|
||||||
});
|
});
|
||||||
scheme.PlaywrightHideHighlightParams = tOptional(tObject({}));
|
|
||||||
scheme.PlaywrightHideHighlightResult = tOptional(tObject({}));
|
|
||||||
scheme.RecorderSource = tObject({
|
scheme.RecorderSource = tObject({
|
||||||
isRecorded: tBoolean,
|
isRecorded: tBoolean,
|
||||||
id: tString,
|
id: tString,
|
||||||
@ -355,22 +353,22 @@ scheme.DebugControllerSetReportStateChangedParams = tObject({
|
|||||||
scheme.DebugControllerSetReportStateChangedResult = tOptional(tObject({}));
|
scheme.DebugControllerSetReportStateChangedResult = tOptional(tObject({}));
|
||||||
scheme.DebugControllerResetForReuseParams = tOptional(tObject({}));
|
scheme.DebugControllerResetForReuseParams = tOptional(tObject({}));
|
||||||
scheme.DebugControllerResetForReuseResult = tOptional(tObject({}));
|
scheme.DebugControllerResetForReuseResult = tOptional(tObject({}));
|
||||||
scheme.DebugControllerNavigateAllParams = tObject({
|
scheme.DebugControllerNavigateParams = tObject({
|
||||||
url: tString,
|
url: tString,
|
||||||
});
|
});
|
||||||
scheme.DebugControllerNavigateAllResult = tOptional(tObject({}));
|
scheme.DebugControllerNavigateResult = tOptional(tObject({}));
|
||||||
scheme.DebugControllerSetRecorderModeParams = tObject({
|
scheme.DebugControllerSetRecorderModeParams = tObject({
|
||||||
mode: tEnum(['inspecting', 'recording', 'none']),
|
mode: tEnum(['inspecting', 'recording', 'none']),
|
||||||
language: tOptional(tString),
|
language: tOptional(tString),
|
||||||
file: tOptional(tString),
|
file: tOptional(tString),
|
||||||
});
|
});
|
||||||
scheme.DebugControllerSetRecorderModeResult = tOptional(tObject({}));
|
scheme.DebugControllerSetRecorderModeResult = tOptional(tObject({}));
|
||||||
scheme.DebugControllerHighlightAllParams = tObject({
|
scheme.DebugControllerHighlightParams = tObject({
|
||||||
selector: tString,
|
selector: tString,
|
||||||
});
|
});
|
||||||
scheme.DebugControllerHighlightAllResult = tOptional(tObject({}));
|
scheme.DebugControllerHighlightResult = tOptional(tObject({}));
|
||||||
scheme.DebugControllerHideHighlightAllParams = tOptional(tObject({}));
|
scheme.DebugControllerHideHighlightParams = tOptional(tObject({}));
|
||||||
scheme.DebugControllerHideHighlightAllResult = tOptional(tObject({}));
|
scheme.DebugControllerHideHighlightResult = tOptional(tObject({}));
|
||||||
scheme.DebugControllerKillParams = tOptional(tObject({}));
|
scheme.DebugControllerKillParams = tOptional(tObject({}));
|
||||||
scheme.DebugControllerKillResult = tOptional(tObject({}));
|
scheme.DebugControllerKillResult = tOptional(tObject({}));
|
||||||
scheme.DebugControllerCloseAllBrowsersParams = tOptional(tObject({}));
|
scheme.DebugControllerCloseAllBrowsersParams = tOptional(tObject({}));
|
||||||
|
|||||||
@ -142,7 +142,6 @@ export class PlaywrightConnection {
|
|||||||
private _initDebugControllerMode(): DebugControllerDispatcher {
|
private _initDebugControllerMode(): DebugControllerDispatcher {
|
||||||
this._debugLog(`engaged reuse controller mode`);
|
this._debugLog(`engaged reuse controller mode`);
|
||||||
const playwright = this._preLaunched.playwright!;
|
const playwright = this._preLaunched.playwright!;
|
||||||
this._cleanups.push(() => gracefullyCloseAll());
|
|
||||||
// Always create new instance based on the reused Playwright instance.
|
// Always create new instance based on the reused Playwright instance.
|
||||||
return new DebugControllerDispatcher(this._dispatcherConnection, playwright.debugController);
|
return new DebugControllerDispatcher(this._dispatcherConnection, playwright.debugController);
|
||||||
}
|
}
|
||||||
@ -169,7 +168,7 @@ export class PlaywrightConnection {
|
|||||||
if (!browser) {
|
if (!browser) {
|
||||||
browser = await playwright[(this._options.browserName || 'chromium') as 'chromium'].launch(serverSideCallMetadata(), {
|
browser = await playwright[(this._options.browserName || 'chromium') as 'chromium'].launch(serverSideCallMetadata(), {
|
||||||
...this._options.launchOptions,
|
...this._options.launchOptions,
|
||||||
headless: false,
|
headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS,
|
||||||
});
|
});
|
||||||
browser.on(Browser.Events.Disconnected, () => {
|
browser.on(Browser.Events.Disconnected, () => {
|
||||||
// Underlying browser did close for some reason - force disconnect the client.
|
// Underlying browser did close for some reason - force disconnect the client.
|
||||||
|
|||||||
@ -670,4 +670,5 @@ const defaultNewContextParamValues: channels.BrowserNewContextForReuseParams = {
|
|||||||
acceptDownloads: true,
|
acceptDownloads: true,
|
||||||
strictSelectors: false,
|
strictSelectors: false,
|
||||||
serviceWorkers: 'allow',
|
serviceWorkers: 'allow',
|
||||||
|
locale: 'en-US',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -42,7 +42,6 @@ export class DebugController extends SdkObject {
|
|||||||
private _autoCloseAllowed = false;
|
private _autoCloseAllowed = false;
|
||||||
private _trackHierarchyListener: InstrumentationListener | undefined;
|
private _trackHierarchyListener: InstrumentationListener | undefined;
|
||||||
private _playwright: Playwright;
|
private _playwright: Playwright;
|
||||||
private _reuseBrowser = false;
|
|
||||||
|
|
||||||
constructor(playwright: Playwright) {
|
constructor(playwright: Playwright) {
|
||||||
super({ attribution: { isInternalPlaywright: true }, instrumentation: createInstrumentation() } as any, undefined, 'DebugController');
|
super({ attribution: { isInternalPlaywright: true }, instrumentation: createInstrumentation() } as any, undefined, 'DebugController');
|
||||||
@ -62,7 +61,6 @@ export class DebugController extends SdkObject {
|
|||||||
if (enabled && !this._trackHierarchyListener) {
|
if (enabled && !this._trackHierarchyListener) {
|
||||||
this._trackHierarchyListener = {
|
this._trackHierarchyListener = {
|
||||||
onPageOpen: () => this._emitSnapshot(),
|
onPageOpen: () => this._emitSnapshot(),
|
||||||
onPageNavigated: () => this._emitSnapshot(),
|
|
||||||
onPageClose: () => this._emitSnapshot(),
|
onPageClose: () => this._emitSnapshot(),
|
||||||
};
|
};
|
||||||
this._playwright.instrumentation.addListener(this._trackHierarchyListener, null);
|
this._playwright.instrumentation.addListener(this._trackHierarchyListener, null);
|
||||||
@ -80,7 +78,7 @@ export class DebugController extends SdkObject {
|
|||||||
await context.resetForReuse(internalMetadata, null);
|
await context.resetForReuse(internalMetadata, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async navigateAll(url: string) {
|
async navigate(url: string) {
|
||||||
for (const p of this._playwright.allPages())
|
for (const p of this._playwright.allPages())
|
||||||
await p.mainFrame().goto(internalMetadata, url);
|
await p.mainFrame().goto(internalMetadata, url);
|
||||||
}
|
}
|
||||||
@ -98,7 +96,7 @@ export class DebugController extends SdkObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this._playwright.allBrowsers().length)
|
if (!this._playwright.allBrowsers().length)
|
||||||
await this._playwright.chromium.launch(internalMetadata, { headless: false });
|
await this._playwright.chromium.launch(internalMetadata, { headless: !!process.env.PW_DEBUG_CONTROLLER_HEADLESS });
|
||||||
// Create page if none.
|
// Create page if none.
|
||||||
const pages = this._playwright.allPages();
|
const pages = this._playwright.allPages();
|
||||||
if (!pages.length) {
|
if (!pages.length) {
|
||||||
@ -132,12 +130,16 @@ export class DebugController extends SdkObject {
|
|||||||
this._autoCloseTimer = setTimeout(heartBeat, 30000);
|
this._autoCloseTimer = setTimeout(heartBeat, 30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async highlightAll(selector: string) {
|
async highlight(selector: string) {
|
||||||
for (const recorder of await this._allRecorders())
|
for (const recorder of await this._allRecorders())
|
||||||
recorder.setHighlightedSelector(selector);
|
recorder.setHighlightedSelector(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
async hideHighlightAll() {
|
async hideHighlight() {
|
||||||
|
// Hide all active recorder highlights.
|
||||||
|
for (const recorder of await this._allRecorders())
|
||||||
|
recorder.setHighlightedSelector('');
|
||||||
|
// Hide all locator.highlight highlights.
|
||||||
await this._playwright.hideHighlight();
|
await this._playwright.hideHighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -44,20 +44,20 @@ export class DebugControllerDispatcher extends Dispatcher<DebugController, chann
|
|||||||
await this._object.resetForReuse();
|
await this._object.resetForReuse();
|
||||||
}
|
}
|
||||||
|
|
||||||
async navigateAll(params: channels.DebugControllerNavigateAllParams) {
|
async navigate(params: channels.DebugControllerNavigateParams) {
|
||||||
await this._object.navigateAll(params.url);
|
await this._object.navigate(params.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setRecorderMode(params: channels.DebugControllerSetRecorderModeParams) {
|
async setRecorderMode(params: channels.DebugControllerSetRecorderModeParams) {
|
||||||
await this._object.setRecorderMode(params);
|
await this._object.setRecorderMode(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async highlightAll(params: channels.DebugControllerHighlightAllParams) {
|
async highlight(params: channels.DebugControllerHighlightParams) {
|
||||||
await this._object.highlightAll(params.selector);
|
await this._object.highlight(params.selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
async hideHighlightAll() {
|
async hideHighlight() {
|
||||||
await this._object.hideHighlightAll();
|
await this._object.hideHighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
async kill() {
|
async kill() {
|
||||||
|
|||||||
@ -62,10 +62,6 @@ export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.Playwr
|
|||||||
return { request: APIRequestContextDispatcher.from(this.parentScope(), request) };
|
return { request: APIRequestContextDispatcher.from(this.parentScope(), request) };
|
||||||
}
|
}
|
||||||
|
|
||||||
async hideHighlight(params: channels.PlaywrightHideHighlightParams, metadata?: channels.Metadata): Promise<channels.PlaywrightHideHighlightResult> {
|
|
||||||
await this._object.hideHighlight();
|
|
||||||
}
|
|
||||||
|
|
||||||
async cleanup() {
|
async cleanup() {
|
||||||
// Cleanup contexts upon disconnect.
|
// Cleanup contexts upon disconnect.
|
||||||
await this._browserDispatcher?.cleanupContexts();
|
await this._browserDispatcher?.cleanupContexts();
|
||||||
|
|||||||
@ -462,8 +462,6 @@ export class FrameManager {
|
|||||||
|
|
||||||
private _fireInternalFrameNavigation(frame: Frame, event: NavigationEvent) {
|
private _fireInternalFrameNavigation(frame: Frame, event: NavigationEvent) {
|
||||||
frame.emit(Frame.Events.InternalNavigation, event);
|
frame.emit(Frame.Events.InternalNavigation, event);
|
||||||
if (event.isPublic && !frame.parentFrame())
|
|
||||||
frame.instrumentation.onPageNavigated(frame._page, event.url);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -63,7 +63,6 @@ export interface Instrumentation {
|
|||||||
onAfterCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
onAfterCall(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||||
onEvent(sdkObject: SdkObject, metadata: CallMetadata): void;
|
onEvent(sdkObject: SdkObject, metadata: CallMetadata): void;
|
||||||
onPageOpen(page: Page): void;
|
onPageOpen(page: Page): void;
|
||||||
onPageNavigated(page: Page, url: string): void;
|
|
||||||
onPageClose(page: Page): void;
|
onPageClose(page: Page): void;
|
||||||
onBrowserOpen(browser: Browser): void;
|
onBrowserOpen(browser: Browser): void;
|
||||||
onBrowserClose(browser: Browser): void;
|
onBrowserClose(browser: Browser): void;
|
||||||
@ -76,7 +75,6 @@ export interface InstrumentationListener {
|
|||||||
onAfterCall?(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
onAfterCall?(sdkObject: SdkObject, metadata: CallMetadata): Promise<void>;
|
||||||
onEvent?(sdkObject: SdkObject, metadata: CallMetadata): void;
|
onEvent?(sdkObject: SdkObject, metadata: CallMetadata): void;
|
||||||
onPageOpen?(page: Page): void;
|
onPageOpen?(page: Page): void;
|
||||||
onPageNavigated?(page: Page, url: string): void;
|
|
||||||
onPageClose?(page: Page): void;
|
onPageClose?(page: Page): void;
|
||||||
onBrowserOpen?(browser: Browser): void;
|
onBrowserOpen?(browser: Browser): void;
|
||||||
onBrowserClose?(browser: Browser): void;
|
onBrowserClose?(browser: Browser): void;
|
||||||
|
|||||||
@ -192,7 +192,6 @@ export class Page extends SdkObject {
|
|||||||
this.pdf = delegate.pdf.bind(delegate);
|
this.pdf = delegate.pdf.bind(delegate);
|
||||||
this.coverage = delegate.coverage ? delegate.coverage() : null;
|
this.coverage = delegate.coverage ? delegate.coverage() : null;
|
||||||
this.selectors = browserContext.selectors();
|
this.selectors = browserContext.selectors();
|
||||||
this.instrumentation.onPageOpen(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async initOpener(opener: PageDelegate | null) {
|
async initOpener(opener: PageDelegate | null) {
|
||||||
@ -218,6 +217,7 @@ export class Page extends SdkObject {
|
|||||||
// corresponding Close event after it is reported on the context.
|
// corresponding Close event after it is reported on the context.
|
||||||
if (this.isClosed())
|
if (this.isClosed())
|
||||||
this.emit(Page.Events.Close);
|
this.emit(Page.Events.Close);
|
||||||
|
this.instrumentation.onPageOpen(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
initializedOrUndefined() {
|
initializedOrUndefined() {
|
||||||
@ -261,25 +261,24 @@ export class Page extends SdkObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_didClose() {
|
_didClose() {
|
||||||
this.instrumentation.onPageClose(this);
|
|
||||||
this._frameManager.dispose();
|
this._frameManager.dispose();
|
||||||
this._frameThrottler.dispose();
|
this._frameThrottler.dispose();
|
||||||
assert(this._closedState !== 'closed', 'Page closed twice');
|
assert(this._closedState !== 'closed', 'Page closed twice');
|
||||||
this._closedState = 'closed';
|
this._closedState = 'closed';
|
||||||
this.emit(Page.Events.Close);
|
this.emit(Page.Events.Close);
|
||||||
this._closedPromise.resolve();
|
this._closedPromise.resolve();
|
||||||
|
this.instrumentation.onPageClose(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_didCrash() {
|
_didCrash() {
|
||||||
this.instrumentation.onPageClose(this);
|
|
||||||
this._frameManager.dispose();
|
this._frameManager.dispose();
|
||||||
this._frameThrottler.dispose();
|
this._frameThrottler.dispose();
|
||||||
this.emit(Page.Events.Crash);
|
this.emit(Page.Events.Crash);
|
||||||
this._crashedPromise.resolve(new Error('Page crashed'));
|
this._crashedPromise.resolve(new Error('Page crashed'));
|
||||||
|
this.instrumentation.onPageClose(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_didDisconnect() {
|
_didDisconnect() {
|
||||||
this.instrumentation.onPageClose(this);
|
|
||||||
this._frameManager.dispose();
|
this._frameManager.dispose();
|
||||||
this._frameThrottler.dispose();
|
this._frameThrottler.dispose();
|
||||||
assert(!this._disconnected, 'Page disconnected twice');
|
assert(!this._disconnected, 'Page disconnected twice');
|
||||||
|
|||||||
@ -519,7 +519,6 @@ export interface PlaywrightEventTarget {
|
|||||||
export interface PlaywrightChannel extends PlaywrightEventTarget, Channel {
|
export interface PlaywrightChannel extends PlaywrightEventTarget, Channel {
|
||||||
_type_Playwright: boolean;
|
_type_Playwright: boolean;
|
||||||
newRequest(params: PlaywrightNewRequestParams, metadata?: Metadata): Promise<PlaywrightNewRequestResult>;
|
newRequest(params: PlaywrightNewRequestParams, metadata?: Metadata): Promise<PlaywrightNewRequestResult>;
|
||||||
hideHighlight(params?: PlaywrightHideHighlightParams, metadata?: Metadata): Promise<PlaywrightHideHighlightResult>;
|
|
||||||
}
|
}
|
||||||
export type PlaywrightNewRequestParams = {
|
export type PlaywrightNewRequestParams = {
|
||||||
baseURL?: string,
|
baseURL?: string,
|
||||||
@ -568,9 +567,6 @@ export type PlaywrightNewRequestOptions = {
|
|||||||
export type PlaywrightNewRequestResult = {
|
export type PlaywrightNewRequestResult = {
|
||||||
request: APIRequestContextChannel,
|
request: APIRequestContextChannel,
|
||||||
};
|
};
|
||||||
export type PlaywrightHideHighlightParams = {};
|
|
||||||
export type PlaywrightHideHighlightOptions = {};
|
|
||||||
export type PlaywrightHideHighlightResult = void;
|
|
||||||
|
|
||||||
export interface PlaywrightEvents {
|
export interface PlaywrightEvents {
|
||||||
}
|
}
|
||||||
@ -601,10 +597,10 @@ export interface DebugControllerChannel extends DebugControllerEventTarget, Chan
|
|||||||
_type_DebugController: boolean;
|
_type_DebugController: boolean;
|
||||||
setReportStateChanged(params: DebugControllerSetReportStateChangedParams, metadata?: Metadata): Promise<DebugControllerSetReportStateChangedResult>;
|
setReportStateChanged(params: DebugControllerSetReportStateChangedParams, metadata?: Metadata): Promise<DebugControllerSetReportStateChangedResult>;
|
||||||
resetForReuse(params?: DebugControllerResetForReuseParams, metadata?: Metadata): Promise<DebugControllerResetForReuseResult>;
|
resetForReuse(params?: DebugControllerResetForReuseParams, metadata?: Metadata): Promise<DebugControllerResetForReuseResult>;
|
||||||
navigateAll(params: DebugControllerNavigateAllParams, metadata?: Metadata): Promise<DebugControllerNavigateAllResult>;
|
navigate(params: DebugControllerNavigateParams, metadata?: Metadata): Promise<DebugControllerNavigateResult>;
|
||||||
setRecorderMode(params: DebugControllerSetRecorderModeParams, metadata?: Metadata): Promise<DebugControllerSetRecorderModeResult>;
|
setRecorderMode(params: DebugControllerSetRecorderModeParams, metadata?: Metadata): Promise<DebugControllerSetRecorderModeResult>;
|
||||||
highlightAll(params: DebugControllerHighlightAllParams, metadata?: Metadata): Promise<DebugControllerHighlightAllResult>;
|
highlight(params: DebugControllerHighlightParams, metadata?: Metadata): Promise<DebugControllerHighlightResult>;
|
||||||
hideHighlightAll(params?: DebugControllerHideHighlightAllParams, metadata?: Metadata): Promise<DebugControllerHideHighlightAllResult>;
|
hideHighlight(params?: DebugControllerHideHighlightParams, metadata?: Metadata): Promise<DebugControllerHideHighlightResult>;
|
||||||
kill(params?: DebugControllerKillParams, metadata?: Metadata): Promise<DebugControllerKillResult>;
|
kill(params?: DebugControllerKillParams, metadata?: Metadata): Promise<DebugControllerKillResult>;
|
||||||
closeAllBrowsers(params?: DebugControllerCloseAllBrowsersParams, metadata?: Metadata): Promise<DebugControllerCloseAllBrowsersResult>;
|
closeAllBrowsers(params?: DebugControllerCloseAllBrowsersParams, metadata?: Metadata): Promise<DebugControllerCloseAllBrowsersResult>;
|
||||||
}
|
}
|
||||||
@ -635,13 +631,13 @@ export type DebugControllerSetReportStateChangedResult = void;
|
|||||||
export type DebugControllerResetForReuseParams = {};
|
export type DebugControllerResetForReuseParams = {};
|
||||||
export type DebugControllerResetForReuseOptions = {};
|
export type DebugControllerResetForReuseOptions = {};
|
||||||
export type DebugControllerResetForReuseResult = void;
|
export type DebugControllerResetForReuseResult = void;
|
||||||
export type DebugControllerNavigateAllParams = {
|
export type DebugControllerNavigateParams = {
|
||||||
url: string,
|
url: string,
|
||||||
};
|
};
|
||||||
export type DebugControllerNavigateAllOptions = {
|
export type DebugControllerNavigateOptions = {
|
||||||
|
|
||||||
};
|
};
|
||||||
export type DebugControllerNavigateAllResult = void;
|
export type DebugControllerNavigateResult = void;
|
||||||
export type DebugControllerSetRecorderModeParams = {
|
export type DebugControllerSetRecorderModeParams = {
|
||||||
mode: 'inspecting' | 'recording' | 'none',
|
mode: 'inspecting' | 'recording' | 'none',
|
||||||
language?: string,
|
language?: string,
|
||||||
@ -652,16 +648,16 @@ export type DebugControllerSetRecorderModeOptions = {
|
|||||||
file?: string,
|
file?: string,
|
||||||
};
|
};
|
||||||
export type DebugControllerSetRecorderModeResult = void;
|
export type DebugControllerSetRecorderModeResult = void;
|
||||||
export type DebugControllerHighlightAllParams = {
|
export type DebugControllerHighlightParams = {
|
||||||
selector: string,
|
selector: string,
|
||||||
};
|
};
|
||||||
export type DebugControllerHighlightAllOptions = {
|
export type DebugControllerHighlightOptions = {
|
||||||
|
|
||||||
};
|
};
|
||||||
export type DebugControllerHighlightAllResult = void;
|
export type DebugControllerHighlightResult = void;
|
||||||
export type DebugControllerHideHighlightAllParams = {};
|
export type DebugControllerHideHighlightParams = {};
|
||||||
export type DebugControllerHideHighlightAllOptions = {};
|
export type DebugControllerHideHighlightOptions = {};
|
||||||
export type DebugControllerHideHighlightAllResult = void;
|
export type DebugControllerHideHighlightResult = void;
|
||||||
export type DebugControllerKillParams = {};
|
export type DebugControllerKillParams = {};
|
||||||
export type DebugControllerKillOptions = {};
|
export type DebugControllerKillOptions = {};
|
||||||
export type DebugControllerKillResult = void;
|
export type DebugControllerKillResult = void;
|
||||||
|
|||||||
@ -636,8 +636,6 @@ Playwright:
|
|||||||
returns:
|
returns:
|
||||||
request: APIRequestContext
|
request: APIRequestContext
|
||||||
|
|
||||||
hideHighlight:
|
|
||||||
|
|
||||||
RecorderSource:
|
RecorderSource:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -666,7 +664,7 @@ DebugController:
|
|||||||
|
|
||||||
resetForReuse:
|
resetForReuse:
|
||||||
|
|
||||||
navigateAll:
|
navigate:
|
||||||
parameters:
|
parameters:
|
||||||
url: string
|
url: string
|
||||||
|
|
||||||
@ -681,11 +679,11 @@ DebugController:
|
|||||||
language: string?
|
language: string?
|
||||||
file: string?
|
file: string?
|
||||||
|
|
||||||
highlightAll:
|
highlight:
|
||||||
parameters:
|
parameters:
|
||||||
selector: string
|
selector: string
|
||||||
|
|
||||||
hideHighlightAll:
|
hideHighlight:
|
||||||
|
|
||||||
kill:
|
kill:
|
||||||
|
|
||||||
|
|||||||
176
tests/config/debugControllerBackend.ts
Normal file
176
tests/config/debugControllerBackend.ts
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
/**
|
||||||
|
* 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 { EventEmitter } from 'events';
|
||||||
|
|
||||||
|
export type ProtocolRequest = {
|
||||||
|
id: number;
|
||||||
|
method: string;
|
||||||
|
params: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProtocolResponse = {
|
||||||
|
id?: number;
|
||||||
|
method?: string;
|
||||||
|
error?: { message: string; data: any; };
|
||||||
|
params?: any;
|
||||||
|
result?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ConnectionTransport {
|
||||||
|
send(s: ProtocolRequest): void;
|
||||||
|
close(): void; // Note: calling close is expected to issue onclose at some point.
|
||||||
|
isClosed(): boolean,
|
||||||
|
onmessage?: (message: ProtocolResponse) => void,
|
||||||
|
onclose?: () => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketTransport implements ConnectionTransport {
|
||||||
|
private _ws: WebSocket;
|
||||||
|
|
||||||
|
onmessage?: (message: ProtocolResponse) => void;
|
||||||
|
onclose?: () => void;
|
||||||
|
readonly wsEndpoint: string;
|
||||||
|
|
||||||
|
static async connect(url: string, headers: Record<string, string> = {}): Promise<WebSocketTransport> {
|
||||||
|
const transport = new WebSocketTransport(url, headers);
|
||||||
|
await new Promise<WebSocketTransport>((fulfill, reject) => {
|
||||||
|
transport._ws.addEventListener('open', async () => {
|
||||||
|
fulfill(transport);
|
||||||
|
});
|
||||||
|
transport._ws.addEventListener('error', event => {
|
||||||
|
reject(new Error('WebSocket error: ' + event.message));
|
||||||
|
transport._ws.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return transport;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(url: string, headers: Record<string, string> = {}) {
|
||||||
|
this.wsEndpoint = url;
|
||||||
|
this._ws = new WebSocket(url, [], {
|
||||||
|
perMessageDeflate: false,
|
||||||
|
maxPayload: 256 * 1024 * 1024, // 256Mb,
|
||||||
|
handshakeTimeout: 30000,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
|
||||||
|
this._ws.addEventListener('message', event => {
|
||||||
|
try {
|
||||||
|
if (this.onmessage)
|
||||||
|
this.onmessage.call(null, JSON.parse(event.data.toString()));
|
||||||
|
} catch (e) {
|
||||||
|
this._ws.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._ws.addEventListener('close', event => {
|
||||||
|
if (this.onclose)
|
||||||
|
this.onclose.call(null);
|
||||||
|
});
|
||||||
|
// Prevent Error: read ECONNRESET.
|
||||||
|
this._ws.addEventListener('error', () => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
isClosed() {
|
||||||
|
return this._ws.readyState === WebSocket.CLOSING || this._ws.readyState === WebSocket.CLOSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
send(message: ProtocolRequest) {
|
||||||
|
this._ws.send(JSON.stringify(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this._ws.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
async closeAndWait() {
|
||||||
|
const promise = new Promise(f => this._ws.once('close', f));
|
||||||
|
this.close();
|
||||||
|
await promise; // Make sure to await the actual disconnect.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Backend extends EventEmitter {
|
||||||
|
private static _lastId = 0;
|
||||||
|
private _callbacks = new Map<number, { fulfill: (a: any) => void, reject: (e: Error) => void }>();
|
||||||
|
private _transport!: WebSocketTransport;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(wsEndpoint: string) {
|
||||||
|
this._transport = await WebSocketTransport.connect(wsEndpoint, {
|
||||||
|
'x-playwright-debug-controller': 'true'
|
||||||
|
});
|
||||||
|
this._transport.onmessage = (message: any) => {
|
||||||
|
if (!message.id) {
|
||||||
|
this.emit(message.method, message.params);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const pair = this._callbacks.get(message.id);
|
||||||
|
if (!pair)
|
||||||
|
return;
|
||||||
|
this._callbacks.delete(message.id);
|
||||||
|
if (message.error) {
|
||||||
|
const error = new Error(message.error.error?.message || message.error.value);
|
||||||
|
error.stack = message.error.error?.stack;
|
||||||
|
pair.reject(error);
|
||||||
|
} else {
|
||||||
|
pair.fulfill(message.result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async resetForReuse() {
|
||||||
|
await this._send('resetForReuse');
|
||||||
|
}
|
||||||
|
|
||||||
|
async navigate(params: { url: string }) {
|
||||||
|
await this._send('navigate', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setMode(params: { mode: 'none' | 'inspecting' | 'recording', language?: string, file?: string }) {
|
||||||
|
await this._send('setRecorderMode', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setReportStateChanged(params: { enabled: boolean }) {
|
||||||
|
await this._send('setReportStateChanged', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async highlight(params: { selector: string }) {
|
||||||
|
await this._send('highlight', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async hideHighlight() {
|
||||||
|
await this._send('hideHighlight');
|
||||||
|
}
|
||||||
|
|
||||||
|
async kill() {
|
||||||
|
this._send('kill');
|
||||||
|
}
|
||||||
|
|
||||||
|
private _send(method: string, params: any = {}): Promise<any> {
|
||||||
|
return new Promise((fulfill, reject) => {
|
||||||
|
const id = ++Backend._lastId;
|
||||||
|
const command = { id, guid: 'DebugController', method, params, metadata: {} };
|
||||||
|
this._transport.send(command as any);
|
||||||
|
this._callbacks.set(id, { fulfill, reject });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
155
tests/library/debug-controller.spec.ts
Normal file
155
tests/library/debug-controller.spec.ts
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
/**
|
||||||
|
* 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 { expect, playwrightTest as baseTest } from '../config/browserTest';
|
||||||
|
import { PlaywrightServer } from '../../packages/playwright-core/lib/remote/playwrightServer';
|
||||||
|
import { createGuid } from '../../packages/playwright-core/lib/utils';
|
||||||
|
import { Backend } from '../config/debugControllerBackend';
|
||||||
|
import type { Browser, BrowserContext } from '@playwright/test';
|
||||||
|
|
||||||
|
type Fixtures = {
|
||||||
|
wsEndpoint: string;
|
||||||
|
backend: Backend;
|
||||||
|
connectedBrowser: Browser & { _newContextForReuse: () => Promise<BrowserContext> };
|
||||||
|
};
|
||||||
|
|
||||||
|
const test = baseTest.extend<Fixtures>({
|
||||||
|
wsEndpoint: async ({ }, use) => {
|
||||||
|
process.env.PW_DEBUG_CONTROLLER_HEADLESS = '1';
|
||||||
|
const server = new PlaywrightServer({ path: '/' + createGuid(), maxConnections: Number.MAX_VALUE, enableSocksProxy: false });
|
||||||
|
const wsEndpoint = await server.listen();
|
||||||
|
await use(wsEndpoint);
|
||||||
|
await server.close();
|
||||||
|
},
|
||||||
|
backend: async ({ wsEndpoint }, use) => {
|
||||||
|
const backend = new Backend();
|
||||||
|
await backend.connect(wsEndpoint);
|
||||||
|
await use(backend);
|
||||||
|
},
|
||||||
|
connectedBrowser: async ({ playwright, wsEndpoint }, use) => {
|
||||||
|
(playwright.chromium as any)._defaultConnectOptions = {
|
||||||
|
wsEndpoint,
|
||||||
|
headers: { 'x-playwright-reuse-context': '1', },
|
||||||
|
};
|
||||||
|
const browser = await playwright.chromium.launch();
|
||||||
|
await use(browser as any);
|
||||||
|
await browser.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test.slow(true, 'All controller tests are slow');
|
||||||
|
|
||||||
|
test('should pick element', async ({ backend, connectedBrowser }) => {
|
||||||
|
const events = [];
|
||||||
|
backend.on('inspectRequested', event => events.push(event));
|
||||||
|
|
||||||
|
await backend.setMode({ mode: 'inspecting' });
|
||||||
|
|
||||||
|
const context = await connectedBrowser._newContextForReuse();
|
||||||
|
const [page] = context.pages();
|
||||||
|
|
||||||
|
await page.locator('body').click();
|
||||||
|
await page.locator('body').click();
|
||||||
|
|
||||||
|
expect(events).toEqual([
|
||||||
|
{
|
||||||
|
selector: 'body',
|
||||||
|
locators: [
|
||||||
|
{ name: 'javascript', value: 'locator(\'body\')' },
|
||||||
|
{ name: 'python', value: 'locator("body")' },
|
||||||
|
{ name: 'java', value: 'locator("body")' },
|
||||||
|
{ name: 'csharp', value: 'Locator("body")' }
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
selector: 'body',
|
||||||
|
locators: [
|
||||||
|
{ name: 'javascript', value: 'locator(\'body\')' },
|
||||||
|
{ name: 'python', value: 'locator("body")' },
|
||||||
|
{ name: 'java', value: 'locator("body")' },
|
||||||
|
{ name: 'csharp', value: 'Locator("body")' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// No events after mode disabled
|
||||||
|
await backend.setMode({ mode: 'none' });
|
||||||
|
await page.locator('body').click();
|
||||||
|
expect(events).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should report pages', async ({ backend, connectedBrowser }) => {
|
||||||
|
const events = [];
|
||||||
|
backend.on('stateChanged', event => events.push(event));
|
||||||
|
await backend.setReportStateChanged({ enabled: true });
|
||||||
|
|
||||||
|
const context = await connectedBrowser._newContextForReuse();
|
||||||
|
const page1 = await context.newPage();
|
||||||
|
const page2 = await context.newPage();
|
||||||
|
await page1.close();
|
||||||
|
await page2.close();
|
||||||
|
|
||||||
|
await backend.setReportStateChanged({ enabled: false });
|
||||||
|
const page3 = await context.newPage();
|
||||||
|
await page3.close();
|
||||||
|
|
||||||
|
expect(events).toEqual([
|
||||||
|
{
|
||||||
|
pageCount: 1,
|
||||||
|
}, {
|
||||||
|
pageCount: 2,
|
||||||
|
}, {
|
||||||
|
pageCount: 1,
|
||||||
|
}, {
|
||||||
|
pageCount: 0,
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should navigate all', async ({ backend, connectedBrowser }) => {
|
||||||
|
const context = await connectedBrowser._newContextForReuse();
|
||||||
|
const page1 = await context.newPage();
|
||||||
|
const page2 = await context.newPage();
|
||||||
|
|
||||||
|
await backend.navigate({ url: 'data:text/plain,Hello world' });
|
||||||
|
|
||||||
|
expect(await page1.evaluate(() => window.location.href)).toBe('data:text/plain,Hello world');
|
||||||
|
expect(await page2.evaluate(() => window.location.href)).toBe('data:text/plain,Hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should reset for reuse', async ({ backend, connectedBrowser }) => {
|
||||||
|
const context = await connectedBrowser._newContextForReuse();
|
||||||
|
const page1 = await context.newPage();
|
||||||
|
const page2 = await context.newPage();
|
||||||
|
await backend.navigate({ url: 'data:text/plain,Hello world' });
|
||||||
|
|
||||||
|
const context2 = await connectedBrowser._newContextForReuse();
|
||||||
|
expect(await context2.pages()[0].evaluate(() => window.location.href)).toBe('about:blank');
|
||||||
|
expect(await page1.evaluate(() => window.location.href)).toBe('about:blank');
|
||||||
|
expect(await page2.evaluate(() => window.location.href).catch(e => e.message)).toContain('Target page, context or browser has been closed');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should highlight all', async ({ backend, connectedBrowser }) => {
|
||||||
|
const context = await connectedBrowser._newContextForReuse();
|
||||||
|
const page1 = await context.newPage();
|
||||||
|
const page2 = await context.newPage();
|
||||||
|
await backend.navigate({ url: 'data:text/html,<button>Submit</button>' });
|
||||||
|
await backend.highlight({ selector: 'button' });
|
||||||
|
await expect(page1.getByText('locator(\'button\')')).toBeVisible();
|
||||||
|
await expect(page2.getByText('locator(\'button\')')).toBeVisible();
|
||||||
|
await backend.hideHighlight();
|
||||||
|
await expect(page1.getByText('locator(\'button\')')).toBeHidden({ timeout: 1000000 });
|
||||||
|
await expect(page2.getByText('locator(\'button\')')).toBeHidden();
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user