Repository navigation
Allow named selector to not do partial matching #880
Description
Hi, thanks for all the work over the years on this! ❤️
I searched and couldn't find a matching issue (tho #872 covers the same area of the codebase). I'd love to be able to choose to have strict matching when using the named selector (ie named behaves identically to named_exact). It seems currently that the behavior's hardcoded:
Mink/src/Element/ElementFinder.php
Lines 48 to 57 in 9b08f62
| public function findAll(string $selector, $locator, string $parentXpath) | |
| { | |
| if ('named' === $selector) { | |
| $items = $this->findAll('named_exact', $locator, $parentXpath); | |
| if (empty($items)) { | |
| $items = $this->findAll('named_partial', $locator, $parentXpath); | |
| } | |
| return $items; | |
| } |
And the element finder's instantiated directly in the session constructor:
Lines 42 to 50 in 9b08f62
| public function __construct(DriverInterface $driver, ?SelectorsHandler $selectorsHandler = null) | |
| { | |
| $this->driver = $driver; | |
| $this->selectorsHandler = $selectorsHandler ?? new SelectorsHandler(); | |
| $this->elementFinder = new ElementFinder($driver, $this->selectorsHandler); | |
| $this->page = new DocumentElement($this); | |
| $driver->setSession($this); | |
| } |
From my perspective it'd be great to either have a way to override the element finder with my own custom class, or to update the default Mink finder to support switching off fallback partial matching.
Do either of those approaches sound like something you'd consider? If not, is there maybe some other solution?
Thanks!
Activity
aik099 commentedon Jul 6, 2026
I see the potential in making a default selector name (the
namedcurrently) configurable. Unsure where this configuration should take place:$locator = 'named'parameter inTraversableElementclass;DocumentElement(aka page) and eachNodeElementit produces;andriokha commentedon Jul 31, 2026
Sorry for the delay in getting back. Another possible option would be to allow
namedto be registered. It's not super clean, but it would be a relatively minimal change:Otherwise I'd consider having it session-level. I don't know the codebase well, so take my suggestions with a pinch of salt :)
We'll need to be careful about where
namedis currently used. For example the following ought to be changed tonamed_partialif I understand correctly, otherwise it'll only return true if the whole document text equals$content:Mink/src/Element/DocumentElement.php
Lines 40 to 50 in 9b08f62
Thanks!
aik099 commentedon Aug 3, 2026
I've researched the code a bit more:
The
ElementFindercould be the right place for this setting, notSession. EveryElement/NodeElement(no matter how deep) ends up holding a reference to the sameElementFinderinstance (inmasterbranch (1.x) that's transitively via the sharedSession; on2-architecture-changesbranch (2.x) it's even more direct —ElementFinder::findAll()constructs newNodeElements passing$thisas their finder). So if we put a setting onElementFinder, every element just sees it automatically — no need to pass anything throughTraversableElement's methods orElement's constructor.Concretely:
ElementFindercould hold a simple mode —'named'(today's exact-then-partial fallback, unchanged default),'named_exact', or'named_partial'— checked at the top of the existing branch infindAll(). Bonus: forcing a single mode is actually cheaper than today, since it skips the fallback lookup.Issues with this approach:
Sessioncurrently doesnew ElementFinder(...)itself with no way to inject a different one (Session.php:46onmaster, same story on2-architecture-changes). So we'd need to open that up too, e.g. an optional?ElementFinder $elementFinderparam onSession's constructor — there's already a precedent for that with the optional?SelectorsHandler $selectorsHandlerparam.DocumentElement::hasContent()also goes through'named'(find('named', array('content', $content))), and "content" matching is supposed to be a substring match. If the exact mode gets forced globally,hasContent()would suddenly require the whole document text to match exactly, which is almost certainly not what anyone wants. Worth checking all the internal'named'call sites (TraversableElement,NodeElement::selectOption,DocumentElement::hasContent) before this ships, since a global switch hits all of them, not just the spot someone actually meant to make exact.Due to later the global switch would break document content lookup, and the per-method switch would break MinkExtension Behat integration that has no way to specify selector name on any
find*call.andriokha commentedon Aug 3, 2026
Thanks for your time!
That works for me! It seems to me there's not tons of difference in using session vs elementfinder. The element finder's instantiated from the session, so a session-level setting can easily be passed into the element finder. For me it's a bit more elegant to add it to the finder (keep everything as local as possible to what it affects) but I think you'd have to make the finder class public, and I guess ideally non-final. The only advantage of using the session I can see is that it's already public.
Yup I mentioned that one earlier. It's an easy fix IIUC (use
named_partial) and I can't find other examples that would be similarly affected.Btw I didn't release there was a 2.x branch - and I notice it doesn't include v1.13.0 in its history - which branch should I be looking at (and working on if I make a PR)?
aik099 commentedon Aug 4, 2026
Ah, then we're all set:
ElementFinderand it will then populate a new class property based on that parameter and decide hownamedselector is handledSessionElementFinderinstanceElementFinderinstance itself (what about@internaland@final?)named_partialselector instead ofnamedon theDocumentElement::hasContent(optimization as well)Sessionspecify mode parameter (or provideElementFinderinstance with mode parameter)As for the 2.x branch would be fine because the
ElementFinderwho decides andSessionis giving it the parameter from an outside. The fact that in the 2.x branchNodeElementis usingElementFinderdirectly (instead ofSession) doesn't break anything.@stof, what bothers me in this whole idea is that
ElementFinderis marked as@finaland@internaland we're changing it. What do you think?Amoifr commentedon Aug 31, 2026
@aik099 on the
@final/@internalworry, I think choosing between your two variants of point 2 settles it rather than trading it off, and the code backs that up.I grepped every file under
src/.new ElementFinderappears in exactly one place,Session::__construct().Element.phponly references the type, and nothing else constructs one:So the two variants are not equivalent with respect to the marker:
ElementFinderinstance toSessionmakes users construct an@internalclass themselves. That is the thing the marker exists to prevent, and it is what makes the idea uncomfortable.Sessionand forwarding it leavesElementFinderwhere it is. Users never name the class. Adding an optional constructor parameter to a class that is@internaland constructed in one place is not a public API change at all, precisely because it is internal. The only public surface added is the new optionalSessionparameter.Put the other way round:
@internalis what makes the first variant awkward and the second one free.@finalis not in the way either, since nothing here needs subclassing.That also keeps the 2.x branch working the way you described, since
NodeElementreceives the finder rather than building it.Happy to implement it that way, on
masterfirst: an optional mode onElementFinderdeciding hownamedresolves, an optional mode onSessionforwarding it, and tests for both resolutions. Point 3 is already out in #881. Just say the word, or wait for @stof if you would rather have his read on the marker first.aik099 commentedon Aug 31, 2026
@Amoifr, please implement it then. I'll agree with your explanation of things.
Pay attention to tests, because we need to check that whatever mode is set at
Sessionconstruction time actually ends up inElementFinderand theElementFinderown tests actually respect any mode given to it.