mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-12 11:50:22 +03:00
feat(api): expose locator.highlight (#12420)
This commit is contained in:
parent
b79bb32c82
commit
61a6cdde70
@ -442,6 +442,10 @@ Attribute name to get the value for.
|
||||
|
||||
### option: Locator.getAttribute.timeout = %%-input-timeout-%%
|
||||
|
||||
## async method: Locator.highlight
|
||||
|
||||
Highlight the corresponding element(s) on the screen. Useful for debugging, don't commit the code that uses [`method: Locator.highlight`].
|
||||
|
||||
## async method: Locator.hover
|
||||
|
||||
This method hovers over the element by performing the following steps:
|
||||
|
@ -113,6 +113,11 @@ export class Locator implements api.Locator {
|
||||
}
|
||||
|
||||
async _highlight() {
|
||||
// VS Code extension uses this one, keep it for now.
|
||||
return this._frame._highlight(this._selector);
|
||||
}
|
||||
|
||||
async highlight() {
|
||||
return this._frame._highlight(this._selector);
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ import { Progress, ProgressController } from './progress';
|
||||
import { SelectorInfo } from './selectors';
|
||||
import * as types from './types';
|
||||
import { TimeoutOptions } from '../common/types';
|
||||
import { isUnderTest } from '../utils/utils';
|
||||
|
||||
type SetInputFilesFiles = channels.ElementHandleSetInputFilesParams['files'];
|
||||
type ActionName = 'click' | 'hover' | 'dblclick' | 'tap' | 'move and up' | 'move and down';
|
||||
@ -99,6 +100,7 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
||||
(() => {
|
||||
${injectedScriptSource.source}
|
||||
return new pwExport(
|
||||
${isUnderTest()},
|
||||
${this.frame._page._delegate.rafCountForStablePosition()},
|
||||
"${this.frame._page._browserContext._browser.options.name}",
|
||||
[${custom.join(',\n')}]
|
||||
|
@ -44,7 +44,7 @@ export class Highlight {
|
||||
this._innerGlassPaneElement.appendChild(this._tooltipElement);
|
||||
|
||||
// Use a closed shadow root to prevent selectors matching our internal previews.
|
||||
this._glassPaneShadow = this._outerGlassPaneElement.attachShadow({ mode: 'closed' });
|
||||
this._glassPaneShadow = this._outerGlassPaneElement.attachShadow({ mode: isUnderTest ? 'open' : 'closed' });
|
||||
this._glassPaneShadow.appendChild(this._innerGlassPaneElement);
|
||||
this._glassPaneShadow.appendChild(this._actionPointElement);
|
||||
const styleElement = document.createElement('style');
|
||||
|
@ -76,8 +76,10 @@ export class InjectedScript {
|
||||
onGlobalListenersRemoved = new Set<() => void>();
|
||||
private _hitTargetInterceptor: undefined | ((event: MouseEvent | PointerEvent | TouchEvent) => void);
|
||||
private _highlight: Highlight | undefined;
|
||||
readonly isUnderTest: boolean;
|
||||
|
||||
constructor(stableRafCount: number, browserName: string, customEngines: { name: string, engine: SelectorEngine}[]) {
|
||||
constructor(isUnderTest: boolean, stableRafCount: number, browserName: string, customEngines: { name: string, engine: SelectorEngine}[]) {
|
||||
this.isUnderTest = isUnderTest;
|
||||
this._evaluator = new SelectorEvaluatorImpl(new Map());
|
||||
|
||||
this._engines = new Map();
|
||||
@ -876,7 +878,7 @@ export class InjectedScript {
|
||||
maskSelectors(selectors: ParsedSelector[]) {
|
||||
if (this._highlight)
|
||||
this.hideHighlight();
|
||||
this._highlight = new Highlight(false);
|
||||
this._highlight = new Highlight(this.isUnderTest);
|
||||
this._highlight.install();
|
||||
const elements = [];
|
||||
for (const selector of selectors)
|
||||
@ -886,7 +888,7 @@ export class InjectedScript {
|
||||
|
||||
highlight(selector: ParsedSelector) {
|
||||
if (!this._highlight) {
|
||||
this._highlight = new Highlight(false);
|
||||
this._highlight = new Highlight(this.isUnderTest);
|
||||
this._highlight.install();
|
||||
}
|
||||
this._runHighlightOnRaf(selector);
|
||||
|
@ -42,13 +42,11 @@ export class Recorder {
|
||||
private _mode: 'none' | 'inspecting' | 'recording' = 'none';
|
||||
private _actionPoint: Point | undefined;
|
||||
private _actionSelector: string | undefined;
|
||||
private _params: { isUnderTest: boolean; };
|
||||
private _highlight: Highlight;
|
||||
|
||||
constructor(injectedScript: InjectedScript, params: { isUnderTest: boolean }) {
|
||||
this._params = params;
|
||||
constructor(injectedScript: InjectedScript) {
|
||||
this._injectedScript = injectedScript;
|
||||
this._highlight = new Highlight(params.isUnderTest);
|
||||
this._highlight = new Highlight(injectedScript.isUnderTest);
|
||||
|
||||
this._refreshListenersIfNeeded();
|
||||
injectedScript.onGlobalListenersRemoved.add(() => this._refreshListenersIfNeeded());
|
||||
@ -57,7 +55,7 @@ export class Recorder {
|
||||
this._pollRecorderMode().catch(e => console.log(e)); // eslint-disable-line no-console
|
||||
};
|
||||
globalThis._playwrightRefreshOverlay();
|
||||
if (params.isUnderTest)
|
||||
if (injectedScript.isUnderTest)
|
||||
console.error('Recorder script ready for test'); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
@ -239,7 +237,7 @@ export class Recorder {
|
||||
const activeElement = this._deepActiveElement(document);
|
||||
const result = activeElement ? generateSelector(this._injectedScript, activeElement, true) : null;
|
||||
this._activeModel = result && result.selector ? result : null;
|
||||
if (this._params.isUnderTest)
|
||||
if (this._injectedScript.isUnderTest)
|
||||
console.error('Highlight updated for test: ' + (result ? result.selector : null)); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
@ -255,7 +253,7 @@ export class Recorder {
|
||||
return;
|
||||
this._hoveredModel = selector ? { selector, elements } : null;
|
||||
this._updateHighlight();
|
||||
if (this._params.isUnderTest)
|
||||
if (this._injectedScript.isUnderTest)
|
||||
console.error('Highlight updated for test: ' + selector); // eslint-disable-line no-console
|
||||
}
|
||||
|
||||
@ -388,6 +386,7 @@ export class Recorder {
|
||||
}
|
||||
|
||||
private async _performAction(action: actions.Action) {
|
||||
this._clearHighlight();
|
||||
this._performingAction = true;
|
||||
await globalThis._playwrightRecorderPerformAction(action).catch(() => {});
|
||||
this._performingAction = false;
|
||||
@ -397,7 +396,7 @@ export class Recorder {
|
||||
// If that was a keyboard action, it similarly requires new selectors for active model.
|
||||
this._onFocus();
|
||||
|
||||
if (this._params.isUnderTest) {
|
||||
if (this._injectedScript.isUnderTest) {
|
||||
// Serialize all to string as we cannot attribute console message to isolated world
|
||||
// in Firefox.
|
||||
console.error('Action performed for test: ' + JSON.stringify({ // eslint-disable-line no-console
|
||||
|
@ -32,7 +32,7 @@ import { IRecorderApp, RecorderApp } from './recorder/recorderApp';
|
||||
import { CallMetadata, InstrumentationListener, SdkObject } from '../instrumentation';
|
||||
import { Point } from '../../common/types';
|
||||
import { CallLog, CallLogStatus, EventData, Mode, Source, UIState } from './recorder/recorderTypes';
|
||||
import { createGuid, isUnderTest, monotonicTime } from '../../utils/utils';
|
||||
import { createGuid, monotonicTime } from '../../utils/utils';
|
||||
import { metadataToCallLog } from './recorder/recorderUtils';
|
||||
import { Debugger } from './debugger';
|
||||
import { EventEmitter } from 'events';
|
||||
@ -362,7 +362,7 @@ class ContextRecorder extends EventEmitter {
|
||||
await this._context.exposeBinding('_playwrightRecorderRecordAction', false,
|
||||
(source: BindingSource, action: actions.Action) => this._recordAction(source.frame, action));
|
||||
|
||||
await this._context.extendInjectedScript(recorderSource.source, { isUnderTest: isUnderTest() });
|
||||
await this._context.extendInjectedScript(recorderSource.source);
|
||||
}
|
||||
|
||||
setEnabled(enabled: boolean) {
|
||||
|
6
packages/playwright-core/types/types.d.ts
vendored
6
packages/playwright-core/types/types.d.ts
vendored
@ -9107,6 +9107,12 @@ export interface Locator {
|
||||
timeout?: number;
|
||||
}): Promise<null|string>;
|
||||
|
||||
/**
|
||||
* Highlight the corresponding element(s) on the screen. Useful for debugging, don't commit the code that uses
|
||||
* [locator.highlight()](https://playwright.dev/docs/api/class-locator#locator-highlight).
|
||||
*/
|
||||
highlight(): Promise<void>;
|
||||
|
||||
/**
|
||||
* This method hovers over the element by performing the following steps:
|
||||
* 1. Wait for [actionability](https://playwright.dev/docs/actionability) checks on the element, unless `force` option is set.
|
||||
|
27
tests/page/locator-highlight.spec.ts
Normal file
27
tests/page/locator-highlight.spec.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* 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 as it, expect } from './pageTest';
|
||||
|
||||
it('should highlight locator', async ({ page }) => {
|
||||
await page.setContent(`<input type='text' />`);
|
||||
await page.locator('input').highlight();
|
||||
await expect(page.locator('x-pw-tooltip')).toHaveText('input');
|
||||
await expect(page.locator('x-pw-highlight')).toBeVisible();
|
||||
const box1 = await page.locator('input').boundingBox();
|
||||
const box2 = await page.locator('x-pw-highlight').boundingBox();
|
||||
expect(box1).toEqual(box2);
|
||||
});
|
Loading…
Reference in New Issue
Block a user