Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 154 additions & 116 deletions guides/traversing-pages.rst
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``
Copy link
Copy Markdown
Member

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.

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.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if we should use child word here, because all find methods operate within element they are called on as mentioned above.


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>`.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 ,you will need to use``findAll``with any of [available selectors]-link to available selectors.

Then in that section we can mentioned, that we have named and css build-in selectors with all usage examples and that a custom one can be created by implementing SelectorInterface.

Tricky part is that user have no way of registering his custom selector since Mink 2.0.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding another example with . and > usage might prove useful.


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');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. the xpathLiteral method call will result in E_DEPRICATED notice
  2. in Mink 2.0 we don't expose getSelectorsHandler from Session, which is unfortunate
  3. in Mink 1.7 escaping values before passing them to named selector isn't required anymore

And we need to use Escaper::escapeLiteral method in example, but not that it only is available since Mink 1.6.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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 SelectorsHandler::xpathLiteral in my own code in places using the named selector, making it easier to locate places where escaping needs to be removed when upgrading. I'm using Escaper::escapeLiteral in places where I'm building Xpath.


$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``
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Maybe we should use table notation here or list or unordered list (ul/li) like it was done before: *``id_or_name``- description.
  2. The id and id_or_name selectors are only available since Mink 1.6

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is using a definition list in the output (<dl>). IMO, it looks much more readable than a table here.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 id_or_name, then it would be useful to know, that they should upgrade to Mink 1.6 to use it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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
So using it currently looks ugly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 some feature .... (since 1.5) text.

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);