fix(text-field): floated label position + visual regression suite + CI split#530
Merged
kelsos merged 3 commits intorotki:mainfrom Apr 30, 2026
Merged
Conversation
The floating-label state had three pre-existing positioning bugs that together produced visibly wrong layouts whenever a prepend icon was present or `dense` was used: - The active label slot dropped `--prepend-w` / `--append-w` from its padding, so the floated label drifted away from the input column for default and filled. Restored both vars in the active rule, and added a dedicated outlined+active override that keeps the floated label anchored to the field's left edge per MD3 (outlined floats over the border, not over the input column). - `outlined+dense+active` inherited `leading-[2.5]` from the dense compound and silently overrode the active slot's `leading-tight`, inflating the label line-box from ~15px to ~30px and pushing the floated label below the fieldset border. Re-asserted `!leading-tight` for that compound. - For `default+dense+active`, the labelText kept stretching to the full inputWrapper height (~32px) and dragged its baseline into the input text. Translated only the labelText span (not the label container) so the underline pseudo stays anchored at the bottom of the input row. `use-prepend-append-width` was measuring `contentRect.width + left`, which silently dropped padding-right. Default's `pr-3`-only prepend therefore reported 12px short of its real outer width and the floated label landed under the icon's trailing padding instead of aligned with the input text. Switched to `offsetWidth` so the value reflects the full border-box.
Set up a pixel-level regression suite that catches CSS-only regressions
the existing behavioral specs miss (label position, dense overlap,
prepend alignment — the class of bugs surfaced and fixed in the
preceding commit).
Layout chosen so additional component fixtures slot in cleanly:
apps/example/
e2e/visual/ # specs + per-spec snapshots
_setup.ts # disable animations, wait for fonts
text-field.visual.spec.ts # 3x2x2 matrix x 3 states
src/
pages/visual/text-field.vue # /visual/text-field route
views/visual/
VisualGrid.vue # generic matrix-cell renderer
TextFieldFixture.vue # feeds VisualGrid with the matrix
Snapshot portability — the single hardest part of pixel testing — is
solved by pinning Playwright's official Docker image
(`mcr.microsoft.com/playwright:v1.59.1-noble`) for both contributor
runs and CI. Any other approach (host Chromium, Ubuntu CI runner) gives
divergent antialiasing across machines and every snapshot fails. The
image's bundled browser binary is kept in sync with the
`@playwright/test` version pinned in `pnpm-workspace.yaml`.
Local workflow:
- `pnpm test:visual` verifies against committed baselines
- `pnpm test:visual:update` regenerates baselines
Both wrap `apps/example/scripts/visual-docker.sh`, which bind-mounts the
repo, anonymous-volumes each `node_modules/` (so a contributor's macOS
arm64 install isn't seen by the linux x64 container), and runs
corepack + pnpm install + build + playwright inside.
CI side: a new `visual` job runs the same image as a `container:` and
parallelises with the existing `ci` job — failures surface via the
existing playwright-report artefact upload pattern, with side-by-side
expected/actual/diff PNGs viewable in the report.
`testIgnore: '**/visual/**'` (gated on `PLAYWRIGHT_VISUAL=1`) keeps the
behavioral `pnpm test:e2e` run unchanged — visual specs only execute
when the dedicated scripts opt in.
Initial 36 snapshots (3 variants x 2 densities x 2 prepend modes x 3
states) capture the post-fix layout from the preceding commit.
Replace the single serial `ci` job with seven jobs that run fully in
parallel — wall time on PRs drops from ~max(commit-lint, ci, visual)
to ~max of the longest test job.
Layout:
commit-lint │ validate │ unit ──┐ ┌── e2e ×2 shards
├─→ coverage │
storybook ┘ └── visual
Job responsibilities:
- `validate` — install + lint + example-app typecheck. Library
typecheck is implicit in `build:types` (run as part of `build:prod`
in every other job), so it's not duplicated here.
- `unit` — install + build:prod + vitest unit tests. Uploads
blob-format coverage as `vitest-report-unit` artefact.
- `storybook` — install + build:prod + build:storybook + storybook
play-function tests via Playwright. Uploads `vitest-report-storybook`
artefact. Splitting the storybook run off from `unit` parallelises
the storybook build (~30-60s) and its Playwright run with the vitest
pass instead of stacking them serially.
- `coverage` — `needs: [unit, storybook]`. Downloads both blobs,
merges via vitest's `--merge-reports`, forwards to codecov.
- `e2e` — sharded ×2 via Playwright's `--shard` flag with a matrix
strategy. `fail-fast: false` so a failure on one shard doesn't
cancel the other; both shards' reports upload separately.
- `visual` — unchanged from the previous infra commit; runs in the
pinned Playwright Docker image.
Total compute rises by ~4-5 min per run because each parallel job
re-runs `pnpm install` and the library build. That's free on OSS
runners and the wall-time cut shows up in every PR review cycle.
Walking through the timing: a shared `build` job uploading dist/ would
*slow* wall-time down (tests would have to wait for the build instead
of running in parallel with each job's own build) — the duplication
is a net win at this scale.
Both `unit`, `storybook` and `e2e` cache `~/.cache/ms-playwright` keyed
on `pnpm-lock.yaml` so the ~150MB chromium download only re-runs when
`@playwright/test` changes — saves ~15-30s per job after the first run
on a branch.
`actions/cache` pinned to v5.0.5; `actions/download-artifact` pinned to
v7 to match the upload action's major version.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #530 +/- ##
==========================================
- Coverage 84.83% 84.83% -0.01%
==========================================
Files 145 145
Lines 5269 5267 -2
Branches 1577 1577
==========================================
- Hits 4470 4468 -2
Misses 799 799 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
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.
Summary
Three logically distinct changes, one PR:
fix(text-field):correct floated label position with prepend / dense (e21f3bb) — fixes long-standing CSS positioning bugs surfaced visually during a manual review ofRuiTextField.test(visual):add Playwright visual regression suite forRuiTextField(5090f59) — pixel-level guard against the class of bug we just fixed (CSS-only regressions that DOM tests can't catch).ci:split into parallel jobs for faster feedback (bd0d187) — wall-time on PRs drops by exposing the test halves in parallel.What's broken (and now fixed)
In the floated state,
RuiTextField's label was misaligned across the cross-product of{default, filled, outlined} × {normal, dense} × {with prepend}. Concretely:contentRect.width + leftand silently droppingpadding-right).leading-[2.5]from the dense compound was overriding the active slot'sleading-tight, inflating the label line-box from 15px → 30px.Root causes — three in
text-field-styles.ts, one inuse-prepend-append-width.ts— see commite21f3bbfor details.Visual regression infrastructure
New suite under
apps/example/e2e/visual/driven by PlaywrighttoHaveScreenshot. Snapshot portability is the hard part of pixel testing — solved by pinning Playwright's official Docker image (mcr.microsoft.com/playwright:v1.59.1-noble) for both contributor runs and CI. Without the pin, snapshots captured locally and on CI diverge by a handful of antialiasing pixels and every run fails.Local workflow:
pnpm test:visual— verify against committed baselinespnpm test:visual:update— regenerate baselinesBoth wrap
scripts/visual-docker.shwhich bind-mounts the repo, anonymous-volumes eachnode_modules/, and runs corepack + pnpm install + build + playwright inside the container.testIgnore: '**/visual/**'(gated onPLAYWRIGHT_VISUAL=1) keeps the existing behaviouralpnpm test:e2erun unchanged — visual specs only execute when the dedicated scripts opt in.CI split
Replace the single serial
cijob with seven jobs running fully in parallel:validate— install + lint + example-app typecheck. (Library typecheck is implicit inbuild:typesin every other job, no need to repeat here.)unit— vitest unit tests, uploads coverage as artefact.storybook— split off so the storybook build (~30-60s) and its Playwright run parallelise with vitest instead of stacking serially. Uploads coverage artefact.coverage—needs: [unit, storybook]. Downloads both blobs, merges, forwards to codecov.e2e— sharded ×2 via Playwright's--shardflag with a matrix strategy.fail-fast: falseso one shard's failure doesn't cancel the other.visual— Playwright Docker image ascontainer:.Total compute rises by ~4-5 min per run because each parallel job re-runs
pnpm installand the library build. That's free on OSS runners and the wall-time cut shows up in every PR review cycle.Test plan
pnpm test:visual— 36/36 pass, captured in pinned containerpnpm typecheck— passespnpm run lint— passes (84 pre-existing warnings, 0 errors)pnpm test:e2e— passes, doesn't pick up visual specs