Fetching values from email in protractor test case - javascript

I need to test a protractor test case in which a user signs up, receives an email, goes to the link provided in the email and fills up his/her details in activation signup form.
The problem is how can I get the redeem token from the email. My email has a link to the activation page which has the auth token like following:
http://127.0.0.1:3000/#/signup/redeem/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJlOTRhYzY3MC1kYTNlLTQyYTUtODVkZS02NDU4ZjVmZGMwYjAiLCJzdWIiOiJ0ZXN0QGNvZWYuY28iLCJpYXQiOjE0Mjc0OTM5MDMsImV4cCI6MTQyODA5ODcwM30.
But how do I fetch that token so that I can build the url or how can I click that button in my email so that I can complete the flow ? I am using mailcatcher to simulate email.

This is something I've solved recently. Hope the solution would also apply for your use-case.
Prerequisites:
mail-listener2 package
understanding of the concept of promises
Step by step instructions:
Install mail-listener2:
npm install mail-listener2 --save-dev
In your protractor config initialize Mail Listener and make it available globally:
onPrepare: function () {
var MailListener = require("mail-listener2");
// here goes your email connection configuration
var mailListener = new MailListener({
username: "imap-username",
password: "imap-password",
host: "imap-host",
port: 993, // imap port
tls: true,
tlsOptions: { rejectUnauthorized: false },
mailbox: "INBOX", // mailbox to monitor
searchFilter: ["UNSEEN", "FLAGGED"], // the search filter being used after an IDLE notification has been retrieved
markSeen: true, // all fetched email willbe marked as seen and not fetched next time
fetchUnreadOnStart: true, // use it only if you want to get all unread email on lib start. Default is `false`,
mailParserOptions: {streamAttachments: true}, // options to be passed to mailParser lib.
attachments: true, // download attachments as they are encountered to the project directory
attachmentOptions: { directory: "attachments/" } // specify a download directory for attachments
});
mailListener.start();
mailListener.on("server:connected", function(){
console.log("Mail listener initialized");
});
global.mailListener = mailListener;
}),
onCleanUp: function () {
mailListener.stop();
},
Create a helper getLastEmail() function which would wait for an email to be retrieved:
function getLastEmail() {
var deferred = protractor.promise.defer();
console.log("Waiting for an email...");
mailListener.on("mail", function(mail){
deferred.fulfill(mail);
});
return deferred.promise;
};
Example test case:
describe("Sample test case", function () {
beforeEach(function () {
browser.get("/#login");
browser.waitForAngular();
});
it("should login with a registration code sent to an email", function () {
element(by.id("username")).sendKeys("MyUserName");
element(by.id("password")).sendKeys("MyPassword");
element(by.id("loginButton")).click();
browser.controlFlow().await(getLastEmail()).then(function (email) {
expect(email.subject).toEqual("New Registration Code");
expect(email.headers.to).toEqual("myemail#email.com");
// extract registration code from the email message
var pattern = /Registration code is: (\w+)/g;
var regCode = pattern.exec(email.text)[1];
console.log(regCode);
});
});
});

The solution I implemented was using mailcatcher API, if you scroll down a bit you'll find the following about the API:
A fairly RESTful URL schema means you can download a list of messages
in JSON from /messages, each message's metadata with
/messages/:id.json, and then the pertinent parts with
/messages/:id.html and /messages/:id.plain for the default HTML and
plain text version, /messages/:id/:cid for individual attachments by
CID, or the whole message with /messages/:id.source.
So we first fetched the whole json response, parse it and fetch the latest email id:
// Returns the last email id
function(emails, user) {
var email, recipient;
for(var i = emails.length - 1; i >= 0; i--) {
email = emails[i];
for(var j = 0; j < email.recipients.length ; j++) {
recipient = email.recipients[j];
if(recipient == "<"+user+">") {
return email.id;
}
}
}
};
using that email id we can get the body of the email by hitting /messages/:id.plain(of course there are more variants like fetching the email source code or email rendered html, we only needed the message) then we can just parse the body to fetch what we want, following is the code:
browser.driver.get(mailcatcherUrl+"/messages");
browser.driver.findElement(by.tagName('body')).getText().then(function(response) {
var emails, lastEmailId, partialTokens ;
emails = JSON.parse(response);
lastEmailId = getLastEmailId(emails, user);
browser.driver.get(mailcatcherUrl+"/messages/"+lastEmailId+".plain");
browser.driver.findElement(by.tagName('body')).getText().then(function(lastEmail) {
// use latestEmail to get what you want.
});
});
And Cheers!

