Skip to content

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
tester-army:mainfrom
samanyudas:fix/web-check-through-label
Open

samanyudas wants to merge 2 commits into
tester-army:mainfrom
samanyudas:fix/web-check-through-label

Conversation

@samanyudas

@samanyudas samanyudas commented Oct 24, 2026 •

Copy link
Copy Markdown

What and why

  • Element Plus draws el-radio-button, el-radio, and el-checkbox with 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, so check, uncheck, and tap on the input failed NOT_ACTIONABLE (<span class="el-radio-button__inner"> intercepts pointer events, element is not visible), and an agent step on such a radio ended AUTOMATION_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 fails NOT_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 (so tap never follows a link), or a boxless one with no rendered label (display: none or visibility: hidden, checkVisibility) is clicked as before.
  • One in-page read (pickStandIn, a locator.evaluate that 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 keeps locator.click, re-resolving, under the same deadline. A locator that never appears still fails NOT_ACTIONABLE (tap did not become actionable in time: ... waiting for locator(...)) within one timeout.
  • setChecked keeps polling the input's checked state after the label click.
  • Holds in a shadow root (getRootNode().elementFromPoint) and inside a frame (the frame's own viewport coordinates).
await screen.getByRole('radio', '安装').check();   // was ACTION_FAILED: intercepts pointer events
await screen.getByRole('checkbox', 'Agree').check(); // was ACTION_FAILED: element is not visible

Issue

Fixes #1276

Verified

Ran it locally: yes

  • packages/web/tests/integration/check-actions.test.ts, the new a control its own label covers cases (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 stay NOT_ACTIONABLE and unchecked; a boxless checkbox whose first label is hidden by display or by visibility and 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 stay NOT_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 failure intercepts pointer events or element is not visible. The review-round tests were red before their fixes (element is not visible on the visibility: hidden label, checked state did not change with the link pressed instead of the radio). On this branch: Tests 59 passed (59); the full web suite Tests 626 passed | 1 skipped (627), pnpm check green.
  • Built CLI on a scratch project whose page carries the real theme-chalk CSS for the three controls (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/web built from main and from this branch:
main this branch
Tests 3 failed | 0 passed (3), 93.94s Tests 3 passed (3), 4.74s
ACTION_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.09s
ACTION_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 734ms
ACTION_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 552ms

Checklist

  • Changeset added (pnpm changeset) for any change to a published package, or the change is testbed, docs, or CI only.
  • Breaking change: title carries !, the changeset body names what breaks and what replaces it, and the deprecation warning landed a window earlier (stability policy in CONTRIBUTING.md).
  • Docs updated in the same PR: the docs/**/*.mdx page for the behavior, and skills/e2e/ if the skill describes it.
  • Contract change: emitted .d.ts reviewed, tests/types/sdk-types.ts updated; wire change edits the schema and both fixtures.
  • Engine contract change (e2e/engine): changesets for e2e, @e2e-dev/web, and @e2e-dev/mobile.

🤖 Generated with Claude Code

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

mintlify Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Preview approval required

This pull request comes from a fork, so Mintlify won't build a preview until someone with write access approves commit f74e0e3.

Approve preview

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread docs/reference/screen.mdx Outdated
Comment thread packages/web/src/actions.ts Outdated
Comment thread packages/web/src/actions.ts
Comment thread packages/web/src/actions.ts Outdated
standIn === null
? null
: target.kind === 'locator'
? (await target.locator.evaluateHandle(labelAt, standIn.labelIndex)).asElement()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f74e0e3: the locator evaluateHandle now runs under the remaining deadline.

Comment thread packages/web/src/actions.ts Outdated
Comment thread docs/reference/screen.mdx Outdated
…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

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Decision executor: check/tap fails (NOT_ACTIONABLE) on Element Plus radio-button — hidden input intercepted by styled span, no fallback target

1 participant