Que signifie vraiment `` alors '' dans CasperJS

97

J'utilise CasperJS pour automatiser une série de clics, de formulaires remplis, d'analyses de données, etc. via un site Web.

Casper semble être organisé en une liste d'étapes prédéfinies sous la forme d' theninstructions (voir leur exemple ici: http://casperjs.org/quickstart.html ) mais on ne sait pas ce qui déclenche réellement la prochaine instruction.

Par exemple, thenattend-il que toutes les demandes en attente se terminent? Est-ce que cela injectJScompte comme une demande en attente? Que se passe-t-il si j'ai une theninstruction imbriquée - enchaînée à la fin d'une openinstruction?

casper.thenOpen('http://example.com/list', function(){
    casper.page.injectJs('/libs/jquery.js');
    casper.evaluate(function(){
        var id = jQuery("span:contains('"+itemName+"')").closest("tr").find("input:first").val();
        casper.open("http://example.com/show/"+id); //what if 'then' was added here?
    });
});

casper.then(function(){
    //parse the 'show' page
});

Je cherche une explication technique du fonctionnement du flux dans CasperJS. Mon problème spécifique est que ma dernière thendéclaration (ci-dessus) s'exécute avant ma casper.opendéclaration et je ne sais pas pourquoi.

bendytree
la source
1
Je cherche toujours une explication du général flowde casperjs, mais j'ai découvert que vous ne pouvez pas faire référence à casper à partir d'un evaluateappel. (c'est-à-dire que vous ne pouvez pas ouvrir une nouvelle URL, un nouveau journal, un écho, etc.). Donc, dans mon cas, evalu a été appelé mais sans moyen d'interagir avec le monde extérieur.
bendytree
1
Je me demandais exactement les mêmes choses mais trop paresseux pour demander. Bonne question!
Nathan
4
evaluate()est pour le code qui s'exécute dans le "navigateur", dans le DOM de la page que phantomjs parcourt. Donc il n'y en a pas casper.open, mais il pourrait y avoir jQuery. Votre exemple n'a donc aucun sens, mais je me demande toujours ce que then()fait réellement.
Nathan

Réponses:

93

then()ajoute essentiellement une nouvelle étape de navigation dans une pile. Une étape est une fonction javascript qui peut faire deux choses différentes:

  1. en attente de l'exécution de l'étape précédente - le cas échéant -
  2. en attente de chargement d'une URL demandée et d'une page associée

Prenons un scénario de navigation simple:

var casper = require('casper').create();

casper.start();

casper.then(function step1() {
    this.echo('this is step one');
});

casper.then(function step2() {
    this.echo('this is step two');
});

casper.thenOpen('http://google.com/', function step3() {
    this.echo('this is step 3 (google.com is loaded)');
});

Vous pouvez imprimer toutes les étapes créées dans la pile comme ceci:

require('utils').dump(casper.steps.map(function(step) {
    return step.toString();
}));

Ça donne:

$ casperjs test-steps.js
[
    "function step1() { this.echo('this is step one'); }",
    "function step2() { this.echo('this is step two'); }",
    "function _step() { this.open(location, settings); }",
    "function step3() { this.echo('this is step 3 (google.com is loaded)'); }"
]

Notez la _step()fonction qui a été ajoutée automatiquement par CasperJS pour charger l'url pour nous; lorsque l'url est chargée, l'étape suivante disponible dans la pile - qui est step3()- est appelée.

Lorsque vous avez défini vos étapes de navigation, les run()exécute une à une de manière séquentielle:

casper.run();

Note de bas de page: les éléments de rappel / d'écoute sont une implémentation du modèle Promise .

NiKo
la source
Dans casperjs 1.0.0-RC1, "test-steps.js" affiche une collection de [objet DOMWindow], au lieu d'une collection de chaînes de définition de fonction.
starlocke
La collection [object DOMWindow] est toujours le résultat dans 1.0.0-RC4; Je me demande où sont allées ces définitions de fonction ...
starlocke
1
J'ai d'abord pensé que CasperJS faisait une nouvelle astuce pour convertir des fonctions en DOMWindows, mais le problème était vraiment "return this.toString ()" vs "return step.toString ()" - j'ai soumis une modification pour la réponse.
starlocke
5
La soi-disant «pile» n'est-elle pas en fait une file d'attente? Les étapes sont exécutées dans l'ordre, s'il s'agissait d'une pile, ne nous attendrions-nous pas à l'étape 3, étape 2, étape 1?
Reut Sharabani
1
Je pense que ça doit être comme ça: vous avez une pile d'étapes. Vous sautez une étape et l'évaluez. Vous créez une file d'attente vide. Toutes les étapes générées en raison du traitement de l'étape en cours sont placées dans cette file d'attente. Une fois l'évaluation de l'étape terminée, toutes les étapes générées dans la file d'attente sont placées au-dessus de la pile, mais en conservant leur ordre dans leur file d'attente. (La même chose que de pousser sur la pile dans l'ordre inverse).
Mark
33

then() enregistre simplement une série d'étapes.

run() et sa famille de fonctions de runner, de rappels et d'écouteurs, sont tous ce qui fait réellement le travail d'exécution de chaque étape.

Chaque fois une étape est terminée, CasperJS vérifieront contre 3 drapeaux: pendingWait, loadInProgresset navigationRequested. Si l'un de ces indicateurs est vrai, ne faites rien, restez inactif jusqu'à une date ultérieure ( setIntervalstyle). Si aucun de ces indicateurs n'est vrai, l'étape suivante sera exécutée.

Depuis CasperJS 1.0.0-RC4, une faille existe, où, dans certaines circonstances temporelles, la méthode "essayer de faire l'étape suivante" sera déclenchée avant que CasperJS n'ait le temps de lever l'un des indicateurs loadInProgressou navigationRequested. La solution est de lever l'un de ces drapeaux avant de quitter toute étape où ces drapeaux devraient être levés (ex: lever un drapeau avant ou après avoir demandé a casper.click()), peut-être comme ceci:

(Remarque: Ceci est seulement illustratif, plus comme psuedocode que le formulaire CasperJS approprié ...)

step_one = function(){
    casper.click(/* something */);
    do_whatever_you_want()
    casper.click(/* something else */); // Click something else, why not?
    more_magic_that_you_like()
    here_be_dragons()
    // Raise a flag before exiting this "step"
    profit()
}

Pour résumer cette solution en une seule ligne de code, j'ai introduit blockStep()dans cette requête d'extraction github , l'extension click()et clickLabel()comme moyen de garantir que nous obtenons le comportement attendu lors de l'utilisation then(). Consultez la demande pour plus d'informations, les modèles d'utilisation et les fichiers de test minimum.

starlocke
la source
1
très utile et excellent aperçu et suggestion sur blockStep, à mon humble avis
Brian M. Hunt
Nous discutons toujours de la solution de "réponse finale" ... J'espère qu'une fois que j'aurai implémenté l'aspect "global defaults", CasperJS fera le pull.
starlocke
1
Alors oui, gardez un œil dessus. :)
starlocke
Avons-nous une solution pour cela? Si oui, qu'est ce que c'est ?
Surender Singh Malik
Merci beaucoup d'avoir expliqué cela. Ce comportement me tue depuis plus d'un an maintenant, car mes tests fonctionnels Casper pour une application lourde Ajax échouent de manière aléatoire tout le temps.
brettjonesdev
0