I had to do the same thing but the mail testing server we were using did not have imap support.
So in case anyone runs into the same issue, I achieved a similar solution as alecxe using mailpop3 npm library.
The thing with the pop3 client, however, was that it doesn't act as a listener so we had to define a helper function that would connect, login and fetch the latest email when we needed to test the latest email.
Something like this:
function getLastEmail() {
var deferred = protractor.promise.defer();
var POP3Client = require("mailpop3");
var client = new POP3Client(port, host, {
tlserrs: false,
enabletls: true,
debug: false
});
client.on("connect", function() {
console.log("CONNECT success");
client.login(username, password);
});
client.on("login", function(status, rawdata) {
if (status) {
console.log("LOGIN/PASS success");
client.retr(1);
} else {
console.log("LOGIN/PASS failed");
client.quit();
}
});
client.on("retr", function(status, msgnumber, data, rawdata) {
if (status === true) {
console.log("RETR success for msgnumber " + msgnumber);
deferred.fulfill(data);
} else {
console.log("RETR failed for msgnumber " + msgnumber);
}
client.quit();
});
return deferred.promise;
}

Related

How to login to gmail account and read a mail in protractor not using the browser

I have a scenario which I need to login to a gmail account and read the content or the message. Then need to get a URL from that message. I can do this using a browser in protractor. But the issue is that gmail account enabled the 2FA. I have achieved this using the core Selenium which has jar files to log in to the gmail account using IMAP protocol.
Can someone please give me a good solution?
You can read emails from Gmail inside protractor tests using mail-listener2 npm package. Check the below example code.
mailListener.ts
const MailListenerClient = require("mail-listener2");
const cheerio = require('cheerio');
const simpleParser = require('mailparser').simpleParser;
export class MailListener {
public mailListener:any;
constructor() {
this.mailListener = new MailListenerClient({
username: "username#gmail.com",
password: "password",
host: "imap.gmail.com",
port: 993,
tls: true,
mailbox: "INBOX",
searchFilter: ["UNSEEN", ["FROM", "fromemail#gmail.com"],["SUBJECT","subject of the email"]],
/*it will search for are "unseen" mail send from "fromemail#gmail.com" with subject "fromemail#gmail.com"*/
connTimeout: 10000,
authTimeout: 5000,
markSeen: true,
mailParserOptions: {streamAttachments: true}, // options to be passed to mailParser lib.
attachments: true, // download attachments as they are encountered to the project directory
attachmentOptions: {directory: "attachments/"},
debug : console.log
});
}
init() {
this.mailListener.start();
}
close() {
this.mailListener.stop();
}
getLinkFromEmail() {
var self = this;
return new Promise((resolve, reject) => {
self.mailListener.on("mail", function (mail) {
/*simpleParser is used to convert string to HTML format*/
simpleParser(mail.eml).then(function (parsedEmail) {
var html = parsedEmail.html;
/* cheerio is used to write query on parsed HTML content
* refer https://www.npmjs.com/package/cheerio
*/
resolve(cheerio.load(html)("a").attr("href"));
});
});
self.mailListener.on("error", function (err) {
reject(err);
});
});
}
}
test.ts
import {MailListener} from "mailListner";
describe("Read email from gmail using imap", function () {
let mailListener = new MailListener();
beforeAll(function(){
mailListener.init();
});
afterAll(function(){
mailListener.close();
})
it("Test email recieved",function(){
let urlFromEmail = mailListener.getLinkFromEmail();
/*Perform some action on UI that triggers email.(Just for example im doing it)*/
element(by.id("email")).sendKeys("email#gmail.com");
element(by.buttonText("Send Email")).click();
expect(urlFromEmail).toEqual("some link");
})
});
I have written the code in typescript and hope you can rewrite the same in javascript. Let me know if this is clear or do I need to add more details to the code.
One of the best practice is to use Gmail API. Take a look at official documentation

Get parameters and store and use them on variables to use on my method

