Use casper function inside casper.evaluate() - javascript

it's possible using casper function inside casper.evaluate() with jquery code inside? I need to iterare elements in a way similar to how jquery does.
I'm loading jquery.js lib
This is my try script:
casper.evaluate(function(){
$('#size-modal .size-panel-title a').each(function(){
$(this).click();
accordionTab = $(this).attr('href');
casper.capture(screenShotOutput + "PDP-" + accordionTab +".png");
});
});
in this page there are 2 accordion and i want a screenshot for each opened accordion.
It seems to works but no feedback is given and it exit form evaluate() on the first capture() iteration.
the test pass without making screenshot.
If i add after evaluate()
casper.capture(screenShotOutput + "PDP-accordion.png");
and comment the capture() inside the evaluate(), i can see that the code before works well, the screenshot is made and each accordion is open.
The problem is that casper use javascript selector so if i specify only
casper.click('#size-modal .size-panel-title a');
casper.capture(screenShotOutput + "PDP-" + accordionTab +".png");
without using casper.evaluate() only one accordion will be opened.
Thanks

What ever you do in "casper.evaluate" is simillar to writing the same piece of code in the console of the browser.Think of it this way and you will know what mistake you have made.
"casper.capture" is a casper specific syntax and no browser understand it.
also this is the reference from the doc's
The concept behind this method is probably the most difficult to understand when discovering CasperJS. As a reminder, think of the
evaluate() method as a gate between the CasperJS environment and the one of the page you have opened;
Everytime you pass a closure to evaluate(), you’re entering the page and execute code as if you were using the browser console.
I hope the picture might help:
And i Agree with whatever #Artjom B. has suggested.

casper.evaluate() is the sandboxed page context. It has no access to casper or other variables that are defined outside.
There are two possibilities to solve this.
Move the loop outside of the page context
var a = '#size-modal .size-panel-title a';
var len = casper.getElementsInfo(a).length;
for(var i = 0; i < len; i++) {
casper.evaluate(function(i, a){
var el = $($(a)[i]);
el.click();
return el.attr('href');
}, i, a);
casper.capture(screenShotOutput + "PDP-" + accordionTab +".png");
}
Trigger capture from the page context
There is the PhantomJS function callPhantom which makes it possible to trigger an event on the outside from the page context:
casper.page.onCallback = function(data){
casper.capture(screenShotOutput + "PDP-" + data +".png");
};
casper.evaluate(function(){
$('#size-modal .size-panel-title a').each(function(){
$(this).click();
window.callPhantom($(this).attr('href'));
});
});

Related

Can't center an element generated from XMLHttpRequest

