Expo AuthSession immediately resolves as "dismiss"ed - react-native

I'm using Expo's AuthSession module to sign into Auth0:
let result = await AuthSession.startAsync({
authUrl: `${auth0Domain}/authorize?` + qs.stringify({
client_id: auth0ClientId,
response_type: 'code',
scope: 'openid profile email offline_access',
redirect_uri: redirectUrl,
code_challenge_method: 'S256',
code_verifier: codeVerifier,
state: oAuthState,
audience: auth0Audience
})
})
if (result.type === 'success') {
...
} else if (
result.type === 'dismiss' ||
(result.type === 'error' && result.errorCode === 'login-declined')
) {
// FIXME: alert never pops without delay here, despite the if correctly evaluating true
// Any way to check if the window has closed, if that's the issue?
await new Promise((resolve) => setTimeout(resolve, 5000))
let retry = await alertAsync(
'Authentication dismissed',
'Cancel signing in?',
'Try again',
'Exit'
)
return retry ? this.loginWithAuth0() : false
}
logger.error('Login failed', result)
throw new Error('Error signing in')
}
In a dev environment I never have an issue, but in a published app the promise often resolves immediately with { type: dismiss }, before the browser closes. Once I caught a glimpse of an alert (that I try to popup in case the session is actually dismissed) even before the AuthSession window opened.
If I add a 5s delay before popping the error up, it does show up 5s after starting the AuthSession rather than it finishing.
Testing on Android.
I looked into migration over to WebBrowser but it seems to be the same implementation.
Is there a consistent way to actually await the Auth process finishing, or should I ditch this whole thing and write my own login screen?
Thanks in advance for any input / direction!

I've put a detailed explanation of the problem and a possible solution https://github.com/expo/expo/issues/6679#issuecomment-570963717
As a temporary workaround, you can call AppState.currentState somewhere in your app, maybe logging it just to add the right listeners before the user clicks and enable the AuthSession (read more in the linked GitHub issue).
Have a look and comment please :)

Related

cypress cy.request 401 unauthorized [duplicate]

