How to activate javascript links with zombie.js - node.js

I am trying to get get zombie.js to activate a link that uses javascript. The page I am testing it on is:
<html>
<body>
<div id="test123">
START_TEXT
</div>
GO<br/>
<script type="text/javascript">
go = function() {
var el = document.getElementById("test123");
el.innerHTML = "CHANGED";
}
</script>
</body>
</html>
The Script I am using is:
var zombie = require("zombie");
var browser = new zombie.Browser;
browser.visit( "http://localhost:8000/testpage.html",
function() {
browser.clickLink("GO", function(e, browser, status) {
var temp = browser.text("div#test123");
console.log("content:", temp);
});
});
I get the error message:
node.js:201
throw e; // process.nextTick error, or 'error' event on first tick
^
Error: Cannot load resource: javascript:go()
at History._resource (/home/julian/temp/node_modules/zombie/lib/zombie/history.coffee:75:15)
at History._pageChanged (/home/julian/temp/node_modules/zombie/lib/zombie/history.coffee:60:21)
at History._assign (/home/julian/temp/node_modules/zombie/lib/zombie/history.coffee:213:19)
at Object.location (/home/julian/temp/node_modules/zombie/lib/zombie/history.coffee:51:24)
at Object.click (/home/julian/temp/node_modules/zombie/lib/zombie/jsdom_patches.coffee:31:59)
at Object.dispatchEvent (/home/julian/temp/node_modules/zombie/node_modules/jsdom/lib/jsdom/level2/html.js:480:47)
at /home/julian/temp/node_modules/zombie/lib/zombie/eventloop.coffee:130:16
at EventLoop.perform (/home/julian/temp/node_modules/zombie/lib/zombie/eventloop.coffee:121:7)
at EventLoop.dispatch (/home/julian/temp/node_modules/zombie/lib/zombie/eventloop.coffee:129:19)
at Browser.dispatchEvent (/home/julian/temp/node_modules/zombie/lib/zombie/browser.coffee:220:30)
When I use
browser.evaluate("go()")
it works.
What am I missing?

Zombie.js doesn't handle links with "javascript:" on href (at the time of this writing).
I fixed it by adding 3 lines to the source code. Look for /node_modules/zombie/lib/zombie/history.coffee and add the 3 lines commented with FIX (beware that in .coffee you must respect indentation, ie. use 2 spaces):
# Location uses this to move to a new URL.
_assign: (url)->
url = #_resolve(url)
# FIX: support for javascript: protocol href
if url.indexOf("javascript:")==0
#_browser.evaluate(url.substr("javascript:".length))
return
was = #_stack[#_index]?.url # before we destroy stack
#_stack = #_stack[0..#_index]
#_stack[++#_index] = new Entry(this, url)
#_pageChanged was
I probably should fork zombie.js on github.com and put this into a Pull Request, but until then you are welcome to do use this snippet, or make that pull request before me.

Well it doesn't seem to understand javascript:code hrefs. Perhaps you can get the url, remove the javascript:-section and evaluate it?
Disclaimer: I haven't used zombie.js myself yet.

The zombie.js API says it takes a CSS selector or the link text as the first parameter for browser.clickLink(). So your code should work.
But try adding an id to the link, if you have control over the page, and using a CSS selector
browser.clickLink('#thelink', function(e, browser, status) {
var temp = browser.text("div#test123");
console.log("content:", temp);
});

Related

Google Tag Manager, requirejs and the Aurelia CLI