Selon la documentation CasperJS :

then()

Signature: then(Function then)

Cette méthode est le moyen standard d'ajouter une nouvelle étape de navigation à la pile, en fournissant une fonction simple:

casper.start('http://google.fr/');

casper.then(function() {
  this.echo('I\'m in your google.');
});

casper.then(function() {
  this.echo('Now, let me write something');
});

casper.then(function() {
  this.echo('Oh well.');
});

casper.run();

Vous pouvez ajouter autant d'étapes que nécessaire. Notez que l' Casperinstance actuelle lie automatiquement le thismot - clé pour vous dans les fonctions d'étape.

Pour exécuter toutes les étapes que vous avez définies, appelez la run()méthode et le tour est joué.

Remarque: vous devez start()l'instance casper pour utiliser la then()méthode.

Attention: les fonctions Step ajoutées à then()sont traitées dans deux cas différents:

  1. lorsque la fonction de l'étape précédente a été exécutée,
  2. lorsque la requête HTTP principale précédente a été exécutée et la page chargée ;

Notez qu'il n'y a pas de définition unique de la page chargée ; est-ce lorsque l'événement DOMReady a été déclenché? Est-ce que "toutes les demandes sont terminées"? Est-ce que "toute la logique d'application est exécutée"? Ou "tous les éléments étant rendus"? La réponse dépend toujours du contexte. C'est pourquoi vous êtes encouragé à toujours utiliser les waitFor()méthodes familiales pour garder un contrôle explicite sur ce que vous attendez réellement.

Une astuce courante consiste à utiliser waitForSelector():

casper.start('http://my.website.com/');

casper.waitForSelector('#plop', function() {
  this.echo('I\'m sure #plop is available in the DOM');
});

casper.run();

Dans les coulisses, le code source deCasper.prototype.then est indiqué ci-dessous:

/**
 * Schedules the next step in the navigation process.
 *
 * @param  function  step  A function to be called as a step
 * @return Casper
 */
Casper.prototype.then = function then(step) {
    "use strict";
    this.checkStarted();
    if (!utils.isFunction(step)) {
        throw new CasperError("You can only define a step as a function");
    }
    // check if casper is running
    if (this.checker === null) {
        // append step to the end of the queue
        step.level = 0;
        this.steps.push(step);
    } else {
        // insert substep a level deeper
        try {
            step.level = this.steps[this.step - 1].level + 1;
        } catch (e) {
            step.level = 0;
        }
        var insertIndex = this.step;
        while (this.steps[insertIndex] && step.level === this.steps[insertIndex].level) {
            insertIndex++;
        }
        this.steps.splice(insertIndex, 0, step);
    }
    this.emit('step.added', step);
    return this;
};

Explication:

En d'autres termes, then()planifie l'étape suivante du processus de navigation.

Lorsqu'elle then()est appelée, une fonction lui est transmise en tant que paramètre qui doit être appelée en tant qu'étape.

Il vérifie si une instance a démarré, et si ce n'est pas le cas, il affiche l'erreur suivante:

CasperError: Casper is not started, can't execute `then()`.

Ensuite, il vérifie si l' pageobjet est null.

Si la condition est vraie, Casper crée un nouvel pageobjet.

Après cela, then()valide le stepparamètre pour vérifier s'il ne s'agit pas d'une fonction.

Si le paramètre n'est pas une fonction, il affiche l'erreur suivante:

CasperError: You can only define a step as a function

Ensuite, la fonction vérifie si Casper est en cours d'exécution.

Si Casper n'est pas en cours d'exécution, then()ajoute l'étape à la fin de la file d'attente.

Sinon, si Casper est en cours d'exécution, il insère une sous-étape d'un niveau plus profond que l'étape précédente.

Enfin, la then()fonction se termine en émettant un step.addedévénement et renvoie l'objet Casper.

Grant Miller
la source