Co naprawdę oznacza „wtedy” w CasperJS

97

Używam CasperJS do automatyzacji serii kliknięć, wypełnionych formularzy, analizowania danych itp. Za pośrednictwem strony internetowej.

Wygląda na to, że Casper jest uporządkowany na liście wstępnie ustawionych kroków w postaci theninstrukcji (zobacz ich przykład tutaj: http://casperjs.org/quickstart.html ), ale nie jest jasne, co powoduje, że następna instrukcja faktycznie działa.

Na przykład, czy thenczeka na zakończenie wszystkich oczekujących żądań? Czy injectJSliczy się jako oczekujące żądanie? Co się stanie, jeśli mam theninstrukcję zagnieżdżoną - połączoną łańcuchem na końcu openinstrukcji?

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

Szukam technicznego wyjaśnienia, jak działa przepływ w CasperJS. Mój specyficzny problem polega na tym, że moje ostatnie thenoświadczenie (powyżej) jest uruchamiane przed moim casper.openoświadczeniem i nie wiem dlaczego.

bendytree
źródło
1
Nadal szukam wyjaśnienia ogólnych flowzasad casperjs, ale odkryłem, że w zasadzie nie możesz odwoływać się do caspera z poziomu evaluatepołączenia. (tzn. nie możesz otworzyć nowego adresu URL, dziennika, echa itp.). Tak więc w moim przypadku wywołano ocenę, ale bez możliwości interakcji ze światem zewnętrznym.
bendytree
1
Zastanawiałem się dokładnie nad tym samym, ale zbyt leniwy, żeby o to zapytać. Dobre pytanie!
Nathan,
4
evaluate()dotyczy kodu uruchamianego w „przeglądarce”, w modelu DOM strony, którą przegląda phantomjs. Więc nie ma casper.open, ale może istnieć jQuery. Więc twój przykład nie ma sensu, ale wciąż zastanawiam się, co then()tak naprawdę działa.
Nathan,

Odpowiedzi:

93

then()po prostu dodaje nowy krok nawigacji w stosie. Krok to funkcja javascript, która może zrobić dwie różne rzeczy:

  1. oczekiwanie na poprzedni krok - jeśli taki istnieje - na wykonanie
  2. czekam na załadowanie żądanego adresu URL i powiązanej strony

Weźmy prosty scenariusz nawigacji:

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

Możesz wydrukować wszystkie utworzone kroki w stosie w następujący sposób:

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

To daje:

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

Zwróć uwagę na _step()funkcję, która została dodana automatycznie przez CasperJS, aby załadować adres URL dla nas; po załadowaniu adresu URL step3()następuje wywołanie kolejnego kroku dostępnego na stosie - którym jest -.

Po zdefiniowaniu kroków nawigacji run()wykonuje je po kolei:

casper.run();

Przypis: funkcja callback / listener jest implementacją wzorca Promise .

NiKo
źródło
W casperjs 1.0.0-RC1 „test-steps.js” wyświetla kolekcję [obiektu DOMWindow] zamiast zbioru ciągów definicji funkcji.
starlocke
Kolekcja [obiekt DOMWindow] nadal jest wynikiem w wersji 1.0.0-RC4; Zastanawiam się, dokąd poszły te definicje funkcji ...
starlocke
1
Początkowo myślałem, że CasperJS robi nową sztuczkę konwertowania funkcji do DOMWindows, ale problem był naprawdę „return this.toString ()” vs „return step.toString ()” - przesłałem edycję odpowiedzi.
starlocke
5
Czy tak zwany „stos” nie jest faktycznie kolejką? Kroki są wykonywane w kolejności, gdyby był to stos, czy nie oczekiwalibyśmy kroku 3, kroku 2, kroku 1?
Reut Sharabani
1
Myślę, że musi wyglądać tak: masz stos stopni. Robisz krok i oceniasz to. Tworzysz pustą kolejkę. Wszelkie kroki wygenerowane w wyniku przetwarzania bieżącego kroku są umieszczane w tej kolejce. Kiedy krok zakończy obliczanie, wszystkie wygenerowane kroki w kolejce są umieszczane na szczycie stosu, ale zachowując ich kolejność w kolejce. (To samo, co wpychanie na stos w odwrotnej kolejności).
Mark
33

then() rejestruje jedynie serię kroków.

run() i jego rodzina funkcji uruchamiających, wywołań zwrotnych i słuchaczy jest tym, co faktycznie wykonuje pracę na każdym kroku.

Ilekroć krokiem jest zakończona, CasperJS sprawdzi przeciwko 3 flagi: pendingWait, loadInProgress, i navigationRequested. Jeśli którakolwiek z tych flag jest prawdziwa, nie rób nic, idź bezczynnie do późniejszego czasu ( setIntervalstyl). Jeśli żadna z tych flag nie jest prawdziwa, zostanie wykonany następny krok.

Począwszy od CasperJS 1.0.0-RC4, istnieje błąd, w którym w pewnych okolicznościach zależnych od czasu metoda „spróbuj zrobić następny krok” zostanie wyzwolona, ​​zanim CasperJS zdąży podnieść jedną z flag loadInProgresslub navigationRequested. Rozwiązaniem jest podniesienie jednej z tych flag przed opuszczeniem jakiegokolwiek kroku, na którym te flagi mają zostać podniesione (np .: podniesienie flagi przed lub po zapytaniu o a casper.click()), może tak:

(Uwaga: to jest tylko ilustracyjne, bardziej jak psuedokod niż właściwy formularz CasperJS ...)

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

Aby zawrzeć to rozwiązanie w pojedynczym wierszu kodu, wprowadziłem blockStep()w tym żądaniu pull z github , rozszerzając click()i clickLabel()jako sposób na zagwarantowanie, że uzyskamy oczekiwane zachowanie podczas używania then(). Sprawdź prośbę o więcej informacji, wzorce użytkowania i minimalną liczbę plików testowych.

starlocke
źródło
1
bardzo pomocny i wspaniały wgląd i sugestia na temat blockStep, IMHO
Brian M. Hunt
Wciąż dyskutujemy o rozwiązaniu „ostatecznej odpowiedzi”… Mam nadzieję, że kiedy zaimplementuję aspekt „globalnych ustawień domyślnych”, CasperJS zrobi wszystko.
starlocke
1
Więc tak, miej oko na to. :)
starlocke
Czy mamy na to jakieś rozwiązanie? jeśli tak, co to jest?
Surender Singh Malik
Dziękuję bardzo za wyjaśnienie. To zachowanie zabijało mnie od ponad roku, ponieważ moje testy funkcjonalne Caspera dla aplikacji obciążonej Ajaxem przez cały czas losowo kończą się niepowodzeniem.
brettjonesdev
0

