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
14 changes: 14 additions & 0 deletions .claude/skills/change-keyword/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,20 @@ Done when every argument is described, the rendered page shows no broken links o
Done when `inv utest`, `inv utest-node` and the affected `inv atest --suite` runs pass, and
`inv lint` is clean.

### 7. Review

Run this step without being asked, once step 6 is done. The review reads commits, so commit the
work on its branch first. Then dispatch a subagent to run the `mattpocock-skills:code-review`
skill, with the branch's merge-base with `main` as the fixed point.

The subagent sees only its prompt, so give it the spec: the issue, plus every decision the
maintainer made that overrides or adds to it, such as a dropped argument.

Relay both axes to the maintainer, and treat each finding as theirs to accept or reject. Check a
finding against the code before relaying it. Apply the accepted ones, then repeat step 6.

Done when the maintainer has ruled on every finding and step 6 passes with the accepted fixes in.

## Removing or renaming a keyword

A keyword is never removed in the release that deprecates it. It is deprecated first, and removed
Expand Down
45 changes: 45 additions & 0 deletions Browser/keywords/interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,51 @@ def focus(self, selector: str):
)
logger.debug(response.log)

@keyword(tags=("Setter", "PageContent"))
def blur(self, selector: str):
"""Removes focus from the element found by ``selector``.

Use this when a page runs validation, formatting or other logic only
after a field loses focus. `Fill Text` and `Type Text` leave the field
focused, so that logic does not run until focus moves away.

*Arguments:*
- ``selector``: Selector of the element. See the `Finding elements` section
for details about the selectors.

Keyword uses strict mode, see `Finding elements` for more details about strict mode.

If there is no element matching the selector, the keyword waits until a
matching element appears in the DOM. It fails if the element does not
appear within the library timeout, which is 10 seconds by default and
can be changed with `Set Browser Timeout`.

If the element does not have focus, the keyword does nothing and passes,
and no blur event is fired. To verify the state of the element, use
`Get Element States`.

``css=:focus`` selects the focused element only when it is in the main
frame and outside shadow DOM. If the focused element is inside an iframe
or a shadow DOM, the selector does not find it reliably, so use a selector
of the element itself. If nothing has focus, the selector matches nothing
and the keyword fails after the timeout.

Example:
| `Fill Text` id=email not-an-email
| `Blur` id=email
| `Get Element States` id=email contains defocused
| `Get Text` css=.validation-error == Please enter a valid email address
|
| # Removes focus from the focused element in the main frame
| `Blur` css=:focus
"""
selector = self.presenter_mode(selector, self.strict_mode)
with self.playwright.grpc_channel() as stub:
response = stub.Blur(
Request().ElementSelector(selector=selector, strict=self.strict_mode)
)
logger.debug(response.log)

@keyword(tags=("Setter", "PageContent"))
def scroll_to(
self,
Expand Down
38 changes: 38 additions & 0 deletions atest/test/02_Content_Keywords/focus.robot
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,41 @@ Focus With Strict
... Focus //input
Set Strict Mode False scope=Test
Focus //input

Blur Removes Focus From Element
Focus \#username_field
Get Element States \#username_field contains focused
Blur \#username_field
Get Element States \#username_field contains defocused

Blur Fires Blur Event
[Setup] New Page ${EVENTS_URL}
Focus \#event_test_input
Blur \#event_test_input
Get Text \#event_log_text *= "type":"blur"

Blur On Element Without Focus Does Nothing
[Setup] New Page ${EVENTS_URL}
Blur \#event_test_input
Get Text \#event_log_text not contains "type":"blur"
Get Element States \#event_test_input contains defocused

Blur On Element Without Focus Keeps Focus On Other Element
[Setup] New Page ${EVENTS_URL}
Focus \#event_test_button
Blur \#event_test_input
Get Element States \#event_test_button contains focused

Blur Element Which Has Focus
Focus \#username_field
Blur css=:focus
Get Element States \#username_field contains defocused

Blur With Strict
Run Keyword And Expect Error
... *strict mode violation*//input*resolved to ${INPUT_ELEMENT_COUNT_IN_LOGIN} elements*
... Blur //input
Set Strict Mode False scope=Test
Focus //input
Blur //input
Get Element States //input >> nth=0 contains defocused
1 change: 1 addition & 0 deletions node/playwright-wrapper/grpc-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ export class PlaywrightServer {
tap = this.wrapping(interaction.tap);
hover = this.wrapping(interaction.hover);
focus = this.wrapping(interaction.focus);
blur = this.wrapping(interaction.blur);
checkCheckbox = this.wrapping(interaction.checkCheckbox);
uncheckCheckbox = this.wrapping(interaction.uncheckCheckbox);
getElement = this.wrapping(evaluation.getElement);
Expand Down
8 changes: 8 additions & 0 deletions node/playwright-wrapper/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,14 @@ export async function focus(request: pb.Request_ElementSelector, state: Playwrig
return emptyWithLog(`Focused element: ${selector}`);
}

export async function blur(request: pb.Request_ElementSelector, state: PlaywrightState): Promise<pb.Response_Empty> {
const selector = request.selector;
const strictMode = request.strict;
const locator = await findLocator(state, selector, strictMode, true);
await locator.blur();
return emptyWithLog(`Blurred element: ${selector}`);
}

export async function checkCheckbox(
request: pb.Request_ElementSelector,
state: PlaywrightState,
Expand Down
2 changes: 2 additions & 0 deletions protobuf/playwright.proto
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,8 @@ service Playwright {
rpc Hover(Request.ElementSelectorWithOptions) returns (Response.Empty);
/* Focuses element specified by selector */
rpc Focus(Request.ElementSelector) returns (Response.Empty);
/* Removes focus from element specified by selector */
rpc Blur(Request.ElementSelector) returns (Response.Empty);
/* Waits for element be in a specific state*/
rpc WaitForElementsState(Request.ElementSelectorWithOptions) returns (Response.Empty);
/* Executes javascript on the active page and retries until the expression is true */
Expand Down
Loading