I have an Aurelia CLI application into which I'm trying to include de Google Tag Manager scripts
<head>
<!-- Google Tag Manager -->
<!--<script>
var dataLayer = [];
dataLayer.push({ 'event': 'hixo' });
</script>
<script>
(function (w, d, s, l, i) {
w[l] = w[l] || []; w[l].push({
'gtm.start':
new Date().getTime(), event: 'gtm.js'
}); var f = d.getElementsByTagName(s)[0],
j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : ''; j.async = true; j.src =
'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'dataLayer', 'GTM-PLSZRC');
</script>-->
<!-- End Google Tag Manager -->
</head>
But as soon as I start the application I get the following requirejs error:
"Mismatched anonymous define() modules"
The Aurelia CLI uses requirejs, but it is completely abstracted so, where and how should I define the Google Tag Manager Script so that it is not handled as an anonymous module?
When you use dataLayer.push() for the first time after loading the google tag manager script, GTM will load additional scripts. These scripts are loaded asynchronously, so these scripts will finish loading after the aurelia-cli bundle (and requirejs which is inside this bundle) has been loaded. This is why RequireJS is complaining of the anonymous define error, even though the GTM script tag is included before the aurelia-cli bundle.
One of the scripts loaded by Google Tag Manager is Fingerprint2 from this URL. Currently this is version 1.4.1 of Fingerprint2, and in the GitHub repository there is this issue mentioning the same Mismatched anonymous define() error. It seems to be fixed by this PR, but since Google Tag Manager loads Fingerprint2 (and we can't override this behavior) we need a workaround to get it working.
A workaround is to load Fingerprint2 yourself synchronously using a script tag and then tell RequireJS to ignore the Mismatched anonymous define() error originating from Google Tag Manager loading Fingerprint2 the second time:
<script src="http://www.clickcease.com/monitor/stat.js"></script>
<script src="scripts/vendor-bundle.js" data-main="aurelia-bootstrapper"></script>
<script>
requirejs.onError = function (err) {
if (err.message.indexOf('Fingerprint2') === -1) {
throw err;
}
};
</script>
Hopefully Clickcease will update their version of Fingerprint2 soon so that this workaround will no longer be necessary.

GreaseMonkey is not processing my button click

I want to get GreaseMonkey to process a button click. The HTML for the button is generated in a perl CGI which is accessed by GM_xmlhttprequest. The javascript to handle the click is in my user script.
Here is the user script. It prepends a div to the top of a webpage and populates that div with what comes from my CGI via AJAX.
// ==UserScript==
// #name button test
// #namespace http://www.webmonkey.com
// #description test that I can intercept a button and process click with AJAX
// #include http*
// #version 1
// #grant GM_xmlhttpRequest
// ==/UserScript==
function processButton() {
alert("got to processButton");
}
var myDiv;
var details = GM_xmlhttpRequest({
method: 'GET',
url:"http://localhost/cgi-bin/buttonTest",
onload: function (response) {
myDiv = document.createElement('div');
myDiv.id = 'mydiv';
myDiv.style.border = '4px solid #000';
myDiv.innerHTML = response.responseText;
document.body.insertBefore(myDiv, document.body.firstChild);
}
});
Here's my CGI.
#!/usr/bin/perl -w
print "Content-type:text/html\n\n";
print qq|<button onclick="processButton()">Click here</button>| ;
When I load a web page I get a new div with the HTML button code in it as I expect. When I click the button nothing happens. No alert. I created an HTML example to make sure I wasn't doing something really stupid. The example works fine.
<html>
<head>
<script>
function processButton() {
alert("got to processButton");
}
</script>
</head>
<body>
<button onclick="processButton()">Click here</button>
</body>
</html>
There's a console error message:
ReferenceError: processButton is not defined
What am I missing?
If I declare the processButton function in the HTML code that includes the button declaration and then put this in my userScript then my code works:
unsafeWindow.processButton = function() {
alert("hijacked processButton");
}
In other words, I'm now able to hijack the button from GM.

Updating cache "bust" variable programatically in require.js

I am trying to update the bust variable in require.js so the browser is forced to re-fetch instead of loading resources from cache. I found here that several people have asked similar question. I am trying to try with a simple piece of code.
<html>
<head>
<title>jQuery+RequireJS Sample Page</title>
<!-- This is a special version of jQuery with RequireJS built-in -->
<script>
var require = {
urlArgs : "bust="+getRandom()
};
</script>
<script data-main="scripts/main" src="scripts/require-jquery.js"></script>
<script>
function getRandom() {
var buildNumber;
$.get("/resource/buildNumber", function(data) {
buildNumber = data;
});
return buildNumber;
}
</script>
</head>
<body>
<h1 id="heading">jQuery+RequireJS Sample Page</h1>
<p>Look at source or inspect the DOM to see how it works.</p>
</body>
I am trying to get the value of my build number from a properties file on the server. But I get the following error:
Uncaught ReferenceError: getRandom is not defined
So I tried this:
<script data-main="scripts/main" src="scripts/require-jquery.js"></script>
<script>
var require = {
urlArgs : "bust="+getRandom()
};
function getRandom() {
var buildNumber;
$.get("/resource/buildNumber", function(data) {
buildNumber = data;
});
return buildNumber;
}
</script>
But I get this error:
Uncaught TypeError: Property 'require' of object [object Object] is not a function
It looks like the bust variable has to be set even before require-jquery.js is declared but how do I access server side APIs without access to jquery libraries? I want to update the bust variable for every build.
Any pointers in the right directions would be really appreciated.
Thanks.

phantomjs and requirejs

codes in file main.js is like this:
phantom.injectJs("libs/require-1.0.7.js");
require.config(
{
baseUrl: ""
}
);
require([], function(){});
when i run "phantomjs main.js" in the commandline, requirejs doesn't work well in the main.js. I know how to use requirejs in the page running in the browser(including phantomjs' way: page.open(url, callback)), but not like above. I tries using requirejs like the main.js, it is a popular problem, i think. Thank you!
I just struggled for some time. My solution is not clean, but it works, and I'm happy with that due to the unfinished api documentation from phantomjs.
Wordy explanation
You need three files. One is your amd phantomjs test file which I'll call "amd.js". The second is your html page to load which I'll name "amd.html". Finally the browser test which I called "amdTestModule.js".
In amd.html, declare your script tag per normal:
<script data-main="amdTestModule.js" src="require.js"></script>
In your phantomjs test file, this is where it gets hacky. Create your page, and load in the 'fs' module. This allows you to open a relative file path.
var page = require('webpage').create();
var fs = require('fs');
page.open('file://' + fs.absolute('tests/amd.html'));
Now since requirejs loads files asynchronously, we can't just pass in a callback into page.open and expect things to go smoothly. We need some way to either
1) Test our module in the browser and communicate the result back to our phantomjs context. Or
2) Tell our phantomjs context that upon loading all the resources, to run a test.
#1 was simpler for my case. I accomplished this via:
page.onConsoleMessage = function(msg) {
msg = msg.split('=');
if (msg[1] === 'success') {
console.log('amd test successful');
} else {
console.log('amd test failed');
}
phantom.exit();
};
**See full code below for my console.log message.
Now phantomjs apparently has an event api built in but it is undocumented. I was also successfully able to get request/response messages from their page.onResourceReceived and page.onResourceRequested - meaning you can debug when all your required modules are loaded. To communicate my test result however, I just used console.log.
Now what happens if the console.log message is never ran? The only way I could think of resolving this was to use setTimeout
setTimeout(function() {
console.log('amd test failed - timeout');
phantom.exit();
}, 500);
That should do it!
Full Code
directory structure
/projectRoot
/tests
- amd.js
- amdTestModule.js
- amd.html
- require.js (which I symlinked)
- <dependencies> (also symlinked)
amd.js
'use strict';
var page = require('webpage').create();
var fs = require('fs');
/*
page.onResourceRequested = function(req) {
console.log('\n');
console.log('REQUEST');
console.log(JSON.stringify(req, null, 4));
console.log('\n');
};
page.onResourceReceived = function(response) {
console.log('\n');
console.log('RESPONSE');
console.log('Response (#' + response.id + ', stage "' + response.stage + '"): ' + JSON.stringify(response, null, 4));
console.log('\n');
};
*/
page.onConsoleMessage = function(msg) {
msg = msg.split('=');
if (msg[1] === 'success') {
console.log('amd test successful');
} else {
console.log('amd test failed');
}
phantom.exit();
};
page.open('file://' + fs.absolute('tests/amd.html'));
setTimeout(function() {
console.log('amd test failed - timeout');
phantom.exit();
}, 500);
amd.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script data-main='amdTestModule.js' src='require.js'></script>
</body>
</html>
amdTestModule.js
require([<dependencies>], function(<dependencies>) {
...
console.log(
(<test>) ? "test=success" : "test=failed"
);
});
console
$ phantomjs tests/amd.js
amd test successful
you are misunderstanding webpage.injectJs()
it's for injecting scripts into the page you are loading, not into the phantomjs runtime environment.
So using .injectJs() is making requirejs load up into your page, not into phantomjs.exe.
That said, phantomjs's runtime environment has an aproximation of commonjs. RequireJs will not run on there by default. If you felt especially (VERY) motivated, you could attempt porting the require-shim made for nodejs, but it doesn't work out of the box, and would require an incredibly deep understanding of the runtimes. for more details: http://requirejs.org/docs/node.html
a better idea:
probably you should make sure you have commonjs versions of your javascript you wish to run. i personally write my code in typescript so i can build for either commonjs or amd. i use commonjs for phantomjs code, and amd for nodejs and browser.

