2016-06-01 16 views
1

Ich versuche eine Seite zu testen, die ansonsten massive asynchron geladene JavaScript-Ressourcen verwendet, mit PHP-Skript, das Mink und Zombie verwendet. Leider schlägt dieser Prozess fehl (hanging processes). Ich habe es endlich geschafft, diesen Fehler an einem minimalen Beispiel zu simulieren, das ich hier veröffentlichen werde.JavaScript überprüfen AJAX geladene Ressourcen mit Mink/Zombie in PHP?

Die erste Seite - die Seite getestet wird - ist im Grunde ein self-contained file, dass das AJAX simuliert ruft selbst Aufruf:

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> 

Also, wenn die Seite geladen wird, es gibt keine Aktion; und sobald die Schaltfläche gedrückt wird: zuerst wird ein HTML-Inhalt, der <script> enthält, durch einen AJAX-Aufruf (dies ist ?getone) zurückgegeben; dann lädt dieses Skript selbst "automatisch" von . Ich bin mir bewusst, das ist wahrscheinlich nicht das Richtige zu tun (siehe JavaScript console.log causes error: "Synchronous XMLHttpRequest on the main thread is deprecated..."), aber es gibt eine ähnliche Organisation in der aktuellen Seite, die ich versuche zu testen.

Diese Seite ausführen zu können, können Sie die Befehlszeilen php Version haben nur> 5,3, und im gleichen Verzeichnis der Datei auszuführen:

php -S localhost:8080 

... und dann, in einem Browser, gehen zu http://127.0.0.1:8080/test_JSload.php.

Auf jeden Fall, wenn ich auf die Schaltfläche klicken, zeigt Firefox Konsole:

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, der Fehler ‚TypeError: ... is not a function‘ genau das gleiche, das ich von Mink/Zombie mit der aktuellen Seite erhalten Ich versuche, inspizieren; obwohl ich glaube nicht, dass die Seite selbst diesen Fehler zeigt, wenn sie als eigenständig bezeichnet wird, wie hier.

Jetzt wollen wir versuchen, diese Seite mit Mink inspizieren (installieren wie auf 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"; 
?> 

das Lauf von einem anderen Terminal-Shell (während die erste läuft noch die PHP-Server Prozess, der test_JSload.php liefert) Ergebnisse mit:

$ 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 

Nun, hier ist der Fehler von Mink/Zombie 'ReferenceError: person is not defined', während in meiner tatsächlichen Seiteninspektion ist es 'TypeError: ... is not a function' - aber ich denke, sie sind ähnlich genug.

Die Frage ist - wie kann ich mit solchen Fehlern umgehen?

Zum Beispiel, hier ist bereits '$session->wait(20000, '(browser.statusCode > 0)');' verwendet, die für browser.statusCode innerhalb JavaScript überprüft; Ich bin mir bewusst, es gibt auch:

... die auch für Daten aus JavaScript fragen können. So etwas - im Prinzip - könnte verwendet werden, um zu "warten", bis bestimmte Ressourcen geladen sind.

Aber das Problem ist, dass die ptest Variable, das das Problem aussetzt, in dem lokal sind Umfang der Funktion OnGetdata(), und so habe ich keine Ahnung, wie man eine Aussage zu konstruieren, die „check“ es von PHP würde - wo ich erwarte, dass nur globale Variablen wie browser und document zugänglich sind.

Also, hat jemand jetzt wie könnte ich ein Mink/Zombie PHP-Skript "warten" für solche "geschachtelte" JavaScript laden - so konnte ich das Laden der Seite nach dem virtuellen Klick, ohne einen Fehler abschließen?


EDIT: Managed dieses Problem auf eine Ebene Zombie JavaScript-Datei zu reduzieren:

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

Ausführen dieser Datei protokolliert exakt die gleichen Fehler:

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 

Also, ich denke, wenn das auf einem gelöst wird Zombie/JS-Level, dann kann es eventuell auf einer Mink/PHP-Ebene gelöst werden ... Beachten Sie jedoch, dass der Aufruf an ?gettwo erst dann erfolgt, nachdem der Fehler ausgegeben wurde (und der letzte Eintrag im Log ist) - so scheint es Zombie kann nicht einfach für synchrone Anforderungen warten ..

+1

Haben Sie basiert auf AJAX-Anfragen einen Wartezustand zu verwenden versucht, wie versuchen: Session-> $ wait ('numberOfSeconds', ‚(0 === Ajax.activeRequestCount) "); ? – lauda

+0

Danke dafür, @lauda - habe es gerade ausprobiert, aber ich bekomme: 'ReferenceError: Ajax ist nicht definiert'. Ich habe es ein bisschen nachgeschaut, und auf dieser Seite: https://github.com/facebook/php-webdriver/wiki/How-to-work-with-jQuery,-Prototype-Dojo-AJAX kann ich das sehen Ajax.activeRequestCount' ist auf die Verwendung von 'prototypejs' zurückzuführen, die ich nirgendwo in diesem Beispiel verwende. Ich benutze jQuery, und ich versuchte '$ session-> wait (20000, '(0 === jQuery.active)');' vor dem 'click()' stattdessen, aber es schlägt nur das gleiche wie in OP beschrieben . – sdbbs

+1

Versuchen Sie auch dies: $ session-> wait (50000, "document.readyState === 'complete'"); Oder versuchen Sie eine andere Wartebedingung, zum Beispiel eine Weile, um auf ein Objekt zu warten, auch nicht, dass findButton Null zurückgibt, wenn das Element nicht gefunden/geladen wird, so dass Sie einige Sekunden lang versuchen können, und wenn Taste! == null dann brechen/zurück den Knopf. Ich benutze immer warten auf Element, das das Objekt zurückgibt oder löst eine Ausnahme und klicken Sie auf, zum Beispiel wie $ this-> waitForElement ('Selektor') -> click(); Wenn Sie ein Beispiel für waitForElement benötigen, lassen Sie es mich bitte wissen. – lauda

Antwort

0

dies versuchen:

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

oder anderen Wartezustand versuchen, zum Beispiel eines do, während für ein Objekt zu warten, auch nicht bitte, dass findButton null zurück, wenn Das Element wird nicht gefunden/geladen, so dass Sie einige Sekunden lang versuchen können, eine Aktion auszuführen, und wenn die Schaltfläche! == null ist, dann brechen Sie die Schaltfläche ab und geben Sie sie zurück.

Ich benutze immer für Element warten, der das Objekt zurückgibt oder eine Ausnahme auslöst, und klicken Sie zum Beispiel so etwas wie:

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

Wenn Sie ein Beispiel für waitForElement benötigen Sie es mich wissen lassen.

Hier sind einige Ajax-Bedingungen, die Sie how-to-make-behat-wait-for-an-ajax-call