cherry-pick(#33632): chore: highlight edited locator while recording
This commit is contained in:
parent
cb0f456e46
commit
0e6434013b
|
|
@ -1018,7 +1018,7 @@ export class Recorder {
|
||||||
private _listeners: (() => void)[] = [];
|
private _listeners: (() => void)[] = [];
|
||||||
private _currentTool: RecorderTool;
|
private _currentTool: RecorderTool;
|
||||||
private _tools: Record<Mode, RecorderTool>;
|
private _tools: Record<Mode, RecorderTool>;
|
||||||
private _actionSelectorModel: HighlightModel | null = null;
|
private _lastHighlightedSelector: string | undefined = undefined;
|
||||||
private _lastHighlightedAriaTemplateJSON: string = 'undefined';
|
private _lastHighlightedAriaTemplateJSON: string = 'undefined';
|
||||||
readonly highlight: Highlight;
|
readonly highlight: Highlight;
|
||||||
readonly overlay: Overlay | undefined;
|
readonly overlay: Overlay | undefined;
|
||||||
|
|
@ -1129,12 +1129,12 @@ export class Recorder {
|
||||||
this._switchCurrentTool();
|
this._switchCurrentTool();
|
||||||
this.overlay?.setUIState(state);
|
this.overlay?.setUIState(state);
|
||||||
|
|
||||||
// Race or scroll.
|
let highlight: HighlightModel | 'clear' | 'noop' = 'noop';
|
||||||
if (this._actionSelectorModel?.selector && !this._actionSelectorModel?.elements.length && !this._lastHighlightedAriaTemplateJSON)
|
if (state.actionSelector !== this._lastHighlightedSelector) {
|
||||||
this._actionSelectorModel = null;
|
this._lastHighlightedSelector = state.actionSelector;
|
||||||
|
const model = state.actionSelector ? querySelector(this.injectedScript, state.actionSelector, this.document) : null;
|
||||||
if (state.actionSelector && state.actionSelector !== this._actionSelectorModel?.selector)
|
highlight = model?.elements.length ? model : 'clear';
|
||||||
this._actionSelectorModel = querySelector(this.injectedScript, state.actionSelector, this.document);
|
}
|
||||||
|
|
||||||
const ariaTemplateJSON = JSON.stringify(state.ariaTemplate);
|
const ariaTemplateJSON = JSON.stringify(state.ariaTemplate);
|
||||||
if (this._lastHighlightedAriaTemplateJSON !== ariaTemplateJSON) {
|
if (this._lastHighlightedAriaTemplateJSON !== ariaTemplateJSON) {
|
||||||
|
|
@ -1142,16 +1142,15 @@ export class Recorder {
|
||||||
const template = state.ariaTemplate ? this.injectedScript.utils.parseYamlTemplate(state.ariaTemplate) : undefined;
|
const template = state.ariaTemplate ? this.injectedScript.utils.parseYamlTemplate(state.ariaTemplate) : undefined;
|
||||||
const elements = template ? this.injectedScript.getAllByAria(this.document, template) : [];
|
const elements = template ? this.injectedScript.getAllByAria(this.document, template) : [];
|
||||||
if (elements.length)
|
if (elements.length)
|
||||||
this._actionSelectorModel = { elements };
|
highlight = { elements };
|
||||||
else
|
else
|
||||||
this._actionSelectorModel = null;
|
highlight = 'clear';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.actionSelector && !state.ariaTemplate)
|
if (highlight === 'clear')
|
||||||
this._actionSelectorModel = null;
|
this.clearHighlight();
|
||||||
|
else if (highlight !== 'noop')
|
||||||
if (this.state.mode === 'none' || this.state.mode === 'standby')
|
this.updateHighlight(highlight, false);
|
||||||
this.updateHighlight(this._actionSelectorModel, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearHighlight() {
|
clearHighlight() {
|
||||||
|
|
@ -1266,6 +1265,8 @@ export class Recorder {
|
||||||
private _onScroll(event: Event) {
|
private _onScroll(event: Event) {
|
||||||
if (!event.isTrusted)
|
if (!event.isTrusted)
|
||||||
return;
|
return;
|
||||||
|
this._lastHighlightedSelector = undefined;
|
||||||
|
this._lastHighlightedAriaTemplateJSON = 'undefined';
|
||||||
this.highlight.hideActionPoint();
|
this.highlight.hideActionPoint();
|
||||||
this._currentTool.onScroll?.(event);
|
this._currentTool.onScroll?.(event);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||||
const language = source.language;
|
const language = source.language;
|
||||||
setLocator(asLocator(language, elementInfo.selector));
|
setLocator(asLocator(language, elementInfo.selector));
|
||||||
setAriaSnapshot(elementInfo.ariaSnapshot);
|
setAriaSnapshot(elementInfo.ariaSnapshot);
|
||||||
|
setAriaSnapshotErrors([]);
|
||||||
if (userGesture && selectedTab !== 'locator' && selectedTab !== 'aria')
|
if (userGesture && selectedTab !== 'locator' && selectedTab !== 'aria')
|
||||||
setSelectedTab('locator');
|
setSelectedTab('locator');
|
||||||
|
|
||||||
|
|
@ -122,9 +123,6 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||||
if (!errors.length)
|
if (!errors.length)
|
||||||
window.dispatch({ event: 'highlightRequested', params: { ariaTemplate: fragment } });
|
window.dispatch({ event: 'highlightRequested', params: { ariaTemplate: fragment } });
|
||||||
}, [mode]);
|
}, [mode]);
|
||||||
const isRecording = mode === 'recording' || mode === 'recording-inspecting';
|
|
||||||
const locatorPlaceholder = isRecording ? '// Unavailable while recording' : (locator ? undefined : '// Pick element or type locator');
|
|
||||||
const ariaPlaceholder = isRecording ? '# Unavailable while recording' : (ariaSnapshot ? undefined : '# Pick element or type snapshot');
|
|
||||||
|
|
||||||
return <div className='recorder'>
|
return <div className='recorder'>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
|
|
@ -191,7 +189,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||||
{
|
{
|
||||||
id: 'locator',
|
id: 'locator',
|
||||||
title: 'Locator',
|
title: 'Locator',
|
||||||
render: () => <CodeMirrorWrapper text={locatorPlaceholder || locator} language={source.language} readOnly={isRecording} focusOnChange={true} onChange={onEditorChange} wrapLines={true} />
|
render: () => <CodeMirrorWrapper text={locator} language={source.language} focusOnChange={true} onChange={onEditorChange} wrapLines={true} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'log',
|
id: 'log',
|
||||||
|
|
@ -201,7 +199,7 @@ export const Recorder: React.FC<RecorderProps> = ({
|
||||||
{
|
{
|
||||||
id: 'aria',
|
id: 'aria',
|
||||||
title: 'Aria snapshot',
|
title: 'Aria snapshot',
|
||||||
render: () => <CodeMirrorWrapper text={ariaPlaceholder || ariaSnapshot || ''} language={'yaml'} readOnly={isRecording} onChange={onAriaEditorChange} highlight={ariaSnapshotErrors} wrapLines={true} />
|
render: () => <CodeMirrorWrapper text={ariaSnapshot || ''} language={'yaml'} onChange={onAriaEditorChange} highlight={ariaSnapshotErrors} wrapLines={true} />
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
selectedTab={selectedTab}
|
selectedTab={selectedTab}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ type Fixtures = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const test = baseTest.extend<Fixtures>({
|
const test = baseTest.extend<Fixtures>({
|
||||||
wsEndpoint: async ({ }, use) => {
|
wsEndpoint: async ({ headless }, use) => {
|
||||||
|
if (headless)
|
||||||
process.env.PW_DEBUG_CONTROLLER_HEADLESS = '1';
|
process.env.PW_DEBUG_CONTROLLER_HEADLESS = '1';
|
||||||
const server = new PlaywrightServer({ mode: 'extension', path: '/' + createGuid(), maxConnections: Number.MAX_VALUE, enableSocksProxy: false });
|
const server = new PlaywrightServer({ mode: 'extension', path: '/' + createGuid(), maxConnections: Number.MAX_VALUE, enableSocksProxy: false });
|
||||||
const wsEndpoint = await server.listen();
|
const wsEndpoint = await server.listen();
|
||||||
|
|
|
||||||
|
|
@ -64,11 +64,10 @@ test.describe(() => {
|
||||||
test('should inspect aria snapshot', async ({ openRecorder }) => {
|
test('should inspect aria snapshot', async ({ openRecorder }) => {
|
||||||
const { recorder } = await openRecorder();
|
const { recorder } = await openRecorder();
|
||||||
await recorder.setContentAndWait(`<main><button>Submit</button></main>`);
|
await recorder.setContentAndWait(`<main><button>Submit</button></main>`);
|
||||||
await recorder.recorderPage.getByRole('button', { name: 'Record' }).click();
|
|
||||||
await recorder.page.click('x-pw-tool-item.pick-locator');
|
await recorder.page.click('x-pw-tool-item.pick-locator');
|
||||||
await recorder.page.hover('button');
|
await recorder.page.hover('button');
|
||||||
await recorder.trustedClick();
|
await recorder.trustedClick();
|
||||||
await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot ' }).click();
|
await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot' }).click();
|
||||||
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
|
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
|
||||||
- textbox
|
- textbox
|
||||||
- text: '- button "Submit"'
|
- text: '- button "Submit"'
|
||||||
|
|
@ -85,12 +84,11 @@ test.describe(() => {
|
||||||
const submitButton = recorder.page.getByRole('button', { name: 'Submit' });
|
const submitButton = recorder.page.getByRole('button', { name: 'Submit' });
|
||||||
const cancelButton = recorder.page.getByRole('button', { name: 'Cancel' });
|
const cancelButton = recorder.page.getByRole('button', { name: 'Cancel' });
|
||||||
|
|
||||||
await recorder.recorderPage.getByRole('button', { name: 'Record' }).click();
|
|
||||||
|
|
||||||
await recorder.page.click('x-pw-tool-item.pick-locator');
|
await recorder.page.click('x-pw-tool-item.pick-locator');
|
||||||
await submitButton.hover();
|
await submitButton.hover();
|
||||||
await recorder.trustedClick();
|
await recorder.trustedClick();
|
||||||
await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot ' }).click();
|
await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot' }).click();
|
||||||
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
|
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
|
||||||
- text: '- button "Submit"'
|
- text: '- button "Submit"'
|
||||||
`);
|
`);
|
||||||
|
|
@ -128,13 +126,12 @@ test.describe(() => {
|
||||||
</main>`);
|
</main>`);
|
||||||
|
|
||||||
const submitButton = recorder.page.getByRole('button', { name: 'Submit' });
|
const submitButton = recorder.page.getByRole('button', { name: 'Submit' });
|
||||||
await recorder.recorderPage.getByRole('button', { name: 'Record' }).click();
|
|
||||||
|
|
||||||
await recorder.page.click('x-pw-tool-item.pick-locator');
|
await recorder.page.click('x-pw-tool-item.pick-locator');
|
||||||
await submitButton.hover();
|
await submitButton.hover();
|
||||||
await recorder.trustedClick();
|
await recorder.trustedClick();
|
||||||
|
|
||||||
await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot ' }).click();
|
await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot' }).click();
|
||||||
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
|
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
|
||||||
- text: '- button "Submit"'
|
- text: '- button "Submit"'
|
||||||
`);
|
`);
|
||||||
|
|
|
||||||
69
tests/library/inspector/cli-codegen-pick-locator.spec.ts
Normal file
69
tests/library/inspector/cli-codegen-pick-locator.spec.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { test, expect } from './inspectorTest';
|
||||||
|
import { roundBox } from '../../page/pageTest';
|
||||||
|
|
||||||
|
test.describe(() => {
|
||||||
|
test.skip(({ mode }) => mode !== 'default');
|
||||||
|
test.skip(({ trace, codegenMode }) => trace === 'on' && codegenMode === 'trace-events');
|
||||||
|
|
||||||
|
test('should inspect locator', async ({ openRecorder }) => {
|
||||||
|
const { recorder } = await openRecorder();
|
||||||
|
await recorder.setContentAndWait(`<main><button>Submit</button></main>`);
|
||||||
|
await recorder.page.click('x-pw-tool-item.pick-locator');
|
||||||
|
await recorder.page.hover('button');
|
||||||
|
await recorder.trustedClick();
|
||||||
|
await recorder.recorderPage.getByRole('tab', { name: 'Locator' }).click();
|
||||||
|
await expect(recorder.recorderPage.locator('.tab-locator .CodeMirror')).toMatchAriaSnapshot(`
|
||||||
|
- text: "getByRole('button', { name: 'Submit' })"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should update locator highlight', async ({ openRecorder }) => {
|
||||||
|
const { recorder } = await openRecorder();
|
||||||
|
await recorder.setContentAndWait(`<main>
|
||||||
|
<button>Submit</button>
|
||||||
|
<button>Cancel</button>
|
||||||
|
</main>`);
|
||||||
|
|
||||||
|
const submitButton = recorder.page.getByRole('button', { name: 'Submit' });
|
||||||
|
const cancelButton = recorder.page.getByRole('button', { name: 'Cancel' });
|
||||||
|
|
||||||
|
await recorder.recorderPage.getByRole('button', { name: 'Record' }).click();
|
||||||
|
|
||||||
|
await recorder.page.click('x-pw-tool-item.pick-locator');
|
||||||
|
await submitButton.hover();
|
||||||
|
await recorder.trustedClick();
|
||||||
|
await recorder.recorderPage.getByRole('tab', { name: 'Locator' }).click();
|
||||||
|
await expect(recorder.recorderPage.locator('.tab-locator .CodeMirror')).toMatchAriaSnapshot(`
|
||||||
|
- text: "getByRole('button', { name: 'Submit' })"
|
||||||
|
`);
|
||||||
|
|
||||||
|
await recorder.recorderPage.locator('.tab-locator .CodeMirror').click();
|
||||||
|
for (let i = 0; i < `Submit' })`.length; i++)
|
||||||
|
await recorder.recorderPage.keyboard.press('Backspace');
|
||||||
|
|
||||||
|
{
|
||||||
|
// Different button.
|
||||||
|
await recorder.recorderPage.locator('.tab-locator .CodeMirror').pressSequentially(`Cancel' })`);
|
||||||
|
await expect(recorder.page.locator('x-pw-highlight')).toBeVisible();
|
||||||
|
const box1 = roundBox(await cancelButton.boundingBox());
|
||||||
|
const box2 = roundBox(await recorder.page.locator('x-pw-highlight').boundingBox());
|
||||||
|
expect(box1).toEqual(box2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue