Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/playwright-core/src/cli/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } });
});
}

Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
6 changes: 5 additions & 1 deletion packages/playwright-core/src/server/debugController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
9 changes: 8 additions & 1 deletion packages/playwright-core/src/server/injected/highlight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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[]) {
Expand Down
3 changes: 2 additions & 1 deletion packages/playwright-core/src/server/injected/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
18 changes: 17 additions & 1 deletion packages/playwright-core/src/server/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand All @@ -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(() => {});
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -155,6 +162,7 @@ export class Recorder implements InstrumentationListener {
mode: this._mode,
actionPoint,
actionSelector,
language: this._currentLanguage
};
return uiState;
});
Expand Down Expand Up @@ -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())
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/server/recorder/csharp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/server/recorder/java.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/server/recorder/javascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion packages/playwright-core/src/server/recorder/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/server/recorder/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 ';
Expand Down
3 changes: 1 addition & 2 deletions packages/playwright-core/src/utils/isomorphic/stringUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/src/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ export interface DebugControllerChannel extends DebugControllerEventTarget, Chan
}
export type DebugControllerInspectRequestedEvent = {
selector: string,
locators: NameValue[],
};
export type DebugControllerBrowsersChangedEvent = {
browsers: {
Expand Down
3 changes: 3 additions & 0 deletions packages/protocol/src/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,9 @@ DebugController:
inspectRequested:
parameters:
selector: string
locators:
type: array
items: NameValue

browsersChanged:
parameters:
Expand Down
1 change: 1 addition & 0 deletions packages/recorder/src/recorder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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' });
Expand Down
3 changes: 2 additions & 1 deletion packages/recorder/src/recorderTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ 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;
};

export type UIState = {
mode: Mode;
actionPoint?: Point;
actionSelector?: string;
language: 'javascript' | 'python' | 'java' | 'csharp';
};

export type CallLogStatus = 'in-progress' | 'done' | 'error' | 'paused';
Expand Down
2 changes: 1 addition & 1 deletion tests/page/locator-highlight.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down