-
Notifications
You must be signed in to change notification settings - Fork 46
Rewrite the chapter about traversing pages #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,164 +1,202 @@ | ||
| Traversing Pages | ||
| ================ | ||
|
|
||
| Now you know how to control the browser itself. But what about traversing | ||
| the current page content? Mink talks to its drivers with `XPath selectors`_, | ||
| but you also have access to `named selectors`_ and `css selectors`_. Mink | ||
| will transform such selectors into XPath queries internally for you. | ||
| Most usages of Mink will involve working with the page opened in your browser. | ||
| This is done thanks to the powerful Element API. This API allows to traverse | ||
| the page (similar to the DOM in Javascript) and to interact with it, which | ||
| will be covered in the :doc:`next chapter </guides/manipulating-pages>`. | ||
|
|
||
| The main class of Mink's selectors engine is ``Behat\Mink\Selector\SelectorsHandler``. | ||
| It handles different selector types, which implements ``Behat\Mink\Selector\SelectorInterface``: | ||
| DocumentElement and NodeElement | ||
| ------------------------------- | ||
|
|
||
| The Element API consists of 2 main classes. The ``DocumentElement`` instance | ||
| represents the page being displayed in the browser, while the ``NodeElement`` | ||
| class is used to represent any element inside the page. Both class are sharing | ||
| a common set of methods to traverse the page (defined in ``TraversableElement``). | ||
|
|
||
| The ``DocumentElement`` instance is accessible through the ``Session::getPage`` method: | ||
|
|
||
| .. code-block:: php | ||
|
|
||
| $cssSelector = new \Behat\Mink\Selector\CssSelector(); | ||
| $page = $session->getPage(); | ||
|
|
||
| // generate XPath query out of CSS: | ||
| echo $cssSelector->translateToXPath('h1 > a'); | ||
| // You can now manipulate the page. | ||
|
|
||
| $handler = new \Behat\Mink\Selector\SelectorsHandler(); | ||
| $handler->registerSelector('css', $cssSelector); | ||
| .. note:: | ||
|
|
||
| // generate XPath query out of CSS: | ||
| echo $handler->selectorToXpath('css', 'h1 > a'); | ||
| The ``DocumentElement`` instance represents the ``<html>`` node in the | ||
| DOM. It is equivalent to ``document.documentElement`` in the Javascript | ||
| DOM API. | ||
|
|
||
| When you initialize ``Selector\SelectorsHandler`` it already has `XPath selectors`_, | ||
| `named selectors`_ and `css selectors`_ registered in it. | ||
| Traversal Methods | ||
| ----------------- | ||
|
|
||
| You can provide a custom selectors handler as a second argument to your session | ||
| instances: | ||
| Elements have 2 main traversal methods: ``ElementInterface::findAll`` returns | ||
| an array of ``NodeElement`` instances matching the provided :ref:`selector <selectors>` | ||
| inside the current element while ``ElementInterface::find`` returns the first | ||
| match or ``null`` when there is none. | ||
|
|
||
| .. code-block:: php | ||
| The ``TraversableElement`` class also provides a bunch of shortcut methods | ||
| on top of ``find()`` to make it easier to achieve many common use cases: | ||
|
|
||
| $session = new \Behat\Mink\Session($driver, | ||
| new \Behat\Mink\Selector\SelectorsHandler() | ||
| ); | ||
| ``ElementInterface::has`` | ||
| Checks whether a child element matches the given selector but without | ||
| returning it. | ||
|
|
||
| Mink will use this handler internally in `find* methods`_. | ||
| ``TraversableElement::findById`` | ||
| Looks for a child element with the given id. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if we should use |
||
|
|
||
| Named Selectors | ||
| ~~~~~~~~~~~~~~~ | ||
| ``TraversableElement::findLink`` | ||
| Looks for a link with the given text, title, id or ``alt`` attribute | ||
| (for images used inside links). | ||
|
|
||
| Named selectors provide a way to get named XPath queries: | ||
| ``TraversableElement::findButton`` | ||
| Looks for a button with the given text, title, id, ``name`` attribute | ||
| or ``alt`` attribute (for images used inside links). | ||
|
|
||
| .. code-block:: php | ||
| ``TraversableElement::findField`` | ||
| Looks for a field (``input``, ``textarea`` or ``select``) with the given | ||
| label, placeholder, id or ``name`` attribute. | ||
|
|
||
| .. note:: | ||
|
|
||
| $selector = new \Behat\Mink\Selector\NamedSelector(); | ||
| $handler = new \Behat\Mink\Selector\SelectorsHandler(array( | ||
| 'named' => $selector | ||
| )); | ||
|
|
||
| // XPath query to find the fieldset: | ||
| $xpath1 = $selector->translateToXPath( | ||
| array('fieldset', 'id|legend') | ||
| ); | ||
| $xpath1 = $handler->selectorToXpath('named', | ||
| array('fieldset', 'id|legend') | ||
| ); | ||
|
|
||
| // XPath query to find the field: | ||
| $xpath2 = $selector->translateToXPath( | ||
| array('field', 'id|name|value|label') | ||
| ); | ||
| $xpath2 = $handler->selectorToXpath('named', | ||
| array('field', 'id|name|value|label') | ||
| ); | ||
|
|
||
| There's whole lot more named selectors for you to use: | ||
|
|
||
| * ``link`` - for searching a link by its href, id, title, img alt or value | ||
| * ``button`` - for searching a button by its name, id, value, img alt or | ||
| title | ||
| * ``link_or_button`` - for searching for both, links and buttons | ||
| * ``content`` - for searching a specific page content (text) | ||
| * ``select`` - for searching a select field by its id, name or label | ||
| * ``checkbox`` - for searching a checkbox by its id, name, or label | ||
| * ``radio`` - for searching a radio button by its id, name, or label | ||
| * ``file`` - for searching a file input by its id, name, or label | ||
| * ``optgroup`` - for searching optgroup by its label | ||
| * ``option`` - for searching an option by its content | ||
| * ``table`` - for searching a table by its id or caption | ||
|
|
||
| CSS Selectors | ||
| ~~~~~~~~~~~~~ | ||
|
|
||
| With ``Selector\CssSelector``, you can use CSS expressions to search page | ||
| elements: | ||
| These shortcuts are returning a single element. If you need to find all | ||
| matches, you will need to use ``findAll`` with the :ref:`named selector <named-selector>`. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's not entirely true. Yes, then find/findAll method needs to be used, but which selector to use is up to user. I'd prefer Then in that section we can mentioned, that we have Tricky part is that user have no way of registering his custom selector since Mink 2.0.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These shortcuts are all about the named selector. And for Mink 2.0, you can register your custom selector at instantiation time. |
||
|
|
||
| Nested Traversing | ||
| ~~~~~~~~~~~~~~~~~ | ||
|
|
||
| Every ``find*()`` method will return a ``Behat\Mink\Element\NodeElement`` instance | ||
| and ``findAll()`` will return an array of such instances. The fun part is | ||
| that you can make same old traversing on such elements as well: | ||
|
|
||
| .. code-block:: php | ||
|
|
||
| $selector = new \Behat\Mink\Selector\CssSelector(); | ||
| $handler = new \Behat\Mink\Selector\SelectorsHandler(array( | ||
| 'css' => $selector | ||
| )); | ||
| $registerForm = $page->find('css', 'form.register'); | ||
|
|
||
| // XPath query to find the link by ID: | ||
| $xpath1 = $selector->translateToXPath('a#ID'); | ||
| $xpath1 = $handler->selectorToXpath('css', 'a#ID'); | ||
| if (null === $registerForm) { | ||
| throw new \Exception('The element is not found'); | ||
| } | ||
|
|
||
| XPath Selectors | ||
| ~~~~~~~~~~~~~~~ | ||
| // find some field INSIDE form with class="register" | ||
| $field = $registerForm->findField('Email'); | ||
|
|
||
| .. _selectors: | ||
|
|
||
| And of course, you can use clean XPath queries: | ||
| Selectors | ||
| --------- | ||
|
|
||
| The ``ElementInterface::find`` and ``ElementInterface::findAll`` methods | ||
| support several kinds of selectors to find elements. | ||
|
|
||
| CSS Selector | ||
| ~~~~~~~~~~~~ | ||
|
|
||
| The ``css`` selector type lets you use CSS expressions to search for elements | ||
| on the page: | ||
|
|
||
| .. code-block:: php | ||
|
|
||
| $xpath = $handler->selectorToXpath('xpath', '//html'); | ||
| $title = $page->find('css', 'h1'); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding another example with |
||
|
|
||
| It's like a proxy method, which will return the same expression you give | ||
| to it. It's used internally in `find* methods`_. | ||
| $buttonIcon = $page->find('css', '.btn > .icon'); | ||
|
|
||
| ``find*`` Methods | ||
| ~~~~~~~~~~~~~~~~~ | ||
| XPath Selector | ||
| ~~~~~~~~~~~~~~ | ||
|
|
||
| So, now we know how to generate XPath queries for specific elements search. | ||
| But how we actually make this search? The answer is ``find*`` methods, | ||
| available on ``DocumentElement`` object. You can get this object from session: | ||
| The ``xpath`` selector type lets you use XPath queries to search for elements | ||
| on the page: | ||
|
|
||
| .. code-block:: php | ||
|
|
||
| $page = $session->getPage(); | ||
| $page = $mink->getSession('sahi')->getPage(); | ||
| $anchorsWithoutUrl = $page->findAll('xpath', '//a[not(@href)]'); | ||
|
|
||
| This object provides two very useful traversing methods: | ||
| .. caution:: | ||
|
|
||
| * ``find()`` - evaluates specific selector on the page content and returns | ||
| the last matched element or ``null``: | ||
| This selector searches for an element inside the current node (which | ||
| is ``<html>`` for the page object). This means that trying to pass it | ||
| the XPath of and element retrieved with ``ElementInterface::getXpath`` | ||
| will not work (this query includes the query for the root node). To check | ||
| whether an element object still exists on the browser page, use ``ElementInterface::isValid`` | ||
| instead. | ||
|
|
||
| .. code-block:: php | ||
| .. _named-selector: | ||
|
|
||
| $fieldElement = $page->find('named', | ||
| array('field', 'id|name|value|label') | ||
| ); | ||
| $elementByCss = $page->find('css', 'h3 > a'); | ||
|
|
||
| * ``findAll()`` - evaluates specific selector on the page content and returns | ||
| an array of matched elements: | ||
| Named Selectors | ||
| ~~~~~~~~~~~~~~~ | ||
|
|
||
| .. code-block:: php | ||
| Named selectors provide a set of reusable queries for common needs. For conditions | ||
| based on the content of elements, the named selector will try to find an | ||
| exact match first. It will then fallback to partial matching in case there | ||
| is no result for the exact match. The ``named_exact`` selector type can be | ||
| used to force using only exact matching. The ``named_partial`` selector type | ||
| can be used to apply partial matching without preferring exact matches. | ||
|
|
||
| $fieldElements = $page->findAll('named', | ||
| array('field', 'id|name|value|label') | ||
| ); | ||
| $elementsByCss = $page->findAll('css', 'h3 > a'); | ||
| For the named selector type, the second argument of the ``find()`` method | ||
| is an array with 2 elements: the name of the query to use and the value to | ||
| search with this query: | ||
|
|
||
| Also, there's a bunch of shortcut methods: | ||
| .. code-block:: php | ||
|
|
||
| * ``findById()`` - will search for an element by its ID | ||
| * ``findLink()`` - will search for a link with ``link`` named selector | ||
| * ``findButton()`` - will search for a button with ``button`` named selector | ||
| * ``findField()`` - will search for a field with ``field`` named selector | ||
| $escapedValue = $session->getSelectorsHandler()->xpathLiteral('Go to top'); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
And we need to use
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to decide whether we write the doc for Mink 1.6 or for Mink 1.7 here (or whether we create 2 branches in the doc). This chapter is currently written for Mink 1.6, which is why it uses this method. Note that I intentionally use |
||
|
|
||
| $topLink = $page->find('named', array('link', $escapedValue); | ||
|
|
||
| .. caution:: | ||
|
|
||
| The named selector requires escaping the value as XPath literal. Otherwise | ||
| the generated XPath query will be invalid. | ||
|
|
||
| The following queries are supported by the named selector: | ||
|
|
||
| ``id`` | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I guess we should mentioned version number, when particular feature was introduced and when it will be deprecated in the documentation, like it's done in Symfony docs.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is using a definition list in the output ( Regarding version where it has been added, I agree for Mink 1.7+ features. However, I'm not sure it makes sense to add it for features which in Mink 1.6 (it has been released 5 months ago, and there was no uptodate doc for 1.4 or 1.5 at all). What do you think
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess many people still use Mink 1.5 and when they see in documentation the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, we have lots of changes between Mink 1.5 and 1.6 in the way function works (because they were not defined in a consistent way in Mink 1.5 because of the crappy driver tests). So making the doc highlight the changes from Mink 1.5 to 1.6 might make our job harder (and we are already missing lots of doc)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not talking about all documentation. Just where new functionality (not bugfixes) were introduced in 1.6 version we need to highlight that.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The versionadded directive is not styled by the RTD theme currently. See readthedocs/sphinx_rtd_theme#99
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wow. I wasn't aware, that there is a special directive for that. I was just thinking about something like |
||
| Searches for an element by its id. | ||
| ``id_or_name`` | ||
| Searches for an element by its id or name. | ||
| ``link`` | ||
| Searches for a link by its id, title, img alt, rel or text. | ||
| ``button`` | ||
| Searches for a button by its name, id, text, img alt or title. | ||
| ``link_or_button`` | ||
| Searches for both links and buttons. | ||
| ``content`` | ||
| Searches for a specific page content (text). | ||
| ``field`` | ||
| Searches for a form field by its id, name, label or placeholder. | ||
| ``select`` | ||
| Searches for a select field by its id, name or label. | ||
| ``checkbox`` | ||
| Searches for a checkbox by its id, name, or label. | ||
| ``radio`` | ||
| Searches for a radio button by its id, name, or label. | ||
| ``file`` | ||
| Searches for a file input by its id, name, or label. | ||
| ``optgroup`` | ||
| Searches for an optgroup by its label. | ||
| ``option`` | ||
| Searches for an option by its content or value. | ||
| ``fieldset`` | ||
| Searches for a fieldset by its id or legend. | ||
| ``table`` | ||
| Searches for a table by its id or caption. | ||
|
|
||
| Custom Selector | ||
| ~~~~~~~~~~~~~~~ | ||
|
|
||
| Nested Traversing | ||
| ~~~~~~~~~~~~~~~~~ | ||
| Mink lets you register your own selector types through implementing the ``Behat\Mink\Selector\SelectorInterface``. | ||
| It should then be registered in the ``SelectorsHandler`` which is the registry | ||
| of available selectors. | ||
|
|
||
| Every ``find*()`` method will return ``Behat\Mink\Element\NodeElement`` instance | ||
| and ``findAll()`` will return an array of such instances. The fun part is | ||
| you can make same old traversing on such elements too: | ||
| The recommended way to register a custom selector is to do it when building | ||
| your ``Session``: | ||
|
|
||
| .. code-block:: php | ||
|
|
||
| $registerForm = $page->find('css', 'form.register'); | ||
| $selector = new \App\MySelector(); | ||
|
|
||
| // find some field INSIDE form with class="register" | ||
| $field = $registerForm->findField('id|name|value|label'); | ||
| $handler = new \Behat\Mink\Selector\SelectorsHandler(); | ||
| $handler->registerSelector('mine', $selector); | ||
|
|
||
| $driver = // ... | ||
|
|
||
| $session = new \Behat\Mink\Session($driver, $handler); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If table is used here, then it will surely have horizontal scrollbar. Maybe ul/li approach will look better.