使用PHP中的Mink / Zombie检查JavaScript AJAX加载的资源?

时间:2016-06-01 15:12:39

标签: php ajax node.js mink zombie.js

我正在尝试使用Mink和Zombie的PHP脚本测试一个页面,否则该页面使用大量异步加载的JavaScript资源。不幸的是,这个过程失败了(留下hanging processes)。我最终设法在一个最小的例子上模拟这个错误,我将在这里发布。

第一页 - 要测试的页面 - 基本上是self-contained file,它通过调用自身来模拟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>

因此,当页面首次加载时,没有动作;按下按钮后:首先通过AJAX调用返回一些包含<script>的HTML内容(这是?getone);然后这个脚本本身从?gettwo“自动”加载。我知道这可能不是正确的事情(参见JavaScript console.log causes error: "Synchronous XMLHttpRequest on the main thread is deprecated..."),但在我试图测试的实际页面中有一个类似的组织。

要运行此页面,您只需拥有命令行php版本&gt; 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 / Zombie得到的错误与我正在尝试检查的实际页面完全相同;虽然我不认为页面本身在独立调用时会出现错误,但就像在这里一样。

现在,让我们尝试使用Mink检查此页面(在nodejs cannot find module 'zombie' with PHP mink上安装):

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";
?>

从另一个终端shell运行它(当第一个仍运行服务于test_JSload.php的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)');”,用于检查JavaScript中的browser.statusCode;我知道还有:

$returnstring = $session->evaluateScript('document.readyState');

...也可以从JavaScript请求数据。所以这样的东西 - 原则上 - 可以用来“等待”,直到加载给定的资源。

但问题是,ptest变量暴露了问题,它位于函数OnGetdata()局部范围内,因此我不知道如何构造一个从PHP“检查”它的语句 - 我希望只能访问browserdocument这样的全局变量。

那么,有没有人现在怎么能有一个Mink / Zombie PHP脚本“等待”这样的“嵌套”JavaScript加载 - 所以我可以在虚拟点击后完成页面的加载,没有错误?

编辑:管理将此问题减少为简单的Zombie JavaScript文件:

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根本无法等待同步请求。

1 个答案:

答案 0 :(得分:0)

试试这个:

$session->wait(50000, "document.readyState === 'complete'");

或尝试其他等待条件,例如等待一个对象,同时请不要指望如果找不到元素,findButton可能会返回null,这样你可以尝试一段时间的do-while和if按钮!== null然后中断/返回按钮。

我总是使用等待返回对象的元素或抛出异常并单击,例如:

$this->waitForElement('selector')->click();

如果您需要waitForElement的示例,请告诉我。

以下是一些ajax条件,您可以尝试how-to-make-behat-wait-for-an-ajax-call