I want to get some parameters and use them to reset password function from firebase.
This is how my link looks like:
http://localhost:8080/passwordreset?mode=resetPassword&oobCode=y6FIOAtRUKYf88Rt5OlEwxUuTyEmb3M4gquZSIseX2UAAAFevpj-gw&apiKey=AIzaSyBaCCvq-ZEfQmdrL7fmElXDjZF_J-tku2I
I want to get mode, oobCode and apiKey.
Here is what I have for now:
export default {
data: function() {
return {
passwordNew: '',
passwordConfirm: '',
mode:'',
actionCode: '',
continueUrl: '',
}
},
methods: {
handleResetPassword: function() {
var accountEmail;
firebase.auth().verifyPasswordResetCode(actionCode).then(function(email) {
var accountEmail = email;
firebase.auth().confirmPasswordReset(this.actionCode, this.passwordNew).then(function(resp) {
alert("Password reset success");
this.$router.push('hello')
}).catch(function(error) {
// Error occurred during confirmation. The code might have expired or the
// password is too weak.
console.log("error 1")
});
}).catch(function(error) {
// Invalid or expired action code. Ask user to try to reset the password
// again.
console.log("error 2")
});
},
}
}
From Firebase documentation:
Some user management actions, such as updating a user's email address
and resetting a user's password, result in emails being sent to the
user. These emails contain links that recipients can open to complete
or cancel the user management action. By default, user management
emails link to the default action handler, which is a web page hosted
at a URL in your project's Firebase Hosting domain.
link: https://firebase.google.com/docs/auth/custom-email-handler
You need to get those parameters and store them on variables, from firebase documentation i got those snippets and just wrote the getParameterByName function:
function getParameterByName( name ){
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?&]"+name+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec( window.location.href );
if( results == null )
return "";
else
return decodeURIComponent(results[1].replace(/\+/g, " "));
}
// Get the action to complete.
var mode = getParameterByName('mode');
// Get the one-time code from the query parameter.
var actionCode = getParameterByName('oobCode');
// (Optional) Get the continue URL from the query parameter if available.
var continueUrl = getParameterByName('continueUrl');
You need to get those parameters first and verify the actioncode on the verifyPasswordResetCode method, then you can change the password and store it along with the action code to the method.
In your export default :
data: function() {
return {
passwordNew: '',
passwordConfirm: '',
mode: mode,
actionCode: actionCode,
continueUrl: continueUrl,
}
},
methods: {
handleResetPassword: function() {
var passwordNew = this.passwordNew
var actionCode = this.actionCode
firebase.auth().verifyPasswordResetCode(actionCode).then(function(email) {
console.log("ActionCode: "+ actionCode);
firebase.auth().confirmPasswordReset(actionCode, passwordNew).then(function(resp) {
alert("Password reset success");
this.$router.push('hello')
}).catch(function(error) {
console.log("error 1"+ error)
});
}).catch(function(error) {
console.log("Action code is invalid"+ error)
});
},
}

Unable to retrieve email information in protractor

Referencing the information from the question Fetching values from email in protractor test case, I am still unable to reference the emails. In my test case, the 'expect' is not getting executed for some unknown reason to me.
Also if I use the line,
browser.controlFlow().await(getLastEmail()).then(...)
There is a 'browser.controlFlow(...).await is not a function error'
conf.js
var MailListener = require("mail-listener2")
exports.config = {
framework: 'jasmine2',
specs: ['./test.js'],
jasmineNodeOpts: { defaultTimeoutInterval: 360000 },
allScriptsTimeout: 60000,
onPrepare: function () {
var mailListener = new MailListener({
username: "username",
password: "password",
host: "imapPort",
port: 993, // imap port
secure: true,
tls: true,
tlsOptions: { rejectUnauthorized: false },
mailbox: "INBOX", // mailbox to monitor
searchFilter: ["UNSEEN", "FLAGGED"], // the search filter being used after an IDLE notification has been retrieved
markSeen: true, // all fetched email willbe marked as seen and not fetched next time
fetchUnreadOnStart: true, // use it only if you want to get all unread email on lib start. Default is `false`,
mailParserOptions: {streamAttachments: true}, // options to be passed to mailParser lib.
attachments: true, // download attachments as they are encountered to the project directory
attachmentOptions: { directory: "attachments/" } // specify a download directory for attachments
})
mailListener.start()
mailListener.on("server:connected", function(){
console.log("Mail listener initialized")
})
mailListener.on("error", function(err){
console.log(err)
})
mailListener.on("server:disconnected", function(){
console.log("imapDisconnected")
})
global.mailListener = mailListener
},
onCleanUp: function () {
mailListener.stop()
}
}
The test case:
describe('Email Testing', function () {
it('should login with a registration code sent to an email', function () {
//this line causes a 'browser.controlFlow(...).await is not a function' error
// browser.controlFlow().await(getLastEmail()).then(function (email) {
getLastEmail().then(function (email) {
// The expect does not get executed as it should fail
expect(email.subject).toEqual('My Subject')
})
})
})
function getLastEmail () {
var deferred = protractor.promise.defer()
console.log('Waiting for an email...')
mailListener.on('mail', function (mail) {
console.log('No Console Log Here!')
deferred.fulfill(mail)
})
return deferred.promise
}
I am not certain what I am missing in my test case to be able to read the subject or body of the email?
Ran into the same issue today. Turns out the API for the webdriver and ControlFlow has been updated and await has been changed to wait. Yeah, subtle difference. See the new API reference here: https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/promise_exports_ControlFlow.html
To schedule a task for the wait condition, change your code to look like this:
browser.controlFlow().wait(getLastEmail()).then(...)
you would basically have to wrap that asynchronous code inside of a promise and the pass that promise/function into the flow.execute()
var flow = protractor.promise.controlFlow();
flow.execute( getLastEmail() ).then(function(email){
text = email.text
});

How to prevent current user get notified?

