Was bedeutet "dann" in CasperJS wirklich?

97

Ich verwende CasperJS, um eine Reihe von Klicks, ausgefüllten Formularen, Parsing-Daten usw. über eine Website zu automatisieren.

Casper scheint in einer Liste voreingestellter Schritte in Form von thenAnweisungen organisiert zu sein (siehe Beispiel hier: http://casperjs.org/quickstart.html ), aber es ist unklar, was die nächste Anweisung tatsächlich auslöst.

thenWarten Sie beispielsweise, bis alle ausstehenden Anforderungen abgeschlossen sind? Zählt dies injectJSals ausstehende Anfrage? Was passiert, wenn ich eine thenAnweisung verschachtelt habe - verkettet an das Ende einer openAnweisung?

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

Ich suche nach einer technischen Erklärung, wie der Fluss in CasperJS funktioniert. Mein spezifisches Problem ist, dass meine letzte thenAussage (oben) vor meiner casper.openAussage läuft und ich nicht weiß warum.

Bendytree
quelle
1
Ich bin immer noch auf der Suche nach einer Erklärung für den General flowvon casperjs, aber ich habe festgestellt, dass Sie casper im Grunde nicht innerhalb eines evaluateAnrufs referenzieren können . (dh Sie können keine neue URL, kein neues Protokoll, kein neues Echo usw. öffnen). In meinem Fall wurde also evalu aufgerufen, aber es gab keine Möglichkeit, mit der Außenwelt zu interagieren.
Bendytree
1
Ich habe mich genau die gleichen Dinge gefragt, aber zu faul, um sie zu fragen. Gute Frage!
Nathan
4
evaluate()ist für Code, der im "Browser" ausgeführt wird, im DOM der Seite, die phantomjs durchsucht. Es gibt also keine casper.open, aber es könnte jQuery geben. Ihr Beispiel macht also keinen Sinn, aber ich frage mich immer noch, was then()tatsächlich funktioniert.
Nathan

Antworten:

93

then()fügt im Grunde einen neuen Navigationsschritt in einen Stapel ein. Ein Schritt ist eine Javascript-Funktion, die zwei verschiedene Dinge tun kann:

  1. Warten auf die Ausführung des vorherigen Schritts (falls vorhanden)
  2. Warten auf das Laden einer angeforderten URL und einer zugehörigen Seite

Nehmen wir ein einfaches Navigationsszenario:

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

Sie können alle erstellten Schritte innerhalb des Stapels wie folgt ausdrucken:

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

Das gibt:

$ 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)'); }"
]

Beachten Sie die _step()Funktion, die von CasperJS automatisch hinzugefügt wurde, um die URL für uns zu laden. Wenn die URL geladen wird, wird der nächste im Stapel verfügbare Schritt step3()aufgerufen.

Wenn Sie Ihre Navigationsschritte definiert haben, führen Sie run()sie nacheinander aus:

casper.run();

Fußnote: Das Callback / Listener-Zeug ist eine Implementierung des Promise-Musters .

NiKo
quelle
In casperjs 1.0.0-RC1 zeigt "test-steps.js" eine Auflistung von [Objekt DOMWindow] anstelle einer Auflistung von Funktionsdefinitionszeichenfolgen an.
Starlocke
Die Auflistung [object DOMWindow] ist weiterhin das Ergebnis in 1.0.0-RC4. Ich frage mich, wohin diese Funktionsdefinitionen gingen ...
Starlocke
1
Anfangs dachte ich, dass CasperJS einen neuen Trick beim Konvertieren von Funktionen in DOMWindows ausführt, aber das Problem war wirklich "return this.toString ()" vs "return step.toString ()" - Ich habe eine Bearbeitung für die Antwort eingereicht.
Starlocke
5
Ist der sogenannte "Stapel" nicht tatsächlich eine Warteschlange? Die Schritte werden der Reihe nach ausgeführt. Wäre es ein Stapel gewesen, würden wir nicht Schritt 3, Schritt 2, Schritt 1 erwarten?
Reut Sharabani
1
Ich denke, es muss so sein: Sie haben einen Stapel von Schritten. Sie springen von einem Schritt ab und bewerten ihn. Sie erstellen eine leere Warteschlange. Alle Schritte, die aufgrund der Verarbeitung des aktuellen Schritts generiert werden, werden in diese Warteschlange gestellt. Wenn die Auswertung des Schritts abgeschlossen ist, werden alle generierten Schritte in der Warteschlange auf den Stapel gelegt, wobei die Reihenfolge in der Warteschlange beibehalten wird. (Das gleiche wie in umgekehrter Reihenfolge auf den Stapel drücken).
Mark
33

then() registriert lediglich eine Reihe von Schritten.

run() und seine Familie von Runner-Funktionen, Rückrufen und Listenern ist alles, was die Arbeit zur Ausführung jedes Schritts tatsächlich erledigt.

Jedes Mal , wenn ein Schritt abgeschlossen ist, wird CasperJS überprüfen gegen 3 - Flags: pendingWait, loadInProgress, und navigationRequested. Wenn eines dieser Flags wahr ist, tun Sie nichts und gehen Sie bis zu einem späteren Zeitpunkt in den Leerlauf ( setIntervalStil). Wenn keines dieser Flags wahr ist, wird der nächste Schritt ausgeführt.

