Modifiers

Modifiers refine an assertion beyond "an element matching the selector exists." They're chained inside the attribute value using CSS-like bracket syntax after the selector:

fs-assert-updated='#count[text-matches=\d+]'
fs-assert-updated='#logo[src=/img/new.png][alt=New Logo]'
fs-assert-updated='.panel[classlist=active:true,hidden:false]'
fs-assert-added-success='.success[text-matches=Order #\d+]'

Multiple modifiers chain by concatenating bracket groups. All modifiers must satisfy for the assertion to pass.

Reserved modifiers

Reserved modifier keys have a fixed meaning that doesn't depend on the target element's attributes.

Modifier Description
text-matches Text content matches a regex (partial match, unanchored)
value-matches Form control .value property matches a regex (partial match, unanchored)
checked Checkbox/radio .checked DOM property
disabled Disabled state (.disabled + aria-disabled="true")
focused Focus state (document.activeElement === el)
focused-within Focus-within state (el.matches(':focus-within'))
count Cardinality — count, count-min, count-max
classlist Class presence check — active:true,hidden:false
detail-matches CustomEvent event.detail filter — string equality on triggers, regex on emitted

Attribute checks (unreserved keys)

Any bracket key that isn't reserved is treated as an attribute check. The key is the attribute name, the value is a regex full match (auto-anchored with ^(?:...)$). See attribute-check.md for the full rules.

fs-assert-updated='#logo[src=/img/new.png][width=100][alt=Logo]'
fs-assert-updated='.panel[data-state=active|ready]'
fs-assert-visible='button[aria-expanded=true]'

Anchoring — the load-bearing rule

  • text-matches and value-matches are partial match (unanchored). Pattern can match anywhere in the string. Use ^exact$ to anchor explicitly.
  • Attribute checks (unreserved keys) are full match (auto-anchored). Pattern must match the entire value.

This difference exists because text content is usually something like "3 items remaining" where a partial match (\d+) is what you want, while attribute values are usually exact tokens (data-state="active") where full match prevents accidental substring matches.

Self-referencing — check the element itself

Omit the selector before the [ to check the instrumented element rather than a descendant:

<div id="todo-count"
  fs-assert-updated="[text-matches=\d+/\d+ remaining]">

An empty selector means "check this element." See self-referencing.md for the full pattern.

Multiple modifier chaining

Every bracket group must pass for the assertion to resolve:

fs-assert-updated='#logo[src=/img/new.png][width=100][alt=Logo]'

Three checks — src, width, alt — all must hold. Missing any one keeps the assertion pending until every modifier is satisfied or the timeout fires.

Common mistakes

  • Using CSS attribute selectors inside modifier values. The bracket parser treats [ as a modifier delimiter. [data-id="123"] .btn[disabled=true] is misparsed. Use id or class selectors in the selector part and put attribute checks in modifier brackets.
  • Using text-matches with a literal dynamic value. Use a regex: [text-matches=\d+] not [text-matches=42]. The latter looks for the literal string "42" which works for static content but breaks the moment the value changes.
  • Using count with self-referencing (no selector). Count of self is always 1. count requires an explicit selector that matches a collection.
  • Using value-matches and expecting MutationObserver to detect .value changes. The .value property is not an HTML attribute — typing doesn't trigger MutationObserver. Pair it with event triggers like change or blur, not with updated.
  • Using value-matches on a checkbox to read its state. checkbox.value returns the static value attribute, not the checked state. Use checked=true for checkbox/radio state.