8000 Mink element selection improvements · Issue #872 · minkphp/Mink · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
Mink element selection improvements #872
Open
@uuf6429

Description

@uuf6429

As mentioned here, eventually I ended up with an improved, (for dev point of view), element selection system on top of Mink.

Let's take a look first at how it's currently working. Let's say we want to check the result of a form submission and in particular we expect a specific text in a specific element (to avoid conflicting with other elements on the same page that may have the same text).

$session->visit('https://example.com/login');
$page = $session->getPage();

// <fill and submit form>

$welcomeDiv = $page->find('css', '.welcome-message');
assert(str_contains($welcomeDiv->getText(), 'Welcome, testuser'));

Mink provides quite a few methods that take selector and locator arguments. While that's not wrong, it is does have a certain learning curve. Since the documentation is a bit lacking, devs will probably have to dig a bit to find the posibilities.

What I'm proposing is to have a specific data structure (class) encapsulating that selector/locator pair.

Interestingly, the code in Selector/ seems to lead in that direction, but seems to fall short of being useful to end users.


Here's a quick backward-compatible prototype of what I mean:

interface ElementInterface  // methods below are added to the existing interface
{
    public function hasElement(ElementSelectorInterface $selector): bool;
    public function findElement(ElementSelectorInterface $selector): null|NodeElement;
    /**
     * @return list<NodeElement>
     */
    public function findElements(ElementSelectorInterface $selector): array;
}

class Element // methods below are added to the existing class
{
    public function hasElement(ElementSelectorInterface $selector): bool
    {
        return $this->has($selector->getType(), $selector->getLocator());
    }

    public function findElement(ElementSelectorInterface $selector): null|NodeElement
    {
        return $this->find($selector->getType(), $selector->getLocator());
    }

    public function findElements(ElementSelectorInterface $selector): array
    {
        return $this->findAll($selector->getType(), $selector->getLocator());
    }
}
namespace Selectors;

interface SelectorInterface
{
    public function getType(): string;   // In mink, this is actually known as the "selector", but
                                         // I renamed it to avoid confusion with the interface name.

    public function getLocator(): string|array;
}

class AbstractSelector implements SelectorInterface
{
    public function __construct(
        private readonly string $type,
        private readonly string|array $locator,
    ){}

    public function getType(): string { return $this->type; }

    public function getLocator(): string|array { return $this->locator; }
}

class Css extends AbstractSelector
{
    public function __construct(
        #[Language('CSS')]
        string $selector,
    ) {
        parent::__construct('css', $selector);
    }
}

class ExactName extends AbstractSelector
{
    /**
     * @param 'fieldset'|'field'|'link'|'button'|'etc' $nameType
     */
    public function __construct(string $nameType, string $nameValue)
    {
        parent::__construct('named', [$nameType, $nameValue]);
    }
}

class Name extends ExactName
{
    public function __construct(string $fieldName)
    {
        parent::__construct('name', $fieldName);
    }
}

class Field extends ExactName
{
    public function __construct(string $fieldName)
    {
        parent::__construct('field', $fieldName);
    }
}

I'd consider adding equivalent methods to get visible elements (i.e. the element must be found and it must be visible), since in most cases, that's what testers intended and also decreases a lot of boilerplate.

voilà:

Image

Some remaining points/questions

  1. Does this mean building the entire object/class hierarchy for each named selector?
    Not sure. It does sound a bit far fetched.
  2. What about the current mink convenience methods (e.g. $page->fillField(..)).
    While the above provides better DX (than the current selector/locator system), the conviniece methods may make more sense. I think we should keep them; at most maybe refactor them to use the new system.
  3. What about other IDEs?
    I believe that anything that doesn't impact the performance of the project and that would improve DX should be added. I'm not familiar with alternatives, but if, for example, there would be a @param-language phpdoc for some specific IDE, I'd be fine adding them.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0