This is my code to get the google translate of the word am giving in the program.Am using phantomjs to achieve this.
Am passing the "Word:asdasdasd" to the text area with the id "source" and triggering the click event.After that trying to check whether the result value which is wrapped under the newly created span tag or not.For that am checking whether its parent with the id "result_box" has more than one childern. If its a case,then we can say span tag is created dynamically once the value is passed to textarea.
var page = require('webpage').create();
console.log("Entering the program");
page.onConsoleMessage = function (msg) {
console.log(msg);
};
page.open("http://translate.google.com", function(status) {
if(status == 'success'){
console.log("Page loaded "+ status);
page.includeJs("//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js", function() {
setTimeout(function() {
var data = page.evaluate(function() {
$('#source').val("asdasdasd");
var ev = document.createEvent("MouseEvents");
ev.initEvent("click", true, true);
document.querySelector("input[id='gt-submit']").dispatchEvent(ev);
console.log("Click on page ");
if( $('#result_box').children.length > 0 ){
console.log("New Span included");
console.log("value: " + $('#result_box').val());
}
else{
console.log("No span");
}
});
console.log("Exiting the program");
phantom.exit()
}, 10000);
});
}
});
Output:
Entering the program
Page loaded success
Click on page
New Span included
value:
Exiting the program
My question here is it says new span tag is created but am not able to get the value of it.Its strange.Whats wrong with the above code?.
Please anyone point me the solution.
Thanks in advance
Update:
Adding points to #Neha,
this worked for me.
setTimeout(function(){
console.log("After 5 sec value: " + document.getElementById('result_box').innerHTML);
},5000);
Related
I'm working on writing a script to delete posts from a Facebook Group, since Facebook's Graph API won't allow a developer to do so unless the posts were made from the developer's account.
So far, I have been able to log into Facebook, then navigate to the desired group page. From there I can get the XPath for each post visible on the page (using the selector a[data-testid='post_chevron_button']). My script fails while trying to call this.click() on each XPath selector.
My current script is as follows:
phantom.casperTest = true;
var x = require('casper').selectXPath;
var casper = require('casper').create({
verbose: true,
pageSettings: {
loadImages: false,
loadPlugins: false,
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4'
}
});
// print out all the messages in the headless browser context
casper.on('remote.message', function(msg) {
this.echo('remote message caught: ' + msg);
});
// print out all the messages in the headless browser context
casper.on("page.error", function(msg, trace) {
this.echo("Page Error: " + msg, "ERROR");
});
var url = 'http://www.facebook.com/';
casper.start(url, function() {
console.log("page loaded");
this.test.assertExists('form#login_form', 'form is found');
this.fill('form#login_form', {
email: '{email}',
pass: '{password}'
}, true);
this.click('#u_0_q');
this.wait(1000, function() {
this.echo("Capturing image of page after login.");
this.capture('loggedin.png');
});
});
casper.thenOpen('https://www.facebook.com/groups/{group-id}/', function() {
this.echo(this.getTitle());
this.wait(1000, function() {
this.capture('group.png');
});
var elements = casper.getElementsInfo("a[data-testid='post_chevron_button']");
var index = 1;
elements.forEach(function(element){
var xpath = '//*[#id="' + element.attributes["id"] + '"]';
console.log(xpath);
this.click(x(xpath));
this.wait(100, function() {
this.capture('chevronlink' + index + '.png');
});
index++;
});
});
casper.run();
When the script gets to this.click(x(xpath)); I get the error message TypeError: undefined is not a constructor (evaluating 'this.click(x(xpath))'). If I simply replace the last bit of code that creates an array and iterates through it with this.click("a[data-testid='post_chevron_button']");, my script has no problem.
Does anyone know what CasperJS doesn't like about calling click() with the XPath selector? XPath appears to be a valid selector going off of CasperJS's docs.
UPDATE
I've updated the title of the question to more accurately describe the desired result.
Per dasmelch's advice, I've reworked the script a bit and incorporated this bit into the script instead (after the casper.thenOpen portion):
casper.then(function() {
var elements = casper.getElementsAttribute("a[data-
testid='post_chevron_button']", 'id');
while (elements.length > 0) {
// get always the last element with target id
element = elements.pop();
(function(element) {
var xpath = '//*[#id="' + element + '"]';
console.log(xpath);
// do it step by step
casper.then(function() {
this.click(x(xpath));
});
casper.then(function() {
this.capture('chevronlink' + element + '.png');
});
// go back to the page with the links (if necessary)
casper.then(function() {
casper.back();
});
})(element);
};
});
I now get this error: Cannot dispatch mousedown event on nonexistent selector: xpath selector: //*[#id="u_0_47"].
Last night, I decided to go about it a little differently. I got closer to the desired end result, but now CasperJS and/or PhantomJS is having trouble finding the elements that are present in the dropdown after clicking the post_chevron_button. Here is what I ended up with (everything prior to casper.thenOpen remains the same in the script shown originally):
casper.thenOpen('https://www.facebook.com/groups/{group-id}/', function() {
this.echo(this.getTitle());
this.wait(1000, function() {
this.capture('group.png');
});
var elements = casper.getElementsInfo("a[data-
testid='post_chevron_button']");
while (elements.length > 0) {
this.click("a[data-testid='post_chevron_button']");
this.wait(1000, function() {
this.capture('chevron_click.png');
console.log("chevron_click.png saved");
});
var chevronLinks = casper.getElementsInfo("a[ajaxify]")
console.log("Found " + chevronLinks.length + " elements with ajaxify attribute.");
var chevronLinksIndex = 1;
chevronLinks.forEach(function(element){
var ajaxifyValue = element.attributes["ajaxify"];
console.log(ajaxifyValue);
if (ajaxifyValue.indexOf("delete.php?group_id={group-id}") !== -1) {
this.click("a[ajaxify='"+ajaxifyValue+"']");
this.wait(100, function(){
this.capture('deletePost' + chevronLinksIndex);
});
chevronLinksIndex++;
}
});
if (chevronLinksIndex === 1) {
break;
}
elements = casper.getElementsInfo("a[data-testid='post_chevron_button']");
}
});
I know there should be an element that contains an ajaxify attribute with the value I'm searching for (because stepping through it in a browser myself shows the element after the click on a[data-testid='post_chevron_button']), but Casper cannot find it. Not only that, my chevron_click.png image file should be updating on each run of this script, but it's not.
Some of the code execution is not happening in order. For instance, the logging of ajaxify attribute values is happening in the console prior to seeing chevron_click.png saved. This may be expected, but unfortunately I don't have a lot of JS experience. This execution order problem may explain why my search for the necessary element is not returning what I expect.
Here is an example of the element needing clicked for deletion of a post:
<a class="_54nc" href="#" rel="async-post"
ajaxify="/ajax/groups/mall/delete.php?group_id={group-id}&message_id=806608486110204&story_dom_id=mall_post_806608486110204%3A6%3A0&entstory_context=%7B%22last_view_time%22%3A1495072771%2C%22fbfeed_context%22%3Atrue%2C%22location_type%22%3A2%2C%22outer_object_element_id%22%3A%22mall_post_806608486110204%3A6%3A0%22%2C%22object_element_id%22%3A%22mall_post_806608486110204%3A6%3A0%22%2C%22is_ad_preview%22%3Afalse%2C%22is_editable%22%3Afalse%2C%22mall_how_many_post_comments%22%3A2%2C%22bump_reason%22%3A0%2C%22story_width%22%3A502%2C%22shimparams%22%3A%7B%22page_type%22%3A16%2C%22actor_id%22%3A664025626%2C%22story_id%22%3A806608486110204%2C%22ad_id%22%3A0%2C%22_ft_%22%3A%22%22%2C%22location%22%3A%22group%22%7D%2C%22story_id%22%3A%22u_0_21%22%2C%22caret_id%22%3A%22u_0_22%22%7D&surface=group_post_chevron"
role="menuitem"><span><span class="_54nh"><div class="_41t5"><i
class="_41t7 img sp_gJvT8CoKHU- sx_0f12ae"></i><i class="_41t8 img
sp_s36yWP_7MD_ sx_7e9f7d"></i>Delete Post</div></span></span></a>
I was able to accomplish what I was trying to do with the Selenium 2 API for .NET.
The solution code is below:
class Program
{
static void Main(string[] args)
{
var options = new ChromeOptions();
options.AddUserProfilePreference("profile.default_content_setting_values.notifications", 2);
using (IWebDriver driver = new ChromeDriver(options))
{
// Maximize window
driver.Manage().Window.Maximize();
// Log into Facebook
driver.Navigate().GoToUrl("http://www.facebook.com/");
driver.FindElement(By.Id("email")).SendKeys("username");
driver.FindElement(By.Id("pass")).SendKeys("password");
driver.FindElement(By.Id("pass")).SendKeys(Keys.Enter);
driver.Navigate().GoToUrl("https://www.facebook.com/groups/{group-id}/");
var chevronPostLinks = driver.FindElements(By.XPath("//a[#data-testid='post_chevron_button']"));
chevronPostLinks.FirstOrDefault().Click();
Thread.Sleep(1000);
var deletePostElements = driver.FindElements(By.XPath("//a[contains(#ajaxify,'delete.php?group_id={group-id}')]"));
while (deletePostElements.Count > 0 && chevronPostLinks.Count > 0)
{
Thread.Sleep(1000);
deletePostElements.Where(x => x.Displayed == true).FirstOrDefault().Click();
Thread.Sleep(1000);
driver.FindElement(By.ClassName("layerConfirm")).Click();
Thread.Sleep(2000);
chevronPostLinks = driver.FindElements(By.XPath("//a[#data-testid='post_chevron_button']"));
if (chevronPostLinks.Count > 0)
{
chevronPostLinks.FirstOrDefault().Click();
}
else
{
driver.Navigate().GoToUrl("https://www.facebook.com/groups/{group-id}/");
chevronPostLinks = driver.FindElements(By.XPath("//a[#data-testid='post_chevron_button']"));
chevronPostLinks.FirstOrDefault().Click();
}
Thread.Sleep(1000);
deletePostElements = driver.FindElements(By.XPath("//a[contains(#ajaxify,'delete.php?group_id={group-id}')]"));
}
}
}
}
There are some improvements I'd like to make, like using Selenium to wait for elements to be visible instead of using Thread.Sleep(), but it's working just fine for my purpose.
You do the xpath stuff correct, but it seems the method forEach is not working for this.
You can grab directly the id of all these elements with casper.getElementsAttribute an iterate easy with a while loop throw that them more easily like that:
...
casper.thenOpen('https://www.facebook.com/groups/{group-id}/', function() {
this.echo(this.getTitle());
this.wait(1000, function() {
this.capture('group.png');
});
});
// do a while loop with where you can use every single element and jump back
casper.then(function() {
var elements = casper.getElementsAttribute("a[data-testid='post_chevron_button']", 'id');
while (elements.length > 0) {
// get always the last element with target id
element = elements.pop();
(function(element) {
var xpath = '//*[#id="' + element + '"]';
console.log(xpath);
// do it step by step
casper.then(function() {
this.click(x(xpath));
});
casper.then(function() {
this.capture('chevronlink' + element + '.png');
});
// go back to the page with the links (if necessary)
casper.then(function() {
casper.back();
});
})(element);
};
});
...
Without looking at FB, i guess you have to go back (casper.back) to the site where the links (elements) are.
I don't understand why updating a label on one page is affecting the label on another page. I did not think the DOM was shared like that. Opening one tab or page successfully updates the label to 'player1', but when I open another tab/pg, it updates both labels to 'player2'.
<script>
var socket = io.connect('http://localhost:3000');
socket.on('connect', function() {
socket.emit('join');
socket.on('joinSuccess', function (playerSlot) {
if (playerSlot === 'player1') {
$("#playerID").text("you are player1");
} else if (playerSlot === 'player2') {
$("#playerID").text("you are player2");
}
}); //end joinSuccess
}); //end connect
I am merely trying to notify the user which player they are.
solution:
else if (playerSlot === 'player2') {
var elm = $("#playerID");
var empty = !elm.text().trim();
if (empty) {
elm.text("you are " + playerSlot);
}
}
Are you pushing the 'joinSuccess' message when new user joins? In such case this message will be passed to both the pages with same playerSlot value. So, all pages will be updated last joined player name.
In such case you can handle this with simple condition,
socket.on('joinSuccess', function (playerSlot) {
var elm = $("#playerID");
if (!elm.text().trim()) {
elm.text("you are " + playerSlot);
}
});
PROBLEM: the function inside page.evaluate doesn't find any img (therefore, console.log(images.length) outputs 0); however, there are many images in the page, and some even have ids.
QUESTION: What's going on? Why $('img') doesn't find anything?
UPDATE 1: This is a <frame> problem. I had to switch to the frame in order to make the jQuery script correctly work.
DETAILS: I'm running a phantomjs script to access a webpage (link) and fetch all available images. It first saves a screenshot of the page just for comparison, and then it should through every <img> tag (using jQuery $('img')) and get the image dimensions and, using phantomjs's page.clipRect, it saves each image inside a folder.
var page = require('webpage').create();
var url = 'http://www.receita.fazenda.gov.br/pessoajuridica/cnpj/cnpjreva/cnpjreva_solicitacao.asp';
page.open(url, function (status) {
console.log("Status: " + status);
if (status === "success") {
page.render('example.png');
}
// Asynchronous call!
page.includeJs('http://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js', function () {
console.log('\n Evaluate Page \n');
// Sandboxed
var images = page.evaluate(function () {
var images = [];
function getImgDimensions($i) {
return {
top: $i.offset().top,
left: $i.offset().left,
width: $i.width(),
height: $i.height(),
}
}
$('img').each(function () {
var img = getImgDimensions($(this));
images.push(img);
});
return images;
});
console.log(images.length);
images.forEach(function (imageObj, index, array) {
page.clipRect = imageObj;
page.render('images/' + index + '.png');
});
// Exit the session
phantom.exit();
});
});
I've looked at the site. The img that you want is inside of an iframe. You first need to switch to it.
Use for example:
page.switchToChildFrame(0);
to switch to the first child frame. Do this before you call page.includeJs().
If you want to do something in the parent page afterwards, you would have to change back with page.switchToParentFrame();.
I have a web application, where some internal pages use an EventSource to receive live updates from the server.
The client code looks like this:
var LiveClient = (function() {
return {
live: function(i) {
var source = new EventSource("/stream/tick");
source.addEventListener('messages.keepalive', function(e) {
console.log("Client "+ i + ' received a message.');
});
}
};
})();
You can see a live demo on heroku: http://eventsourcetest.herokuapp.com/test/test/1. If you open the developer console, you will see a message printed every time an event is received.
The problem is that when visiting internal links, the EventSource remains open, causing messages to be printed even after the visitor moves from one page to another - so if you visit the three links on the top, you will get messages from three sources.
How can I close the previous connection after the user moves from one internal page to another?
A hacky workaround that I tried was to use a global variable for the EventSource object, like this:
var LiveClient = (function() {
return {
live_global: function(i) {
// We set source as global, otherwise we were left
// with sources remaining open after visiting internal
// pages
if (typeof source != "undefined" && source != null) {
if (source.OPEN) {
source.close();
console.log("Closed source");
}
}
source = new EventSource("/stream/tick");
source.addEventListener('messages.keepalive', function(e) {
console.log("Client "+ i + ' received a message.');
});
}
};
})();
Demo here: http://eventsourcetest.herokuapp.com/test/test_global/1, but I am looking for a solution that would avoid the use of a global variable if possible.
The HTML code that is generated is:
Page 1 |
Page 2 |
Page 3 |
<p>This is page 3</p>
<script>
$(function() {
LiveClient.live_global(3);
});
</script>
or with LiveClient.live_global(1); for the case with the global variable.
Try this. I haven't tested it. If it works, you might be able to replace LiveClient.source with this.source which is a lot cleaner imo.
var LiveClient = (function() {
return {
source: null,
live_global: function(i) {
// We set source as global, otherwise we were left
// with sources remaining open after visiting internal
// pages
if (typeof LiveClient.source != "undefined" && LiveClient.source != null) {
if (source.OPEN) {
source.close();
console.log("Closed source");
}
}
LiveClient.source = new EventSource("/stream/tick");
LiveClient.source.addEventListener('messages.keepalive', function(e) {
console.log("Client "+ i + ' received a message.');
});
}
};
})();
may someone of you can help me to find this problem?
I've got an xpage with client-side js-code included which should be executed when you decide to leave the page. In the client-side js you refer to a button and click it automatically. This button got some server-side js code included and change the flag from a document from ("opened by ..." to "").
The thing is that somehow the client-side js did not work in all different browsers except the current IE (10.0.5) and throws the error:
unable to load http://urlofthedocument/... status:0
The funny thing about this is, when I insert an alert()-method right after the click()-method everything works fine in every browser. But as I don't want to include this alert statement I figure out there must be something different to avoid this. (A short pause instead of the alert-method also did not work.)
My CS JS-Code:
window.onbeforeunload = WarnEditMode;
function WarnEditMode(){
if(needUnloadConfirm == true){
var el = window.document.getElementById("#{id:Hidden4SSJS}");
el.click();
//document.open();
//document.write(el);
//document.close();
//alert("You're about to leave the page");
//pause(5000);
}
}
function pause(millis){
var date = new Date();
var curDate = null;
do { curDate = new Date(); }
while(curDate-date < millis)
}
This refers to to button, which executes following SS JS code, after it is clicked:
try{
print("Hidden4SSJS-Button-Test # Person");
var db:NotesDatabase = database;
var agt:NotesAgent;
var doc:NotesDocument = XPPersonDoc.getDocument()
agt = db.getAgent("(XPUnlockDocument)");
agt.run(doc.getNoteID());
}catch(e){
_dump(e);
}
May you guys can help me with this?
I would do this using the XSP object with a hidden computed field (and not your special button)...
Something like this:
function WarnEditMode(){
if(needUnloadConfirm == true){
XSP.partialRefreshGet("#{id:unlockDocCF1}", {
params: {
'$$xspsubmitvalue': 'needToUnlock'
},
onComplete: function () {
alert('You are about to leave this page and the document has been unlocked.');
},
onError : function (e) {
alert('You are about to leave this page and the document has NOT been unlocked.\n' + e);
}
);
}
pause(5000);
}
Then the computed field's javascript would be something like this:
try{
var sval = #Explode(context.getSubmittedValue(), ',');
if (sval == null) return result + " no action.";
if (!"needToUnlock".equals(sval[0])) return result + " no action.";
print("Hidden4SSJS-Button-Test # Person");
var db:NotesDatabase = database;
var agt:NotesAgent;
var doc:NotesDocument = XPPersonDoc.getDocument()
agt = db.getAgent("(XPUnlockDocument)");
agt.run(doc.getNoteID());
return 'document unlocked.';
}catch(e){
_dump(e);
}