Что на самом деле означает слово «тогда» в CasperJS

97

Я использую CasperJS для автоматизации серии кликов, заполненных форм, анализа данных и т. Д. Через веб-сайт.

Кажется, что Casper организован в список предустановленных шагов в форме thenоператоров (см. Их пример здесь: http://casperjs.org/quickstart.html ), но неясно, что запускает следующий оператор на самом деле.

Например, thenожидает ли завершения всех ожидающих запросов? Считается ли injectJSожидающим запросом? Что произойдет, если у меня есть thenинструкция, вложенная в конец openинструкции?

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

Я ищу техническое объяснение того, как поток работает в CasperJS. Моя конкретная проблема в том, что мое последнее thenутверждение (выше) выполняется перед моим casper.openутверждением, и я не знаю почему.

Bendytree
источник
1
Я все еще ищу объяснение общих flowпринципов casperjs, но обнаружил, что вы не можете ссылаться на casper из evaluateвызова. (т.е. вы не можете открыть новый URL, журнал, эхо и т. д.). Итак, в моем случае была вызвана оценка, но без возможности взаимодействия с внешним миром.
bendytree
1
Мне было интересно то же самое, но мне было лень спрашивать. Хороший вопрос!
Натан
4
evaluate()предназначен для кода, который выполняется в «браузере», в DOM страницы, которую просматривает phantomjs. Так что там нет casper.open, но может быть jQuery. Итак, ваш пример не имеет смысла, но мне все еще интересно, что на then()самом деле.
Натан

Ответы:

93

then()в основном добавляет новый шаг навигации в стек. Шаг - это функция javascript, которая может делать две разные вещи:

  1. ожидание выполнения предыдущего шага - если таковой имеется -
  2. ожидание загрузки запрошенного URL и связанной страницы

Возьмем простой сценарий навигации:

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

Вы можете распечатать все созданные шаги в стеке следующим образом:

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

Это дает:

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

Обратите внимание на _step()функцию, которая была автоматически добавлена ​​CasperJS для загрузки URL-адреса для нас; когда URL-адрес загружен, вызывается следующий шаг, доступный в стеке step3().

Когда вы определили шаги навигации, run()выполняйте их один за другим последовательно:

casper.run();

Сноска: материал обратного вызова / слушателя - это реализация шаблона обещания .

NiKo
источник
В casperjs 1.0.0-RC1 "test-steps.js" отображает коллекцию [объект DOMWindow] вместо коллекции строк определения функции.
starlocke 01
Коллекция [объект DOMWindow] по-прежнему является результатом 1.0.0-RC4; Интересно, куда
делись
1
Сначала я думал, что CasperJS выполняет новый трюк по преобразованию функций в DOMWindows, но на самом деле проблема заключалась в «return this.toString ()» vs «return step.toString ()» - я отправил правку для ответа.
starlocke 01
5
Разве так называемый «стек» не является очередью? Шаги выполняются по порядку, если бы это был стек, разве мы не ожидали бы шага 3, шаг 2, шаг 1?
Реут Шарабани
1
Думаю, должно быть так: у вас есть стопка шагов. Вы делаете шаг и оцениваете его. Вы создаете пустую очередь. Любые шаги, сгенерированные в результате обработки текущего шага, помещаются в эту очередь. Когда шаг завершает оценку, все сгенерированные шаги в очереди помещаются на вершину стека, но с сохранением их порядка в своей очереди. (То же, что и вставка в стопку в обратном порядке).
Марк
33

then() просто регистрирует серию шагов.

run() и его семейство функций-исполнителей, обратных вызовов и слушателей - это все, что на самом деле выполняет работу по выполнению каждого шага.

Всякий раз , когда шаг завершен, CasperJS будет проверять против 3 флагов: pendingWait, loadInProgress, и navigationRequested. Если какой-либо из этих флагов истинен, ничего не делайте, переходите в режим ожидания до более позднего времени ( setIntervalстиль). Если ни один из этих флагов не истинен, будет выполнен следующий шаг.

Что касается CasperJS 1.0.0-RC4, существует ошибка, при которой при определенных обстоятельствах, зависящих от времени, метод «попытаться выполнить следующий шаг» будет срабатывать до того, как CasperJS успеет поднять один из флагов loadInProgressили navigationRequested. Решение состоит в том, чтобы поднять один из этих флагов перед тем, как выйти из любого шага, на котором эти флаги должны быть подняты (например: поднять флаг до или после запроса a casper.click()), может быть так:

(Примечание: это только иллюстрация, больше похоже на псевдокод, чем на правильную форму 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()
}

Чтобы обернуть это решение в одну строку кода, я представил blockStep()в этом запросе на перенос на github расширение click()и clickLabel()как средство, помогающее гарантировать получение ожидаемого поведения при использовании then(). Ознакомьтесь с запросом для получения дополнительной информации, шаблонов использования и минимального количества тестовых файлов.

Starlocke
источник
1
очень полезно и отличное понимание и предложение blockStep, ИМХО
Брайан М. Хант
Мы все еще обсуждаем решение «окончательного ответа» ... Я надеюсь, что после того, как я реализовал аспект «глобальных значений по умолчанию», CasperJS сделает все возможное.
starlocke
1
Так что да, следи за этим. :)
starlocke 07
У нас есть какое-нибудь решение для этого? если да, то что это?
Сурендер Сингх Малик,
Большое спасибо за объяснение. Это поведение меня убивает уже больше года, так как мои функциональные тесты Casper для приложения с тяжелым Ajax постоянно дают сбой случайным образом.
brettjonesdev
0

