Я пытаюсь протестировать страницу, которая в противном случае использует массивные асинхронно загруженные ресурсы JavaScript, с помощью скрипта PHP, который использует Mink и Zombie. К сожалению, этот процесс не удается (оставив позади процессы зависания). Мне наконец удалось смоделировать эту ошибку на минимальном примере, который я опубликую здесь.
Первая страница — тестируемая страница — в основном автономный файл, который имитирует вызовы AJAX, вызывая себя:
test_JSload.php
<?php
if (array_key_exists("QUERY_STRING", $_SERVER)) {
if ($_SERVER["QUERY_STRING"] == "getone") {
echo "<!doctype html>
<html>
<head>
<script src='test_JSload.php?gettwo'></script>
</head>
</html>
";
exit;
}
if ($_SERVER["QUERY_STRING"] == "gettwo") {
header('Content-Type: application/javascript');
echo "function person(firstName) {
this.firstName = firstName;
this.changeName = function (name) {
this.firstName = name;
};
}
";
exit;
}
}
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
.my_btn { background-color:yellow; }
</style>
<script src="http://code.jquery.com/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
var thishref = window.location.href.slice(0, window.location.href.indexOf('?')+1);
var qstr = window.location.href.slice(window.location.href.indexOf('?')+1);
function OnGetdata(inbtn) {
console.log("OnGetdata; loading ?getone via AJAX call");
//~ $.ajax(thishref + "?getone", { // works
var ptest = {}; // init as empty object
console.log(" ptest pre ajax is ", ptest);
$.ajax({url: thishref + "?getone",
async: true, // still "Synchronous XMLHttpRequest on the main thread is deprecated", because we load a script; https://stackoverflow.com/q/24639335
success: function(data) {
console.log("got getone data "); //, data);
$("#dataholder").html(data);
ptest = new person("AHA");
console.log(" ptest post getone is ", ptest);
},
error: function(xhr, ajaxOptions, thrownError) {
console.log("getone error " + thishref + " : " + xhr.status + " / " + thrownError);
}
});
ptest.changeName("Somename");
console.log(" ptest post ajax is ", ptest);
}
ondocready = function() {
$("#getdatabtn").click(function(){
OnGetdata(this);
});
}
$(document).ready(ondocready);
</script>
</head><body>
<h1>Hello World!</h1>
<button type="button" id="getdatabtn" class="my_btn">Get Data!</button>
<div id="dataholder"></div>
</body>
</html>
Итак, когда страница загружается впервые, никаких действий не происходит; и как только кнопка нажата: сначала некоторый HTML-контент, который содержит <script>
, возвращается (это ?getone
) посредством вызова AJAX; тогда сам скрипт загружается «автоматически» из ?gettwo
, Я знаю, что это, вероятно, не правильно делать (см. JavaScript console.log вызывает ошибку: "Синхронный XMLHttpRequest в основном потоке устарел …"), но на странице, которую я пытаюсь проверить, есть похожая организация.
Чтобы запустить эту страницу, вы можете просто иметь командную строку php
версия> 5.3, и запустить в том же каталоге файла:
php -S localhost:8080
… а затем в браузере перейдите к http://127.0.0.1:8080/test_JSload.php
,
В любом случае, когда я нажимаю кнопку, консоль Firefox показывает:
OnGetdata; loading ?getone via AJAX call test_JSload.php:13:3
ptest pre ajax is Object { } test_JSload.php:16:3
TypeError: ptest.changeName is not a function test_JSload.php:31:3
got getone data test_JSload.php:21:7
Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help http://xhr.spec.whatwg.org/ jquery-1.12.4.min.js:4:26272
ptest post getone is Object { firstName: "AHA", changeName: person/this.changeName(name) } test_JSload.php:24:7
Кстати, ошибкаTypeError: ... is not a function
«это то же самое, что я получаю от Минка / Зомби с настоящей страницей, которую я пытаюсь проверить; хотя я не думаю, что сама страница выдает эту ошибку, когда вызывается автономно, как здесь.
Теперь давайте попробуем проверить эту страницу с помощью Mink (установить как на nodejs не может найти модуль «зомби» с PHP норкой):
test_JSload_mink.php
<?php
$nodeModPath = "/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules";
# composer autoload:
require_once __DIR__ . '/vendor/autoload.php';$zsrv = new \Behat\Mink\Driver\NodeJS\Server\ZombieServer();
$zsrv->setNodeModulesPath($nodeModPath . "/"); # needs to end with a trailing '/'
$driver = new \Behat\Mink\Driver\ZombieDriver( $zsrv );
$session = new \Behat\Mink\Session($driver);
// start the session
$session->start();
$session->visit("http://127.0.0.1:8080/test_JSload.php");
$session->wait(20000, '(browser.statusCode > 0)'); ### THIS makes things work?!
$statcode = $session->getStatusCode();
echo " current URL: " . $session->getCurrentUrl() ."\n";
echo " status code: " . $statcode ."\n";
$page = $session->getPage();
$el_button = $page->findButton('getdatabtn');
echo "Check: el_b " . gettype($el_button) . "\n";
echo " pressing/clicking the button\n";
$el_button->click();
echo "Page URL after click: ". $session->getCurrentUrl() . "\n";
?>
Запуск этого из другой терминальной оболочки (в то время как первый все еще выполняет процесс сервера php, который обслуживает test_JSload.php
) результаты с:
$ php test_JSload_mink.php
current URL: http://127.0.0.1:8080/test_JSload.php
status code: 200
Check: el_b object
pressing/clicking the button
PHP Fatal error: Uncaught exception 'Behat\Mink\Exception\DriverException' with message 'Error while processing event 'click': "ReferenceError: person is not defined\n at Object.OnGetdata.$.ajax.success (http://127.0.0.1:8080/test_JSload.php:script:16:19)\n at i (http://code.jquery.com/jquery-1.12.4.min.js:2:27449)\n at Object.j.fireWith [as resolveWith] (http://code.jquery.com/jquery-1.12.4.min.js:2:28213)\n at y (http://code.jquery.com/jquery-1.12.4.min.js:4:22721)\n at XMLHttpRequest.c (http://code.jquery.com/jquery-1.12.4.min.js:4:26925)\n at callListeners (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/node_modules/jsdom/lib/jsdom/events/EventTarget.js:170:34)\n at dispatchPhase (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/node_modules/jsdom/lib/jsdom/events/EventTarget.js:159:7)\n at XMLHttpRequest.EventTarget.dispatchEvent (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/node_modules/jsdom/lib/jsdom/events/EventTarget.js:115: in /media/Data1/work/bbs/gits/econdk-vis-01_git/test/test_php_mink/vendor/behat/mink-zombie-driver/src/ZombieDriver.php on line 880
Теперь ошибка Mink / Zombie:ReferenceError: person is not defined
‘, в то время как в моей реальной проверке страницы, это’TypeError: ... is not a function
‘- но я думаю, что они достаточно похожи.
Вопрос в том, как я могу справиться с такими ошибками?
Например, здесь уже есть$session->wait(20000, '(browser.statusCode > 0)');
используется, который проверяет browser.statusCode
внутри JavaScript; Я знаю, что есть также:
$returnstring = $session->evaluateScript('document.readyState');
… который также может запрашивать данные из JavaScript. Таким образом, что-то вроде этого — в принципе — можно использовать для «ожидания» загрузки данных ресурсов.
Но проблема здесь в том, что ptest
переменная, которая выставляет проблему, находится в местный сфера деятельности OnGetdata()
и поэтому я понятия не имею, как создать оператор, который будет «проверять» его из PHP — где я ожидаю только глобальные переменные, такие как browser
а также document
быть доступным.
Итак, кто-нибудь теперь, как я мог иметь сценарий Mink / Zombie PHP «ждать» такой «вложенной» загрузки JavaScript — чтобы я мог завершить загрузку страницы, после виртуального щелчка, без ошибки?
РЕДАКТИРОВАТЬ: удалось уменьшить эту проблему до простого файла JavaScript Zombie:
test_JSload_zombie.js
// call with:
// DEBUG=zombie NODE_PATH=/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules node test_JSload_zombie.js
var Browser = require("zombie");
browser = new Browser({waitDuration: 30*1000, runScripts: true});
browser.visit("http://127.0.0.1:8080/test_JSload.php", function () {
browser.pressButton('Get Data!'); //, done);
console.log(' person, post-click pre-wait:', browser.evaluate('typeof(person)'), browser.evaluate('$("script").length') ); // $("script").length is 2
var checkCondition = function () {
var ret =browser.evaluate("(typeof(person) != 'undefined')");
console.log("ret is " + ret);
return ret;
};
browser.wait({function: checkCondition, duration: 100000}, function() {
console.log(' person, post-wait:', browser.evaluate('typeof(person)'), browser.evaluate('$("script").length')); // $("script").length is 3
/// browser.dump(); // not too much info
});
});
При запуске этого файла регистрируется точно такая же ошибка:
zombie Opened window http://127.0.0.1:8080/test_JSload.php +0ms
zombie GET http://127.0.0.1:8080/test_JSload.php => 200 +198ms
zombie Loaded document http://127.0.0.1:8080/test_JSload.php +233ms
zombie GET http://code.jquery.com/jquery-1.12.4.min.js => 200 +207ms
zombie Event loop is empty +476ms
OnGetdata; loading ?getone via AJAX call
ptest pre ajax is Object {}
zombie XHR readystatechange http://127.0.0.1:8080/test_JSload.php?getone +54ms
zombie XHR loadstart http://127.0.0.1:8080/test_JSload.php?getone +3ms
zombie TypeError: ptest.changeName is not a function
at OnGetdata (http://127.0.0.1:8080/test_JSload.php:script:24:9)
at null.<anonymous> (http://127.0.0.1:8080/test_JSload.php:script:30:5)
at n.event.dispatch (http://code.jquery.com/jquery-1.12.4.min.js:3:12444)
at r.handle (http://code.jquery.com/jquery-1.12.4.min.js:3:9173)
at callListeners (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/node_modules/jsdom/lib/jsdom/events/EventTarget.js:170:34)
at dispatchPhase (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/node_modules/jsdom/lib/jsdom/events/EventTarget.js:159:7)
at EventTarget.dispatchEvent (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/node_modules/jsdom/lib/jsdom/events/EventTarget.js:115:3)
at DOM.EventTarget.dispatchEvent (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/lib/dom/jsdom_patches.js:155:31)
at define.proto.dispatchEvent (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:365:55)
at Browser.fire (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/lib/index.js:424:14)
in http://127.0.0.1:8080/test_JSload.php +24ms
person, post-click pre-wait: undefined 2
ret is false
zombie GET http://127.0.0.1:8080/test_JSload.php?getone => 200 +36ms
zombie XHR readystatechange http://127.0.0.1:8080/test_JSload.php?getone +7ms
zombie XHR readystatechange http://127.0.0.1:8080/test_JSload.php?getone +0ms
ret is false
got getone data
zombie ReferenceError: person is not defined
at Object.OnGetdata.$.ajax.success (http://127.0.0.1:8080/test_JSload.php:script:16:19)
at i (http://code.jquery.com/jquery-1.12.4.min.js:2:27449)
at Object.j.fireWith [as resolveWith] (http://code.jquery.com/jquery-1.12.4.min.js:2:28213)
at y (http://code.jquery.com/jquery-1.12.4.min.js:4:22721)
at XMLHttpRequest.c (http://code.jquery.com/jquery-1.12.4.min.js:4:26925)
at callListeners (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/node_modules/jsdom/lib/jsdom/events/EventTarget.js:170:34)
at dispatchPhase (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/node_modules/jsdom/lib/jsdom/events/EventTarget.js:159:7)
at XMLHttpRequest.EventTarget.dispatchEvent (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/node_modules/jsdom/lib/jsdom/events/EventTarget.js:115:3)
at XMLHttpRequest.DOM.EventTarget.dispatchEvent (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/lib/dom/jsdom_patches.js:155:31)
at XMLHttpRequest._fire (/home/USERNAME/.nvm/versions/node/v4.0.0/lib/node_modules/zombie/lib/xhr.js:242:12)
in http://127.0.0.1:8080/test_JSload.php +73ms
person, post-wait: undefined 3
zombie XHR readystatechange http://127.0.0.1:8080/test_JSload.php?getone +4ms
zombie XHR progress http://127.0.0.1:8080/test_JSload.php?getone +0ms
zombie XHR load http://127.0.0.1:8080/test_JSload.php?getone +1ms
zombie XHR loadend http://127.0.0.1:8080/test_JSload.php?getone +0ms
zombie GET http://127.0.0.1:8080/test_JSload.php?gettwo => 200 +18ms
Итак, я думаю, что если это будет решено на уровне Zombie / JS, то это в конечном итоге может быть решено на уровне Mink / PHP … Обратите внимание, однако, что вызов ?gettwo
происходит только после того, как ошибка была выдана (и является последней записью в журнале) — кажется, что Zombie просто не может ждать синхронных запросов ..
Попробуйте это тоже:
$session->wait(50000, "document.readyState === 'complete'");
Или попробуйте другое условие ожидания, например, do do для ожидания объекта, также обратите внимание, что findButton может возвращать null, если элемент не найден / не загружен, поэтому вы можете попробовать do-while в течение нескольких секунд и кнопку if! == ноль, а затем сломать / вернуть кнопку.
Я всегда использую ожидание элемента, который возвращает объект или выдает исключение и щелкаю, например что-то вроде:
$this->waitForElement('selector')->click();
Если вам нужен пример waitForElement, пожалуйста, дайте мне знать.
Вот некоторые условия ajax, которые вы можете попробовать как к подпиточной behat-ждать-за-ан-АЯКС-вызова
Других решений пока нет …