Ab CasperJS 1.0.0-RC4 liegt ein Fehler vor, bei dem unter bestimmten zeitbasierten Umständen die Methode "Nächsten Schritt versuchen" ausgelöst wird, bevor CasperJS Zeit hatte, eines der Flags loadInProgressoder eines der navigationRequestedFlags zu setzen. Die Lösung besteht darin, eines dieser Flags zu hissen, bevor Sie einen Schritt verlassen, in dem diese Flags voraussichtlich gehisst werden (z. B. ein Flag entweder vor oder nach dem Anfordern eines casper.click()), wie folgt:

(Hinweis: Dies ist nur zur Veranschaulichung, eher wie Pseudocode als wie die richtige CasperJS-Form ...)

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()
}

Um diese Lösung in einer einzigen Codezeile zusammenzufassen, habe ich blockStep()diese Github- Pull-Anforderung erweitert , erweitert click()und clickLabel()als Mittel, um sicherzustellen , dass wir das erwartete Verhalten bei der Verwendung erhalten then(). Weitere Informationen, Verwendungsmuster und Mindesttestdateien finden Sie in der Anfrage.

Starlocke
quelle
1
sehr hilfreich und tolle Einblicke und Vorschläge auf blockStep, IMHO
Brian M. Hunt
Wir diskutieren immer noch über die "endgültige Antwort" -Lösung ... Ich hoffe, dass CasperJS nach der Implementierung des Aspekts "globale Standardeinstellungen" den Ausschlag geben wird.
Starlocke
1
Also ja, behalte es im Auge. :)
Starlocke
Haben wir eine Lösung dafür? wenn ja was ist das
Surender Singh Malik
Vielen Dank für die Erklärung. Dieses Verhalten bringt mich seit über einem Jahr um, da meine Casper-Funktionstests für eine Ajax-schwere Anwendung die ganze Zeit zufällig fehlschlagen.
Brettjonesdev
0

Gemäß der CasperJS-Dokumentation :

then()

Unterschrift: then(Function then)

Diese Methode ist die Standardmethode, um dem Stapel einen neuen Navigationsschritt hinzuzufügen, indem eine einfache Funktion bereitgestellt wird:

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

Sie können beliebig viele Schritte hinzufügen. Beachten Sie, dass die aktuelle CasperInstanz das thisSchlüsselwort automatisch innerhalb von Schrittfunktionen für Sie bindet .

Rufen Sie die run()Methode und voila auf, um alle von Ihnen definierten Schritte auszuführen.

Hinweis: Sie müssen start()die Casper-Instanz verwenden, um die then()Methode verwenden zu können.

Warnung: Die hinzugefügten then()Schrittfunktionen werden in zwei verschiedenen Fällen verarbeitet:

  1. wenn die vorherige Schrittfunktion ausgeführt wurde,
  2. wenn die vorherige HTTP-Hauptanforderung ausgeführt und die Seite geladen wurde ;

Beachten Sie, dass keine einzelne Definition der geladenen Seite vorhanden ist . Ist es, wenn das DOMReady-Ereignis ausgelöst wurde? Ist es "alle Anfragen werden erledigt"? Ist es "alle Anwendungslogik wird ausgeführt"? Oder "alle Elemente werden gerendert"? Die Antwort hängt immer vom Kontext ab. Aus diesem Grund sollten Sie immer die waitFor()Familienmethoden verwenden, um die explizite Kontrolle darüber zu behalten, was Sie tatsächlich erwarten.

Ein üblicher Trick ist waitForSelector():

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

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

casper.run();

Hinter den Kulissen wird der Quellcode fürCasper.prototype.then unten gezeigt:

/**
 * 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;
};

Erläuterung:

Mit anderen Worten, then()plant den nächsten Schritt im Navigationsprozess.

Beim then()Aufruf wird eine Funktion als Parameter übergeben, die als Schritt aufgerufen werden soll.

Es wird überprüft, ob eine Instanz gestartet wurde, und wenn dies nicht der Fall ist, wird der folgende Fehler angezeigt:

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

Als nächstes wird geprüft, ob das pageObjekt ist null.

Wenn die Bedingung erfüllt ist, erstellt Casper ein neues pageObjekt.

Danach wird then()überprüft die stepParameter zu überprüfen , ob es nicht eine Funktion ist.

Wenn der Parameter keine Funktion ist, wird der folgende Fehler angezeigt:

CasperError: You can only define a step as a function

Anschließend prüft die Funktion, ob Casper ausgeführt wird.

Wenn Casper nicht ausgeführt wird, then()wird der Schritt an das Ende der Warteschlange angehängt.

Andernfalls fügt Casper, wenn es ausgeführt wird, einen Teilschritt ein, der eine Ebene tiefer als der vorherige Schritt ist.

Schließlich gibt die then()Funktion ein step.addedEreignis aus und gibt das Casper-Objekt zurück.

Grant Miller
quelle