Согласно документации CasperJS :

then()

Подпись: then(Function then)

Этот метод является стандартным способом добавления нового шага навигации в стек, предоставляя простую функцию:

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

Вы можете добавить столько шагов, сколько вам нужно. Обратите внимание, что текущий Casperэкземпляр автоматически связывает thisключевое слово для вас в пошаговых функциях.

Чтобы выполнить все шаги, которые вы определили, вызовите run()метод и вуаля.

Примечание.start() Для использования then()метода необходим экземпляр casper .

Предупреждение: добавленные пошаговые функции then()обрабатываются в двух разных случаях:

  1. когда функция предыдущего шага была выполнена,
  2. когда предыдущий основной HTTP-запрос был выполнен и страница загружена ;

Обратите внимание, что нет единого определения загруженной страницы ; это когда сработало событие DOMReady? Это «все запросы выполняются»? Это «выполняется вся логика приложения»? Или «визуализируются все элементы»? Ответ всегда зависит от контекста. Поэтому вам рекомендуется всегда использовать waitFor()семейные методы, чтобы четко контролировать то, что вы на самом деле ожидаете.

Обычный прием - использовать waitForSelector():

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

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

casper.run();

За кулисами, исходный кодCasper.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;
};

Пояснение:

Другими словами, then()планирует следующий шаг в процессе навигации.

Когда then()вызывается, ему передается функция как параметр, который должен вызываться как шаг.

Он проверяет, запущен ли экземпляр, и, если нет, отображает следующую ошибку:

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

Затем он проверяет, есть ли pageобъект null.

Если условие истинно, Каспер создает новый pageобъект.

После этого then()проверяет stepпараметр, чтобы проверить, не является ли он функцией.

Если параметр не является функцией, отображается следующая ошибка:

CasperError: You can only define a step as a function

Затем функция проверяет, запущен ли Каспер.

Если Каспер не запущен, then()добавляет шаг в конец очереди.

В противном случае, если Casper запущен, он вставляет подшаг на уровень глубже, чем предыдущий.

Наконец, then()функция завершается генерацией step.addedсобытия и возвращает объект Casper.

Грант Миллер
источник