I want to save/persist/preserve a cookie or localStorage token that is set by a cy.request(), so that I don't have to use a custom command to login on every test. This should work for tokens like jwt (json web tokens) that are stored in the client's localStorage.
To update this thread, there is already a better solution available for preserving cookies (by #bkucera); but now there is a workaround available now to save and restore local storage between the tests (in case needed). I recently faced this issue; and found this solution working.
This solution is by using helper commands and consuming them inside the tests,
Inside - cypress/support/<some_command>.js
let LOCAL_STORAGE_MEMORY = {};
Cypress.Commands.add("saveLocalStorage", () => {
Object.keys(localStorage).forEach(key => {
LOCAL_STORAGE_MEMORY[key] = localStorage[key];
});
});
Cypress.Commands.add("restoreLocalStorage", () => {
Object.keys(LOCAL_STORAGE_MEMORY).forEach(key => {
localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]);
});
});
Then in test,
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
Reference: https://github.com/cypress-io/cypress/issues/461#issuecomment-392070888
From the Cypress docs
For persisting cookies: By default, Cypress automatically clears all cookies before each test to prevent state from building up.
You can configure specific cookies to be preserved across tests using the Cypress.Cookies api:
// now any cookie with the name 'session_id' will
// not be cleared before each test runs
Cypress.Cookies.defaults({
preserve: "session_id"
})
NOTE: Before Cypress v5.0 the configuration key is "whitelist", not "preserve".
For persisting localStorage: It's not built in ATM, but you can achieve it manually right now because the method thats clear local storage is publicly exposed as Cypress.LocalStorage.clear.
You can backup this method and override it based on the keys sent in.
const clear = Cypress.LocalStorage.clear
Cypress.LocalStorage.clear = function (keys, ls, rs) {
// do something with the keys here
if (keys) {
return clear.apply(this, arguments)
}
}
You can add your own login command to Cypress, and use the cypress-localstorage-commands package to persist localStorage between tests.
In support/commands:
import "cypress-localstorage-commands";
Cypress.Commands.add('loginAs', (UserEmail, UserPwd) => {
cy.request({
method: 'POST',
url: "/loginWithToken",
body: {
user: {
email: UserEmail,
password: UserPwd,
}
}
})
.its('body')
.then((body) => {
cy.setLocalStorage("accessToken", body.accessToken);
cy.setLocalStorage("refreshToken", body.refreshToken);
});
});
Inside your tests:
describe("when user FOO is logged in", ()=> {
before(() => {
cy.loginAs("foo#foo.com", "fooPassword");
cy.saveLocalStorage();
});
beforeEach(() => {
cy.visit("/your-private-page");
cy.restoreLocalStorage();
});
it('should exist accessToken in localStorage', () => {
cy.getLocalStorage("accessToken").should("exist");
});
it('should exist refreshToken in localStorage', () => {
cy.getLocalStorage("refreshToken").should("exist");
});
});
Here is the solution that worked for me:
Cypress.LocalStorage.clear = function (keys, ls, rs) {
return;
before(() => {
LocalStorage.clear();
Login();
})
Control of cookie clearing is supported by Cypress: https://docs.cypress.io/api/cypress-api/cookies.html
I'm not sure about local storage, but for cookies, I ended up doing the following to store all cookies between tests once.
beforeEach(function () {
cy.getCookies().then(cookies => {
const namesOfCookies = cookies.map(c => c.name)
Cypress.Cookies.preserveOnce(...namesOfCookies)
})
})
According to the documentation, Cypress.Cookies.defaults will maintain the changes for every test run after that. In my opinion, this is not ideal as this increases test suite coupling.
I added a more robust response in this Cypress issue: https://github.com/cypress-io/cypress/issues/959#issuecomment-828077512
I know this is an old question but wanted to share my solution either way in case someone needs it.
For keeping a google token cookie, there is a library called
cypress-social-login. It seems to have other OAuth providers as a milestone.
It's recommended by the cypress team and can be found on the cypress plugin page.
https://github.com/lirantal/cypress-social-logins
This Cypress library makes it possible to perform third-party logins
(think oauth) for services such as GitHub, Google or Facebook.
It does so by delegating the login process to a puppeteer flow that
performs the login and returns the cookies for the application under
test so they can be set by the calling Cypress flow for the duration
of the test.
I can see suggestions to use whitelist. But it does not seem to work during cypress run.
Tried below methods in before() and beforeEach() respectively:
Cypress.Cookies.defaults({
whitelist: "token"
})
and
Cypress.Cookies.preserveOnce('token');
But none seemed to work. But either method working fine while cypress open i.e. GUI mode. Any ideas where I am coming short?
2023 Updated on Cypress v12 or more:
Since Cypress Version 12 you can use the new cy.session()
it cache and restore cookies, localStorage, and sessionStorage (i.e. session data) in order to recreate a consistent browser context between tests.
Here's how to use it
// Caching session when logging in via page visit
cy.session(name, () => {
cy.visit('/login')
cy.get('[data-test=name]').type(name)
cy.get('[data-test=password]').type('s3cr3t')
cy.get('form').contains('Log In').click()
cy.url().should('contain', '/login-successful')
})

Access_token manipulation in e2e using protractor

I am new to e2e test automation. I am trying to access the access_token that if access token is issued, the app should be direct to specified URL, otherwise it should navigate to Login page. In beforeAll() function I have used this above mentioned condition but its not working i.e. even if token isn't issued, if condition returns true. Anyone have any idea how can we handle this?
if(browser.executeScript("window.localStorage.access_token")!==undefined) does the same work when it is changed to if(browser.executeScript("window.localStorage.access_token"))
beforeAll(()=>{
login = new LoginPage();
addRoom = new AddRoomClass();
return new Promise((resolve)=>{
setTimeout(()=>{
addRoom.navigateTo().then(()=>{
if(browser.executeScript("window.localStorage.access_token")!==undefined){
console.log(browser.executeScript("window.localStorage.access_token"));
console.log('authenticated user');
resolve();
}
else{
return new Promise((resolve)=>{
login.navigateTo().then(()=>{
login.getEmailInput().sendKeys('someone#gmail.com');
login.getPasswordInput().sendKeys('password');
login.getLoginButton().click().then(()=>{
setTimeout(()=>{
expect(browser.getCurrentUrl()).toContain('organization').then(()=>{
resolve();
});
},40000);
});
});
}).then(()=>{
resolve();
});
}
});
},6000)
});
});
Code snippet is attached.
shot in a dark... your code that you evaluate in browser's console is missing return like this:
browser.executeScript("return window.localStorage.access_token")

problem accessing elements using id/name for login form in cypress

I am trying to login to a form written in angular js but cypress throws the following exception:
Uncaught TypeError: $(...).materialScrollTop is not a function
This error originated from your application code, not from Cypress.
When Cypress detects uncaught errors originating from your application it will automatically fail the current test.
This behavior is configurable, and you can choose to turn this off by listening to the 'uncaught:exception' event.
https://on.cypress.io/uncaught-exception-from-application
This is the cypress login code:
context('TestLogin', () => {
it('Test Login', () => {
cy.visit('url');
cy.get('input[id=Email]').type('email', {force: true});
cy.get('input[id=Password]').type('passcode', { force: true });
cy.get('button[type=submit]').click();
})
})
Since the login has a csrf token, I have used cy.request() as follows and I do get a response with status code 200 but when re-loading the site it goes back to login page.
describe("Tests for AntiForgeryToken", function () {
// variable from config, that contain Identity Server URL
const identityUrl = Cypress.config("identityServerUrl")
// command declaration that we are going to use in tests
// allows us to create request to server
Cypress.Commands.add("loginByToken", function (token, login, password) {
cy.request({
method: "POST",
failOnStatusCode: false,
url: `${identityUrl}/Account/Login`,
form: true,
body: {
email: login,
password: password,
__RequestVerificationToken: token,
RememberLogin: false
}
})
})
it("Should parse token from response body and return 200", function () {
cy.request(`${identityUrl}/Account/Login`)
.its("body")
.then((body) => {
const $html = Cypress.$(body)
// when the page is rendered
// we are trying to find the Request Token in the body of page
const token = $html.find("input[name=__RequestVerificationToken]").val()
// POST request with token and login data
// then we simply verify whether Indentity Server authorized us
cy.loginByToken(token, "test#test.com", "Test_1234")
.then((resp) => {
expect(resp.status).to.eq(200)
})
})
cy.visit(`${identityUrl}/Account/`);
})
Cypress documentation didn't provide much info about the exception.
Any insights from cypress experts are helpful.
As evident from the error, Cypress is failing the test as it found an exception in your application,this is not a cypress level exception but an uncaught exception in your app which is causing cypress to fail the test, this is pretty useful as you can check if its an actual error in your app and log it for the dev team to fix, check if you are able to reproduce this manually, either way i think the application code should be fixed to either fix the bug or catch the exception and return a valuable error message. If you want to disable this feature you can turn off all uncaught exception handling, so in your index.js or whatever file is the entry point add the following:
Cypress.on('uncaught:exception', (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
// you can also add a Debugger here to analyze the error
debugger;
return false;
});
not sure if turning this off will help as looks like there is something in your application which could be an issue, but this is just for informational purposes that you can turn this feature off if you needed to.
Here is the documentation for further reading : Cypress Events documentation
hope this helps

React-native Geolocation. Wait for user's response to request

iOS issue only:
I am using the following code to get the users location.
navigator.geolocation.getCurrentPosition(
(position) => {
console.log("Native GEO GOOD", position);
return resolve(position)
},
(err) => {
console.log("Native GEO BADD", err);
return reject(err)
},
{ enableHighAccuracy: false, timeout: 5000, maximumAge: 0 },
)
The above code opens a dialog box, from which the user can allow my app to geolocate.
The problem is I want to wait until the user actually responds using the dialog box before calling the error or success callback.
I tried to use: requestAuthorization(). But that just opens the dialog box and I have no way to telling when the user has accepted the request to geolocate.
What I would like to do is ask the users permission to geolocate, then after the user accepts, try to geolocate the user.
But I don't see how to do that using react-native geolocation.
If requestAuthorization() took a callback option for when the user responds to the dialog box, that would solve my issue.
In React-Native using Expo (https://expo.io) you ask for permissions using a Promise and then act on the promise (hopefully when permission is given).
Permissions.askAsync((Permissions.LOCATION)
.then(({status}) => {
//your code here after permission is granted
});
);
If you aren't using expo, there is a Component call react-native-permissions (https://github.com/yonahforst/react-native-permissions.git) that allows you to request permissions using a promise like my example but without expo. Their example shows the request setting state to let you know the permissions status which you can act on.
Permissions.request('location', { type: 'always' }).then(response => {
this.setState({ locationPermission: response })
})

Why won't CasperJS accept a redirect request after clicking on a link?

I'm having more Casper troubles, this time I've told Casper to click on a href that will redirect it to a page that will redirect it to the twitter app auth page:
var casper = require('casper').create({
verbose: false,
logLevel: 'debug',
waitTimeout: 10000
});
phantom.cookiesEnabled = true;
casper.start('http://foo.bar', function afterstart() {
if (this.exists('#back-to-top')) {
this.echo('BOOYA! foo.bar is loaded', 'INFO');
} else {
this.echo('Page didnt load, something went all screwy.', 'ERROR');
}
});
casper.then(function signin() {
this.click('a[href="/sys/login"]' );
});
casper.then(function tellsignin() {
if (this.exists('#not-logged-in ')) {
this.echo("I clicked on twitter login button, so now the twitter login page is loaded.", 'INFO');
} else {
this.echo('Twitter login link didnt work, something went wacky');
};
});
When I run it, the console error output is this, plus some other stuff that isn't important:
Test file: testingtest.js
BOOYA! foo.bar is loaded
Twitter login link didnt work, something went wacky
I've tried looking up explanations, and found this group thread, but it unfortunately doesn't provide any answers for my problem.
Does anyone know any possible reasons why Casper isn't redirecting?
I've resolved the issue thanks to all of your help and support, lol! What happened is that the tellsignin function was being executed before the browser had time to be redirected to the twitter app auth page, so I just added a this.waitForText('Authorize foo-bar app to use your account?') to the signin function.