in phantomjs,how to pass variables from page context to outside the page context - phantomjs

consider the following snippet.how can i access the value of the page url outside the page context?globally accessing the value was not working either.callbacks wasn't clear to me in approach.
page.onUrlChanged = function(targetUrl) {
console.log('New URL: ' + targetUrl);
};
page.onConsoleMessage = function (msg) {
console.log(msg);
};
var abc=page.open(url,function(status){
page.evaluate(function(){
//some code;
})
return page.url;
});
console.log(abc);
the code always gives undefined page url.

PhantomJS documents are very much recommended: http://phantomjs.org/api/webpage/method/evaluate.html
page.open(url,function(status){
var current_url = page.evaluate(function(){
return document.location.href;
})
console.log(current_url);
});

Related

How to mock an image with a fixture in cypress

I'm using cypress to test my VueJS application. The one thing I'm having trouble with is mocking an image to be displayed on the page. For my use case, I'm simply loading a user profile with the following code:
describe('Test Login', () => {
it('Can Login', () => {
cy.server();
cy.route({
method: 'GET',
url: '/api/account/',
response: 'fx:profile.json',
});
cy.route('**/media/demo1.png', 'fx:demo1.png');
});
});
fixtures/profile.json
{
"avatar": "http://localhost:8080/media/demo1.png",
"username": "cypress",
"email": "email#cypress.io",
"pk": 1,
"is_staff": true,
"is_superuser": true,
"is_active": true
}
The profile fixture data is loading correctly in the test. In my fixtures folder, I also have a demo1.png file. I am expecting this image to be loaded and displayed on the page during my test, but it is being displayed as a broken image.
In the network tab, it shows demo1.png as a broken image with a 200 response code and type of text/html.
The cypress documentation mostly discusses images in the context of uploading images, but I haven't been able to find an example of how I can mock an image that is loaded through a <img> tag. Is there an easier way of doing this?
I am not sure if this answer can help you. But at least it is a workaround for this problem ;-)
Say we have a HTML like this:
<html>
<body>
<button id="button">load</button>
<div id="profile">
</div>
<script>
function httpGetAsync(theUrl, callback)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
callback(JSON.parse(xmlHttp.responseText));
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
document.getElementById("button").addEventListener("click", () => {
httpGetAsync("/api/account/", (result) => {
var div = document.querySelector("#profile");
var img = document.createElement("img");
img.src = result.avatar;
div.appendChild(img)
})
})
</script>
</body>
</html>
source: HTTP GET request in JavaScript?
And you want to load the profile after the click was done. Then you can use MutationObserver to replace the img.src.
First, write the MutationObserver:
var observeDOM = (function(){
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
return function( obj, callback ){
if( !obj || !obj.nodeType === 1 ) return; // validation
if( MutationObserver ){
// define a new observer
var obs = new MutationObserver(function(mutations, observer){
callback(mutations);
})
// have the observer observe foo for changes in children
obs.observe( obj, { childList:true, subtree:true });
}
else if( window.addEventListener ){
obj.addEventListener('DOMNodeInserted', callback, false);
obj.addEventListener('DOMNodeRemoved', callback, false);
}
}
})();
(heavily copy & pasted from Detect changes in the DOM)
Now you are able to do this:
describe('Test Login', () => {
it('Can Login', () => {
var win = null;
cy.server();
cy.route({
method: 'GET',
url: '/api/account/',
response: 'fx:profile.json'
});
cy.visit("index.html").then(w => {
cy.get("#profile").then(pro => {
var e = pro[0];
observeDOM(e, (m) => {
// add a red dot image
m[0].addedNodes[0].src = "data:image/png;base64,"+
"iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGP"+
"C/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IA"+
"AAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1J"+
"REFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jq"+
"ch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0"+
"vr4MkhoXe0rZigAAAABJRU5ErkJggg=="
})
})
cy.get("button").click()
})
});
});
(yeah at least some lines of code are written on my own ;-P)
You can read the image from the img.src attribute from the fixtures folder. For the sake of simplicity I have used a static base64 string here.
And the result:
We are not using this kind of stuff in our aurelia app but I tried similar things in a private project some time ago.

Meter.call() pass variables

I'm trying to use Meteor.call() and pass two variables in to complete the method
Template.RegisterForm.events({
'submit form': function(event) {
event.preventDefault();
var emailVar = event.target.registerEmail.value;
var passwordVar = event.target.registerPassword.value;
Meteor.call('registerUser', emailVar, passwordVar )
}
});
However when I call the method, emailVar and passwordVar do not pass through.
registerUser: function (email, password) {
var id = Accounts.createUser({
email: email,
password: password
});
}
they come up undefined??
You're calling the Meteor method fine,
var emailVar = event.target.registerEmail.value;
var passwordVar = event.target.registerPassword.value;
is likely your problematic code. event.target refers to the DOM element that initiated the event, in this case being the form. In turn, registerEmail and registerPassword will be undefined
See
https://jsfiddle.net/fqp3a1mq/ for an overview on using jQuery to grab your form data.
In the context of your specific issue, a clean solution is:
Template.form.events({
'submit form'(event) {
const $form = $(event.target);
const formData = $form.serializeArray();
const params = {};
event.preventDefault();
// loop over our form data and create a params object for our method
formData.forEach(data => {
params[data.name] = data.value;
});
Meteor.call('methodName', params);
},
});
Meteor.methods({
methodName(params) {
...
},
});
With this approach, if you have an input named registerEmail, then it will exist as params.registerEmail in your Meteor method.
Hope this helps.

