¿Qué significa realmente 'entonces' en CasperJS

97

Estoy usando CasperJS para automatizar una serie de clics, formularios completados, análisis de datos, etc. a través de un sitio web.

Casper parece estar organizado en una lista de pasos preestablecidos en forma de thendeclaraciones (vea su ejemplo aquí: http://casperjs.org/quickstart.html ) pero no está claro qué desencadena la siguiente declaración para que se ejecute realmente.

Por ejemplo, ¿ thenespera a que se completen todas las solicitudes pendientes? ¿ injectJSCuenta como una solicitud pendiente? ¿Qué sucede si tengo una thendeclaración anidada, encadenada al final de una opendeclaración?

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

Estoy buscando una explicación técnica de cómo funciona el flujo en CasperJS. Mi problema específico es que mi última thendeclaración (arriba) se ejecuta antes de mi casper.opendeclaración y no sé por qué.

bendytree
fuente
1
Todavía estoy buscando una explicación del general flowde casperjs, pero descubrí que básicamente no se puede hacer referencia a casper desde dentro de una evaluatellamada. (es decir, no puede abrir una nueva URL, registro, eco, etc.). Entonces, en mi caso, se llamaba a evaluar, pero no había forma de interactuar con el mundo exterior.
bendytree
1
Me preguntaba exactamente las mismas cosas, pero era demasiado perezoso para preguntar. ¡Buena pregunta!
Nathan
4
evaluate()es para código que se ejecuta en el "navegador", en el DOM de la página que está navegando phantomjs. Entonces no hay casper.openallí, pero podría haber jQuery. Entonces tu ejemplo no tiene sentido, pero todavía me pregunto qué es lo que then()realmente hace.
Nathan

Respuestas:

93

then()básicamente agrega un nuevo paso de navegación en una pila. Un paso es una función de JavaScript que puede hacer dos cosas diferentes:

  1. esperando que se ejecute el paso anterior, si lo hay
  2. esperando que se cargue una URL solicitada y una página relacionada

Tomemos un escenario de navegación 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)');
});

Puede imprimir todos los pasos creados dentro de la pila de esta manera:

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

Eso da:

$ 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 la _step()función que CasperJS ha agregado automáticamente para cargar la URL por nosotros; cuando se carga la URL, se llama al siguiente paso disponible en la pila, que es step3().

Cuando haya definido sus pasos de navegación, run()ejecútelos uno por uno secuencialmente:

casper.run();

Nota al pie: la devolución de llamada / escucha es una implementación del patrón Promise .

NiKo
fuente
En casperjs 1.0.0-RC1, "test-steps.js" muestra una colección de [object DOMWindow], en lugar de una colección de cadenas de definición de funciones.
starlocke
La colección [object DOMWindow] sigue siendo el resultado de 1.0.0-RC4; Me pregunto dónde fueron esas definiciones de función ...
starlocke
1
Inicialmente pensé que CasperJS estaba haciendo un nuevo truco para convertir funciones en DOMWindows, pero el problema era realmente "return this.toString ()" vs "return step.toString ()" - Envié una edición para la respuesta.
starlocke
5
¿No es la llamada 'pila' en realidad una cola? Los pasos se ejecutan en orden, si hubiera sido una pila, ¿no esperaríamos el paso 3, paso 2, paso 1?
Reut Sharabani
1
Creo que debe ser así: tienes una pila de pasos. Sacas un paso y lo evalúas. Creas una cola vacía. Cualquier paso generado debido al procesamiento del paso actual se coloca en esta cola. Cuando el paso ha terminado de evaluarse, todos los pasos generados en la cola se colocan en la parte superior de la pila, pero conservando su orden dentro de su cola. (Lo mismo que empujar sobre la pila en orden inverso).
Mark
33

then() simplemente registra una serie de pasos.

run() y su familia de funciones de corredor, devoluciones de llamada y oyentes, son todos los que realmente hacen el trabajo de ejecutar cada paso.

