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
8 changes: 8 additions & 0 deletions docs/src/api/class-locator.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]>
Expand Down
4 changes: 4 additions & 0 deletions packages/playwright-core/src/client/locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/protocol/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const commandsWithTracingSnapshots = new Set([
'Frame.evalOnSelectorAll',
'Frame.addScriptTag',
'Frame.addStyleTag',
'Frame.blur',
'Frame.check',
'Frame.click',
'Frame.dragAndDrop',
Expand Down
6 changes: 6 additions & 0 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
5 changes: 5 additions & 0 deletions packages/playwright-core/src/server/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
8 changes: 8 additions & 0 deletions packages/playwright-core/src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
14 changes: 14 additions & 0 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions packages/protocol/src/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2134,6 +2134,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>;
Expand Down Expand Up @@ -2238,6 +2239,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,
Expand Down
8 changes: 8 additions & 0 deletions packages/protocol/src/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,14 @@ Frame:
tracing:
snapshot: true

blur:
parameters:
selector: string
strict: boolean?
timeout: number?
tracing:
snapshot: true

check:
parameters:
selector: string
Expand Down
19 changes: 18 additions & 1 deletion tests/page/locator-misc-1.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down