Skip to content

feat: support arrayContaining in toHaveText - #2211

Merged
dprevost-LMI merged 3 commits into
webdriverio:mainfrom
JustasMonkev:feat/to-have-text-array-containing
Sep 22, 2026
Merged

dprevost-LMI merged 3 commits into
webdriverio:mainfrom
JustasMonkev:feat/to-have-text-array-containing

Conversation

@JustasMonkev

Copy link
Copy Markdown
Contributor

Adds arrayContaining() support to toHaveText() with $$().

Works with extra elements, any order, nested matchers, .not, and retries.

Closes #2004.

@greptile-apps

greptile-apps Bot commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds collection-level arrayContaining() support to toHaveText and related value matchers.

  • Centralizes collection snapshot extraction, concurrent reads, retries, and asymmetric comparison in the shared command executor.
  • Supports Jest/WebdriverIO and Jasmine matchers, nested asymmetric matchers, negation, aliases, and scalar array-valued properties.
  • Expands runtime, framework type, failure-message, retry, and documentation coverage.
  • The three previous findings are resolved: deterministic terminal failures now abort, collection reads run concurrently, and all supported framework type suites have coverage.

Confidence Score: 5/5

The PR appears safe to merge, with the previously reported retry, concurrency, and framework type-coverage issues resolved.

The shared executor preserves retry and negation contracts, refreshes selector-backed collections, terminates static invalid inputs, reads collection values concurrently, and retains raw getter values for whole-snapshot matching. No actionable new defect or outstanding previous finding remains.

Important Files Changed

Filename Overview
src/util/executeCommand.ts Centralizes collection snapshot extraction, concurrent value reads, refetch behavior, and asymmetric comparison for opted-in matchers.
src/utils.ts Adds protocol-based recognition for Jest/WebdriverIO and Jasmine array-containing matchers.
src/jasmineUtils.ts Supplies Jasmine-compatible containment semantics for nested collection matching.
src/matchers/element/toHaveText.ts Migrates toHaveText collection matching to the shared executor and preserves terminal validation behavior.
src/matchers/element/toHaveElementProperty.ts Enables collection subset matching while retaining scalar array-valued property assertions.
types/expect-webdriverio.d.ts Extends the Jasmine asymmetric matcher protocol to accept its optional pretty-printer.
test/matchers/element/arrayContaining.test.ts Covers collection subset behavior across value matchers, aliases, options, nested matchers, and scalar properties.
test/matchers/element/toHaveText.test.ts Exercises terminal failures, concurrent reads, refetching, retries, negation, diagnostics, and matcher reuse.
docs/API.md Documents supported matchers, collection semantics, retries, options, and Jasmine compatibility.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[Element collection] --> B[Shared command executor]
    B --> C[Resolve or refresh elements]
    C --> D[Read values concurrently]
    D --> E[Build ordered value snapshot]
    E --> F[Apply arrayContaining once]
    F --> G{Assertion passes?}
    G -->|Yes| H[Return assertion result]
    G -->|No, retryable| C
    G -->|No, terminal| H
Loading

Reviews (3): Last reviewed commit: "feat: centralize arrayContaining across ..." | Re-trigger Greptile

Comment thread src/matchers/element/toHaveText.ts Outdated
Comment thread src/matchers/element/toHaveText.ts Outdated
Comment thread test-types/mocha/mocha.test-d.ts
@dprevost-LMI

Copy link
Copy Markdown
Contributor

@greptile, could this be extended to all matchers instead of just toHaveText?

@greptile-apps

greptile-apps Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Yes, but I’d generalize the execution strategy rather than duplicate the toHaveText branch.

arrayContaining compares one snapshot array, whereas executeCommandWithStrategy currently compares each element independently against an indexed expected value (src/util/executeCommand.ts). The new logic in src/matchers/element/toHaveText.ts also has matcher-specific value collection (getText()), refetching, and retry behavior.

A good follow-up would add a generic collection-comparison path that:

  • resolves one actual value per element;
  • applies the asymmetric matcher once to the resulting array;
  • preserves element-array refetching, retries, empty-array handling, and error propagation;
  • lets each matcher provide only its value getter and any option normalization.

That could cover array-valued matchers such as toHaveText, toHaveHTML, toHaveAttribute, toHaveValue, and possibly classes. It should not automatically apply to every matcher: assertions like toBeDisplayed need explicit semantics (for example, every element must be displayed), and style/structural matchers may need different collection behavior.

So yes—this is a sensible abstraction, but I’d keep this PR focused and extract the shared collection strategy in a follow-up. The shared implementation should also detect the asymmetric matcher through a utility rather than relying on the constructor.name === 'ArrayContaining' check.

