fix: Better support of existence for toHaveAttribute & toHaveElementProperty#2128
fix: Better support of existence for toHaveAttribute & toHaveElementProperty#2128dprevost-LMI wants to merge 11 commits into
toHaveAttribute & toHaveElementProperty#2128Conversation
Greptile SummaryThis PR improves the ergonomics of
Confidence Score: 5/5The change is safe to merge — the new dispatch logic is well-tested, waitUntil only consumes wait/interval so the options-slicing approach is correct, and all existing behavior is preserved. The core logic is straightforward: a type-guard classifies the third argument and routes to the existing helper functions with the right options. The fix to forward wait/interval from user-supplied options to waitUntil in the existence-check path is correct and verified by the expanded test suite. No existing call signatures are broken, and the new overloads are fully covered by type tests and unit tests. src/util/commandOptionsUtils.ts warrants a second look for the some vs every predicate (discussed in prior review thread).
|
| Filename | Overview |
|---|---|
| src/util/commandOptionsUtils.ts | New type-guard utility; uses some predicate on a whitelist — any object with at least one matching key is treated as options (discussed in prior thread). Orphan comment numbers (// 3, // 4 with no // 1 or // 2) suggest incomplete cleanup. |
| src/matchers/element/toHaveAttribute.ts | Adds overloads and runtime dispatch via isStringOptions; fixes existence check timing; waitUntil now correctly receives caller-provided wait/interval instead of empty object. |
| src/matchers/element/toHaveElementProperty.ts | Mirrors toHaveAttribute pattern; fixes the long-standing undefined-expectedValue bug by extending the null check to also cover undefined; adds correct message branching for existence vs value assertions. |
| types/expect-webdriverio.d.ts | Expands toHaveAttribute and toHaveElementProperty type declarations with overloaded call signatures; also adds number to the property value union and deprecation annotations. |
| test/matchers/element/toHaveAttribute.test.ts | Substantially expanded test suite covering existence, value-match, deprecated overloads, and error message snapshots; switches from .call({}) to a structured thisContext pattern. |
| test/util/commandOptionsUtils.test.ts | New unit tests for isStringOptions covering empty objects, all whitelisted keys, primitives, RegExps, arrays, and asymmetric matchers. |
| docs/API.md | Removes the toHaveAttr section entirely and adds new existence-check usage examples without sub-headings, making the section harder to navigate; no deprecation notice for toHaveAttr remains. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["toHaveAttribute(el, attr, valueOrOptions, options)"] --> B{isStringOptions check}
B -- "true - e.g. wait: 0" --> C[options = valueOrOptions\nvalue = undefined]
B -- "false - string/RegExp/undefined" --> D[value = valueOrOptions\noptions = 4th arg or DEFAULT_OPTIONS]
C --> E{value defined?}
D --> E
E -- yes --> F[toHaveAttributeAndValue\nwaitUntil with wait+interval]
E -- no --> G[toHaveAttributeFn\nwaitUntil with wait+interval]
F --> H[compareText result]
G --> I[existence check result]
J["toHaveElementProperty(el, prop, valueOrOptions, options)"] --> K{isStringOptions check}
K -- "true" --> L[options = valueOrOptions\nvalue = undefined]
K -- "false" --> M[value = valueOrOptions\noptions = 4th arg or DEFAULT]
L --> N{value undefined?}
M --> N
N -- "yes - existence message format" --> O[condition: null/undefined expectedValue triggers existence path]
N -- "no - value message format" --> O
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A["toHaveAttribute(el, attr, valueOrOptions, options)"] --> B{isStringOptions check}
B -- "true - e.g. wait: 0" --> C[options = valueOrOptions\nvalue = undefined]
B -- "false - string/RegExp/undefined" --> D[value = valueOrOptions\noptions = 4th arg or DEFAULT_OPTIONS]
C --> E{value defined?}
D --> E
E -- yes --> F[toHaveAttributeAndValue\nwaitUntil with wait+interval]
E -- no --> G[toHaveAttributeFn\nwaitUntil with wait+interval]
F --> H[compareText result]
G --> I[existence check result]
J["toHaveElementProperty(el, prop, valueOrOptions, options)"] --> K{isStringOptions check}
K -- "true" --> L[options = valueOrOptions\nvalue = undefined]
K -- "false" --> M[value = valueOrOptions\noptions = 4th arg or DEFAULT]
L --> N{value undefined?}
M --> N
N -- "yes - existence message format" --> O[condition: null/undefined expectedValue triggers existence path]
N -- "no - value message format" --> O
Reviews (6): Last reviewed commit: "Fix doc" | Re-trigger Greptile
| // Condition B: At least one key in the object exists in our whitelist | ||
| return objKeys.some(key => allowedKeys.has(key)) | ||
| } |
There was a problem hiding this comment.
"At least one matching key" heuristic can produce false positives
isStringOptions returns true as long as the object contains any key from the allowlist. An object like { wait: 1, href: '/path' } (e.g. someone incorrectly passing a WebdriverIO link object) would be classified as StringOptions, causing toHaveAttribute to skip the value check entirely and fall through to the attribute-existence path. A stricter rule — e.g. returning true only when all keys are in the allowlist — would prevent this class of silent misrouting.
There was a problem hiding this comment.
All keys are optional so I cannot do so. Do you have other suggestions?
There was a problem hiding this comment.
The suggestion isn't about requiring all allowlisted keys to be present — it's the reverse: requiring that all keys that are present must be in the allowlist. You'd change some to every:
| // Condition B: At least one key in the object exists in our whitelist | |
| return objKeys.some(key => allowedKeys.has(key)) | |
| } | |
| return objKeys.every(key => allowedKeys.has(key)) |
{}→true(vacuously passes, as before){ wait: 1 }→true{ wait: 1, href: '/path' }→false(hrefisn't in the allowlist)
This closes the false-positive gap without requiring any specific key to be present, which respects the fully-optional nature of the interface.
…ndefined - Deprecate `toHaveAttr`, there is no gain to have it since `toHaveAttribute` is clearer - Allow passing options to `toHaveAttribute` without needing `undefined` - All case passing `undefined` as value is deprecated. - Add some refactoring done during support of $$ to ease changes -Fix options not passed when checking prop existences
41a7811 to
832a822
Compare
Ease the signature when checking the existence of an attribute so that we can do
instead of