Add EuiComboBoxObject Playwright helper to @elastic/eui-test-helpers#9644
Add EuiComboBoxObject Playwright helper to @elastic/eui-test-helpers#9644steliosmavro wants to merge 14 commits into
EuiComboBoxObject Playwright helper to @elastic/eui-test-helpers#9644Conversation
Implements milestone 2 of elastic#798. Adds BaseObject + EuiComboBoxObject with selectOption / clear / getSelectedOptions, validation tests against EUI Storybook (including a multi-combo isolation test using a new MultipleInstances story), Playwright config aligned with kbn-scout, and typescript lint via tsc --noEmit. Updates the existing selectors.ts to fix `data-test-subj` matching: EUI propagates the consumer's data-test-subj into the options list as a whitespace-joined token, so exact `=` was broken; switched OPTION/SELECTED_OPTION to factory functions using `~=`.
Per review feedback, switch the EuiComboBoxObject API from `selectOption(label)` (add-semantic) to `setSelectedOptions(labels)` (replace-semantic). The setter form is symmetric with `getSelectedOptions`, idempotent against the desired end-state, and removes the ambiguity flagged in review around what `selectOption` does when the label is already selected. Implementation: naive replace (clear + add each), with set-equality short circuit when current matches target. Per-label add logic extracted to a private addOption() for readability. Adds a test that explicitly verifies the replace semantic against an existing multi-selection.
Per review feedback, use sorted-array equality for both the early-return short-circuit and the post-action guard. Previously the short-circuit used length+every-includes (set-equality) while the guard used sort + toEqual; same concept, two idioms. The unified form also lets us reuse the sorted target array between the two checks.
Replace page.keyboard.press('Escape') with searchInput.blur() — key events
bubble to document-level handlers (modal/flyout close listeners), blur does
not. Move the multi-instance isolation test to its own spec so beforeEach
is meaningful for every test in each file and specs run in parallel workers.
…ombobox-component
| const inputValue = await this.searchInput.inputValue(); | ||
| return inputValue ? [inputValue] : []; |
There was a problem hiding this comment.
Could you please help me understand this part better?
To me it reads like this: if there are no selected options / pills, return the search input text (if any). If that's true, wouldn't that be misleading? Let's say I start typing into the combobox but don't select anything, then it would still be returned as a selected option while actually nothing is technically selected?
There was a problem hiding this comment.
I also think we need clarification here, so when it stops working we can understand what's missing. Could you double check few places in Kibana apps to see if it is only "pins" and "inputValue" that we should handle, how the later stores multiple values (comma, comma + space, smth else?)
There was a problem hiding this comment.
@pheyos I addressed this by introducing hasConfirmedInputSelection() a private helper that returns true only when all three conditions are met:
- The combo is in
asPlainTextmode - The input is not empty
- EUI has not marked the combo as invalid (i.e.
searchValueis set but no option was confirmed)
So if you type something without selecting getSelectedOptions correctly returns []. We also added a dedicated test: "getSelectedOptions returns [] when text is typed but not confirmed"
Also added a README.md file specific to combobox component to describe how different config combinations result into two basic pill modes (although methods might take into account more parameters behind the scenes i.e. onCreateOption:true also check invalid state).
@dmlemeshko asPlainText is a shape of singleSelection, Boolean(singleSelection) evaluates to true meaning each new selection replaces the previous one. Multiple values are not possible in this mode. The delimiter prop can trigger onCreateOption multiple times for comma separated input but since singleSelection=true each call replaces the previous selection, confirmed this by testing. On top of that, eui's asPlainText input always renders only selectedOptions?.[0]?.label regardless of state and our getSelectedOptions() reads the input value directly so it always reflects what's actually shown in the UI. Good catch though it was worth verifying! 👍
| import { defineConfig, devices } from '@playwright/test'; | ||
|
|
||
| const STORYBOOK_PORT = 6006; | ||
| const STORYBOOK_URL = `http://localhost:${STORYBOOK_PORT}`; |
There was a problem hiding this comment.
This will be different in CI, so we should provide an environment variable to override it from the outside.
In CI we can either point it to the deployed prod/PR env of Storybook (https://eui.elastic.co/storybook or https://eui.elastic.co/pr_9644/storybook), or spin up a local HTTP server serving files from the built storybook directory.
This is out of scope of this PR, but I wanted to point it out, so we don't forget about it
There was a problem hiding this comment.
Noted, we will update this part later when the CI integration comes to place.
…ection, private internals, docs
99f96b2 to
d67eead
Compare
💚 Build SucceededHistory
|
💚 Build Succeeded
History
|
Summary
First Playwright Component Object in
@elastic/eui-test-helpers:EuiComboBoxObject. Implements Milestone 2 of the eui-test-helpers epic.The plan is to wire one component (EuiComboBox) end-to-end first; subsequent helpers follow incrementally.
@elastic/eui-test-helperspackage #9616) — package scaffolding ✅ (@tkajtoch)What's in this PR
src/playwright/base_object.ts— base class for all Playwright Component Objects. Storesscope,root, andtestSubj.EuiComboBoxObject— three public methods:getSelectedOptions(),setSelectedOptions(labels),clear(). Configuration-agnostic: all three auto-detect the combo box configuration (pill mode vs. plain-text mode,isClearable,onCreateOption) and dispatch to the right internal strategy without the caller needing to know which variant they are in.selectors.ts— single source of truth for alldata-test-subjand CSS selector constants. Never inlined in helpers or specs.playwright.config.ts— defaults track kbn-scout's config (testIdAttribute: 'data-test-subj', conservative timeouts,retries: 0).singleSelection=true,singleSelection={{ asPlainText: true }},isClearable=false,onCreateOption, and multi-instance isolation.AsPlainTextandWithOnCreateOptionadded tocombo_box.stories.tsxfor validation tests that cannot be configured via URL args alone.MultipleInstancestaggedvrt-only(hidden from sidebar, accessible via URL).package.json—peerDependenciesMetamarks@playwright/testas optional so RTL/Cypress consumers don't get an unneeded peer.Selector fix in
selectors.tsThe existing
OPTION/SELECTED_OPTIONconstants used[data-test-subj="comboBoxOptionsList"](exact match). EUI propagates the consumer'sdata-test-subjto the options list as${testSubj}-optionsList, joined viaclassNames— so the actual attribute value is"comboBoxOptionsList foo-optionsList"(whitespace-separated). Exact=doesn't match;~=does.Switched to factory functions
optionFor(testSubj)/selectedOptionFor(testSubj)/optionsListFor(testSubj)using~=. Verified by temp-breaking the scoping, observing the multi-combo test fail, and reverting.Screenshots
N/AImpact Assessment
🔴 Breaking changes— Package isprivate: true, no external consumers.💅 Visual changes🧪 Test impact— Adds new tests; doesn't change existing behavior.🔧 Hard to integrateImpact level: 🟢 None
Release Readiness
packages/test-helpers/README.mdupdated with usage, design principles, and contribution guide.Figma:Migration guide:QA instructions for reviewer
Expected: 29/29 tests pass in ~8–10s.
Checklist before marking Ready for Review
QA: light/dark modes, high contrast, mobile, browsers, keyboard, screen readerQA: Tested in CodeSandbox and Kibana— Kibana consumption is M4.QA: Tested docs changesEuiComboBoxprop configurations, and multi-combo isolation.Changelog— Package is private, not released.Breaking changes labelReviewer checklist