@dprevost-LMI

Copy link
Copy Markdown
Contributor

@greptile, instead of doing a double implementation, why not do one centralized implementation for all matchers? Can you propose a solution?

So yes—this is a sensible abstraction, but I’d keep this PR focused and extract the shared collection strategy in a follow-up. The shared implementation should also detect the asymmetric matcher through a utility rather than relying on the constructor.name === 'ArrayContaining' check.

@greptile-apps

greptile-apps Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Yes. I’d move this into executeCommandWithStrategy as an optional collection-comparison mode, so all element matchers share resolution, refetching, concurrency, errors, and retries.

export type CollectionCompare<Actual, Expected> = {
    isSupported: (expected: unknown) => expected is Expected
    read: (element: WebdriverIO.Element) => Promise<Actual>
    compare: (actual: Actual[], expected: Expected) => boolean
}

Extend executeCommandWithStrategy with collectionCompare?: CollectionCompare<Actual, Expected>. Before dispatching to the existing legacy/strict per-element strategy, the shared implementation should:

  • resolve through awaitElementOrArray();
  • return a terminal result for invalid subjects and static empty arrays;
  • refresh strictly refetchable ElementArray values on retries;
  • call read for every element concurrently via Promise.allSettled, preserving element order and rethrowing the first rejection;
  • return compare(actual, expected) as the normal, non-negated success value.

waitUntil already applies .not, retries, and timeout handling around that result, so none of those concerns need to be repeated in individual matchers.

toHaveText would then only provide matcher-specific behavior:

collectionCompare: {
    isSupported: isArrayContainingMatcher,
    read: (element) => element.getText(),
    compare: (actual, expected) => expected.asymmetricMatch(actual),
},

The same option can be supplied by toHaveHTML, toHaveAttribute, toHaveElementClass, and future collection assertions with only their read callback changed. Existing scalar, array-indexed, legacy, and strict behavior remains on the current path when isSupported returns false.

I’d add isArrayContainingMatcher beside isAsymmetricMatcher in src/utils.ts (or the asymmetric utilities) and use a protocol/symbol/shape-based check that supports Jest, Jasmine, and WebdriverIO matchers. The matcher execution should not rely on constructor.name === 'ArrayContaining', since that is framework-specific and breaks for custom or prototype-less asymmetric matchers.

This gives us one centralized implementation without making the common strategy aware of getText() or any other matcher-specific command.

@JustasMonkev

Copy link
Copy Markdown
Contributor Author

@dprevost-LMI
should I do it? in this PR?

Yes. I’d move this into executeCommandWithStrategy as an optional collection-comparison mode, so all element matchers share resolution, refetching, concurrency, errors, and retries.

@dprevost-LMI

Copy link
Copy Markdown
Contributor

@JustasMonkev, that would be preferable if you are open to it! executeCommandWithStrategy was designed to apply the implementation to all matchers, so we can try to minimize per-matcher behaviour

@JustasMonkev

Copy link
Copy Markdown
Contributor Author

@JustasMonkev, that would be preferable if you are open to it! executeCommandWithStrategy was designed to apply the implementation to all matchers, so we can try to minimize per-matcher behaviour

Gotcha, thanks for clearing it up. I’ll handle it tomorrow.

@dprevost-LMI

Copy link
Copy Markdown
Contributor

@JustasMonkev, that would be preferable if you are open to it! executeCommandWithStrategy was designed to apply the implementation to all matchers, so we can try to minimize per-matcher behaviour

Gotcha, thanks for clearing it up. I’ll handle it tomorrow.

BTW, FYI, if you need similar behaviour in the short term, potentially expect.oneOf could help!

@JustasMonkev

Copy link
Copy Markdown
Contributor Author

@JustasMonkev, that would be preferable if you are open to it! executeCommandWithStrategy was designed to apply the implementation to all matchers, so we can try to minimize per-matcher behaviour

Gotcha, thanks for clearing it up. I’ll handle it tomorrow.

BTW, FYI, if you need similar behaviour in the short term, potentially expect.oneOf could help!

Thanks for the tip! I’ve pushed the centralized implementation in da6960d. This also covers the case where both values must be present, with extra elements allowed. @dprevost-LMI

@JustasMonkev

Copy link
Copy Markdown
Contributor Author

Should I do anything else?
@dprevost-LMI

@dprevost-LMI

Copy link
Copy Markdown
Contributor

No, I'm just swamped

@dprevost-LMI
dprevost-LMI merged commit 132d36f into webdriverio:main Sep 22, 2026
13 checks passed
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.

Support of arrayContaining

2 participants