Ранее на этой неделе я посмотрел код, использующий CasperJS . CasperJS — это утилита для тестирования PhantomJS , автономного (то есть виртуального) браузера Webkit. Это, вероятно, несправедливо, но мне нравится думать о Каспере как о сверхмощном Керле. Надеюсь, вы знаете Curl как инструмент командной строки, который позволяет вам выполнять сетевые запросы и работать с результатом. В отличие от Curl, CasperJS (и PhantomJS) могут взаимодействовать с результатами, как настоящий браузер. Это позволяет провести несколько классных тестов / утилит. Я только начал царапать поверхность инструмента, но подумал, что поделюсь интересной небольшой проблемой, с которой столкнулся мой коллега.
Мой коллега, Пол Робертсон (умный и дружелюбный парень, которому нужно снова запустить свой блог), создал скрипт CasperJS, который сканировал набор URL-адресов и загружал HTML-код в локальный каталог. Это само по себе звучит так же, как Curl. Но эти страницы были особенными. Они использовали jQuery для выполнения XHR-запроса к JSON-файлу. Как только запрос XHR был выполнен, данные были преобразованы в HTML и преобразованы в DOM. Здесь светит CasperJS / PhantomJS. Он смог сказать браузеру без головы дождаться появления определенного селектора (тот, который используется в коде JavaScript для рендеринга HTML) и только потом фактически сохранить результат. Круто, верно?
Чтобы дать вам представление, вот пример файла. Этот загружается в простой фрагмент HTML, но вы поняли идею.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <title></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width"> </head> <body> <div id="main"></div> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script> <script> $(document).ready(function() { var $main = $("#main"); //fetch the content and insert it... //timeout here just to fake some network latency window.setTimeout(function() { $.get("data.html", function(res) { $main.append(res); }); }, 800); }); </script> </body> </html>
Ничего странного там нет, так что я не буду это обсуждать. Теперь давайте посмотрим на скрипт CasperJS. Это упрощенная версия того, что написал Павел. Даже если вы впервые в жизни видите CasperJS в действии, вы, вероятно, можете понять, что происходит.
var casper = require("casper").create(); var fs = require("fs"); var outputFolder = "./output/"; var timeoutLength = 2000; // TODO: delete contents of output folder if(fs.exists(outputFolder)) { console.log("Removing "+outputFolder); fs.removeTree(outputFolder); } casper.start(); var url = "http://localhost/testingzone/trash/testtransition2.html"; var selector = "#dynamicContent"; console.log("Getting "+url); casper.thenOpen(url); casper.waitForSelector(selector, function then() { var htmlToWrite = this.getHTML(); var outputFile = outputFolder + "testoutput.html"; fs.write(outputFile, htmlToWrite, "w"); console.log("Wrote html to " + outputFile); }, function timeout() { this.echo("Selector not found after " + timeoutLength + " ms"); }, timeoutLength); casper.run();
В результате получается HTML-файл с содержимым, в том числе загруженным через jQuery. Просто для полноты, вот фрагмент HTML. Обратите внимание, что у него есть селектор, который ждет мой скрипт.
<div id="dynamicContent"> This is so cool. </div>
Woot! Итак, как я уже сказал — что это на самом деле чертовски круто. Мы выдвинули контент для тестирования, и кто-то указал на что-то странное. Область, в которую загружался динамический контент, имела странный серый оттенок. Если вам интересно, это второй результат Google для «нечетного серого затемнения»:
Я немного покопался и обнаружил что-то действительно странное. У div, в который было вставлено динамическое содержимое, было следующее:
style="display: block; opacity: 0.04429836168227741; "
WTF
Я сделал еще несколько нажатий — на этот раз на оригинальной версии — и затем я увидел это. Когда динамический контент был загружен, jQuery выполнял какое-то необычное действие постепенного появления / исчезновения. Потому что … модно. Поэтому я немного изменил свой оригинальный код, чтобы попытаться воссоздать это:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <title></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width"> </head> <body> <div id="main">Existing HTML...</div> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script> <script> $(document).ready(function() { var $main = $("#main"); //fetch the content and insert it... //timeout here just to fake some network latency window.setTimeout(function() { $main.fadeOut(function() { $.get("data.html", function(res) { $main.html(res); $main.fadeIn(); }) }) }, 20); }); </script> </body> </html>
Обратите внимание на использование причудливого угасающего действия. Необычные! Затем я перезапустил свой скрипт CasperJS и сразу увидел, что то же самое было добавлено в мой вывод. Это было исчезать! CasperJS увидел элемент DOM, потому что, ну, он был там, но jQuery не закончил его возвращение. Я обнаружил, что у CasperJS была простая команда ожидания, поэтому я добавил это:
casper.waitForSelector(selector, function then() { this.wait(1000); var htmlToWrite = this.getHTML(); var outputFile = outputFolder + "testoutput.html"; fs.write(outputFile, htmlToWrite, "w"); console.log("Wrote html to " + outputFile); }, function timeout() { this.echo("Selector not found after " + timeoutLength + " ms"); }, timeoutLength);
И это сделало это. Технически я мог бы использовать меньшее ожидание (длительность перехода jQuery по умолчанию равна 400), но это казалось проще и помогло.