feat(api): expose locator.highlight (#12420)

This commit is contained in:
Pavel Feldman 2022-03-01 13:56:21 -08:00 committed by GitHub
parent b79bb32c82
commit 61a6cdde70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 59 additions and 14 deletions

View File

@ -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:

View File

@ -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);
}

View File

@ -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')}]

View File

@ -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');

View File

@ -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);

View File

@ -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

View File

@ -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) {

View File

@ -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.

View 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);
});