From cfa554972be172e212f29f797623d4f9d21a1c2b Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 5 Oct 2022 16:59:34 -0800 Subject: [PATCH] cherry-pick(#17855): chore: use api selectors in codegen hover --- packages/playwright-core/src/cli/driver.ts | 4 ++-- .../playwright-core/src/protocol/validator.ts | 1 + .../src/server/debugController.ts | 6 +++++- .../dispatchers/debugControllerDispatcher.ts | 4 ++-- .../src/server/injected/highlight.ts | 9 ++++++++- .../src/server/injected/recorder.ts | 3 ++- .../src/server/isomorphic/locatorGenerators.ts | 2 +- .../playwright-core/src/server/recorder.ts | 18 +++++++++++++++++- .../src/server/recorder/csharp.ts | 4 ++-- .../src/server/recorder/java.ts | 4 ++-- .../src/server/recorder/javascript.ts | 4 ++-- .../src/server/recorder/language.ts | 4 +++- .../src/server/recorder/python.ts | 4 ++-- .../src/utils/isomorphic/stringUtils.ts | 3 +-- packages/protocol/src/channels.ts | 1 + packages/protocol/src/protocol.yml | 3 +++ packages/recorder/src/recorder.tsx | 1 + packages/recorder/src/recorderTypes.ts | 3 ++- tests/page/locator-highlight.spec.ts | 2 +- 19 files changed, 58 insertions(+), 22 deletions(-) diff --git a/packages/playwright-core/src/cli/driver.ts b/packages/playwright-core/src/cli/driver.ts index a0542d59d7..3733a7f784 100644 --- a/packages/playwright-core/src/cli/driver.ts +++ b/packages/playwright-core/src/cli/driver.ts @@ -90,8 +90,8 @@ class ProtocolHandler { this._controller.on(DebugController.Events.BrowsersChanged, browsers => { process.send!({ method: 'browsersChanged', params: { browsers } }); }); - this._controller.on(DebugController.Events.InspectRequested, selector => { - process.send!({ method: 'inspectRequested', params: { selector } }); + this._controller.on(DebugController.Events.InspectRequested, ({ selector, locators }) => { + process.send!({ method: 'inspectRequested', params: { selector, locators } }); }); } diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 2f7080237c..a63bb2c99c 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -334,6 +334,7 @@ scheme.RecorderSource = tObject({ scheme.DebugControllerInitializer = tOptional(tObject({})); scheme.DebugControllerInspectRequestedEvent = tObject({ selector: tString, + locators: tArray(tType('NameValue')), }); scheme.DebugControllerBrowsersChangedEvent = tObject({ browsers: tArray(tObject({ diff --git a/packages/playwright-core/src/server/debugController.ts b/packages/playwright-core/src/server/debugController.ts index 6a07fd0047..3c01a5ea68 100644 --- a/packages/playwright-core/src/server/debugController.ts +++ b/packages/playwright-core/src/server/debugController.ts @@ -23,6 +23,9 @@ import type { InstrumentationListener } from './instrumentation'; import type { Playwright } from './playwright'; import { Recorder } from './recorder'; import { EmptyRecorderApp } from './recorder/recorderApp'; +import { asLocator } from './isomorphic/locatorGenerators'; +import type { Language } from './isomorphic/locatorGenerators'; +import type { NameValue } from '../common/types'; const internalMetadata = serverSideCallMetadata(); @@ -215,7 +218,8 @@ class InspectingRecorderApp extends EmptyRecorderApp { } override async setSelector(selector: string): Promise { - this._debugController.emit(DebugController.Events.InspectRequested, selector); + const locators: NameValue[] = ['javascript', 'python', 'java', 'csharp'].map(l => ({ name: l, value: asLocator(l as Language, selector) })); + this._debugController.emit(DebugController.Events.InspectRequested, { selector, locators }); } override async setSources(sources: Source[]): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts index 4fc56ebfda..d820996e55 100644 --- a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts @@ -28,8 +28,8 @@ export class DebugControllerDispatcher extends Dispatcher { this._dispatchEvent('browsersChanged', { browsers }); }); - this._object.on(DebugController.Events.InspectRequested, selector => { - this._dispatchEvent('inspectRequested', { selector }); + this._object.on(DebugController.Events.InspectRequested, ({ selector, locators }) => { + this._dispatchEvent('inspectRequested', { selector, locators }); }); this._object.on(DebugController.Events.SourcesChanged, sources => { this._dispatchEvent('sourcesChanged', { sources }); diff --git a/packages/playwright-core/src/server/injected/highlight.ts b/packages/playwright-core/src/server/injected/highlight.ts index 55d00d6a86..e90d1fc52b 100644 --- a/packages/playwright-core/src/server/injected/highlight.ts +++ b/packages/playwright-core/src/server/injected/highlight.ts @@ -17,6 +17,8 @@ import { stringifySelector } from '../isomorphic/selectorParser'; import type { ParsedSelector } from '../isomorphic/selectorParser'; import type { InjectedScript } from './injectedScript'; +import { asLocator } from '../isomorphic/locatorGenerators'; +import type { Language } from '../isomorphic/locatorGenerators'; type HighlightEntry = { targetElement: Element, @@ -35,6 +37,7 @@ export class Highlight { private _isUnderTest: boolean; private _injectedScript: InjectedScript; private _rafRequest: number | undefined; + private _language: Language = 'javascript'; constructor(injectedScript: InjectedScript) { this._injectedScript = injectedScript; @@ -102,6 +105,10 @@ export class Highlight { document.documentElement.appendChild(this._glassPaneElement); } + setLanguage(language: Language) { + this._language = language; + } + runHighlightOnRaf(selector: ParsedSelector) { if (this._rafRequest) cancelAnimationFrame(this._rafRequest); @@ -145,7 +152,7 @@ export class Highlight { color = '#dc6f6f7f'; else color = elements.length > 1 ? '#f6b26b7f' : '#6fa8dc7f'; - this._innerUpdateHighlight(elements, { color, tooltipText: selector }); + this._innerUpdateHighlight(elements, { color, tooltipText: selector ? asLocator(this._language, selector) : '' }); } maskElements(elements: Element[]) { diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index 64427f8f0a..ae974af105 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -94,7 +94,8 @@ class Recorder { return; } - const { mode, actionPoint, actionSelector } = state; + const { mode, actionPoint, actionSelector, language } = state; + this._highlight.setLanguage(language); if (mode !== this._mode) { this._mode = mode; this._clearHighlight(); diff --git a/packages/playwright-core/src/server/isomorphic/locatorGenerators.ts b/packages/playwright-core/src/server/isomorphic/locatorGenerators.ts index d040c545ac..63ee2be25f 100644 --- a/packages/playwright-core/src/server/isomorphic/locatorGenerators.ts +++ b/packages/playwright-core/src/server/isomorphic/locatorGenerators.ts @@ -19,7 +19,7 @@ import type { CSSComplexSelectorList } from '../isomorphic/cssParser'; import { parseAttributeSelector, parseSelector, stringifySelector } from '../isomorphic/selectorParser'; import type { ParsedSelector } from '../isomorphic/selectorParser'; -type Language = 'javascript' | 'python' | 'java' | 'csharp'; +export type Language = 'javascript' | 'python' | 'java' | 'csharp'; export type LocatorType = 'default' | 'role' | 'text' | 'label' | 'placeholder' | 'alt' | 'title' | 'test-id' | 'nth' | 'first' | 'last' | 'has-text'; export type LocatorBase = 'page' | 'locator' | 'frame-locator'; diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index 06b23af0bc..7abd400aab 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -40,7 +40,7 @@ import { metadataToCallLog } from './recorder/recorderUtils'; import { Debugger } from './debugger'; import { EventEmitter } from 'events'; import { raceAgainstTimeout } from '../utils/timeoutRunner'; -import type { LanguageGenerator } from './recorder/language'; +import type { Language, LanguageGenerator } from './recorder/language'; type BindingSource = { frame: Frame, page: Page }; @@ -59,6 +59,7 @@ export class Recorder implements InstrumentationListener { private _handleSIGINT: boolean | undefined; private _recorderAppFactory: (recorder: Recorder) => Promise; private _omitCallTracking = false; + private _currentLanguage: Language; static showInspector(context: BrowserContext) { Recorder.show(context, {}).catch(() => {}); @@ -83,6 +84,7 @@ export class Recorder implements InstrumentationListener { this._debugger = Debugger.lookup(context)!; this._handleSIGINT = params.handleSIGINT; context.instrumentation.addListener(this, context); + this._currentLanguage = this._contextRecorder.languageName(); } private static async defaultRecorderAppFactory(recorder: Recorder) { @@ -111,6 +113,11 @@ export class Recorder implements InstrumentationListener { this._debugger.resume(true); return; } + if (data.event === 'fileChanged') { + this._currentLanguage = this._contextRecorder.languageName(data.params.file); + this._refreshOverlay(); + return; + } if (data.event === 'resume') { this._debugger.resume(false); return; @@ -155,6 +162,7 @@ export class Recorder implements InstrumentationListener { mode: this._mode, actionPoint, actionSelector, + language: this._currentLanguage }; return uiState; }); @@ -381,6 +389,14 @@ class ContextRecorder extends EventEmitter { this._generator?.restart(); } + languageName(id?: string): Language { + for (const lang of this._orderedLanguages) { + if (!id || lang.id === id) + return lang.highlighter; + } + return 'javascript'; + } + async install() { this._context.on(BrowserContext.Events.Page, page => this._onPage(page)); for (const page of this._context.pages()) diff --git a/packages/playwright-core/src/server/recorder/csharp.ts b/packages/playwright-core/src/server/recorder/csharp.ts index 37d36e8e2c..548eeb2694 100644 --- a/packages/playwright-core/src/server/recorder/csharp.ts +++ b/packages/playwright-core/src/server/recorder/csharp.ts @@ -15,7 +15,7 @@ */ import type { BrowserContextOptions } from '../../..'; -import type { LanguageGenerator, LanguageGeneratorOptions } from './language'; +import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language'; import { sanitizeDeviceOptions, toSignalMap } from './language'; import type { ActionInContext } from './codeGenerator'; import type { Action } from './recorderActions'; @@ -31,7 +31,7 @@ export class CSharpLanguageGenerator implements LanguageGenerator { id: string; groupName = '.NET C#'; name: string; - highlighter = 'csharp'; + highlighter = 'csharp' as Language; _mode: CSharpLanguageMode; constructor(mode: CSharpLanguageMode) { diff --git a/packages/playwright-core/src/server/recorder/java.ts b/packages/playwright-core/src/server/recorder/java.ts index 26aa518ca8..cb18c157ff 100644 --- a/packages/playwright-core/src/server/recorder/java.ts +++ b/packages/playwright-core/src/server/recorder/java.ts @@ -15,7 +15,7 @@ */ import type { BrowserContextOptions } from '../../..'; -import type { LanguageGenerator, LanguageGeneratorOptions } from './language'; +import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language'; import { toSignalMap } from './language'; import type { ActionInContext } from './codeGenerator'; import type { Action } from './recorderActions'; @@ -30,7 +30,7 @@ export class JavaLanguageGenerator implements LanguageGenerator { id = 'java'; groupName = 'Java'; name = 'Library'; - highlighter = 'java'; + highlighter = 'java' as Language; generateAction(actionInContext: ActionInContext): string { const action = actionInContext.action; diff --git a/packages/playwright-core/src/server/recorder/javascript.ts b/packages/playwright-core/src/server/recorder/javascript.ts index e8449680fe..213fd5a6c0 100644 --- a/packages/playwright-core/src/server/recorder/javascript.ts +++ b/packages/playwright-core/src/server/recorder/javascript.ts @@ -15,7 +15,7 @@ */ import type { BrowserContextOptions } from '../../..'; -import type { LanguageGenerator, LanguageGeneratorOptions } from './language'; +import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language'; import { sanitizeDeviceOptions, toSignalMap } from './language'; import type { ActionInContext } from './codeGenerator'; import type { Action } from './recorderActions'; @@ -29,7 +29,7 @@ export class JavaScriptLanguageGenerator implements LanguageGenerator { id: string; groupName = 'Node.js'; name: string; - highlighter = 'javascript'; + highlighter = 'javascript' as Language; private _isTest: boolean; constructor(isTest: boolean) { diff --git a/packages/playwright-core/src/server/recorder/language.ts b/packages/playwright-core/src/server/recorder/language.ts index 8a5c919966..68760136e3 100644 --- a/packages/playwright-core/src/server/recorder/language.ts +++ b/packages/playwright-core/src/server/recorder/language.ts @@ -15,8 +15,10 @@ */ import type { BrowserContextOptions, LaunchOptions } from '../../..'; +import type { Language } from '../isomorphic/locatorGenerators'; import type { ActionInContext } from './codeGenerator'; import type { Action, DialogSignal, DownloadSignal, NavigationSignal, PopupSignal } from './recorderActions'; +export type { Language } from '../isomorphic/locatorGenerators'; export type LanguageGeneratorOptions = { browserName: string; @@ -33,7 +35,7 @@ export interface LanguageGenerator { id: string; groupName: string; name: string; - highlighter: string; + highlighter: Language; generateHeader(options: LanguageGeneratorOptions): string; generateAction(actionInContext: ActionInContext): string; generateFooter(saveStorage: string | undefined): string; diff --git a/packages/playwright-core/src/server/recorder/python.ts b/packages/playwright-core/src/server/recorder/python.ts index 01ec2e3e27..8e58563b9f 100644 --- a/packages/playwright-core/src/server/recorder/python.ts +++ b/packages/playwright-core/src/server/recorder/python.ts @@ -15,7 +15,7 @@ */ import type { BrowserContextOptions } from '../../..'; -import type { LanguageGenerator, LanguageGeneratorOptions } from './language'; +import type { Language, LanguageGenerator, LanguageGeneratorOptions } from './language'; import { sanitizeDeviceOptions, toSignalMap } from './language'; import type { ActionInContext } from './codeGenerator'; import type { Action } from './recorderActions'; @@ -29,7 +29,7 @@ export class PythonLanguageGenerator implements LanguageGenerator { id: string; groupName = 'Python'; name: string; - highlighter = 'python'; + highlighter = 'python' as Language; private _awaitPrefix: '' | 'await '; private _asyncPrefix: '' | 'async '; diff --git a/packages/playwright-core/src/utils/isomorphic/stringUtils.ts b/packages/playwright-core/src/utils/isomorphic/stringUtils.ts index 24833813a5..831e7bbb81 100644 --- a/packages/playwright-core/src/utils/isomorphic/stringUtils.ts +++ b/packages/playwright-core/src/utils/isomorphic/stringUtils.ts @@ -32,8 +32,7 @@ export function toTitleCase(name: string) { } export function toSnakeCase(name: string): string { - const toSnakeCaseRegex = /((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))/g; - return name.replace(toSnakeCaseRegex, `_$1`).toLowerCase(); + return name.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase(); } export function cssEscape(s: string): string { diff --git a/packages/protocol/src/channels.ts b/packages/protocol/src/channels.ts index d8cd1ae1b9..953ee6f1ef 100644 --- a/packages/protocol/src/channels.ts +++ b/packages/protocol/src/channels.ts @@ -610,6 +610,7 @@ export interface DebugControllerChannel extends DebugControllerEventTarget, Chan } export type DebugControllerInspectRequestedEvent = { selector: string, + locators: NameValue[], }; export type DebugControllerBrowsersChangedEvent = { browsers: { diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 4996ce243a..4c20c06b95 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -693,6 +693,9 @@ DebugController: inspectRequested: parameters: selector: string + locators: + type: array + items: NameValue browsersChanged: parameters: diff --git a/packages/recorder/src/recorder.tsx b/packages/recorder/src/recorder.tsx index 70c0717a42..f5cf8740e2 100644 --- a/packages/recorder/src/recorder.tsx +++ b/packages/recorder/src/recorder.tsx @@ -132,6 +132,7 @@ export const Recorder: React.FC = ({
Target:
{ window.dispatch({ event: 'clear' }); diff --git a/packages/recorder/src/recorderTypes.ts b/packages/recorder/src/recorderTypes.ts index c41b750b50..5abbb2ec26 100644 --- a/packages/recorder/src/recorderTypes.ts +++ b/packages/recorder/src/recorderTypes.ts @@ -19,7 +19,7 @@ export type Point = { x: number, y: number }; export type Mode = 'inspecting' | 'recording' | 'none'; export type EventData = { - event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated'; + event: 'clear' | 'resume' | 'step' | 'pause' | 'setMode' | 'selectorUpdated' | 'fileChanged'; params: any; }; @@ -27,6 +27,7 @@ export type UIState = { mode: Mode; actionPoint?: Point; actionSelector?: string; + language: 'javascript' | 'python' | 'java' | 'csharp'; }; export type CallLogStatus = 'in-progress' | 'done' | 'error' | 'paused'; diff --git a/tests/page/locator-highlight.spec.ts b/tests/page/locator-highlight.spec.ts index d313173be9..bb69968284 100644 --- a/tests/page/locator-highlight.spec.ts +++ b/tests/page/locator-highlight.spec.ts @@ -27,7 +27,7 @@ it('should highlight locator', async ({ page, isAndroid }) => { const textPromise = waitForTestLog(page, 'Highlight text for test: '); const boxPromise = waitForTestLog<{ x: number, y: number, width: number, height: number }>(page, 'Highlight box for test: '); await page.locator('input').highlight(); - expect(await textPromise).toBe('input'); + expect(await textPromise).toBe('locator(\'input\')'); let box1 = await page.locator('input').boundingBox(); let box2 = await boxPromise;