Mink, Sahi et un gros DOM sont dans un bateau

… le bateau coule !

tl;dr > Voir directement la pull request

Ça fait environ deux semaines que nous utilisons Behat pour élargir notre couverture de tests fonctionnels sur notre application, et je dois dire qu’on est quand même très content, c’est facile, ludique et relativement stable. Pour donner une idée on en est à 30 scénarios, environ 400 steps et une douzaine de comportements persos.

Seulement sur certaines pages très lourdes, on a eu presque aléatoirement quelques problèmes quand on utilisait Mink avec Sahi

Avec Sahi (aucun problème avec Goutte) dès qu’on tentait de faire une manipulation quelconque du DOM, un clic ou n’importe quoi on se faisait jeter comme des malpropres, et l’affichage du contenu du DOM était pour le moins étrange…

Feature: la feature du gros DOM
  Mais pourquoi mon DOM ne s'affiche pas ???

  @javascript
  Scenario: Alors là ça devrait afficher mon DOM                        
    Given I am  on "fo.php/foo/bar" 

|  http://gpillet-desktop:120/fo.php/foo/bar
|  
|  <html>
|  
|  </html>

    Then print last response

1 scenario (1 passed)
2 steps (2 passed)
0m8.704s

Alors c’est vrai que notre application web est plutôt complexe, le DOM peut faire plus de 2000 lignes avec des imbrications dans tous les sens. Au début on pensait que c’était un problème de parsing : soit notre DOM était cassé, soit on avait des caractères illégaux. Le premier réflexe est de passer le code au validateur XHTML, et là on ne trouve pas de problème particulier. Ensuite nous avons essayé par dichotomie de trouver l’erreur : échec, en supprimant un bout de code ça fonctionne, en laissant tout sauf le code précedent, ça fonctionne aussi.

Nous en avons donc déduit que c’était un problème de volume.

En poussant un peu l’investigation je suis tombé sur ce code dans src/Behat/Mink/Driver/SahiDriver.php

/**
 * @see     Behat\Mink\Driver\DriverInterface::getContent()
 */
public function getContent()
{
    $html = $this->evaluateScript('document.getElementsByTagName("html")[0].innerHTML');

    $html = preg_replace(array(
        '/<\!--SAHI_INJECT_START--\>.*\<\!--SAHI_INJECT_END--\>/s',
        '/\<script\>\/\*\<\!\[CDATA\[\*\/\/\*----\>\*\/__sahi.*\<\!--SAHI_INJECT_END--\>/s'
    ), '', $html);
    $html = html_entity_decode($html);

    return "<html>\n$html\n</html>";
}

Il s’agit d’enlever de notre DOM du code injecté par Sahi (juste pour ne pas l’afficher ?). J’avais testé le document.getElementsByTagName(« html »)[0].innerHTML dans mon navigateur et obtenu un résultat, j’en ai donc déduit que l’erreur pouvait effectivement venir du preg_replace, je copie donc mon code dans une chaîne et je test ça à la main dans un petit script PHP. Après une petite heure d’arrachage de cheveux je ne comprends toujours pas pourquoi preg_replace me renvoit une chaîne vide alors que mon DOM est bien formé.

Je m’apperçois enfin que preg_last_error() me renvoit un code « 2 » qui correspond à PREG_BACKTRACK_LIMIT_ERROR qui signifie que la limite définie par l’option pcre.backtrack-limit a été atteinte. Et là je me dit…

« Dis donc PHP, si t’arrives pas à exécuter ma RegExp, tu pourrais peut être péter une fatale non ? »

Bon d’accord, preg_replace retourne NULL si il y a eu une erreur, mais bon faut le savoir non ? La solution la plus simple que j’ai trouvé est d’ajouter le flag U PCRE-UNGREEDY (non gourmand) aux deux expressions régulières ce qui baisse considérablement leurs consommation, ça tombe bien, dans ce cas précis ça suffit largement.

Vous pouvez voir la pull request sur le dépot Github de Mink.

 

Serv-Tech

 

Laisser un commentaire