El objeto Crawler en testing en Symfony

El objeto Crawler permite recorrer documentos HTML o XML, seleccionar nodos, y encontrar enlaces y fomularios

Contenido modificable

Si ves errores o quieres modificar/añadir contenidos, puedes crear un pull request. Gracias

Una instancia de Crawler es devuelta cada vez que se hace un request con Client. El objeto Crawler permite recorrer documentos HTML o XML, seleccionar nodos, y encontrar enlaces y fomularios.

Traversing

Como jQuery, el Crawler tiene métodos para recorrer el DOM de un documento HTML/XML. El siguiente ejemplo encuentra todos los elementos input[type=submit], obtiene el último en la página y selecciona su elemento padre inmediato:

$newCrawler = $crawler->filter('input[type=submit]')
    ->last()
    ->parents()
    ->first()
;

Hay muchos otros métodos disponibles:

filter('h1.title'). Nodos que coinciden con un selector CSS. filterXpath('h1'). Nodos que coinciden con una expresión XPath. eq(1). Nodo para el index especificado. first(). Primer nodo. last(). Ultimo nodo. siblings(). Hermanos. nextAll(). Todos los hermanos siguientes. previousAll(). Todos los hermanos anteriores. parents(). Devuelve los nodo madre. children(). Devuelve los nodos hijo. reduce($lambda). Nodos para los cuales el callable no devuelve false.

Ya que cada uno de estos métodos devuelve una instancia Crawler nueva, puedes especificar la selección del nodo encadenando los métodos:

$crawler
    ->filter('h1')
    ->reduce(function ($node, $i) {
        if (!$node->getAttribute('class')) {
            return false;
        }
    })
    ->first()
;

Puedes usar la función count() para obtener el número de nodos guardados en un Crawler: count($crawler).

Extraer información

El Crawler puede extraer información de los nodos:

// Devuelve el valor del atributo del primer nodo
$crawler->attr('class');

// Devuelve el valor del nodo del primer nodo
$crawler->text();

// Extrae un array de atributos de todos los nodos
// (_text devuelve el valor del nodo)
// devuelve un array para cada elemento del crawler,
// cada uno con su valor y href
$info = $crawler->extract(array('_text', 'href'));

// Ejecuta un lambda para cada nodo y devuelve un array de resultados
$data = $crawler->each(function ($node, $i) {
    return $node->attr('href');
});

Enlaces

Para seleccionar enlaces puedes usar los métodos de recorrido anteriores o el shorcut selectLink():

$crawler->selectLink('Click here');

Esto selecciona todos los enlaces que contienen el texto dado, o imágenes clickables en las cuales el atributo alt contiene el texto dado. Como los otros métodos de filtrado, esto devuelve otro objeto Crawler.

Una vez que has seleccionado un enlace, tienes acceso a un objeto especial Link, que tiene métodos de ayuda específicos para enlaces (como getMethod() y getUri()). Para hacer click en el link, utiliza el método del cliente click() y pásale un objeto Link:

$link = $crawler->selectLink('Click here')->link();
$client->click($link);

Formularios

Los formularios pueden ser seleccionados empleando sus botones, que pueden seleccionarse con el método selectButton(), como los enlaces:

$buttonCrawlerNode = $crawler->selectButton('submit');

Fíjate que seleccionas botones y no formularios ya que un formulario puede tener varios botones. Si recorres la API, ten en cuenta que debes buscar un botón.

El método selectButton() puede seleccionar etiquetas button y enviar etiquetas input. Utiliza varias partes de los botones para encontrarlas:

  • El valor del atributo value.
  • El valor de los atributos id o alt para imágenes.
  • El valor de los atributos id o name para etiquetas button.

Una vez que tienes un Crawler representando un botón, puedes llamar al método form() para obtener una instancia Form del formulario que envuelve el nodo del botón:

$form = $buttonCrawlerNode->form();

Cuando llamamos al método form(), podemos también pasar un array de valores de campos que sobreescriben a los que están por defecto:

$form = $buttonCrawlerNode->form(array(
    'name'              => 'Fabien',
    'my_form[subject]'  => 'Symfony rocks!',
));

Si quieres simular un método HTTP específico para el formulario, pásalo como segundo argumento:

$form = $buttonCrawlerNode->form(array(), 'DELETE');

Client puede crear instancias de Form:

$client->submit($form);

Los valores de los campos pueden también pasarse en el segundo argumento del método submit():

$client->submit($form, array(
    'name'              => 'Fabien',
    'my_form[subject]'  => 'Symfony rocks!',
));

Para situaciones más complejas, utiliza la instancia Form como un array para establecer el valor de cada campo individualmente:

// Cambiar el valor de un campo
$form['name'] = 'Fabien';
$form['my_form[subject]'] = 'Symfony rocks!';

Hay también una API para manipular los valores de los campos según sus tipos:

// Selecciona una opción o un radio
$form['country']->select('France');

// Selecciona una checkbox
$form['like_symfony']->tick();

// Sube un archivo
$form['photo']->upload('/path/to/lucas.jpg');

Puedes también seleccionar valores inválidos.

Puedes obtener los valores que serán enviados llamando al método getValues() en el objeto Form. Los archivos subidos están disponibles en un array separado devuelto por getFiles(). Los métodos getPhpValues() y getPhpFiles() también devuelven los valores enviados, pero en formato PHP (convierte los keys con corchetes en arrays de PHP).

Añadir y eliminar formularios a una Collection

Si empleas una Collection de formularios, no puedes añadir campos a un formulario existente con $form['task[tags][0][name]'] = 'foo';. Esto devuelve un error Unreachable field "..." porque $form sólo puede usarse para establecer valores de campos existentes. Para añadir nuevos campos, tienes que añadir los valores al array:

// Obtiene el formulario
$form = $crawler->filter('button')->form();

// Obtiene los valores
$values = $form->getPhpValues();

// Añade campos a los valores
$values['task']['tag'][0]['name'] = 'foo';
$values['task']['tag'][1]['name'] = 'bar';

// Envía el formualrio con los campos nuevos y existentes
$crawler = $this->client->request($form->getMethod(), $form->getUri(), $values,
    $form->getPhpFiles());

// Las dos etiquetas se han añadido a la collection
$this->assertEquals(2, $crawler->filter('ul.tags > li')->count());

Donde task[tags][0][name] es el nombre de un campo creado con JavaScript.

Puedes eliminar un campo existente, por ejemplo una etiqueta:

// Obtener los valores del formualrio
$values = $form->getPhpValues();

// Eliminar la primera etiqueta
unset($values['task']['tags'][0]);

// Enviar los datos
$crawler = $client->request($form->getMethod(), $form->getUri(),
    $values, $form->getPhpFiles());

// La etiqueta se ha eliminado
$this->assertEquals(0, $crawler->filter('ul.tags > li')->count());