Cypress - testing a contact form with google recaptcha - recaptcha

How can I test a contact form with google recaptcha ?
I want to test if "We will respond you soon." message appear.

I created my own Cypress command in order to test Google reCAPTCHA
Cypress.Commands.add('solveGoogleReCAPTCHA', () => {
// Wait until the iframe (Google reCAPTCHA) is totally loaded
cy.wait(500);
cy.get('#g-recaptcha *> iframe')
.then($iframe => {
const $body = $iframe.contents().find('body');
cy.wrap($body)
.find('.recaptcha-checkbox-border')
.should('be.visible')
.click();
});
});
I combined this command with the instructions given by Google:
https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do
So, I had to do minor changes to my source code:
export const RECAPTCHA_SITE_KEY: string = window.Cypress
? '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI'
: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';

After some attempts, I came up with this:
Cypress.Commands.add('confirmCaptcha', function () {
cy.get('iframe')
.first()
.then((recaptchaIframe) => {
const body = recaptchaIframe.contents()
cy.wrap(body).find('.recaptcha-checkbox-border').should('be.visible').click()
})
})
Also make sure you have this in your cypress.json file, otherwise iFrames cannot be accessed:
"chromeWebSecurity": false

This works for me, which has no need for a cy.wait(500) or other fixed amount of time, because it uses the cypress implicit wait in its for the iframe contents to load.
cy.get('iframe')
.first()
.its('0.contentDocument.body')
.should('not.be.undefined')
.and('not.be.empty')
.then(cy.wrap)
.find('#recaptcha-anchor')
.should('be.visible')
.click();
This can be added as a custom command as well. It is based off of this blog post: https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress/

Cypress has good suggestions on their best practice page: https://docs.cypress.io/guides/references/best-practices.html#Visiting-external-sites
Basically it comes down to this: if it is not your own application, just stub it. Trust the 3th party to test their own page.

Related

Is there a way to "force" a visit?