I'm making an app that allows user to like and comment on other user post. I'm using Parse as my backend. I'm able to notified user everytime their post liked or commented. However if current user like or comment on their own post this current user still notified. How can I prevent this?
Here is the js code that I use:
Parse.Cloud.afterSave('Likes', function(request) {
// read pointer async
request.object.get("likedPost").fetch().then(function(like){
// 'post' is the commentedPost object here
var liker = like.get('createdBy');
// proceed with the rest of your code - unchanged
var query = new Parse.Query(Parse.Installation);
query.equalTo('jooveUser', liker);
Parse.Push.send({
where: query, // Set our Installation query.
data: {
alert: message = request.user.get('username') + ' liked your post',
badge: "Increment",
sound: "facebook_pop.mp3",
t : "l",
lid : request.object.id,
pid: request.object.get('likedPostId'),
lu : request.user.get('username'),
ca : request.object.createdAt,
pf : request.user.get('profilePicture')
}
}, {
success: function() {
console.log("push sent")
},
error: function(err) {
console.log("push not sent");
}
});
});
});
If I understand the context of where this code is correctly,
I recommend checking
if request.user.get("username") != Parse.CurrentUser.get("username")
Before sending out the push notification
Where is your cloud function being called from? If you're calling it from your ios code, then before you call the cloud code function, just prelude it with something like this:
if (PFUser.currentUser?.valueForKey("userName") as! String) != (parseUser.valueForKey("userName") as! String)

Twitter authentication in Codebird JS

I am very new to integrating social sites into a website. I somewhat managed to integrate Facebook, but I have no idea how to integrate Twitter.
I want to login through a Twitter account, then get the username and some other data from Twitter. I have a consumer key and consumer secret. I'm not sure how to proceed from here, and my Google searches haven't helped so far.
I am trying with codebird js:
$(function() {
$('#twitter').click(function(e) {
e.preventDefault();
var cb = new Codebird;
cb.setConsumerKey("redacted", "redacted");
cb.__call(
"oauth_requestToken",
{ oauth_callback: "http://127.0.0.1:49479/" },
function (reply, rate, err) {
if (err) {
console.log("error response or timeout exceeded" + err.error);
}
if (reply) {
// stores it
cb.setToken(reply.oauth_token, reply.oauth_token_secret);
// gets the authorize screen URL
cb.__call(
"oauth_authorize",
{},
function (auth_url) {
window.codebird_auth = window.open(auth_url);
}
);
}
}
);
cb.__call(
"account_verifyCredentials",
{},
function(reply) {
console.log(reply);
}
);
})
});
But I get
Your credentials do not allow access to this resource
How can I resolve this and get the user data? I am open to using an alternate Twitter implementation.
You cannot call cb._call( "account_verifyCredentials"... there.
The code only has a request token, NOT an access token, which you will only receive after the user authorizes your app (on the Twitter auth popup).
You are using the "callback URL without PIN" method, as documented on the README. So you'll need to implement that example code on your http://127.0.0.1:49479/ page.
Also, this essentially requires that you store the oauth credentials somewhere. In my example below, I've used localStorage.
$(function () {
$('#twitter').click(function (e) {
e.preventDefault();
var cb = new Codebird;
cb.setConsumerKey("CeDhZjVa0d8W02gWuflPWQmmo", "YO4RI2UoinJ95sonHGnxtYt4XFtlAhIEyt89oJ8ZajClOyZhka");
var oauth_token = localStorage.getItem("oauth_token");
var oauth_token_secret = localStorage.getItem("oauth_token_secret");
if (oauth_token && oauth_token_secret) {
cb.setToken(oauth_token, oauth_token_secret);
} else {
cb.__call(
"oauth_requestToken", {
oauth_callback: "http://127.0.0.1:49479/"
},
function (reply, rate, err) {
if (err) {
console.log("error response or timeout exceeded" + err.error);
}
if (reply) {
console.log("reply", reply)
// stores it
cb.setToken(reply.oauth_token, reply.oauth_token_secret);
// save the token for the redirect (after user authorizes)
// we'll want to compare these values
localStorage.setItem("oauth_token", reply.oauth_token);
localStorage.setItem("oauth_token_secret", reply.oauth_token_secret);
// gets the authorize screen URL
cb.__call(
"oauth_authorize", {},
function (auth_url) {
console.log("auth_url", auth_url);
// JSFiddle doesn't open windows:
// window.open(auth_url);
$("#authorize").attr("href", auth_url);
// after user authorizes, user will be redirected to
// http://127.0.0.1:49479/?oauth_token=[some_token]&oauth_verifier=[some_verifier]
// then follow this section for coding that page:
// https://github.com/jublonet/codebird-js#authenticating-using-a-callback-url-without-pin
});
}
});
}
})
});
Also made a JSFiddle

Categories

Resources