I am building a fairly complex web app. The main page loads and then all menu items (300+), form submissions, ect. are loaded via XMLHttpRequest. I have a basic "panel" template that allows the the panel to look and act (drag, resize, ect.) like a child window of the app. I load all XMLHttpRequest requested pages into the the content section of the "panel" template.
The problem I am running into is that if I try to center the new "panel" it does not seem to find the new "panels" size. My code is setup so that when a menu item is clicked it runs a function that calls the XMLHttpRequest function, the originating function passes to the XMLHttpRequest a callback function. The callback function then clones the panel template, so I can change several element attributes, I then append the response to the cloned "panel" template, in a document fragment. And then all that is appended to the displayed HTML, after which I find the new "panels" size and try to center it but it always fails.
As each function has a lot more going on than just what I spelled out above what follows is hopefully an accurate striped down version of the relevant parts of the code.
The XMLHttpRequest function in nothing unusual, and once it has a successful response the callback will run the "OpenPanel" function (see below).
Callback function:
function OpenPanel(e, response)
{
var rescontent = response.querySelector('.content');
var newid = rescontent.getAttribute('data-id');
var titlebar = rescontent.getAttribute('data-title');
var frag = document.createDocumentFragment();
var clonepanel = doc.getElementById('paneltemplate').cloneNode(true);
clonepanel.id = newid;
frag.appendChild(clonepanel);
frag.querySelector('.titlebar').innerHTML = titlebar;
var replacelem = frag.querySelector('.content');
replacelem.parentNode.replaceChild(rescontent, replacelem);
doc.getElementById('mainbody').appendChild(frag);
var newpanel = document.getElementById(newid);
newpanel.addEventListener('mousedown', PanelSelect, true);
newpanel.style.cssText = PanelPosition(newpanel);
}
PanelPosition function:
function PanelPosition(panel)
{
var lh = panel.clientHeight;
var lw = panel.clientWidth;
var wh = panel.parentNode.clientHeight;
var ww = panel.parentNode.clientWidth;
var paneltoppos = (wh - lh) / 2;
var panelleftpos = (ww - lw) / 2;
return 'top: ' + paneltoppos + 'px; left: ' + panelleftpos + 'px;';
}
I tried using setTimeout with a delay of 1ms, but that causes the panel to flash on the screen, in the wrong position, before its moved. Which from my perspective makes the app feel cheap or like only a second best effort was given. And even if it didn't flash setTimeout seems like a hack more than a solution.
I have tried this code with a few different "pages" (xhr requests) and I almost get the sense that the XMLHttpRequest hasn't finished loading when the callback function is ran (which I doubt is possible). For example, I put
console.log('top: '+wh+' - '+lh+'(wh - lh) left: '+ww+' - '+lw+'(ww - lw)');
in the "PanelPosition" function, and without the setTimeout the panel height (lh) and width (lw) are between 100 and 200 pixels. But with setTimeout the panels usually are over 500 pixels in height and width. And of course that severely effects where centered is.
I have tried several searches over the last few days but nothing has turned up. So if there is a good post or article describing the problem and the solution, feel free point me to it.
Should note that as I am running the web app exclusively in node-webkit/nw.js (chromium/webkit browser) there is no need for a cross-browser solution.
Well I guess I am going to answer my own question.
While looking for something completely unrelated I found this this SO post. The accepted answer gives a clue that explains the issue.
Unfortunately, it seems that you have to hand the controls back to the browser (using setTimeout() as you did) before the final dimensions can be observed; luckily, the timeout can be very short.
Basically javascript does not draw the appended element till the end of the function call. So setTimeout is one solution to my problem. But, there is a second solution. If I just have to wait till the end of the function then lets make that one heck of a small (focused) function. So I just moved all the code need to create the appended "panel" to a totally separate function. And in so doing I solved another pending issue.
Edit:
Or not. Apparently a separate function doesn't work now, but it did before I posted. Who knows maybe I didn't save the change before reloading the page.

Script not working on page load, but working from console

I wrote a script that's running from ebay listing iframe. It's working fine, it runs on $(document).ready(), sends an AJAX request to a remote server, gets some info, manipulate the DOM on 'success' callback, everything working perfect...
However, I added a piece of code, which should get the document.referrer, and extract some keywords from it, if they exist. Specifically, if a user searches ebay for a product, and clicks on my product from the results, the function extracts the keywords he entered.
Now, the problem is, that function is not running on page load at all. It seems like it blocks the script when it comes to that part. This is the function:
function getKeywords(){
var index = window.parent.document.referrer.indexOf('_nkw');
if (index >= 0){
var currentIndex = index + 5;
var keywords = '';
while (window.parent.document.referrer[currentIndex] !== '&'){
keywords += window.parent.document.referrer[currentIndex++];
}
keywords = keywords.split('+');
return keywords;
}
}
And I tried calling two logs right after it:
console.log('referrer: ' + window.parent.document.referrer);
console.log(getKeywords());
None of them is working. It's like when it comes to that 'window.parent.document.referrer' part, it stops completely.
But, when I put this all in a console, and run it, it works perfectly. It logs the right referrer, and the right keywords.
Does anybody know what might be the issue here?
The reason it is working on the console is because your window object is the outer window reference and not your iframe. Besides that, on the console:
window.parent === window
// ==> true
So, on in fact you are running window.document.referrer and not your frame's window.parent.document.referrer.
If you want to access your frame's window object you should something like
var myFrame = document.getElementsByClassName('my-frame-class')[0];
myFrame.contentWindow === window
// ==> false
myFrame.contentWindow.parent.window === window
// ==> true
This might help you debug your problem, but I guess the browser is just preventing an inner iframe from accessing the parent's window object.

chrome.tabs.create/executeScript > call function that belongs to the page

