chore: allow highlighting aria template from extension (#33594)

This commit is contained in:
Pavel Feldman 2024-11-13 21:33:38 -08:00 коммит произвёл GitHub
Родитель a8af7cc435
Коммит 4817483ff2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
9 изменённых файлов: 51 добавлений и 20 удалений

Просмотреть файл

@ -422,7 +422,8 @@ scheme.DebugControllerSetRecorderModeParams = tObject({
}); });
scheme.DebugControllerSetRecorderModeResult = tOptional(tObject({})); scheme.DebugControllerSetRecorderModeResult = tOptional(tObject({}));
scheme.DebugControllerHighlightParams = tObject({ scheme.DebugControllerHighlightParams = tObject({
selector: tString, selector: tOptional(tString),
ariaTemplate: tOptional(tString),
}); });
scheme.DebugControllerHighlightResult = tOptional(tObject({})); scheme.DebugControllerHighlightResult = tOptional(tObject({}));
scheme.DebugControllerHideHighlightParams = tOptional(tObject({})); scheme.DebugControllerHideHighlightParams = tOptional(tObject({}));

Просмотреть файл

@ -15,12 +15,16 @@
*/ */
import { parseYamlTemplate } from '../utils/isomorphic/ariaSnapshot'; import { parseYamlTemplate } from '../utils/isomorphic/ariaSnapshot';
import type { AriaTemplateNode } from '@isomorphic/ariaSnapshot'; import type { AriaTemplateNode, ParsedYaml } from '@isomorphic/ariaSnapshot';
import { yaml } from '../utilsBundle'; import { yaml } from '../utilsBundle';
export function parseAriaSnapshot(text: string): AriaTemplateNode { export function parseAriaSnapshot(text: string): AriaTemplateNode {
const fragment = yaml.parse(text); return parseYamlTemplate(parseYamlForAriaSnapshot(text));
if (!Array.isArray(fragment)) }
throw new Error('Expected object key starting with "- ":\n\n' + text + '\n');
return parseYamlTemplate(fragment); export function parseYamlForAriaSnapshot(text: string): ParsedYaml {
const parsed = yaml.parse(text);
if (!Array.isArray(parsed))
throw new Error('Expected object key starting with "- ":\n\n' + text + '\n');
return parsed;
} }

Просмотреть файл

