Skip to content

Commit 0e64340

Browse files
committed
cherry-pick(#33632): chore: highlight edited locator while recording
1 parent cb0f456 commit 0e64340

File tree

5 files changed

+93
-27
lines changed

5 files changed

+93
-27
lines changed

packages/playwright-core/src/server/injected/recorder/recorder.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,7 +1018,7 @@ export class Recorder {
10181018
private _listeners: (() => void)[] = [];
10191019
private _currentTool: RecorderTool;
10201020
private _tools: Record<Mode, RecorderTool>;
1021-
private _actionSelectorModel: HighlightModel | null = null;
1021+
private _lastHighlightedSelector: string | undefined = undefined;
10221022
private _lastHighlightedAriaTemplateJSON: string = 'undefined';
10231023
readonly highlight: Highlight;
10241024
readonly overlay: Overlay | undefined;
@@ -1129,29 +1129,28 @@ export class Recorder {
11291129
this._switchCurrentTool();
11301130
this.overlay?.setUIState(state);
11311131

1132-
// Race or scroll.
1133-
if (this._actionSelectorModel?.selector && !this._actionSelectorModel?.elements.length && !this._lastHighlightedAriaTemplateJSON)
1134-
this._actionSelectorModel = null;
1135-
1136-
if (state.actionSelector && state.actionSelector !== this._actionSelectorModel?.selector)
1137-
this._actionSelectorModel = querySelector(this.injectedScript, state.actionSelector, this.document);
1132+
let highlight: HighlightModel | 'clear' | 'noop' = 'noop';
1133+
if (state.actionSelector !== this._lastHighlightedSelector) {
1134+
this._lastHighlightedSelector = state.actionSelector;
1135+
const model = state.actionSelector ? querySelector(this.injectedScript, state.actionSelector, this.document) : null;
1136+
highlight = model?.elements.length ? model : 'clear';
1137+
}
11381138

11391139
const ariaTemplateJSON = JSON.stringify(state.ariaTemplate);
11401140
if (this._lastHighlightedAriaTemplateJSON !== ariaTemplateJSON) {
11411141
this._lastHighlightedAriaTemplateJSON = ariaTemplateJSON;
11421142
const template = state.ariaTemplate ? this.injectedScript.utils.parseYamlTemplate(state.ariaTemplate) : undefined;
11431143
const elements = template ? this.injectedScript.getAllByAria(this.document, template) : [];
11441144
if (elements.length)
1145-
this._actionSelectorModel = { elements };
1145+
highlight = { elements };
11461146
else
1147-
this._actionSelectorModel = null;
1147+
highlight = 'clear';
11481148
}
11491149

1150-
if (!state.actionSelector && !state.ariaTemplate)
1151-
this._actionSelectorModel = null;
1152-
1153-
if (this.state.mode === 'none' || this.state.mode === 'standby')
1154-
this.updateHighlight(this._actionSelectorModel, false);
1150+
if (highlight === 'clear')
1151+
this.clearHighlight();
1152+
else if (highlight !== 'noop')
1153+
this.updateHighlight(highlight, false);
11551154
}
11561155

11571156
clearHighlight() {
@@ -1266,6 +1265,8 @@ export class Recorder {
12661265
private _onScroll(event: Event) {
12671266
if (!event.isTrusted)
12681267
return;
1268+
this._lastHighlightedSelector = undefined;
1269+
this._lastHighlightedAriaTemplateJSON = 'undefined';
12691270
this.highlight.hideActionPoint();
12701271
this._currentTool.onScroll?.(event);
12711272
}

packages/recorder/src/recorder.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export const Recorder: React.FC<RecorderProps> = ({
6767
const language = source.language;
6868
setLocator(asLocator(language, elementInfo.selector));
6969
setAriaSnapshot(elementInfo.ariaSnapshot);
70+
setAriaSnapshotErrors([]);
7071
if (userGesture && selectedTab !== 'locator' && selectedTab !== 'aria')
7172
setSelectedTab('locator');
7273

@@ -122,9 +123,6 @@ export const Recorder: React.FC<RecorderProps> = ({
122123
if (!errors.length)
123124
window.dispatch({ event: 'highlightRequested', params: { ariaTemplate: fragment } });
124125
}, [mode]);
125-
const isRecording = mode === 'recording' || mode === 'recording-inspecting';
126-
const locatorPlaceholder = isRecording ? '// Unavailable while recording' : (locator ? undefined : '// Pick element or type locator');
127-
const ariaPlaceholder = isRecording ? '# Unavailable while recording' : (ariaSnapshot ? undefined : '# Pick element or type snapshot');
128126

129127
return <div className='recorder'>
130128
<Toolbar>
@@ -191,7 +189,7 @@ export const Recorder: React.FC<RecorderProps> = ({
191189
{
192190
id: 'locator',
193191
title: 'Locator',
194-
render: () => <CodeMirrorWrapper text={locatorPlaceholder || locator} language={source.language} readOnly={isRecording} focusOnChange={true} onChange={onEditorChange} wrapLines={true} />
192+
render: () => <CodeMirrorWrapper text={locator} language={source.language} focusOnChange={true} onChange={onEditorChange} wrapLines={true} />
195193
},
196194
{
197195
id: 'log',
@@ -201,7 +199,7 @@ export const Recorder: React.FC<RecorderProps> = ({
201199
{
202200
id: 'aria',
203201
title: 'Aria snapshot',
204-
render: () => <CodeMirrorWrapper text={ariaPlaceholder || ariaSnapshot || ''} language={'yaml'} readOnly={isRecording} onChange={onAriaEditorChange} highlight={ariaSnapshotErrors} wrapLines={true} />
202+
render: () => <CodeMirrorWrapper text={ariaSnapshot || ''} language={'yaml'} onChange={onAriaEditorChange} highlight={ariaSnapshotErrors} wrapLines={true} />
205203
},
206204
]}
207205
selectedTab={selectedTab}

tests/library/debug-controller.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ type Fixtures = {
3131
};
3232

3333
const test = baseTest.extend<Fixtures>({
34-
wsEndpoint: async ({ }, use) => {
35-
process.env.PW_DEBUG_CONTROLLER_HEADLESS = '1';
34+
wsEndpoint: async ({ headless }, use) => {
35+
if (headless)
36+
process.env.PW_DEBUG_CONTROLLER_HEADLESS = '1';
3637
const server = new PlaywrightServer({ mode: 'extension', path: '/' + createGuid(), maxConnections: Number.MAX_VALUE, enableSocksProxy: false });
3738
const wsEndpoint = await server.listen();
3839
await use(wsEndpoint);

tests/library/inspector/cli-codegen-aria.spec.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,10 @@ test.describe(() => {
6464
test('should inspect aria snapshot', async ({ openRecorder }) => {
6565
const { recorder } = await openRecorder();
6666
await recorder.setContentAndWait(`<main><button>Submit</button></main>`);
67-
await recorder.recorderPage.getByRole('button', { name: 'Record' }).click();
6867
await recorder.page.click('x-pw-tool-item.pick-locator');
6968
await recorder.page.hover('button');
7069
await recorder.trustedClick();
71-
await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot ' }).click();
70+
await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot' }).click();
7271
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
7372
- textbox
7473
- text: '- button "Submit"'
@@ -85,12 +84,11 @@ test.describe(() => {
8584
const submitButton = recorder.page.getByRole('button', { name: 'Submit' });
8685
const cancelButton = recorder.page.getByRole('button', { name: 'Cancel' });
8786

88-
await recorder.recorderPage.getByRole('button', { name: 'Record' }).click();
8987

9088
await recorder.page.click('x-pw-tool-item.pick-locator');
9189
await submitButton.hover();
9290
await recorder.trustedClick();
93-
await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot ' }).click();
91+
await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot' }).click();
9492
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
9593
- text: '- button "Submit"'
9694
`);
@@ -128,13 +126,12 @@ test.describe(() => {
128126
</main>`);
129127

130128
const submitButton = recorder.page.getByRole('button', { name: 'Submit' });
131-
await recorder.recorderPage.getByRole('button', { name: 'Record' }).click();
132129

133130
await recorder.page.click('x-pw-tool-item.pick-locator');
134131
await submitButton.hover();
135132
await recorder.trustedClick();
136133

137-
await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot ' }).click();
134+
await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot' }).click();
138135
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
139136
- text: '- button "Submit"'
140137
`);
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { test, expect } from './inspectorTest';
18+
import { roundBox } from '../../page/pageTest';
19+
20+
test.describe(() => {
21+
test.skip(({ mode }) => mode !== 'default');
22+
test.skip(({ trace, codegenMode }) => trace === 'on' && codegenMode === 'trace-events');
23+
24+
test('should inspect locator', async ({ openRecorder }) => {
25+
const { recorder } = await openRecorder();
26+
await recorder.setContentAndWait(`<main><button>Submit</button></main>`);
27+
await recorder.page.click('x-pw-tool-item.pick-locator');
28+
await recorder.page.hover('button');
29+
await recorder.trustedClick();
30+
await recorder.recorderPage.getByRole('tab', { name: 'Locator' }).click();
31+
await expect(recorder.recorderPage.locator('.tab-locator .CodeMirror')).toMatchAriaSnapshot(`
32+
- text: "getByRole('button', { name: 'Submit' })"
33+
`);
34+
});
35+
36+
test('should update locator highlight', async ({ openRecorder }) => {
37+
const { recorder } = await openRecorder();
38+
await recorder.setContentAndWait(`<main>
39+
<button>Submit</button>
40+
<button>Cancel</button>
41+
</main>`);
42+
43+
const submitButton = recorder.page.getByRole('button', { name: 'Submit' });
44+
const cancelButton = recorder.page.getByRole('button', { name: 'Cancel' });
45+
46+
await recorder.recorderPage.getByRole('button', { name: 'Record' }).click();
47+
48+
await recorder.page.click('x-pw-tool-item.pick-locator');
49+
await submitButton.hover();
50+
await recorder.trustedClick();
51+
await recorder.recorderPage.getByRole('tab', { name: 'Locator' }).click();
52+
await expect(recorder.recorderPage.locator('.tab-locator .CodeMirror')).toMatchAriaSnapshot(`
53+
- text: "getByRole('button', { name: 'Submit' })"
54+
`);
55+
56+
await recorder.recorderPage.locator('.tab-locator .CodeMirror').click();
57+
for (let i = 0; i < `Submit' })`.length; i++)
58+
await recorder.recorderPage.keyboard.press('Backspace');
59+
60+
{
61+
// Different button.
62+
await recorder.recorderPage.locator('.tab-locator .CodeMirror').pressSequentially(`Cancel' })`);
63+
await expect(recorder.page.locator('x-pw-highlight')).toBeVisible();
64+
const box1 = roundBox(await cancelButton.boundingBox());
65+
const box2 = roundBox(await recorder.page.locator('x-pw-highlight').boundingBox());
66+
expect(box1).toEqual(box2);
67+
}
68+
});
69+
});

0 commit comments

Comments
 (0)