O que "então" realmente significa no CasperJS

97

Estou usando o CasperJS para automatizar uma série de cliques, formulários preenchidos, análise de dados, etc. através de um site.

Casper parece ser organizado em uma lista de etapas predefinidas na forma de theninstruções (veja o exemplo deles aqui: http://casperjs.org/quickstart.html ), mas não está claro o que aciona a próxima instrução para realmente ser executada.

Por exemplo, thenaguarda a conclusão de todas as solicitações pendentes? Conta injectJScomo um pedido pendente? O que acontece se eu tiver uma theninstrução aninhada - encadeada ao final de uma openinstrução?

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

Estou procurando uma explicação técnica de como funciona o fluxo no CasperJS. Meu problema específico é que minha última thendeclaração (acima) é executada antes da minha casper.opendeclaração e não sei por quê.

bendytree
fonte
1
Ainda estou procurando uma explicação geral flowsobre casperjs, mas descobri que basicamente você não pode fazer referência a casper em uma evaluatechamada. (ou seja, você não pode abrir um novo url, log, echo, etc). Portanto, no meu caso, a avaliação estava sendo chamada, mas não havia como interagir com o mundo externo.
bendytree
1
Eu estava me perguntando exatamente as mesmas coisas, mas com preguiça de perguntar. Boa pergunta!
Nathan
4
evaluate()é para código que roda no "navegador", no DOM da página que o phantomjs está navegando. Portanto, não há casper.openlá, mas pode haver jQuery. Portanto, seu exemplo não faz sentido, mas ainda me pergunto o que then()realmente faz.
Nathan

Respostas:

93

then()basicamente adiciona uma nova etapa de navegação em uma pilha. Uma etapa é uma função javascript que pode fazer duas coisas diferentes:

  1. aguardando a etapa anterior - se houver - sendo executada
  2. esperando que um url solicitado e uma página relacionada carreguem

Vejamos um cenário de navegação simples:

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

Você pode imprimir todas as etapas criadas dentro da pilha assim:

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

Isso dá:

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

Observe a _step()função que foi adicionada automaticamente pelo CasperJS para carregar a url para nós; quando o url é carregado, a próxima etapa disponível na pilha - que éstep3() - é chamada.

Depois de definir suas etapas de navegação, run()execute-as uma a uma sequencialmente:

casper.run();

Nota de rodapé: o material de callback / listener é uma implementação do padrão Promise .

NiKo
fonte
Em casperjs 1.0.0-RC1, "test-steps.js" exibe uma coleção de [object DOMWindow], em vez de uma coleção de strings de definição de função.
starlocke 01 de
A coleção [object DOMWindow] ainda é o resultado em 1.0.0-RC4; Eu me pergunto para onde essas definições de função foram ...
starlocke 01 de
1
Inicialmente, pensei que o CasperJS estava fazendo um novo truque para converter funções em DOMWindows, mas o problema era realmente "return this.toString ()" vs "return step.toString ()" - enviei uma edição para a resposta.
starlocke 01 de
5
A chamada 'pilha' não é na verdade uma fila? As etapas são executadas em ordem. Se fosse uma pilha, não esperaríamos etapa 3, etapa 2, etapa 1?
Reut Sharabani
1
Acho que deve ser assim: Você tem uma pilha de etapas. Você pula uma etapa e a avalia. Você cria uma fila vazia. Quaisquer etapas geradas devido ao processamento da etapa atual são colocadas nesta fila. Quando a avaliação da etapa termina, todas as etapas geradas na fila são colocadas no topo da pilha, mas preservando sua ordem na fila. (O mesmo que empurrar para a pilha na ordem inversa).
Marcos
33

then() simplesmente registra uma série de etapas.

run() e sua família de funções de execução, retornos de chamada e ouvintes são os que realmente fazem o trabalho de execução de cada etapa.

Sempre que uma etapa é concluída, CasperJS irá verificar contra 3 bandeiras: pendingWait, loadInProgress, e navigationRequested. Se algum desses sinalizadores for verdadeiro, não faça nada, fique ocioso até um momento posterior ( setIntervalestilo). Se nenhum desses sinalizadores for verdadeiro, a próxima etapa será executada.

A partir do CasperJS 1.0.0-RC4, existe uma falha, onde, sob certas circunstâncias baseadas no tempo, o método "tentar fazer a próxima etapa" será acionado antes que o CasperJS tenha tempo para levantar uma das bandeiras loadInProgressou navigationRequested. A solução é levantar uma dessas bandeiras antes de sair de qualquer etapa em que se espera que essas bandeiras sejam levantadas (ex: levante uma bandeira antes ou depois de pedir um casper.click()), talvez assim:

(Nota: Isto é apenas ilustrativo, mais como psuedocode do que a forma CasperJS adequada ...)

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

Para resumir essa solução em uma única linha de código, eu apresentei blockStep()neste github pull request , estendendo click()e clickLabel()como um meio de ajudar a garantir que obteremos o comportamento esperado ao usar then(). Verifique a solicitação para obter mais informações, padrões de uso e arquivos de teste mínimos.

Starlocke
fonte
1
muito útil e ótimo insight e sugestão em blockStep, IMHO
Brian M. Hunt
Ainda estamos discutindo a solução da "resposta final" ... Espero que, assim que implementar o aspecto dos "padrões globais", o CasperJS faça o pull.
starlocke
1
Então, sim, fique de olho nisso. :)
starlocke
Temos alguma solução para isso? se sim o que é?
Surender Singh Malik
Muito obrigado por explicar isso. Esse comportamento está me matando há mais de um ano, pois meus testes funcionais de Casper para um aplicativo pesado com Ajax falham aleatoriamente o tempo todo.
brettjonesdev
0

