Checking JavaScript AJAX loaded resources with Min

2019-06-07 04:52发布

I'm trying to test a page, which otherwise uses massive asynchronously loaded JavaScript resources, with PHP script that uses Mink and Zombie. Unfortunately, this process fails (leaving behind hanging processes). I have managed finally to simulate this error on a minimal example, which I'll post here.

The first page - the page to be tested - is basically a self-contained file, that simulates the AJAX calls by calling itself:

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>

So, when the page first loads, there's no action; and as soon as the button is pressed: first some HTML content, which contains <script>, is returned (this is ?getone) through an AJAX call; then this script itself loads "automatically" from ?gettwo. I'm aware this is probably not the right thing to do (see JavaScript console.log causes error: "Synchronous XMLHttpRequest on the main thread is deprecated..."), but there is a similar organization in the actual page I'm trying to test.

To run this page, you can just have the command-line php version > 5.3, and run in the same directory of the file:

php -S localhost:8080

... and then, in a browser, go to http://127.0.0.1:8080/test_JSload.php.

In any case, once I click the button, Firefox console shows:

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

Btw, the error 'TypeError: ... is not a function' is the exact same I get from Mink/Zombie with the actual page I'm trying to inspect; although I don't think the page itself exhibits that error when called standalone, as it does here.

Now, let's try inspect this page with Mink (install as on 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";
?>

Running this from another terminal shell (while the first one still runs the php server process that serves test_JSload.php) results with:

$ 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

Now, here the error from Mink/Zombie is 'ReferenceError: person is not defined', whereas in my actual page inspection, it is 'TypeError: ... is not a function' - but I think they are similar enough.

The question is - how can I handle errors like this?

For instance, here there is already '$session->wait(20000, '(browser.statusCode > 0)');' used, which checks for browser.statusCode inside JavaScript; I'm aware there is also:

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

... which can also ask for data from JavaScript. So something like this - in principle - could be used to "wait" until given resources are loaded.

But the problem here is that the ptest variable, which exposes the problem, is in the local scope of function OnGetdata(), and so I have no idea how to construct a statement that would "check" it from PHP - where I expect only global variables like browser and document to be accessible.

So, does anyone now how could I have a Mink/Zombie PHP script "wait" for such "nested" JavaScript loading - so I could complete the loading of the page, after the virtual click, without an error?


EDIT: Managed to reduce this problem to a plain Zombie JavaScript file:

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

Running this file, logs the exact same error:

  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

So, I guess if this gets solved on a Zombie/JS level, then it can eventually be solved on a Mink/PHP level... Note however, that the call to ?gettwo happens only after the error has been emitted (and is the final entry in the log) - it seems that Zombie simply cannot wait for synchronous requests..

1条回答
放荡不羁爱自由
2楼-- · 2019-06-07 05:26

Try this too:

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

Or try other wait condition, for example a do while to wait for an object, also please not that findButton may return null if the element is not found/loaded so you can try a do-while for some seconds and if button !== null then break/return the button.

I always use wait for element that returns the object or throws an exception and click, for example something like:

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

If you need an example of waitForElement please let me know.

Here are some ajax conditions that you can try how-to-make-behat-wait-for-an-ajax-call

查看更多
登录 后发表回答