Cypress End To End testing interacting with Metamask - automated-tests

I am writing a couple of end to end tests for a web application but in order to test the sign in the user has to first connect its Metamask wallet. I was wondering if there is a way to interact with the Metmask extension once it pops up. My current tests gets the user up to the point where it clicks on a link and in the page and it will cause the Metamask extension to pop up. But I have been having trouble grabbing/interacting with the elements inside the Metamask pop up extension. Is there a way to interact with the extension through Cypress or will I have to take a different approach ?
ChatGPT has been giving me mixed answers telling me I can interact with it using cy.task() and sometimes telling me that I can directly grab elements within the extension through cypress.get() but haven't gotten it to work yet.
This is what my tests looks like at the moment:
cy.task('MetamaskPopup',()=>{
return new Promise((resolve, reject)=>{
globalThis.ethereum.send({
method:'eth_requestAccounts'
},(error, result)=>{
if(error){
reject(error);
}
else {
resolve(error)
}
})
})
})
cy.visit(Cypress.env('base_url_auth'))
cy.get("#root").find(".Auth").find(".Card").find('h4').contains('Connect Metamask - click here to continue.').click();
cy.wait(15000);
cy.task('checkMetamaskpopu').then(()=>{
cy.get('#app-content').shold('be.visible')
})
}
)
And this is my cypress.config file:
e2e: {
setupNodeEvents(on, config) {
on('task',{
MetamaskPopup(ms){
return new Promise((resolve)=>{
setTimeout(()=> resolve(null),ms)
})
}
})
},
},
});

Related

Ways to test if my app is making the correct amount of api calls

I'm testing a react app with cypress, and cypress is really good at checking if atleast n many are calls are made on load, and not so good at checking if only those calls were made. The app itself is a pretty standard react app that uses window.fetch for api calls. Is there another service, like cypress something similar, that's got the ability to test that in an automated fashion?
For instance, if you visit the site logged in we have three config calls. If a developer commits code that accidentally calls one of those twice, ideally I'd like an automated test to catch that.
I don't want to do it in Cypress because it seems like that's not what the product is really intended to do unless you hack it with an undocumented solution.
Seems like you need a hard wait to ensure you check after all calls have been issued.
Also recommend stubbing so that there's no latency waiting for server response.
it('check for excess calls', () => {
cy.intercept('https://jsonplaceholder.typicode.com/todos/1', {}).as('fetch')
cy.visit('/').then(() => {
// wait for expected calls
cy.wait('#fetch')
cy.wait('#fetch')
cy.wait('#fetch')
cy.wait(500)
cy.get('#fetch.all') // check the call count
.should(calls => {
expect(calls).to.have.length(3) // no extra calls
})
})
})
Tested with this app HTML
<script>
fetch('https://jsonplaceholder.typicode.com/todos/1')
fetch('https://jsonplaceholder.typicode.com/todos/1')
fetch('https://jsonplaceholder.typicode.com/todos/1')
</script>
You may want to burn-test to get the right wait time, see Burning Tests with cypress-grep
Alternatively you can wait four times and catch the error
it('check for excess calls', () => {
cy.intercept('https://jsonplaceholder.typicode.com/todos/1', {}).as('fetch')
cy.visit('html/call-limit.html').then(() => {
cy.wait('#fetch')
cy.wait('#fetch')
cy.wait('#fetch')
Cypress.once('fail', (error) => {
// catch ignore failure on 4th cy.wait('#fetch')
if (error.message.includes('No request ever occurred')) {
return
}
throw error // rethrow to fail for another reason
})
cy.wait('#fetch') // should fail and trigger listener above
cy.get('#fetch.all')
.should(calls => {
expect(calls).to.have.length(3)
})
})
})

Ionic Storage doesn't update values

