feat: locator.blur() (#18303)

Note this is only available on Locator. Fixes #10724.
This commit is contained in:
Dmitry Gozman 2022-10-25 06:10:40 -07:00 committed by GitHub
parent 6c3f3068b6
commit 329b3eadb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 96 additions and 1 deletions

View File

@ -18,6 +18,14 @@ Returns an array of `node.innerText` values for all matching nodes.
Returns an array of `node.textContent` values for all matching nodes.
## async method: Locator.blur
* since: v1.28
Calls [blur](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur) on the element.
### option: Locator.blur.timeout = %%-input-timeout-%%
* since: v1.28
## async method: Locator.boundingBox
* since: v1.14
- returns: <[null]|[Object]>

View File

@ -198,6 +198,10 @@ export class Locator implements api.Locator {
return this._frame.focus(this._selector, { strict: true, ...options });
}
async blur(options?: TimeoutOptions): Promise<void> {
await this._frame._channel.blur({ selector: this._selector, strict: true, ...options });
}
async count(): Promise<number> {
return this._frame._queryCount(this._selector);
}

View File

@ -44,6 +44,7 @@ export const commandsWithTracingSnapshots = new Set([
'Frame.evalOnSelectorAll',
'Frame.addScriptTag',
'Frame.addStyleTag',
'Frame.blur',
'Frame.check',
'Frame.click',
'Frame.dragAndDrop',

View File

@ -1203,6 +1203,12 @@ scheme.FrameAddStyleTagParams = tObject({
scheme.FrameAddStyleTagResult = tObject({
element: tChannel(['ElementHandle']),
});
scheme.FrameBlurParams = tObject({
selector: tString,
strict: tOptional(tBoolean),
timeout: tOptional(tNumber),
});
scheme.FrameBlurResult = tOptional(tObject({}));
scheme.FrameCheckParams = tObject({
selector: tString,
strict: tOptional(tBoolean),

View File

@ -152,6 +152,10 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, Pa
await this._frame.focus(metadata, params.selector, params);
}
async blur(params: channels.FrameBlurParams, metadata: CallMetadata): Promise<void> {
await this._frame.blur(metadata, params.selector, params);
}
async textContent(params: channels.FrameTextContentParams, metadata: CallMetadata): Promise<channels.FrameTextContentResult> {
const value = await this._frame.textContent(metadata, params.selector, params);
return { value: value === null ? undefined : value };

View File

@ -668,6 +668,11 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return await this.evaluateInUtility(([injected, node, resetSelectionIfNotFocused]) => injected.focusNode(node, resetSelectionIfNotFocused), resetSelectionIfNotFocused);
}
async _blur(progress: Progress): Promise<'error:notconnected' | 'done'> {
progress.throwIfAborted(); // Avoid action that has side-effects.
return await this.evaluateInUtility(([injected, node]) => injected.blurNode(node), {});
}
async type(metadata: CallMetadata, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions): Promise<void> {
const controller = new ProgressController(metadata, this);
return controller.run(async progress => {

View File

@ -1191,6 +1191,14 @@ export class Frame extends SdkObject {
}, this._page._timeoutSettings.timeout(options));
}
async blur(metadata: CallMetadata, selector: string, options: types.TimeoutOptions & types.StrictOptions = {}) {
const controller = new ProgressController(metadata, this);
await controller.run(async progress => {
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, handle => handle._blur(progress)));
await this._page._doSlowMo();
}, this._page._timeoutSettings.timeout(options));
}
async textContent(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions = {}): Promise<string | null> {
return this._scheduleRerunnableTask(metadata, selector, (progress, element) => element.textContent, undefined, options);
}

View File

@ -766,6 +766,15 @@ export class InjectedScript {
return 'done';
}
blurNode(node: Node): 'error:notconnected' | 'done' {
if (!node.isConnected)
return 'error:notconnected';
if (node.nodeType !== Node.ELEMENT_NODE)
throw this.createStacklessError('Node is not an element');
(node as HTMLElement | SVGElement).blur();
return 'done';
}
setInputFiles(node: Node, payloads: { name: string, mimeType: string, buffer: string }[]) {
if (node.nodeType !== Node.ELEMENT_NODE)
return 'Node is not of type HTMLElement';

View File

@ -9487,6 +9487,20 @@ export interface Locator {
*/
allTextContents(): Promise<Array<string>>;
/**
* Calls [blur](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur) on the element.
* @param options
*/
blur(options?: {
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by
* using the
* [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout)
* or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods.
*/
timeout?: number;
}): Promise<void>;
/**
* This method returns the bounding box of the element, or `null` if the element is not visible. The bounding box is
* calculated relative to the main frame viewport - which is usually the same as the browser window.

View File

@ -2131,6 +2131,7 @@ export interface FrameChannel extends FrameEventTarget, Channel {
evalOnSelectorAll(params: FrameEvalOnSelectorAllParams, metadata?: Metadata): Promise<FrameEvalOnSelectorAllResult>;
addScriptTag(params: FrameAddScriptTagParams, metadata?: Metadata): Promise<FrameAddScriptTagResult>;
addStyleTag(params: FrameAddStyleTagParams, metadata?: Metadata): Promise<FrameAddStyleTagResult>;
blur(params: FrameBlurParams, metadata?: Metadata): Promise<FrameBlurResult>;
check(params: FrameCheckParams, metadata?: Metadata): Promise<FrameCheckResult>;
click(params: FrameClickParams, metadata?: Metadata): Promise<FrameClickResult>;
content(params?: FrameContentParams, metadata?: Metadata): Promise<FrameContentResult>;
@ -2235,6 +2236,16 @@ export type FrameAddStyleTagOptions = {
export type FrameAddStyleTagResult = {
element: ElementHandleChannel,
};
export type FrameBlurParams = {
selector: string,
strict?: boolean,
timeout?: number,
};
export type FrameBlurOptions = {
strict?: boolean,
timeout?: number,
};
export type FrameBlurResult = void;
export type FrameCheckParams = {
selector: string,
strict?: boolean,

View File

@ -1586,6 +1586,14 @@ Frame:
tracing:
snapshot: true
blur:
parameters:
selector: string
strict: boolean?
timeout: number?
tracing:
snapshot: true
check:
parameters:
selector: string

View File

@ -89,12 +89,29 @@ it('should select single option', async ({ page, server }) => {
expect(await page.evaluate(() => window['result'].onChange)).toEqual(['blue']);
});
it('should focus a button', async ({ page, server }) => {
it('should focus and blur a button', async ({ page, server }) => {
await page.goto(server.PREFIX + '/input/button.html');
const button = page.locator('button');
expect(await button.evaluate(button => document.activeElement === button)).toBe(false);
let focused = false;
let blurred = false;
await page.exposeFunction('focusEvent', () => focused = true);
await page.exposeFunction('blurEvent', () => blurred = true);
await button.evaluate(button => {
button.addEventListener('focus', window['focusEvent']);
button.addEventListener('blur', window['blurEvent']);
});
await button.focus();
expect(focused).toBe(true);
expect(blurred).toBe(false);
expect(await button.evaluate(button => document.activeElement === button)).toBe(true);
await button.blur();
expect(focused).toBe(true);
expect(blurred).toBe(true);
expect(await button.evaluate(button => document.activeElement === button)).toBe(false);
});
it('focus should respect strictness', async ({ page, server }) => {