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
29 changes: 15 additions & 14 deletions packages/playwright-core/src/server/injected/recorder/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1018,7 +1018,7 @@ export class Recorder {
private _listeners: (() => void)[] = [];
private _currentTool: RecorderTool;
private _tools: Record<Mode, RecorderTool>;
private _actionSelectorModel: HighlightModel | null = null;
private _lastHighlightedSelector: string | undefined = undefined;
private _lastHighlightedAriaTemplateJSON: string = 'undefined';
readonly highlight: Highlight;
readonly overlay: Overlay | undefined;
Expand Down Expand Up @@ -1129,29 +1129,28 @@ export class Recorder {
this._switchCurrentTool();
this.overlay?.setUIState(state);

// Race or scroll.
if (this._actionSelectorModel?.selector && !this._actionSelectorModel?.elements.length && !this._lastHighlightedAriaTemplateJSON)
this._actionSelectorModel = null;

if (state.actionSelector && state.actionSelector !== this._actionSelectorModel?.selector)
this._actionSelectorModel = querySelector(this.injectedScript, state.actionSelector, this.document);
let highlight: HighlightModel | 'clear' | 'noop' = 'noop';
if (state.actionSelector !== this._lastHighlightedSelector) {
this._lastHighlightedSelector = state.actionSelector;
const model = state.actionSelector ? querySelector(this.injectedScript, state.actionSelector, this.document) : null;
highlight = model?.elements.length ? model : 'clear';
}

const ariaTemplateJSON = JSON.stringify(state.ariaTemplate);
if (this._lastHighlightedAriaTemplateJSON !== ariaTemplateJSON) {
this._lastHighlightedAriaTemplateJSON = ariaTemplateJSON;
const template = state.ariaTemplate ? this.injectedScript.utils.parseYamlTemplate(state.ariaTemplate) : undefined;
const elements = template ? this.injectedScript.getAllByAria(this.document, template) : [];
if (elements.length)
this._actionSelectorModel = { elements };
highlight = { elements };
else
this._actionSelectorModel = null;
highlight = 'clear';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this always clear highlight when recorder switches from state.ariaTemplate to state.actionSelector? I think this should be:

else if (highlight === 'noop')
  highlight = 'clear';

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code only runs if the template was the thing that changed last, so should be ok?

}

if (!state.actionSelector && !state.ariaTemplate)
this._actionSelectorModel = null;

if (this.state.mode === 'none' || this.state.mode === 'standby')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mode check is gone. Do we clear highlight upon performing an action on the recorder side?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we do, so should be unrelated to that scenario.

this.updateHighlight(this._actionSelectorModel, false);
if (highlight === 'clear')
this.clearHighlight();
else if (highlight !== 'noop')
this.updateHighlight(highlight, false);
}

clearHighlight() {
Expand Down Expand Up @@ -1266,6 +1265,8 @@ export class Recorder {
private _onScroll(event: Event) {
if (!event.isTrusted)
return;
this._lastHighlightedSelector = undefined;
this._lastHighlightedAriaTemplateJSON = 'undefined';
this.highlight.hideActionPoint();
this._currentTool.onScroll?.(event);
}
Expand Down
8 changes: 3 additions & 5 deletions packages/recorder/src/recorder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const Recorder: React.FC<RecorderProps> = ({
const language = source.language;
setLocator(asLocator(language, elementInfo.selector));
setAriaSnapshot(elementInfo.ariaSnapshot);
setAriaSnapshotErrors([]);
if (userGesture && selectedTab !== 'locator' && selectedTab !== 'aria')
setSelectedTab('locator');

Expand Down Expand Up @@ -122,9 +123,6 @@ export const Recorder: React.FC<RecorderProps> = ({
if (!errors.length)
window.dispatch({ event: 'highlightRequested', params: { ariaTemplate: fragment } });
}, [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'>
<Toolbar>
Expand Down Expand Up @@ -191,7 +189,7 @@ export const Recorder: React.FC<RecorderProps> = ({
{
id: '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',
Expand All @@ -201,7 +199,7 @@ export const Recorder: React.FC<RecorderProps> = ({
{
id: 'aria',
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}
Expand Down
5 changes: 3 additions & 2 deletions tests/library/debug-controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ type Fixtures = {
};

const test = baseTest.extend<Fixtures>({
wsEndpoint: async ({ }, use) => {
process.env.PW_DEBUG_CONTROLLER_HEADLESS = '1';
wsEndpoint: async ({ headless }, use) => {
if (headless)
process.env.PW_DEBUG_CONTROLLER_HEADLESS = '1';
const server = new PlaywrightServer({ mode: 'extension', path: '/' + createGuid(), maxConnections: Number.MAX_VALUE, enableSocksProxy: false });
const wsEndpoint = await server.listen();
await use(wsEndpoint);
Expand Down
9 changes: 3 additions & 6 deletions tests/library/inspector/cli-codegen-aria.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,10 @@ test.describe(() => {
test('should inspect aria snapshot', async ({ openRecorder }) => {
const { recorder } = await openRecorder();
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.hover('button');
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(`
- textbox
- text: '- button "Submit"'
Expand All @@ -85,12 +84,11 @@ test.describe(() => {
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: 'Aria snapshot ' }).click();
await recorder.recorderPage.getByRole('tab', { name: 'Aria snapshot' }).click();
await expect(recorder.recorderPage.locator('.tab-aria .CodeMirror')).toMatchAriaSnapshot(`
- text: '- button "Submit"'
`);
Expand Down Expand Up @@ -128,13 +126,12 @@ test.describe(() => {
</main>`);

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 submitButton.hover();
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(`
- text: '- button "Submit"'
`);
Expand Down
69 changes: 69 additions & 0 deletions tests/library/inspector/cli-codegen-pick-locator.spec.ts
Original file line number Diff line number Diff line change
@@ -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