after I successfully log into my app using Firebase I want to store a bunch of information (like, user email, user uid, user name...) and use it throughout my app. The best way I found for this is to use Ionic Storage.
Now, in the first login works fine, but If I log out and log in with another user, the first user info is still showing instead of the new one. Note that I am cleaning all my storage when the user hits log out. My code:
Auth validation (guard): I am checking user auth status again after login.
return this.AFauth.authState.pipe(map(auth => {
if (auth !== null && auth !== undefined) {
this.saveUserInStorage(auth);
return true;
} else {
this.router.navigate(['/home']);
return false;
}
}))
Saving Firebase info in Storage
saveUserInStorage(auth) {
this.storage.ready().then(() => {
this.storage.clear(); //cleaning again just in case...
this.storage.set('user_uid', auth.uid);
this.storage.set('user_name', auth.displayName);
this.storage.set('user_email', auth.email);
this.storage.set('user_photoUrl', auth.photoUrl);
}).catch(err => {
console.log('no pudimos guardar');
});
}
Logout function
logOutUser() {
firebase.auth().signOut().then(result => {
// after user hits logout, I erase my storage
this.storage.remove('user_email');
this.storage.clear();
this.router.navigate(['/home']);
}).catch(error => {
console.log(error);
// An error happened.
});
}
I have to reload my webpage to see the last user logged in.
This might not have anything to do with your storage but rather you have to reset your forms or fields where the information is shown or change the way this information is loaded. I would suggest two options:
On the pages use ionViewWillEnter and put the code in there where the information is pulled out of the storage. (this is probably the easiest)
Use BehaviorSubjects for always emitting a new event when the info is changed and listen to those events where the information is used.
The reason for this is, that every page, once created won't create itself again. You can see this if you console Log something in ngOnInit. Therefore your old information sticks to the page until you find another way to update it. (with ionViewWillEnter or Observables)

Insert new collection after function runs on server

When I return the geocode from googles API I'm trying to save it into my database. I've been trying to use the code below, to just insert a Test document with no luck. I think it has something to do with meteor being asynchronous. If I run the insert function before the googleMapsClient.geocode function it works fine. Can someone show me what I'm doing wrong.
Meteor.methods({
'myTestFunction'() {
googleMapsClient.geocode({
address: 'test address'
}, function(err, response) {
if (!err) {
Test.insert({test: 'test name'});
}
});
}
});
I see now where you got the idea to run the NPM library on the client side, but this is not what you really want here. You should be getting some errors on the server side of your meteor instance when you run the initial piece of code you gave us here. The problem is that the google npm library runs in it's own thread, this prevents us from using Meteor's methods. The easiest thing you could do is wrap the function with Meteor.wrapAsync so it would look something like this.
try {
var wrappedGeocode = Meteor.wrapAsync(googleMapsClient.geocode);
var results = wrappedGeocode({ address : "testAddress" });
console.log("results ", results);
Test.insert({ test : results });
} catch (err) {
throw new Meteor.Error('error code', 'error message');
}
You can find more info by looking at this thread, there are others dealing with the same issue as well
You should run the googleMapsClient.geocode() function on the client side, and the Test.insert() function on the server side (via a method). Try this:
Server side
Meteor.methods({
'insertIntoTest'(json) {
Test.insert({results: json.results});
}
});
Client side
googleMapsClient.geocode({
address: 'test address'
}, function(err, response) {
if (!err) {
Meteor.call('insertIntoTest', response.json);
}
});
Meteor Methods should be available on the both the server and client sides. Therefore make sure that your method is accessible by server; via proper importing on /server/main.js or proper folder structuring.
(If a method contains a secret logic run on the server, it should be isolated from the method runs on both server & client, though)

Meteor.userId() not available in ConnectHandlers

