mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: use api selectors in codegen hover (#17855)
This commit is contained in:
parent
ed6ecbca2a
commit
f2685cab95
@ -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 } });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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<void> {
|
||||
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<void> {
|
||||
|
||||
@ -28,8 +28,8 @@ export class DebugControllerDispatcher extends Dispatcher<DebugController, chann
|
||||
this._object.on(DebugController.Events.BrowsersChanged, browsers => {
|
||||
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 });
|
||||
|
||||
@ -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[]) {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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<IRecorderApp>;
|
||||
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())
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 ';
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -610,6 +610,7 @@ export interface DebugControllerChannel extends DebugControllerEventTarget, Chan
|
||||
}
|
||||
export type DebugControllerInspectRequestedEvent = {
|
||||
selector: string,
|
||||
locators: NameValue[],
|
||||
};
|
||||
export type DebugControllerBrowsersChangedEvent = {
|
||||
browsers: {
|
||||
|
||||
@ -693,6 +693,9 @@ DebugController:
|
||||
inspectRequested:
|
||||
parameters:
|
||||
selector: string
|
||||
locators:
|
||||
type: array
|
||||
items: NameValue
|
||||
|
||||
browsersChanged:
|
||||
parameters:
|
||||
|
||||
@ -132,6 +132,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||
<div>Target:</div>
|
||||
<select className='recorder-chooser' hidden={!sources.length} value={fileId} onChange={event => {
|
||||
setFileId(event.target.selectedOptions[0].value);
|
||||
window.dispatch({ event: 'fileChanged', params: { file: event.target.selectedOptions[0].value } });
|
||||
}}>{renderSourceOptions(sources)}</select>
|
||||
<ToolbarButton icon='clear-all' title='Clear' disabled={!source || !source.text} onClick={() => {
|
||||
window.dispatch({ event: 'clear' });
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -27,7 +27,7 @@ it('should highlight locator', async ({ page, isAndroid }) => {
|
||||
const textPromise = waitForTestLog<string>(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;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user