I'm developing an extension for Google Chrome and the problem I'm having is I need to be able to call a JavaScript function that belongs to the webpage that's opened in the tab.
For details, the website is my website, therefore I know that function does exist. That function does a lot of things based on a string value. I want the user to be able to highlight text on any webpage, click a button from the Chrome extension that automatically loads my webpage and calls that function with the highlighted text as it's value.
Here's what I got so far:
chrome.tabs.create({ url: "https://mywebsite.com" }, function (tab) {
var c = "initPlayer('" + request.text + "');"; ////'request.text' is the highlighted text which works
chrome.tabs.executeScript(tab.id, { code: c });
});
But Chrome console says: "Uncaught ReferenceError: initPlayer is not defined."
I know that function does exist as it is in my code on my own website.
Any help is highly appreciated. Thanks!
This happens because pages and content scripts run inside two separate javascript contexts. This means that content scripts cannot acces functions and variables inside a page directly: you'll need to inject a script into the page itself to make it work.
Here is a simple solution:
chrome.tabs.create({url: "https://mywebsite.com"}, function (tab) {
var c = "var s = document.createElement('script');\
s.textContent = \"initPlayer('" + request.text + "');\";\
document.head.appendChild(s);"
chrome.tabs.executeScript(tab.id, {code: c});
});
NOTE: since January 2021, use Manifest V3 with chrome.scripting.executeScript() instead of chrome.tabs.executeScript().
With the above code you will basically:
Create the tab
Inject the code (variable c) into it as a content script that will:
Create a script with the code you want to execute on the page
Inject the script in the page and, therefore, run its code in the page context

JavaScript page bruteforcer

I have written a very simple script to test one page for possible discounts options.
But I have faced the problem that once I do 'button.click();' the page loading is blocked until I complete my function, so following actions like button.click(); do not make sense.
Is that possible to make page to load while I am inside of the function? This should be ran in Developer toolbar in safari (I believe I cannot set onload event for the page on my side, so need to do that without using even handlers).
var card = document.getElementById('discount_card');
var button = $('.buttonTotal');
var disc_val = document.getElementById('cart_discount').firstChild;
for(var i=init;i<=finish;i++){
card.value = i;
button.click();
disc = disc_val.data;
if(disc > my_discount) console.log(disc + " : " + i);
}
Does order of buttons clicking matters? If no, try to replace button.click(); with (function (button) {setTimeout(function () {button.click();}, 1)})(button);
If order matters, you still can use setTimeout, but you'll be needed to change your script logic (replace loop with recursion).

Chrome JavaScript location object

I am trying to start 3 applications from a browser by use of custom protocol names associated with these applications. This might look familiar to other threads started on stackoverflow, I believe that they do not help in resolving this issue so please dont close this thread just yet, it needs a different approach than those suggested in other threads.
example:
ts3server://a.b.c?property1=value1&property2=value2
...
...
to start these applications I would do
location.href = ts3server://a.b.c?property1=value1&property2=value2
location.href = ...
location.href = ...
which would work in FF but not in Chrome
I figured that it might by optimizing the number of writes when there will be effectively only the last change present.
So i did this:
function a ()
{
var apps = ['ts3server://...', 'anotherapp://...', '...'];
b(apps);
}
function b (apps)
{
if (apps.length == 0) return;
location.href = apps[0]; alert(apps[0]);
setTimeout(function (rest) {return function () {b(rest);};} (apps.slice(1)), 1);
}
But it didn't solve my problem (actually only the first location.href assignment is taken into account and even though the other calls happen long enough after the first one (thanks to changing the timeout delay to lets say 10000) the applications do not get started (the alerts are displayed).
If I try accessing each of the URIs separately the apps get started (first I call location.href = uri1 by clicking on one button, then I call location.href = uri2 by clicking again on another button).
Replacing:
location.href = ...
with:
var form = document.createElement('form');
form.action = ...
document.body.appendChild(form);
form.submit();
does not help either, nor does:
var frame = document.createElement('iframe');
frame.src = ...
document.body.appendChild(frame);
Is it possible to do what I am trying to do? How would it be done?
EDIT:
a reworded summary
i want to start MULTIPLE applications after one click on a link or a button like element. I want to achieve that with starting applications associated to custom protocols ... i would hold a list of links (in each link there is one protocol used) and i would try to do "location.src = link" for all items of the list. Which when used with 'for' does optimize to assigning only once (the last value) so i make the function something like recursive function with delay (which eliminates the optimization and really forces 3 distinct calls of location.src = list[head] when the list gets sliced before each call so that all the links are taken into account and they are assigned to the location.src. This all works just fine in Mozilla Firefox, but in google, after the first assignment the rest of the assignments lose effect (they are probably performed but dont trigger the associated application launch))
Are you having trouble looping through the elements? if so try the for..in statement here
Or are you having trouble navigating? if so try window.location.assign(new_location);
[edit]
You can also use window.location = "...";
[edit]
Ok so I did some work, and here is what I got. in the example I open a random ace of spades link. which is a custom protocol. click here and then click on the "click me". The comments show where the JSFiddle debugger found errors.

Categories

Resources