I am trying to create a file upload feature in Meteor where a logged in user is able to upload a file to the server under a directory named after their username. I have the basics working but when I take it a step further by checking the logged in user ID, things start breaking. Specifically:
WebApp.connectHandlers.use('/upload/', function(req, res) {
if (this.userId) {
// Do cool stuff.
} else {
res.writeHead(500, {"content-type":"text/html"});
res.end("this.userId = " + this.userId); // End the response.
}
});
Result:
this.userId = undefined
And...
WebApp.connectHandlers.use('/upload/', function(req, res) {
if (Meteor.userId()) {
// Do cool stuff.
} else {
res.writeHead(500, {"content-type":"text/html"});
res.end("Meteor.userId() = " + Meteor.userId()); // End the response.
}
});
Result:
Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
at Object.Meteor.userId (packages/accounts-base/accounts_server.js:19:1)
at Object.Package [as handle] (packages/cool_package/upload.js:34:1)
at next (/Users/me/.meteor/packages/webapp/.1.2.0.19shc3d++os+web.browser+web.cordova/npm/node_modules/connect/lib/proto.js:190:15)
at Function.app.handle (/Users/me/.meteor/packages/webapp/.1.2.0.19shc3d++os+web.browser+web.cordova/npm/node_modules/connect/lib/proto.js:198:3)
at Object.fn [as handle] (/Users/me/.meteor/packages/webapp/.1.2.0.19shc3d++os+web.browser+web.cordova/npm/node_modules/connect/lib/proto.js:74:14)
at next (/Users/me/.meteor/packages/webapp/.1.2.0.19shc3d++os+web.browser+web.cordova/npm/node_modules/connect/lib/proto.js:190:15)
at Object.WebAppInternals.staticFilesMiddleware (packages/webapp/webapp_server.js:331:1)
at packages/webapp/webapp_server.js:625:1
The code above is included in a Meteor package I'm developing. The package.js file specifies that the code should run on the server:
api.add_files("upload.js", "server");
So my questions are:
What is the correct way to check the logged in user ID and username?
Can this code be moved to an Iron Router route instead?
It looks like the line
WebApp.connectHandlers.use('/upload/', function(req, res) {
Is Express.js or similar code -- if so, you have broken out of the Meteor frameowrk providing your own REST services etc. If that is the case you also have to provide your own user management and authentication scheme for incoming REST calls, just as you would in any other bare-bones REST applications

Meteor: Using iron router and custom authentication issue

I might have this pretty close but I'm lacking the knowledge to fix this last issue.
I wanted to use a custom authentication system instead of using accounts-ui so I could track some additional details about each user.
Everything worked great until I get to the resetPassword part. If a user submits their email address in the forgotPassword form, the email is received. But when you click the reset password link in the email it does not display the resetPassword template.
This is on SO here:
Meteor account email verify fails two ways
And the iron-router github issue tracker here (which has the most fixes though is more focused on the enrollmentemail than resetPassword which I'm assuming should be very similar):
Iron-router swallows Accounts.sendEnrollmentEmail
If I understand correctly from the iron-router issue tracker above, iron-router doesn't (or didn't and maybe still doesn't) support hashbang urls like that being sent in the reset password email. A URL like:
http://localhost:3000/#/reset-password/T4rPxcVNWKwBONHSRajSk7dNZvM_YRxTLyzxZVv5SuU
Meteor was then updated so that meteor accounts-base strips out everything after the # and stores them in variables in the Accounts namespace.
While I think I understand all of that, now the question is why I can't get the suggestions in the issue tracker to work for my reset password code. I'm using everything that is in the custom auth system by Julien Le Coupanec and then I've done the following from the issue tracker:
router.js
Router.map(function() {
this.route('invList', {path: '/'});
this.route('resetPassword', {
controller: 'AccountController',
path: '/reset-password/:token',
action: 'resetPassword'
});
});
AccountController = RouteController.extend({
resetPassword: function () {
Accounts.resetPassword(this.params.token, function () {
Router.go('/reset-password');
});
}
});
overrideaccounts.js in /server
(function () {
"use strict";
Accounts.urls.resetPassword = function (token) {
return Meteor.absoluteUrl('reset-password/' + token);
};
Accounts.urls.verifyEmail = function (token) {
return Meteor.absoluteUrl('verify-email/' + token);
};
Accounts.urls.enrollAccount = function (token) {
return Meteor.absoluteUrl('enroll-account/' + token);
};
})();
I'm wondering if the issues isn't related to either bad routing on my part (likely since I don't have my head wrapped around it well yet), if I put "server code" as is listed in the issue track in the right place, or if the session related code below is what is causing the resetPassword template to not display. Or something else that I'm missing of course.
main.js
//forgotPassword helper and event handler
Template.main.helpers({
showForgotPassword: function() {
return Session.get('showForgotPassword');
},
resetPassword: function(){
return Session.get('resetPassword');
}
});
After spending many hours on what I thought would be a really simple authentication system, I'm still at a loss. Appreciate any advice!
Don't struggle with hacking the hash and iron router, just back to Meteor original design flow.
When user click the verify link in email, it lead back to "/" (home), so just did this:
Template.home.created = function() {
if (Accounts._verifyEmailToken) {
Accounts.verifyEmail(Accounts._verifyEmailToken, function(err){
if (err != null) {
// handle the error
} else {
// do what you want, maybe redirec to some route show verify successful message
}
});
}
};
I did this and verify email right, same way worked for enroll, reset password...

Resources