I'm using the cy.visit() command but the website i'm visiting (which i don't own) doesn't always fire the load event, although the content itself that i need for testing does appear on the website.
Despite the content appearing, since the load event is not fired sometimes (for some reason which i can't fix since i don't have ownership over this website), the cy.visit() command fails.
Is there a way to "force" it somehow, similar to how we can pass { force: true} for the cy.click() command?
Add the below to your cypress commands file
Cypress.Commands.add('forceVisit', url => {
cy.window().then(win => {
return win.open(url, '_self');
});
});
And in your tests, you can call
cy.forceVisit("www.google.com")
It's hard to simulate the problem, but I think I managed by setting pageLoadTimeout really low (30ms).
You can catch the onLoad fail in an event handler and checking for the page load error message.
I recommend doing it in a beforeEach().
beforeEach(() => {
Cypress.config("pageLoadTimeout", 30) // set this to whatever time length
// you feel is appropriate to start testing
// You'll need to experiment to get this right
// and in CI it will be a lot longer
cy.once('fail', (err) => { // "once" to just catch a single error
const message = err.parsedStack[0].message
if (message.match(/Timed out after waiting `\d+ms` for your remote page to load/)) {
return false
}
throw err // any other error, fail it
})
cy.visit('www.example.com');
})
it('checks the heading of the page', () => {
cy.get('h1').should('have.text', 'Example Domain') // ✅
})
As you can already assume, that is highly discouraged. It also really depends on how it fails and with which errors, but, without any code to reproduce, you may want to try this if you haven't already:
cy.visit('/', {failOnStatusCode: false});
Reference: https://docs.cypress.io/api/commands/visit#Arguments

Protractor does not perceive a quick change

This is my protractor test:
it("should check email validity", function(){
var resetButton = element(by.id('reset-button'));
element(by.model('Contact.email')).sendKeys('nick');
element.all(by.css('.form-control-error')).each(function (elem, index) {
if (index===1) {
expect(elem.isPresent()).toBe(true);
element(by.model('Contact.email')).sendKeys('#gmail.com').then(
function(){
expect(elem.isPresent()).toBe(false);
}
)
}
});
});
Behind that code there is a form with some input texts. The second one includes the email.form-control-erroris an error message which appears whenever the email format is not correct. The first time expect(elem.isPresent()).toBe(true);passes the test, the second time it does not, even if the error message disappears from the UI. It seems that Protractor does not perceive the fast change; however, it should because it is inside a promise. Do you have any explanation for that?
You should make things more reliable by adding a wait for the element to become not present ("stale") after sending the keys:
element(by.model('Contact.email')).sendKeys('#gmail.com');
var EC = protractor.ExpectedConditions;
browser.wait(EC.stalenessOf(elem), 5000);
expect(elem.isPresent()).toBe(false);

How to use Jest to test React rendered async data?

I am using React for render and Jest/Jasmine for test. I have test written using old Jest/Jasmine waitsFor and runs but these are gone now in Jasmine 2 and I am not sure how to replace with new done asyncs.
In my code React renders a small page about a user. That page has an AJAX call to fetch user posts. I want to test that user posts have come back nice, and waitsFor was very, very good at this: wait until user has some post, then continue.
I looked online at lots of people talking about using AJAX calls inside Jest test which is not what I want. My Jest test has no idea about AJAX call being made, so I need a way to wait until results come back.
Here is my current code with waitsFor and runs:
it('loads user post', () => {
var page = TestUtils.renderIntoDocument(
<UserPage params={{user: 'fizzbuzz', 'pass': 'xxx'}} />
);
waitsFor(() => {
return page.state.posts.length > 0;
}, "post loaded", 10000);
runs(() => {
var posts = TestUtils.scryRenderedDOMComponentsWithClass(page, 'post');
expect(posts.length).toEqual(10);
});
});
How can I delete the waitsFor and runs and replace with Jasmine 2.0 code that works? All Jest test knows is that page.state.posts.length must be greater than 0 before expecting anything.
You should refactor this test into two unit tests that will provide a more rigorous testing of your code. It would make the tests more independent of one another and help identify errors in a more refined scope. These won't be exact as I do not know what your code is like, but here's something along the lines I would expect to see: -
it('generates the expected properties for a page', function () {
var page = TestUtils.renderIntoDocument(
<UserPage params={{user: 'fizzbuzz', 'pass': 'xxx'}} />
);
expect(page.someProperty).toBeDefined();
expect(page.user).toEqual('fizzbuzz');
});
it('generates the correct number of posts from a given page object', function () {
var fakePage = {
// put your fake mock data here that TestUtils expects
};
var posts = TestUtils.scryRenderedDOMComponentsWithClass(fakePage, 'post');
expect(posts.length).toEqual(10);
});
I am not too sure what is happening in your renderIntoDocument function so the top test may be a little broken... It looks like there is either too much going on inside the function, or you need to test the calls that function is making instead. If you elaborate on what it does I'll edit the answer.

CasperJS form fill sometimes stays on the current page

I have a simple casperjs test to submit a search form on my homepage. Then I assert that the title on the landing page is correct.
Works fine on my computer (OSX 10.9.2) but on my colleague's laptops (a Win 7 and Win 8), the test fails randomly because casper "thinks" it is still on the search page.
casper.test.begin('Search', function(test) {
casper.start("http://localhost:8080/site", function() {
this.fill(searchForm, { query: goodQuery }, true);
});
casper.then(function() {
// sometimes fails, says it's "My Project" main title
test.assertTitle('Search Result', 'Search result title is ok');
});
}
Introducing a casper.waitFor(3000) before checking the page title does not change the outcome. I've also tried to replace the then step by a waitForUrl, but it fails after 5 secs, saying it is still on the current page.
Plenty of other tests work fine on all computers but it's the only one with form submition.
Any hints on how to solve or properly work around this? I'd rather not simulate a click on the submit button (more coupling to the form internals) if possible (but it would be okay I guess).
Thanks
$ casperjs --version
1.1.0-beta3
$ phantomjs --version
1.9.7
EDIT: submitting the form and waitForUrldid not help. I found out that the test actually runs fine on its own, even on the Windows 7 machine. But when I run two tests:
01 search.js (the one described above)
02 menu.js (a simple one, merely containing assertExists)
'search.js' fails most of the time... and sometimes 'menu.js' fails instead! I suspect some mishandled concurrent access, although it consistently works on OSX. I must be doing something wrong. Both tests have the same structure:
casper.test.begin('Some test', function(test) {
casper.start(someUrl, function() {
// some test
});
casper.run(function() {
test.done();
});
});
Any clue?
Try :
casper.test.begin('Search', function(test) {
casper.start("http://localhost:8080/site", function() {
this.fill(searchForm, {
query: goodQuery
},false);
this.click("your selector for submit button");
});
casper.then(function() {//you should use waitForUrl/Selector/Text() instead
// sometimes fails, says it's "My Project" main title
test.assertTitle('Search Result', 'Search result title is ok');
});
casper.run(function() {
this.test.comment('------ Tests over ----\n');
test.done();
});
});
It's better to submit the form by clicking. Sometimes (often) it doesn't pass putting the fill arg at true. Just put the correct selector for the submit button.
You should wait for an item to appear on the following page. I would change your code to the following:
casper.test.begin('Search', function(test) {
casper.start("http://localhost:8080/site", function() {
this.fill(searchForm, { query: goodQuery }, true);
});
casper.waitForSelector('#someSelectorOnNextPage', function() {
test.assertTitle('Search Result', 'Search result title is ok');
});
}
I also experience same issue. Suprisingly adding empty then() handler fixes that in v1.1.0-beta3. I don't think this is expected behavior though:
casper.test.begin('Search', function(test) {
casper.start("http://localhost:8080/site", function() {
this.fill(searchForm, { query: goodQuery }, true);
});
// Do nothing here, just call it as a placeholder
// Here http://localhost:8080/site sends us to the next endpoint
casper.then(function() {});
// Now this is the final page we actually want to assert
casper.then(function() {
test.assertTitle('Search Result', 'Search result title is ok');
});
}
EDIT:
Although question author says casper.waitForUrl() didn't work for them, it did work for me as an alternative solution.
What does look strange is that in verbose mode whatever returns a 301 status code along with Location Header is recognized as HTTP 200 response by Casper.
EDIT 2:
Well obviously it doesn't happen every time, but what I noticed is that Casper sometimes doubles the previous response (that's why I thought it recognizes some specific HTTP codes as 200 mistakenly and that's why author's code functioned as if it stayed on same page after form submission) and sometimes not.
waitForUrl() fixes that obviously but there is still some underneath issue in Casper which scares me a bit and I hope I will find some time to report it with all the dumps to Casper issue tracker.

Protractor : How to wait for page complete after click a button?

In a test spec, I need to click a button on a web page, and wait for the new page completely loaded.
emailEl.sendKeys('jack');
passwordEl.sendKeys('123pwd');
btnLoginEl.click();
// ...Here need to wait for page complete... How?
ptor.waitForAngular();
expect(ptor.getCurrentUrl()).toEqual(url + 'abc#/efg');
Depending on what you want to do, you can try:
browser.waitForAngular();
or
btnLoginEl.click().then(function() {
// do some stuff
});
to solve the promise. It would be better if you can do that in the beforeEach.
NB: I noticed that the expect() waits for the promise inside (i.e. getCurrentUrl) to be solved before comparing.
I just had a look at the source - Protractor is waiting for Angular only in a few cases (like when element.all is invoked, or setting / getting location).
So Protractor won't wait for Angular to stabilise after every command.
Also, it looks like sometimes in my tests I had a race between Angular digest cycle and click event, so sometimes I have to do:
elm.click();
browser.driver.sleep(1000);
browser.waitForAngular();
using sleep to wait for execution to enter AngularJS context (triggered by click event).
You don't need to wait. Protractor automatically waits for angular to be ready and then it executes the next step in the control flow.
With Protractor, you can use the following approach
var EC = protractor.ExpectedConditions;
// Wait for new page url to contain newPageName
browser.wait(EC.urlContains('newPageName'), 10000);
So your code will look something like,
emailEl.sendKeys('jack');
passwordEl.sendKeys('123pwd');
btnLoginEl.click();
var EC = protractor.ExpectedConditions;
// Wait for new page url to contain efg
ptor.wait(EC.urlContains('efg'), 10000);
expect(ptor.getCurrentUrl()).toEqual(url + 'abc#/efg');
Note: This may not mean that new page has finished loading and DOM is ready. The subsequent 'expect()' statement will ensure Protractor waits for DOM to be available for test.
Reference: Protractor ExpectedConditions
In this case, you can used:
Page Object:
waitForURLContain(urlExpected: string, timeout: number) {
try {
const condition = browser.ExpectedConditions;
browser.wait(condition.urlContains(urlExpected), timeout);
} catch (e) {
console.error('URL not contain text.', e);
};
}
Page Test:
page.waitForURLContain('abc#/efg', 30000);
I typically just add something to the control flow, i.e.:
it('should navigate to the logfile page when attempting ' +
'to access the user login page, after logging in', function() {
userLoginPage.login(true);
userLoginPage.get();
logfilePage.expectLogfilePage();
});
logfilePage:
function login() {
element(by.buttonText('Login')).click();
// Adding this to the control flow will ensure the resulting page is loaded before moving on
browser.getLocationAbsUrl();
}
Use this I think it's better
*isAngularSite(false);*
browser.get(crmUrl);
login.username.sendKeys(username);
login.password.sendKeys(password);
login.submit.click();
*isAngularSite(true);*
For you to use this setting of isAngularSite should put this in your protractor.conf.js here:
global.isAngularSite = function(flag) {
browser.ignoreSynchronization = !flag;
};
to wait until the click itself is complete (ie to resolve the Promise), use await keyword
it('test case 1', async () => {
await login.submit.click();
})
This will stop the command queue until the click (sendKeys, sleep or any other command) is finished
If you're lucky and you're on angular page that is built well and doesn't have micro and macro tasks pending then Protractor should wait by itself until the page is ready. But sometimes you need to handle waiting yourself, for example when logging in through a page that is not Angular (read how to find out if page has pending tasks and how to work with non angular pages)
In the case you're handling the waiting manually, browser.wait is the way to go. Just pass a function to it that would have a condition which to wait for. For example wait until there is no loading animation on the page
let $animation = $$('.loading');
await browser.wait(
async () => (await animation.count()) === 0, // function; if returns true it stops waiting; can wait for anything in the world if you get creative with it
5000, // timeout
`message on timeout`
);
Make sure to use await
you can do something like this
emailEl.sendKeys('jack');
passwordEl.sendKeys('123pwd');
btnLoginEl.click().then(function(){
browser.wait(5000);
});
browser.waitForAngular();
btnLoginEl.click().then(function() { Do Something });
to solve the promise.

Resources