I'm working with CasperJS on a webpage that has a search box. Using Casper, I'm successfully able to perform a search and see that the form is filled and execute the search, however I'm having trouble specifically getting the number of results to test that it's valid.
When I look at the source of the webpage, I have the XPath of the element and it's nested in a few divs.
But when I try to either do assertExists() on that path, or even return the result of getElementByXPath() to a var, it doesn't work. In the first case the test fails and in the second case it prints null.
This is the XPath:
//*[#id="total"]
Here is what the snippet of source looks like:
<div id="pgContent"><div id="results_pagination1_container">
<span style="float: right; font-size: .9em">
Found <span id="total">721</span> item(s)</span>
</div>
This is the CasperJS code, relevant to here.
casper.test.begin(
'Testing that we get the right amount of results for a search query',
2, function suite(test) {
casper.start(catapult, function() {
test.assertTitle("Search", "Search"); // Good
test.assertExists('#input-searchbox'); // Good
this.fillSelectors('form#inputForm', {
'input[name="queryStr"]' : 'airplane'
}, true);
//this.click('input#btnSearch');
});
casper.then(function() {
var resultsNum = __utils__.getElementByXPath('//*[#id="total"]');
this.echo(resultsNum);
test.assertExists('//*[#id="total"]');
});
Clientutils and DOM access only in page context
The CasperJS clientutils module is only defined in the page context. Whenever CasperJS starts, it injects those utils into the page for later convenience. The problem is that the page and its DOM are only accessible from inside of the evaluate() function.
Another thing to remember is that you cannot get DOM nodes out of the page context. Either you do your stuff completely in the page context (inside of evaluate()) or you get a representation of the node like the textContent property:
var resultsNum = this.evaluate(function(){
return __utils__.getElementByXPath('//*[#id="total"]').textContent;
});
Here is the bit from the documentation (since CasperJS is built on top of PhantomJS):
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.
Closures, functions, DOM nodes, etc. will not work!
Difference between CSS selectors and XPath expressions
The other thing is that if not otherwise noted through the name all functions assume that the selectors are CSS selectors. Looking at test.assertExists('//*[#id="total"]'); makes it clear that '//*[#id="total"]' is not a CSS selector, but an XPath expression. You can use XPath expressions in CasperJS through the XPath utility:
var x = require('casper').selectXPath;
test.assertExists(x('//*[#id="total"]'));
or you can simply use the equivalent CSS selector:
test.assertExists('#total');
Related
i am trying a drag and drop in an iframe and to do that i need to pass xpath in find since i cant find a unique element to pass in cy.get()
currently i am trying
cy.xpath('//div[#class="unlayer-editor"]//iframe[#src]')
.should("be.visible")
.find('//div[#class = "blopockbder-coent-tols43 col-sm-12"]//div[#aria-describedby="t8ppy-tooltip-9"]')
but this isnt working
i am using cypress for automation
Not an expert on xpath, but I think .find() can't be mixed with an xpath selector.
Two things to try
// chain 2nd xpath in place of .find()
cy.get('div[class="unlayer-editor"] iframe[id="my-iframes-id"]')
.should("be.visible")
.xpath('//div[#class = "blopockbder-coent-tols43 col-sm-12"]//div[#aria-describedby="t8ppy-tooltip-9"]')
or
// use .within() instead of .find() (roughly equivalent)
cy.get('div[class="unlayer-editor"] iframe[id="my-iframes-id"]')
.should("be.visible")
.within(() => {
cy.xpath('//div[#class = "blopockbder-coent-tols43 col-sm-12"]//div[#aria-describedby="t8ppy-tooltip-9"]')
})
Other things that might need adjusting
The iframe selection generally needs a follow-up command to get it's document body (ref Working with iframes)
// get the iframe document body any select within it
cy.get('div[class="unlayer-editor"] iframe[id="my-iframes-id"]')
.its('0.contentDocument.body', { log: false }).should('not.be.empty')
.within(() => {
cy.xpath('//div[#class = "blopockbder-coent-tols43 col-sm-12"]//div[#aria-describedby="t8ppy-tooltip-9"]')
})
Some of those classes in the path like col-sm-12 are purely display-oriented and may be different if you test at different devices. Once the test works, try removing them to make the test more robust.
I want to capture a string representation of a generated href and use it in a later page that I have navigated to.
I have tried capturing the anchor using cy.get(), cy.find() etc so I can click() it later but obviously that link goes stale after navigating away from the page. I tried chaining into then(), I also tried using .getAttribute(), but I get an object back not a string.
I am struggling to find a way to get to the underlying values of the cypress objects, even calling cy.document() returns a cypress representation instead of what intuitively I’d have guessed was THE DOM document. Clearly I have no idea what Cypress is doing, or much of a clue about the opinions of the framework other than I get the feeling that I am trying to implement some Cypress anti pattern.
I don’t really care how, but I would like to capture the generated url that I can locate in an anchor tags href in the page. Please can someone suggest how I could capture the string value so that I might be able to use it in a cy.request() later?
I can post some examples later when I am at my computer if needed, but I suspect my description is sufficient, but please let me know otherwise.
Example:
describe('Test Description', function () {
var capturedHref = "";
before(function() {
// can set the name here, it becomes the context text on an anchor
generateLinkViaAppFunctionality();
// find by the content text taken from name on link generation
capturedHref = <href from link>
}
it('Test', function() {
cy.visit('some app page');
doStuff();
cy.visit(capturedHref);
})
}
Is this working for you? I used anchor in cypress document to test.
describe('Test anchor link', function(){
it('test', function(){
let anchorlink = '';
let url = 'https://docs.cypress.io/guides/overview/why-cypress.html#Who-uses-Cypress';
cy.visit(url);
cy.get('#Cypress-ecosystem > a').then(($anchorlink)=>{
anchorlink = $anchorlink.prop('href');
console.log(anchorlink);
})
});
});
The full url is printed in console.
You can use it later.
I am using CasperJS for functional testing, and I want to check if an error message is displaying in a page.
I am using:
test.assertSelectorHasText('#iccid-error', 'CC-ICCID-01');
That works fine but only if I use the whole text (not part of it).
According to the documentation, the CasperJS function assertSelectorHasText():
Asserts that given text exists in elements matching the provided selector expression.
In other words, this function will work for full and partial strings, and the test will pass if the string exists within the element.
I ran a simple test to verify the accuracy of the function.
Command Line:
casperjs casperjs.js
Result:
# How to Detect Partial Text within a Selector using CasperJS
PASS Find "CC" within the selector "#iccid-error"
PASS Find "CC-ICCID" within the selector "#iccid-error"
PASS Find "CC-ICCID-01" within the selector "#iccid-error"
PASS How to Detect Partial Text within a Selector using CasperJS (3 tests)
casperjs.js:
phantom.casperTest = true;
var casper = require('casper').create();
casper.test.begin('How to Detect Partial Text within a Selector using CasperJS', 3, function (test) {
casper.start('https://example.com/test.php', function () {
test.assertSelectorHasText('#iccid-error', 'CC');
test.assertSelectorHasText('#iccid-error', 'CC-ICCID');
test.assertSelectorHasText('#iccid-error', 'CC-ICCID-01');
}).run(function () {
test.done();
});
});
casper.on('run.complete', function () {
this.exit();
});
example.com/test.php:
<!DOCTYPE html>
<html>
<body>
<div id="iccid-error">CC-ICCID-01</div>
</body>
</html>
As a result, I can conclude that the function assertSelectorHasText() does in fact search for partial strings in the element matching the selector given.
So essentially I'm trying to build my own version of GitHub's tree slider. The relevant Javascript/JQuery code is:
// handles clicking a link to move through the tree
$('#slider a').click(function() {
history.pushState({ path: this.path }, '', this.href) // change the URL in the browser using HTML5 history module
$.get(this.href, function(data) {
$('#slider').slideTo(data) // handle the page transition, preventing full page reloads
})
return false
})
// binds hitting the back button in the browser to prevent full page reloads
$(window).bind('popstate', function() {
$('#slider').slideTo(location.pathname)
}
Ok, hopefully that's understandable. Now here's my interpretation of what's going on here, followed by my problem/issue:
The callback function for the GET request when navigating through the tree is the slideTo method, and an HTML string is passed in as an argument to that function. I'm assuming that slideTo is a function defined elsewhere in the script or in a custom library, as I can't find it in the JQuery documentation. So, for my purposes, I'm trying to build my own version of this function. But the argument passed into this function, "data", is just the string of HTML returned from the GET request. However, this isn't just a snippet of HTML that I can append to a div in the document, because if I perform the same GET request (e.g. by typing the url into a web browser) I would expect to see a whole webpage and not just a piece of one.
So, within this callback function that I am defining, I would need to parse the "data" argument into a DOM so that I can extract the relevant nodes and then perform the animated transition. However, this doesn't make sense to me. It generally seems like a Bad Idea. It doesn't make sense that the client would have to parse a whole string of HTML just to access part of the DOM. GitHub claims this method is faster than a full page reload. But if my interpretation is correct, the client still has to parse a full string of HTML whether navigating through the tree by clicking (and running the callback function) or by doing full page loads such as by typing the new URL in the browser. So I'm stuck with either parsing the returned HTML string into a DOM, or ideally only fetching part of an HTML document.
Is there a way to simply load the fetched document into a Javascript or JQuery DOM object so I can easily manipulate it? or even better, is there a way to fetch only an element with an arbitrary id without doing some crazy server-side stuff (which I already tried but ended up being too spaghetti code and difficult to maintain)?
I've also already tried simply parsing the data argument into a JQuery object, but that involved a roundabout solution that only seems to work half the time, using javascript methods to strip the HTML of unwanted things, like doctype declarations and head tags:
var d = document.createElement('html');
d.innerHTML = data;
body = div.getElementsByTagName("body")[0].innerHTML;
var newDOM = $(body);
// finally I have a JQuery DOM context that I can use,
// but for some reason it doesn't always seem to work quite right
How would you approach this problem? When I write this code myself and try to make it work on my own, I feel like no matter what I do, I'm doing something horribly inefficient and hacky.
Is there a way to easily return a JQuery DOM object with a GET request? or better, just return part of a document fetched with a GET request?
Just wrap it; jQuery will parse it.
$(data) // in your callback
Imagine you want to parse a <p> tag in your normal HTML web page. You probably would use something like:
var p = $('<p>');
Right? So you have to use the same approach to parse an entire HTML document and then, navigate through the DOM tree to get the specific elements you want. Therefore, you just need to say:
$.get(this.href, function(data) {
var html = $(data);
// (...) Navigating through the DOM tree
$('#slider').slideTo( HTMLportion );
});
Notice that it also works for XML documents, so if you need to download via AJAX a XML document from the server, parse the inner information and display it on the client-side, the method is exactly the same, ok?
I hope it helps you :)
P.S: Don't ever forget to put semicolons at the end of each JavaScript sentence. Probably, if you don't put them, the engine would work but it is better to be safe and write them always!
I'm trying to test a page using CasperJS, in particular I want to poke the data model a bunch. Let's say I've got a basic function called taxes, and I want to ensure that it uses the right tax rate. So I'd like something like:
this.test.assert(taxes(100, 'Ontario') === 15, "Check ontario tax rate");
Rather than filling out a form and seeing what it prints. That taxes method exists in the global scope, so I'm able to execute it quite easily from anywhere (including from the console in firebug or Chrome). But it's not in the right scope for that to work inside CasperJS (I think? I'm getting ReferenceError: Can't find variable: taxes.
It seems like I'm missing something simple.
TL;DR: How do I execute an on-page bit of JS directly inside a CasperJS test?
Have you tried using evaluate()?
relevant quote: "execute code as if you were using the browser console."
something along the lines of:
casper.evaluate(function(amount, province) {
return taxes(amount, province);
}, {100, 'Ontario'});
Use assertEvalEquals() method.
If you're calling the method via a jQuery-style reference, make sure to explicitly include the library, lest you'll get the ReferenceError:
var casper = require('casper').create({
clientScripts: ['js/jquery-1.7.2.js']
});
...
casper.start('foo.php',
function() {
console.log(this.evaluate(function() {
return $('taxes').text();
}));
});
casper.run();
See: https://groups.google.com/forum/#!msg/casperjs/2uyUOqdzShw/bHWrJYXni40J
If you're calling it implicitly in the global scope (i.e., straight javascript, rather than, for example, $('taxes')), you might have to explicitly prepend the window or document namespace to the reference:
document.querySelector('#taxes').value = taxes_text;