De acordo com a documentação do CasperJS :

then()

Assinatura: then(Function then)

Este método é a maneira padrão de adicionar uma nova etapa de navegação à pilha, fornecendo uma função simples:

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

Você pode adicionar quantas etapas forem necessárias. Observe que a Casperinstância atual vincula automaticamente a thispalavra - chave para você nas funções de etapa.

Para executar todas as etapas definidas, chame o run()método e voila.

Nota: Você deve start()a instância casper para usar o then()método.

Aviso: as funções de etapa adicionadas a then()são processadas em dois casos diferentes:

  1. quando a função da etapa anterior foi executada,
  2. quando a solicitação HTTP principal anterior foi executada e a página carregada ;

Observe que não há uma definição única de página carregada ; é quando o evento DOMReady foi acionado? É "todos os pedidos sendo concluídos"? É "toda a lógica do aplicativo sendo executada"? Ou "todos os elementos sendo renderizados"? A resposta sempre depende do contexto. Por isso você é encorajado a sempre usar owaitFor() métodos família para manter controle explícito sobre o que você realmente espera.

Um truque comum é usar waitForSelector():

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

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

casper.run();

Nos bastidores, o código-fonte doCasper.prototype.then é mostrado abaixo:

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

Explicação:

Em outras palavras, then() programa a próxima etapa do processo de navegação.

Quando then() é chamado, é passada uma função como parâmetro que deve ser chamada como uma etapa.

Ele verifica se uma instância foi iniciada e, se não foi, exibe o seguinte erro:

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

Em seguida, ele verifica se o pageobjeto é null.

Se a condição for verdadeira, Casper cria um novo pageobjeto.

Após isso, then()valida o stepparâmetro para verificar se não é uma função.

Se o parâmetro não for uma função, ele exibe o seguinte erro:

CasperError: You can only define a step as a function

Então, a função verifica se o Casper está rodando.

Se Casper não estiver funcionando, then() anexa a etapa ao final da fila.

Caso contrário, se Casper estiver em execução, ele insere uma subetapa um nível mais profundo do que a etapa anterior.

Finalmente, a then()função conclui emitindo um step.addedevento e retorna o objeto Casper.

Grant Miller
fonte