Repository navigation
fix(web): check a radio or checkbox through its own label when the label covers it - #1355
Open
samanyudas wants to merge 2 commits into
Open
samanyudas wants to merge 2 commits into
samanyudas wants to merge 2 commits into
Conversation
…bel covers it Element Plus hides the native input under its label's styled spans or gives it no box, so Playwright's hit-target check refused it. check, uncheck, and tap now click the label at the input's point when the element at that point is the label's own content, or the input's first label with a box when the input has none; anything else on top, a disabled input, an input on top itself, or a boxless input whose labels are all hidden is clicked as before. One in-page read decides whether a label stands in, so a tap on anything else pays that read and keeps locator.click, under one deadline. A locator that never appears still fails NOT_ACTIONABLE within one timeout. Fixes tester-army#1276 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
samanyudas
requested review from
okwasniewski and
szymonrybczak
as code owners
October 24, 2026 17:37
Preview approval requiredThis pull request comes from a fork, so Mintlify won't build a preview until someone with write access approves commit |
Contributor
There was a problem hiding this comment.
1 issue found across 4 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/web/src/actions.ts">
<violation number="1" location="packages/web/src/actions.ts:158">
P2: The second locator resolution is unbounded, so a detach or re-render between the two evaluations can outlive the action timeout. Pass the remaining deadline to `evaluateHandle` before clicking the label.</violation>
</file>
Heads up: you’ve reached your flex budget. Increase your flex budget or wait for usage to reset.
View guided diff | Re-trigger cubic
| standIn === null | ||
| ? null | ||
| : target.kind === 'locator' | ||
| ? (await target.locator.evaluateHandle(labelAt, standIn.labelIndex)).asElement() |
Contributor
There was a problem hiding this comment.
P2: The second locator resolution is unbounded, so a detach or re-render between the two evaluations can outlive the action timeout. Pass the remaining deadline to evaluateHandle before clicking the label.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/web/src/actions.ts, line 158:
<comment>The second locator resolution is unbounded, so a detach or re-render between the two evaluations can outlive the action timeout. Pass the remaining deadline to `evaluateHandle` before clicking the label.</comment>
<file context>
@@ -88,6 +88,87 @@ async function setChecked(target: ActionTarget, checked: boolean, operation: Con
+ standIn === null
+ ? null
+ : target.kind === 'locator'
+ ? (await target.locator.evaluateHandle(labelAt, standIn.labelIndex)).asElement()
+ : (await target.element.evaluateHandle(labelAt, standIn.labelIndex)).asElement();
+ if (standIn === null || label === null) {
</file context>
Suggested change
| ? (await target.locator.evaluateHandle(labelAt, standIn.labelIndex)).asElement() | |
| ? (await target.locator.evaluateHandle(labelAt, standIn.labelIndex, { timeout: remaining() })).asElement() |
Author
There was a problem hiding this comment.
Fixed in f74e0e3: the locator evaluateHandle now runs under the remaining deadline.
…ls, and links over the input A control a disabled fieldset holds is refused by :disabled, not only the attribute. A boxless control picks a label that is rendered (checkVisibility), since visibility: hidden keeps its box. No label stands in when the element at the click point, the input's centre or a boxless label's centre, is a link, button, or other interactive descendant of the label: the click would press that instead, and a tap could follow a link. The locator's label resolution runs under the action's remaining deadline. Docs: uncheck reaches a checkbox only, the boxless case needs a visible label, and a link or button in the label still fails. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
el-radio-button,el-radio, andel-checkboxwith the native input under the label's styled spans (opacity: 0; z-index: -1), or with no box at all (width: 0; height: 0). Playwright's hit-target check accepts only the target or its descendants, socheck,uncheck, andtapon the input failedNOT_ACTIONABLE(<span class="el-radio-button__inner"> intercepts pointer events,element is not visible), and an agent step on such a radio endedAUTOMATION_UNSUPPORTED. Plain Playwright.check()fails the same way.clickControl(packages/web/src/actions.ts) now clicks a native checkbox or radio through its own label when the element at the input's center is inside one of its labels, or the input has no box (then its first label with a box). The click lands at the input's point relative to the label, so a link elsewhere in the label is not what gets pressed, and Playwright's own actionability checks run on the label: the label's content passes, anything else on top still failsNOT_ACTIONABLE. A disabled input (a disabled<fieldset>'s included,:disabled), an input that is on top itself, one covered by something outside its labels, one whose label puts a link, button, or other interactive element at the click point (sotapnever follows a link), or a boxless one with no rendered label (display: noneorvisibility: hidden,checkVisibility) is clicked as before.pickStandIn, alocator.evaluatethat waits for the locator under the action's timeout) decides whether a label stands in; the label handle is resolved only then. A tap on anything else pays that one read and keepslocator.click, re-resolving, under the same deadline. A locator that never appears still failsNOT_ACTIONABLE(tap did not become actionable in time: ... waiting for locator(...)) within one timeout.setCheckedkeeps polling the input's checked state after the label click.getRootNode().elementFromPoint) and inside a frame (the frame's own viewport coordinates).Issue
Fixes #1276
Verified
Ran it locally: yes
packages/web/tests/integration/check-actions.test.ts, the newa control its own label coverscases (check and tap on each of the three Element Plus shapes, through a locator and an element handle; a modal-covered and a disabled control that must stayNOT_ACTIONABLEand unchecked; a boxless checkbox whose first label is hidden bydisplayor byvisibilityand whose second is not, and one whose every label is hidden; a radio under a link in its own label and a boxless checkbox whose label has a link at its centre, both of which must stayNOT_ACTIONABLE, unchecked, and on the same URL; a radio-button inside<fieldset disabled>; a shadow root; a frame; a tap on a locator that never appears). With the source stashed to main:Tests 15 failed | 31 passed (46), every failureintercepts pointer eventsorelement is not visible. The review-round tests were red before their fixes (element is not visibleon thevisibility: hiddenlabel,checked state did not changewith the link pressed instead of the radio). On this branch:Tests 59 passed (59); the full web suiteTests 626 passed | 1 skipped (627),pnpm checkgreen.el-radio-button.css,el-radio.css,el-checkbox.css) and three deterministic tests,node node_modules/e2e/dist/cli/bin.js run,@e2e-dev/webbuilt from main and from this branch:Tests 3 failed | 0 passed (3), 93.94sTests 3 passed (3), 4.74sACTION_FAILED: check did not become actionable in time: elementHandle.click: Timeout 29490ms exceeded. <span class="el-radio-button__inner">安装</span> intercepts pointer events: getByRole("radio", name: "安装")✓ checks an Element Plus radio button 1.09sACTION_FAILED: check did not become actionable in time: elementHandle.click: Timeout 29549ms exceeded. element is not visible: getByRole("checkbox", name: "Agree")✓ checks an Element Plus checkbox 734msACTION_FAILED: tap did not become actionable in time: locator.click: Timeout 29602ms exceeded. <span class="el-radio__inner"></span> intercepts pointer events: getByRole("radio", name: "Large")✓ clicks an Element Plus radio 552msChecklist
pnpm changeset) for any change to a published package, or the change is testbed, docs, or CI only.!, the changeset body names what breaks and what replaces it, and the deprecation warning landed a window earlier (stability policy in CONTRIBUTING.md).docs/**/*.mdxpage for the behavior, andskills/e2e/if the skill describes it..d.tsreviewed,tests/types/sdk-types.tsupdated; wire change edits the schema and both fixtures.e2e/engine): changesets fore2e,@e2e-dev/web, and@e2e-dev/mobile.🤖 Generated with Claude Code