Zgodnie z dokumentacją CasperJS :

then()

Podpis: then(Function then)

Ta metoda jest standardowym sposobem dodawania nowego kroku nawigacji do stosu, zapewniając prostą funkcję:

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

Możesz dodać tyle kroków, ile potrzebujesz. Zwróć uwagę, że bieżąca Casperinstancja automatycznie wiąże thissłowo kluczowe w funkcjach krokowych.

Aby uruchomić wszystkie zdefiniowane kroki, wywołaj run()metodę i voila.

Uwaga:start() aby użyć tej then()metody, musisz mieć instancję casper .

Ostrzeżenie: dodane funkcje kroków then()są przetwarzane w dwóch różnych przypadkach:

  1. kiedy funkcja poprzedniego kroku została wykonana,
  2. kiedy poprzednie główne żądanie HTTP zostało wykonane i strona załadowana ;

Zauważ, że nie ma jednej definicji załadowanej strony ; czy to kiedy zdarzenie DOMReady zostało wywołane? Czy chodzi o „zakończenie wszystkich żądań”? Czy jest to „wykonywana jest cała logika aplikacji”? A może „renderowane są wszystkie elementy”? Odpowiedź zawsze zależy od kontekstu. Dlatego zachęcamy do korzystania z waitFor()rodzinnych metod, aby zachować wyraźną kontrolę nad tym, czego faktycznie oczekujesz.

Typową sztuczką jest użycie waitForSelector():

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

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

casper.run();

Za kulisami poniżej przedstawiono kod źródłowyCasper.prototype.then :

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

Wyjaśnienie:

Innymi słowy, then()planuje następny krok w procesie nawigacji.

Gdy then()jest wywoływana, przekazywana jest funkcja jako parametr, który ma zostać wywołany jako krok.

Sprawdza, czy wystąpienie zostało uruchomione, a jeśli tak się nie stało, wyświetla następujący błąd:

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

Następnie sprawdza, czy pageobiekt jest null.

Jeśli warunek jest spełniony, Casper tworzy nowy pageobiekt.

Następnie then()sprawdza poprawność stepparametru, aby sprawdzić, czy nie jest to funkcja.

Jeśli parametr nie jest funkcją, wyświetla następujący błąd:

CasperError: You can only define a step as a function

Następnie funkcja sprawdza, czy Casper działa.

Jeśli Casper nie jest uruchomiony, then()dołącza krok na koniec kolejki.

W przeciwnym razie, jeśli Casper działa, wstawia podetap o poziom głębszy niż w poprzednim kroku.

Na koniec then()funkcja kończy się wyemitowaniem step.addedzdarzenia i zwraca obiekt Casper.

Grant Miller
źródło