JQuery include in phantomjs is not working

My code is:
var url = 'https://search.yahoo.com/',
page = new WebPage(),
fs = require('fs');
page.settings.userAgent = 'Mozilla/5.0 (platform; rv:geckoversion) Gecko/geckotrail appname/appversion';
page.onConsoleMessage = function (msg) {
console.log(msg);
};
page.open(url, function(status) {
if (status !== 'success')
{
console.log('Unable to access network');
phantom.exit();
return;
}
else
{
page.includeJs("https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js", function()
{
page.evaluate(function()
{
$('#yschsp').val("ask question");
$(".sbb").click();
});
page.onLoadFinished = function(status) {
var content = page.content;
fs.write('1.html', content, 'w');
console.log($('#link-1').val());
phantom.exit();
};
});
}
});
JQuery works perfect in page.evaluate but does not work in page.onLoadFinished. I get an error
Can't get variable: $
That means that in function page.onLoadFinished jquery is not working. But I can not understand why?
Since jQuery is loaded into the page context, you can only use there. The only function that interfaces with the page context is evaluate (and the other evaluate functions).
So this line
console.log($('#link-1').val());
must be inside of an evaluate callback. Since you have a page.onConsoleMessage event handler you will receive the console message from the page context.
The other thing is that adding an page.onLoadFinished event handler after the page has loaded isn't doing anything useful. You can remove the handler surrounding your code since the page load is finished when the page.open callback is called.
If #link-1 is not yet loaded, you should either log the value after a static timeout (setTimeout) or use waitFor.

PhantomJS passing variable to page.open

I need to capture an URL on one page and then open the target. What's the most elegant solution? The code below doesn't work, because the variable url is basically local.
function() {
page.open("https://www.google.com/blah");
},
function() {
page.evaluate(function() {
var url=document.getElementById('link42')[0]; //URL captured
});
},
//opening the target
function() {
page.open(url);
},
function() {
page.evaluate(function() {
console.log(document.querySelectorAll('html')[0].outerHTML);
});
}
You can probably make it global. You just have to mind how you get the url out of the page context. It is not possible to return DOM elements, but you can return everything that is JSON serializable.
Quote from page.evaluate:
Note: The arguments and the return value to the evaluate function must be a simple primitive object. The rule of thumb: if it can be serialized via JSON, then it is fine.
var url;
// function chainer
function() {
page.open("https://www.google.com/blah");
},
function() {
url = page.evaluate(function() {
return document.getElementById('link42').href; // href captured
});
},
//opening the target
function() {
page.open(url);
}
getElementById only returns a single element, so you can't use [0].

Exposing variables from PhantomJS call to injectJS

I've followed examples for injecting jQuery from the getting started page and that works just fine. I have a local copy of jQuery in the same directory, and do something like...
if(page.injectJs('jquery.min.js')) {
page.evaluate(function(){
//Use jQuery or $
}
}
When I try to inject my own script(s), none of the functions are available to me. Say I have a script called myScript.js that just has
function doSomething() {
// doing something...
}
I cannot then use doSomething like...
if(page.injectJs('myScript.js')) {
console.log('myScript injected... I think');
page.evaluate(function() {
doSomething();
});
} else {
console.log('Failed to inject myScript');
}
I've tried
window.doSomething = function() {};
and
document.doSomething = function() {};
as well with no luck, as well as trying to call them with window.doSomething() or document.doSomething() in the subsequent page.evaluate().
The following works for me, maybe some other part of your app logic is wrong:
inject.coffee
page = require('webpage').create()
page.onConsoleMessage = (msg) -> console.log msg
page.open "http://www.phantomjs.org", (status) ->
if status is "success"
page.includeJs "http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js", ->
if page.injectJs "do.js"
page.evaluate ->
title = echoAndReturnTitle('hello')
console.log title
phantom.exit()
do.coffee:
window.echoAndReturnTitle = (arg) ->
console.log "echoing '#{arg}'"
console.log $(".explanation").text()
return document.title
Result:
> phantomjs inject.coffee
echoing 'hello'
PhantomJS is a headless WebKit with JavaScript API.
It has fast and native support for various web standards:
DOM handling, CSS selector, JSON, Canvas, and SVG.
PhantomJS is created by Ariya Hidayat.
PhantomJS: Headless WebKit with JavaScript API
or if you prefer JavaScript (they're auto-generated and a little ugly):
`inject.js':
// Generated by CoffeeScript 1.3.1
(function() {
var page;
page = require('webpage').create();
page.onConsoleMessage = function(msg) {
return console.log(msg);
};
page.open("http://www.phantomjs.org", function(status) {
if (status === "success") {
return page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js", function() {
if (page.injectJs("do.js")) {
page.evaluate(function() {
var title;
title = echoAndReturnTitle('hello');
return console.log(title);
});
return phantom.exit();
}
});
}
});
}).call(this);
do.js:
// Generated by CoffeeScript 1.3.1
(function() {
window.echoAndReturnTitle = function(arg) {
console.log("echoing '" + arg + "'");
console.log($(".explanation").text());
return document.title;
};
}).call(this);