@ -24,6 +24,7 @@ import type { Playwright } from './playwright';
import { Recorder } from './recorder'; import { Recorder } from './recorder';
import { EmptyRecorderApp } from './recorder/recorderApp'; import { EmptyRecorderApp } from './recorder/recorderApp';
import { asLocator, type Language } from '../utils'; import { asLocator, type Language } from '../utils';
import { parseYamlForAriaSnapshot } from './ariaSnapshot';
const internalMetadata = serverSideCallMetadata(); const internalMetadata = serverSideCallMetadata();
@ -142,9 +143,13 @@ export class DebugController extends SdkObject {
this._autoCloseTimer = setTimeout(heartBeat, 30000); this._autoCloseTimer = setTimeout(heartBeat, 30000);
} }
async highlight(selector: string) { async highlight(params: { selector?: string, ariaTemplate?: string }) {
for (const recorder of await this._allRecorders()) for (const recorder of await this._allRecorders()) {
recorder.setHighlightedSelector(this._sdkLanguage, selector); if (params.ariaTemplate)
recorder.setHighlightedAriaTemplate(parseYamlForAriaSnapshot(params.ariaTemplate));
else if (params.selector)
recorder.setHighlightedSelector(this._sdkLanguage, params.selector);
}
} }
async hideHighlight() { async hideHighlight() {

Просмотреть файл

@ -68,7 +68,7 @@ export class DebugControllerDispatcher extends Dispatcher<DebugController, chann
} }
async highlight(params: channels.DebugControllerHighlightParams) { async highlight(params: channels.DebugControllerHighlightParams) {
await this._object.highlight(params.selector); await this._object.highlight(params);
} }
async hideHighlight() { async hideHighlight() {

Просмотреть файл

@ -40,7 +40,7 @@ export class Recorder implements InstrumentationListener, IRecorder {
readonly handleSIGINT: boolean | undefined; readonly handleSIGINT: boolean | undefined;
private _context: BrowserContext; private _context: BrowserContext;
private _mode: Mode; private _mode: Mode;
private _highlightedElement: { selector?: string, ariaSnapshot?: ParsedYaml } = {}; private _highlightedElement: { selector?: string, ariaTemplate?: ParsedYaml } = {};
private _overlayState: OverlayState = { offsetX: 0 }; private _overlayState: OverlayState = { offsetX: 0 };
private _recorderApp: IRecorderApp | null = null; private _recorderApp: IRecorderApp | null = null;
private _currentCallsMetadata = new Map<CallMetadata, SdkObject>(); private _currentCallsMetadata = new Map<CallMetadata, SdkObject>();
@ -107,8 +107,8 @@ export class Recorder implements InstrumentationListener, IRecorder {
if (data.event === 'highlightRequested') { if (data.event === 'highlightRequested') {
if (data.params.selector) if (data.params.selector)
this.setHighlightedSelector(this._currentLanguage, data.params.selector); this.setHighlightedSelector(this._currentLanguage, data.params.selector);
if (data.params.ariaSnapshot) if (data.params.ariaTemplate)
this.setHighlightedAriaSnapshot(data.params.ariaSnapshot); this.setHighlightedAriaTemplate(data.params.ariaTemplate);
return; return;
} }
if (data.event === 'step') { if (data.event === 'step') {
@ -169,7 +169,7 @@ export class Recorder implements InstrumentationListener, IRecorder {
mode: this._mode, mode: this._mode,
actionPoint, actionPoint,
actionSelector, actionSelector,
ariaTemplate: this._highlightedElement.ariaSnapshot, ariaTemplate: this._highlightedElement.ariaTemplate,
language: this._currentLanguage, language: this._currentLanguage,
testIdAttributeName: this._contextRecorder.testIdAttributeName(), testIdAttributeName: this._contextRecorder.testIdAttributeName(),
overlay: this._overlayState, overlay: this._overlayState,
@ -245,8 +245,8 @@ export class Recorder implements InstrumentationListener, IRecorder {
this._refreshOverlay(); this._refreshOverlay();
} }
setHighlightedAriaSnapshot(ariaSnapshot: ParsedYaml) { setHighlightedAriaTemplate(ariaTemplate: ParsedYaml) {
this._highlightedElement = { ariaSnapshot }; this._highlightedElement = { ariaTemplate };
this._refreshOverlay(); this._refreshOverlay();
} }

Просмотреть файл

@ -741,10 +741,12 @@ export type DebugControllerSetRecorderModeOptions = {
}; };
export type DebugControllerSetRecorderModeResult = void; export type DebugControllerSetRecorderModeResult = void;
export type DebugControllerHighlightParams = { export type DebugControllerHighlightParams = {
selector: string, selector?: string,
ariaTemplate?: string,
}; };
export type DebugControllerHighlightOptions = { export type DebugControllerHighlightOptions = {
selector?: string,
ariaTemplate?: string,
}; };
export type DebugControllerHighlightResult = void; export type DebugControllerHighlightResult = void;
export type DebugControllerHideHighlightParams = {}; export type DebugControllerHideHighlightParams = {};

Просмотреть файл

@ -791,7 +791,8 @@ DebugController:
highlight: highlight:
parameters: parameters:
selector: string selector: string?
ariaTemplate: string?
hideHighlight: hideHighlight:

Просмотреть файл

@ -120,7 +120,7 @@ export const Recorder: React.FC<RecorderProps> = ({
setAriaSnapshotErrors(errors); setAriaSnapshotErrors(errors);
setAriaSnapshot(ariaSnapshot); setAriaSnapshot(ariaSnapshot);
if (!errors.length) if (!errors.length)
window.dispatch({ event: 'highlightRequested', params: { ariaSnapshot: fragment } }); window.dispatch({ event: 'highlightRequested', params: { ariaTemplate: fragment } });
}, [mode]); }, [mode]);
const isRecording = mode === 'recording' || mode === 'recording-inspecting'; const isRecording = mode === 'recording' || mode === 'recording-inspecting';
const locatorPlaceholder = isRecording ? '// Unavailable while recording' : (locator ? undefined : '// Pick element or type locator'); const locatorPlaceholder = isRecording ? '// Unavailable while recording' : (locator ? undefined : '// Pick element or type locator');

Просмотреть файл

@ -20,6 +20,7 @@ import { createGuid } from '../../packages/playwright-core/lib/utils/crypto';
import { Backend } from '../config/debugControllerBackend'; import { Backend } from '../config/debugControllerBackend';
import type { Browser, BrowserContext } from '@playwright/test'; import type { Browser, BrowserContext } from '@playwright/test';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import { roundBox } from '../page/pageTest';
type BrowserWithReuse = Browser & { _newContextForReuse: () => Promise<BrowserContext> }; type BrowserWithReuse = Browser & { _newContextForReuse: () => Promise<BrowserContext> };
type Fixtures = { type Fixtures = {
@ -279,3 +280,20 @@ test('should highlight inside iframe', async ({ backend, connectedBrowser }, tes
await expect(highlight).toHaveCount(1); await expect(highlight).toHaveCount(1);
await expect(page.locator('x-pw-highlight')).toHaveCount(1); await expect(page.locator('x-pw-highlight')).toHaveCount(1);
}); });
test('should highlight aria template', async ({ backend, connectedBrowser }, testInfo) => {
const context = await connectedBrowser._newContextForReuse();
const page = await context.newPage();
await backend.navigate({ url: `data:text/html,<button>Submit</button>` });
const button = page.getByRole('button');
const highlight = page.locator('x-pw-highlight');
await backend.highlight({ ariaTemplate: `- button "Submit2"` });
await expect(highlight).toHaveCount(0);
await backend.highlight({ ariaTemplate: `- button "Submit"` });
const box1 = roundBox(await button.boundingBox());
const box2 = roundBox(await highlight.boundingBox());
expect(box1).toEqual(box2);
});