how to run nodejs flatiron/director example

I tried to run the https://github.com/flatiron/director#client-side example to get familiar with director.js.
I am not able to setup the flatiron module on the client-side.
In my html page (say, <my_project>/page.html) I replaced the location of director.js with
a location of its counterpart from my project:
<my_project>/node_modules/flatiron/node_modules/director/lib/director.js
Upon loading the <my_project>/page.html page in the browser
I got errors: export and Router not defined.
First idea: After all, on the browser side there is no nodejs...
Ok, I thought that browserify could help me with it.
I generated a single 'browser-side' bundle (was it necessary?):
my_project> node node_modules/browserify/bin/cli.js node_modules/flatiron/node_modules/director/lib director.js -o cs_director.js
and I used it in the line: <script src="cs_director.js"></script>
The problem is that the error
Uncaught ReferenceError: Router is not defined
(anonymous function)
still appears so I guess the whole example will not work.
I am new to node/js and I am not sure if it makes sens what I have done in my case described above...
Does anybody how to solve it?
Or generally, how to use 'isomorphic' stuff on a browser-side?
The html examples on Github just refer to the same .js files
as server-side examples ...
Can you recommend any tutorials, examples?
Thanks,
-gvlax
You can find a browser-specific build of director here which has all of the server code stripped away.
Thanks DeadDEnD,
Now it works like a charm!
I have no idea how I could missed that info in readme ... I read the manual first, I swear:-)
Here is my sample code:
<!html>
<html>
<head>
<script src="director-1.0.7.min.js"></script>
<script>
var author = function () { console.log("author called.");},
books = function () { console.log("books called."); },
viewBook = function(bookId) { console.log("viewBook called."); };
var routes = {
'/author': author,
'/books': [books, function() { console.log("anonymous fun called."); }],
'/books/view/:bookId': viewBook
};
var router = Router(routes);
router.init();
</script>
</head>
<body>
Click me to call two functions at a time.
</body>
</html>
--gvlax

Resources