I'm trying to write Jasmine test code for a custom JS module(?) that cookies return visitors and redirects them to their cookie'd region.
The JS works fine, but it doesn't seem to be recognized by Jasmine / Protractor as those pages don't redirect as they should.
my spec.js file:
describe("cookies", function() {
var bridgepage = ('http://0.0.0.0:4567/');
var citypage = ('http://0.0.0.0:4567/nyc/');
// cookied visitors
it('should redirect to city pages for cookied visitors', function(){
browser.get(citypage);
browser.get(bridgepage)
browser.sleep(1000);
expect(browser.getCurrentUrl()).toEqual(citypage);
})
});
folder structure:
- source
-- javascripts
---- lib
-- all.js
-- cookies.js // file to test
- spec
-- javascripts
---- helpers
---- supoort
-- spec.js // specs
cookies.js file:
var cookies = (function() {
var utma = $.cookie("__utma");
var serviceArea = $.cookie('first_visit_service_area')
var isFirstVisit = function() {
if (utma == null) {
return false;
}
else {
var utmaArray = utma.split('.');
var firstVisitTime = utmaArray[2];
var currentVisitTime = utmaArray[4];
return (firstVisitTime == currentVisitTime);
}
}
[...]
// Set cookies according to (...)
// If the user has never visited the site, (...)
// If the user (...), (...)
if (utma == null) {
// user has apparently disabled cookies or blocked google analytics cookies
return;
}
if (window.location.pathname == '/' && serviceArea) {
redirectServiceArea();
}
if (isFirstVisit) {
setCookies();
}
}());
This my first attempt at writing JS tests, so any help would be much appreciated! :)
Related
I am a newbie as far as web development is concerned and even more so with Google App Scripts and OAuth2.0. Having said that, I have researched enough and also tried several tricks, but still can't get past this issue.
I borrowed sample from here:
Google Developers - Client API Library
Then created an Apps Script project with an index.html file with code from that page. I also created a project on the developer console, created a client ID, API key and turned on the required API support. I also made the required changes to the sample to reflect the new client ID and API key.
The index.html page is served from HTML Service with SandBox Mode set to IFRAME. If I load the URL in a browser window (say using incognito mode) and click "Authorize" button, it opens the Google sign-in window. But after signing in, it opens two new tabs with messages
Please close this window
and the original browser window shows no change.
The JavaScript console shows error messages like these:
Unsafe JavaScript attempt to initiate navigation for frame with URL ''
from frame with URL
https://accounts.google.com/o/oauth2/postmessageRelay?parent=https%3A%2F%2F…6lxdpyio6iqy-script.googleusercontent.com#rpctoken=288384029&forcesecure=1.
The frame attempting navigation is sandboxed, and is therefore
disallowed from navigating its ancestors.
From the messages, it seems its an effect of using IFRAME and some sort of security feature is preventing the callback being delivered to the original window. If I reload the original window, things work OK. But that's not what I would ideally like.
How do I work around this issue? Its a very simple project and I can provide source code if that helps.
Thanks,
Pavan
Edit: Here is the sample code I'm trying. You would need to have your client ID and API Key and also set JS origins in the Google Console for things to work:
Code.gs
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('index').setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
index.html
<!--
Copyright (c) 2011 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
To run this sample, replace YOUR API KEY with your application's API key.
It can be found at https://code.google.com/apis/console/?api=plus under API Access.
Activate the Google+ service at https://code.google.com/apis/console/ under Services
-->
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
</head>
<body>
<!--Add a button for the user to click to initiate auth sequence -->
<button id="authorize-button" style="visibility: hidden">Authorize</button>
<script type="text/javascript">
// Enter a client ID for a web application from the Google Developer Console.
// The provided clientId will only work if the sample is run directly from
// https://google-api-javascript-client.googlecode.com/hg/samples/authSample.html
// In your Developer Console project, add a JavaScript origin that corresponds to the domain
// where you will be running the script.
var clientId = 'YOUR_CLIENT_ID';
// Enter the API key from the Google Develoepr Console - to handle any unauthenticated
// requests in the code.
// The provided key works for this sample only when run from
// https://google-api-javascript-client.googlecode.com/hg/samples/authSample.html
// To use in your own application, replace this API key with your own.
var apiKey = 'YOUR API KEY';
// To enter one or more authentication scopes, refer to the documentation for the API.
var scopes = 'https://www.googleapis.com/auth/plus.me';
// Use a button to handle authentication the first time.
function handleClientLoad() {
gapi.client.setApiKey(apiKey);
window.setTimeout(checkAuth,1);
}
function checkAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true, response_type: 'token'}, handleAuthResult);
}
function handleAuthResult(authResult) {
var authorizeButton = document.getElementById('authorize-button');
if (authResult && !authResult.error) {
authorizeButton.style.visibility = 'hidden';
makeApiCall();
} else {
authorizeButton.style.visibility = '';
authorizeButton.onclick = handleAuthClick;
}
}
function handleAuthClick(event) {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false, response_type: 'token'}, handleAuthResult);
return false;
}
// Load the API and make an API call. Display the results on the screen.
function makeApiCall() {
gapi.client.load('plus', 'v1', function() {
var request = gapi.client.plus.people.get({
'userId': 'me'
});
request.execute(function(resp) {
var heading = document.createElement('h4');
var image = document.createElement('img');
image.src = resp.image.url;
heading.appendChild(image);
heading.appendChild(document.createTextNode(resp.displayName));
heading.appendChild(document.createTextNode(resp.emails[0].value));
document.getElementById('content').appendChild(heading);
});
});
}
</script>
<script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
<div id="content"></div>
<p>Retrieves your profile name using the Google Plus API.</p>
</body>
</html>
found a solution... not nice but works oO:
the trick is to remove the oauth2relay iframes before the auth window is closed. after the window is closed you have to add the frames again and do a immediate request, if that works the user authorized the app.
be careful:
this script does not check if the user meanwhile is logged out or the token is expired, as long as the webapp window is open the same token is used.
Code.js:
function doGet(e) {
return HtmlService.createTemplateFromFile('Index').evaluate().setTitle(formSettings.title).setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function include(file) {
return HtmlService.createHtmlOutputFromFile(file).getContent();
}
function doPost(meta) {
if (!meta || !meta.auth) {
throw new Error('not authorized');
return;
}
var auth = JSON.parse(UrlFetchApp.fetch('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + meta.auth.access_token, { muteHttpExceptions: true }).getContentText());
if (auth.error || !auth.email) {
throw new Error('not authorized');
return;
}
if (typeof this[meta.method + '_'] == 'function') {
return this[meta.method + '_'](auth.email, meta.data);
}
throw new Error('unknown method');
}
function test_(email, data) {
return email;
}
Index.html:
<html>
<head>
<?!= include('JavaScript'); ?>
</head>
<body>
<div class="content-wrapper">
</div>
</body>
</html>
Javascript.html:
<script type='text/javascript' src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type='text/javascript' src="//apis.google.com/js/client.js?onload=apiLoaded" async></script>
<script type='text/javascript'>
var clientId = '*************-********************************.apps.googleusercontent.com';
var scopes = ['https://www.googleapis.com/auth/plus.me', 'https://www.googleapis.com/auth/userinfo.email'];
var loaded = false;
var auth = null;
function apiLoaded() {
loaded = true;
login();
}
window._open = window.open;
window._windows = [];
window.open = function(url) {
var w = window._open.apply(window,arguments);
window._windows.push(w);
return w;
}
function login(step) {
step || (step = 0);
if (!loaded) {
return;
}
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: (step <= 0 || step >= 2) }, function(authResult) {
if (authResult) {
if (authResult.error) {
if (authResult.error == 'immediate_failed' && authResult.error_subtype == 'access_denied' && step <= 0) {
var interval = setInterval(function() {
var $ifr = $('iframe');//[id^=oauth2relay]');
if (!window._windows.length) {
clearInterval(interval);
return;
}
if ($ifr.length) {
clearInterval(interval);
$ifr.detach();
var w = window._windows.pop();
if (w) {
var interval2 = setInterval(function() {
if (w.closed) {
clearInterval(interval2);
$('body').append($ifr);
login(2);
}
});
} else {
$('body').append($ifr);
}
}
},500);
login(1);
} else if (authResult.error == 'immediate_failed' && authResult.error_subtype == 'access_denied' && step >= 2) {
//user canceled auth
} else {
//error
}
} else {
auth = authResult;
doPost('test', { some: 'data' }, 'test');
}
} else {
//error
}
});
}
function test() {
console.log(arguments);
}
//with this method you can do a post request to webapp server
function doPost(method, data, callbackName) {
data || (data = {});
google.script.run.withSuccessHandler(onSuccess).withFailureHandler(onError).withUserObject({ callback: callbackName }).doPost({ method: method, data: data, auth: auth });
}
function onSuccess(data, meta) {
if (typeof window[meta.callback] == 'function') {
window[meta.callback](null, data);
}
}
function onError(err, meta) {
if (typeof window[meta.callback] == 'function') {
window[meta.callback](err);
}
}
</script>
So I've setup my first JS design pattern - but I've run into an issue.
Here is my code on fiddle:
http://jsfiddle.net/jrutter/CtMNX/
var emailSignup = {
'config': {
// Configurable Options
'container': $('#email-signup'),
'emailButton': $('#email-submit'),
'emailInput': $('#email-input'),
'brontoDirectAddURL': 'URL',
'brontoListID': '0bbc03ec000000000000000000000003287b',
},
'init': function (config) {
// stays the same
// provide for custom configuration via init()
if (config && typeof (config) == 'object') {
$.extend(emailSignup.config, config);
}
// Create and/or cache some DOM elements
emailSignup.$container = $(emailSignup.config.container);
emailSignup.$button = $(emailSignup.config.emailButton);
emailSignup.$input = $(emailSignup.config.emailInput);
emailSignup.$brontoURL = emailSignup.config.brontoDirectAddURL;
emailSignup.$brontoList = emailSignup.config.brontoListID;
// Add email track to drop image pixel into for submission
emailSignup.$container.append('<div class="email-error"></div>');
emailSignup.$container.append('<div class="email-track"></div>');
// Call getEmaile
emailSignup.getEmail(emailSignup.$button, emailSignup.$input);
// make a note that the initialization is complete
emailSignup.initialized = true;
},
'getEmail': function ($button, $input) {
// click event
emailSignup.$button.click(function () {
// get the val
var $emailVal = $(emailSignup.$input).val();
// Call validateEmail
console.log($emailVal);
emailSignup.validateEmail($emailVal);
return false;
});
},
'validateEmail': function ($emailVal) {
var $emailRegEx = /^([\w-\.]+#([\w-]+\.)+[\w-]{2,4})?$/;
//console.log($emailVal);
if ($emailVal == '') {
$(".email-error").html('<p>You forgot to enter an email address.</p>');
} else if (!$emailRegEx.test($emailVal)) {
$(".email-error").html('<p>Please enter a valid email address.</p>');
} else {
$(".email-error").hide();
emailSignup.submitEmail($emailVal);
}
},
'submitEmail': function ($emailVal) {
$(".email-track").html('<img src=' + emailSignup.$brontoURL+'&email='+$emailVal+'&list1=' + emailSignup.$brontoList + '" width="0" height="0" border="0" alt=""/>');
},
};
Its a function to add a subscriber to an email list via bronto - it works perfectly when the script is included on the page and the init function is setup on the page too. But when I include the script in a shared header and try to fire the function from the document-ready, it doesnt seem to be working.
Also, if I try to pass in a 'container' - that also is breaking the script. Not sure what Im doing wrong? But if I pass in the URL - that does work!
$(function () {
emailSignup.init({
'brontoDirectAddURL':'URL','container':'#email-signup'
});
});
Any advice would be greatly appreciated!
Change the following code...
emailSignup.$container = emailSignup.config.container;
emailSignup.$button = emailSignup.config.emailButton;
emailSignup.$input = emailSignup.config.emailInput;
emailSignup.$brontoURL = emailSignup.config.brontoDirectAddURL;
emailSignup.$brontoList = emailSignup.config.brontoListID;
into the following...
// Create and/or cache some DOM elements
emailSignup.$container = $(emailSignup.config.container);
emailSignup.$button = $(emailSignup.config.emailButton);
emailSignup.$input = $(emailSignup.config.emailInput);
emailSignup.$brontoURL = $(emailSignup.config.brontoDirectAddURL);
emailSignup.$brontoList = $(emailSignup.config.brontoListID);
// Add email track to drop image pixel into for submission
emailSignup.$container.append('<div class="email-error"></div>');
emailSignup.$container.append('<div class="email-track"></div>');
You can not call append on a string. I've update your JSFiddle.
Your default config object contains jQuery collections. However, you are simply passing the string "#email-signup" as your container instead of $("#email-signup"). That's where the error is coming from. Your initial call should thusly be:
$(function () {
emailSignup.init({
'brontoDirectAddURL':'URL','container': $('#email-signup')
});
});
Note that as your initial module includes some jQuery calls, you will need to wrap the whole emailSignup mess into a $(document).ready() as well.
You may consider reconfiguring this whole thing as a jQuery plugin.
I am trying to wrap a PhantomJS script in a node.js process. The phantom script grabs a url from the arguments provided on the command line and outputs a pdf (much similar to the rasterize.js example included with the pahntom install).
The phantom script I have works fine, it's just my employer wants a node script if possible. No problem, I can use the node-phantom node module to wrap it.
But now I've hit a stumbling block, my phantom script has:
var page = require('webpage').create();
So, node.js is trying to find a module called 'webpage', the 'webpage' module is built into the phantom install so node can't find it. As far as I can tell, there is no npm module called 'webpage'.
'webpage' is used like this:
page.open(address, function (status) {
if (status !== 'success') {
// --- Error opening the webpage ---
console.log('Unable to load the address!');
} else {
// --- Keep Looping Until Render Completes ---
window.setTimeout(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
where address is the url specified on the command line and output is another argument, the name and type of the file.
Can anyone help me out? This is quite an abstract one so I'm not expecting much if I'm honest, worth a try though.
Thanks.
EDIT - Approx 2hrs later
I now have this which throws out a PDF:
var phanty = require('node-phantom');
var system = require('system');
phanty.create(function(err,phantom) {
//var page = require('webpage').create();
var address;
var output;
var size;
if (system.args.length < 4 || system.args.length > 6) {
// --- Bad Input ---
console.log('Wrong usage, you need to specify the BLAH BLAH BLAH');
phantom.exit(1);
} else {
phantom.createPage(function(err,page){
// --- Set Variables, Web Address, Output ---
address = system.args[2];
output = system.args[3];
page.viewportSize = { width: 600, height: 600 };
// --- Set Variables, Web Address ---
if (system.args.length > 4 && system.args[3].substr(-4) === ".pdf") {
// --- PDF Specific ---
size = system.args[4].split('*');
page.paperSize = size.length === 2 ? { width: size[0], height: size[1], margin: '0px' }
: { format: system.args[4], orientation: 'portrait', margin: '1cm' };
}
// --- Zoom Factor (Should Never Be Set) ---
if (system.args.length > 5) {
page.zoomFactor = system.args[5];
} else {
page.zoomFactor = 1;
}
//----------------------------------------------------
page.open(address ,function(err,status){
if (status !== 'success') {
// --- Error opening the webpage ---
console.log('Unable to load the address!');
} else {
// --- Keep Looping Until Render Completes ---
process.nextTick(function () {
page.render(output);
phantom.exit();
}, 200);
}
});
});
}
});
But! It's not the right size! The page object created using the phantom 'webpage' create() function looks like this before it's passed the URL:
Whereas mine in my node script, looks like this:
Is it possible to hard code the properties to achieve A4 formatting? What properties am I missing?
I'm so close!
It should be something like:
var phantom=require('../node-phantom');
phantom.create(function(error,ph){
ph.createPage(function(err,page){
page.open(url ,function(err,status){
// do something
});
});
});
Your confusion here is because you want to reuse the same concepts and metaphors from your PhantomJS script. It does not work that way. I suggest that you spend some time studying the included tests of node-phantom, see https://github.com/alexscheelmeyer/node-phantom/tree/master/test.
Using https://github.com/sgentle/phantomjs-node I have made an A4 page in nodejs using phantom with the following code:
phantom.create(function(ph){
ph.createPage(function(page) {
page.set("paperSize", { format: "A4", orientation: 'portrait', margin: '1cm' });
page.open("http://www.google.com", function(status) {
page.render("google.pdf", function(){
console.log("page rendered");
ph.exit();
})
})
})
});
Side Note:
the page.set() function takes any variable that you would set in the rasterize.js example. See how paperSize is set above and compare it to the relevant lines in rasterize.js
I want to prevent my system from loading the same script more than once, because different modules can be combined and I use third party libraries that I don't want to manipulate.
Has anyone done this before?
How about RequireJS? Seems to be what you're looking for.
As the libraries such as Require JS didn't solve my problems, I made my own solution, which I'm posting below.
My system is also made by different modules. In the main module, I have a loader for the dependencies of all the modules (php, js and css files). After the dependencies have been loaded, the app triggers an event and sets a global variable which prevents double inclusion of files.
Hope it helps. If you have any doubts, just let me know.
The code:
//Main
var main = {
init: function(){
//Dependencies to load (php, js or css)
var deps = [
'/helpers/edit/v/edit.php',
'/helpers/edit/css/edit.css',
'/helpers/validate/js/jquery.validate.js,messages_pt_BR.js'
];
//Load initial pack
if (!window.editReady){
//Load dependencies
this.load('edit',deps);
//Bind loaded event
$('body').on('editReady',function(){
//Set editLoaded to avoid double ajax requests
window.editReady = true;
//Do whatever you need after it's loaded
});
}
},
//Load external resources
load: function(name,data_urls){
var url, ext;
var len = data_urls.length;
var i = 0;
$(data_urls).each(function(){
//Get proper file
$.get(this, function(data) {
url = this.url;
ext = url.split('.').pop();
switch(ext){
case 'php':
this.appended
$(data).appendTo('body');
break;
case 'css':
$('<link/>')
.attr({
'rel':'stylesheet',
'href':url
}).appendTo('head');
break;
}
//Check if all files are included
i += 1;
if (i == len) {
$("body").trigger(name+"Ready");
}
});
});
}
};
var modules = {
themes : {
init : function(){
//Load dependencies
var deps = [
'/helpers/plupload/js/plupload.js,plupload.html5.js,plupload.flash.js'
];
if (!window.themesReady){
//Set themesReady to avoid double ajax requests
window.themesReady = true;
//Load dependencies
main.load('themes',deps);
$('body').on('themesReady',function(){
//Do whatever you need after it's ready
});
}
}
}
}
main.init();
I am new to phantomjs, Java script and WebScraping in General. What I want to do is basic http authentication and then visit another URL to get some information. Here is what I have till now. Please tell me what I am doing wrong.
var page = require('webpage').create();
var system = require('system');
page.onConsoleMessage = function(msg) {
console.log(msg);
};
page.onAlert = function(msg) {
console.log('alert!!>' + msg);
};
page.settings.userName = "foo";
page.settings.password = "bar";
page.open("http://localhost/login", function(status) {
console.log(status);
var retval = page.evaluate(function() {
return "test";
});
console.log(retval);
page.open("http://localhost/ticket/" + system.args[1], function(status) {
if ( status === "success" ) {
page.injectJs("jquery.min.js");
var k = page.evaluate(function () {
var a = $("div.description > h3 + p");
if (a.length == 2) {
console.log(a.slice(-1).text())
}
else {
console.log(a.slice(-2).text())
}
//return document.getElementById('addfiles');
});
}
});
phantom.exit();
});
I am passing an argument to this file: a ticket number which gets appended to the 2nd URL.
I would recommend CasperJS highly for this.
CasperJS is an open source navigation scripting & testing utility written in Javascript and based on PhantomJS — the scriptable headless WebKit engine. It eases the process of defining a full navigation scenario and provides useful high-level functions, methods & syntactic sugar for doing common tasks such as:
defining & ordering browsing navigation steps
filling & submitting forms
clicking & following links
capturing screenshots of a page (or part of it)
testing remote DOM
logging events
downloading resources, including binary ones
writing functional test suites, saving results as JUnit XML
scraping Web contents
(from the CasperJS website)
I recently spent a day trying to get PhantomJS by itself to do things like fill out a log-in form and navigate to the next page.
CasperJS has a nice API purpose built for forms as well:
http://docs.casperjs.org/en/latest/modules/casper.html#fill
var casper = require('casper').create();
casper.start('http://some.tld/contact.form', function() {
this.fill('form#contact-form', {
'subject': 'I am watching you',
'content': 'So be careful.',
'civility': 'Mr',
'name': 'Chuck Norris',
'email': 'chuck#norris.com',
'cc': true,
'attachment': '/Users/chuck/roundhousekick.doc'
}, true);
});
casper.then(function() {
this.evaluateOrDie(function() {
return /message sent/.test(document.body.innerText);
}, 'sending message failed');
});
casper.run(function() {
this.echo('message sent').exit();
});