Cada vez que se completa un paso, CasperJS comprobarán contra 3 banderas: pendingWait, loadInProgress, y navigationRequested. Si alguna de esas banderas es verdadera, entonces no haga nada, permanezca inactivo hasta un momento posterior ( setIntervalestilo). Si ninguno de esos indicadores es verdadero, se ejecutará el siguiente paso.

A partir de CasperJS 1.0.0-RC4, existe una falla, donde, bajo ciertas circunstancias basadas en el tiempo, el método "intentar hacer el siguiente paso" se activará antes de que CasperJS tenga tiempo de activar una de las banderas loadInProgresso navigationRequested. La solución es levantar una de esas banderas antes de salir de cualquier paso donde se espera que se levanten esas banderas (por ejemplo, levantar una bandera antes o después de pedir una casper.click()), tal vez así:

(Nota: Esto es solo ilustrativo, más como un psuedocode que un formulario CasperJS adecuado ...)

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 esa solución en una sola línea de código, blockStep()presenté en esta solicitud de extracción de github , extendiendo click()y clickLabel()como un medio para ayudar a garantizar que obtenemos el comportamiento esperado al usar then(). Consulte la solicitud para obtener más información, patrones de uso y archivos de prueba mínimos.

Starlocke
fuente
1
muy útil y gran conocimiento y sugerencia blockStep, en mi humilde opinión
Brian M. Hunt
Todavía estamos discutiendo la solución de "respuesta final" ... Espero que una vez que implemente el aspecto de "valores predeterminados globales", CasperJS hará el tirón.
starlocke
1
Así que sí, échale un ojo. :)
starlocke
¿Tenemos alguna solución para esto? y si si, que ?
Surender Singh Malik
Muchas gracias por explicarme esto. Este comportamiento me ha estado matando durante más de un año, ya que mis pruebas funcionales de Casper para una aplicación con mucho Ajax fallan aleatoriamente todo el tiempo.
brettjonesdev
0

Según la documentación de CasperJS :

then()

Firma: then(Function then)

Este método es la forma estándar de agregar un nuevo paso de navegación a la pila, proporcionando una función 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();

Puede agregar tantos pasos como necesite. Tenga en cuenta que la Casperinstancia actual enlaza automáticamente la thispalabra clave dentro de las funciones de paso.

Para ejecutar todos los pasos que definió, llame al run()método y listo.

Nota: debe tener start()la instancia de casper para poder utilizar el then()método.

Advertencia: Las funciones de paso agregadas then()se procesan en dos casos diferentes:

  1. cuando se ha ejecutado la función del paso anterior,
  2. cuando se ha ejecutado la solicitud HTTP principal anterior y se ha cargado la página ;

Tenga en cuenta que no existe una definición única de página cargada ; ¿Es cuando se ha activado el evento DOMReady? ¿Se trata de "finalizar todas las solicitudes"? ¿Se está ejecutando "toda la lógica de la aplicación"? ¿O "todos los elementos se están renderizando"? La respuesta siempre depende del contexto. Por lo tanto, se le anima a usar siempre los waitFor()métodos familiares para mantener un control explícito sobre lo que realmente espera.

Un truco común es 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();

Detrás de escena, el código fuente deCasper.prototype.then se muestra a continuación:

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

Explicación:

En otras palabras, then()programa el siguiente paso del proceso de navegación.

Cuando then()se llama, se le pasa una función como parámetro que debe llamarse como paso.

Comprueba si se ha iniciado una instancia y, si no, muestra el siguiente error:

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

A continuación, comprueba si el pageobjeto es null.

Si la condición es verdadera, Casper crea un nuevo pageobjeto.

Después de eso, then()valida el stepparámetro para verificar si no es una función.

Si el parámetro no es una función, muestra el siguiente error:

CasperError: You can only define a step as a function

Luego, la función verifica si Casper se está ejecutando.

Si Casper no se está ejecutando, then()agrega el paso al final de la cola.

De lo contrario, si Casper se está ejecutando, inserta un subpaso un nivel más profundo que el paso anterior.

Finalmente, la then()función concluye emitiendo un step.addedevento y devuelve el objeto Casper.

Grant Miller
fuente