cypress icon indicating copy to clipboard operation
cypress copied to clipboard

Automatically accept geolocation permission / mock geolocation for tests

Open akozmanyan1 opened this issue 5 years ago • 32 comments

Current behavior:

I have a map and a button in my app. When user clicks the button it identifies it’s current position and map pin moves to the current location of the user. This does not happen when running cypress tests.

Desired behavior:

When running cypress test I want to be able to get the location of user and the map to show that position changes.

Steps to reproduce:

I created a simple POC – https://github.com/akozmanyan1/geolocation-bug Just download and run it - '$ npm run serve'. You also will need to open cypress - '$ ./node_modules/.bin/cypress open'

Versions

Cypress - Running Electron 59 OS: Windows 10 browser: Google Chrome - Version 69.0.3497.100

akozmanyan1 avatar Oct 29 '18 11:10 akozmanyan1

Hey @akozmanyan1, thanks so much for providing a reproducible repo!

I notice that when running this locally, my browser prompts for permission to access my location. My guess is that Cypress is not handling this dialog properly when automated.

screen shot 2018-10-29 at 11 54 19 am

Can you disable the checking for permission on this dialog when run within Cypress? I would like to verify that this is indeed the problem.

Here's some instruction on identifying whether you app is running within Cypress.

jennifer-shehane avatar Oct 29 '18 16:10 jennifer-shehane

Hi @jennifer-shehane , I don't think I can disable this alert. This is Chrome asking for permission to share the location with the website. The problem is that no such dialog appears in Cypress. In addition, I can't find if there is a switch in Cypress which can turn this off this dialog.

akozmanyan1 avatar Oct 29 '18 23:10 akozmanyan1

@akozmanyan1 This is definitely something that Cypress should handle and can handle by utilizing Chrome debugger protocol.

There aren't any really easy workarounds for this.

You could search for ways to emulate geolocation in Chrome, but none of those solutions are going to work in CI when run in Electron.

jennifer-shehane avatar Oct 30 '18 17:10 jennifer-shehane

@akozmanyan1 Did you found a way to mock/stub navigator.geolocation ?

swapab avatar Nov 22 '18 16:11 swapab

@swapab with the stub method you can mock a position or an error

For example, an approach like

function fakeLocation(latitude, longitude) {
  return {
    onBeforeLoad(win) {
      cy.stub(win.navigator.geolocation, "getCurrentPosition", (cb, err) => {
        if (latitude && longitude) {
          return cb({ coords: { latitude, longitude } });
        }
        throw err({ code: 1 }); // 1: rejected, 2: unable, 3: timeout
      });
    }
  };
}

And

cy.visit("http://www.cypress.io", fakeLocation()); // Error
cy.visit("http://www.cypress.io", fakeLocation(48, 2)); // France

But better with cypress commands

nico2che avatar Dec 04 '18 11:12 nico2che

I'd like to see this fixed too. With the rise of PWAs location detection, notifications, etc, is becoming a common feature, and we should be able to emulate user behaviour with these dialogues.

attilavago avatar Mar 09 '19 10:03 attilavago

I'm having the same issue. I've attempted to use the following method:

plugins/index.js

module.exports = (on, config) => {
    on('before:browser:launch', (browser = {}, args) => {
        args.push('--disable-search-geolocation-disclosure');
        return args;
    })
}

But the UI popup for authorizing geolocation still appears in Chrome.

andrewhl avatar Mar 15 '19 21:03 andrewhl

Any updates on this?

I tried to use a geolocation API for form auto-filling, but I cannot test it with Cypress because of the geolocation permission popup.

jasonlimantoro avatar Mar 24 '19 10:03 jasonlimantoro

Any updates on this?

I tried to use a geolocation API for form auto-filling, but I cannot test it with Cypress because of the geolocation permission popup.

My use-case is very similar.

attilavago avatar Mar 24 '19 17:03 attilavago

For now, I finally decided to use the stubbed method as per @nico2che 's answer.

cy.fixture('location.json').as('fakeLocation');
cy.get('@fakeLocation').then(fakeLocation => {
  cy
    .visit('some-url', {
      onBeforeLoad (win) {
	cy
	  .stub(win.navigator.geolocation, 'getCurrentPosition')
	  .callsFake((cb) => {
             return cb(fakeLocation);
	  });
      },
  });
});

where location.json is a fixture for fake location

{
  "coords": {
    "latitude": 48,
    "longitude": 2
  }
}

The difference is that I use it in the onBeforeLoad option argument for visit()'s method. And I use it in the beforeAll() hooks, in my specs.

It works for me now. Hope it helps.

jasonlimantoro avatar Mar 24 '19 18:03 jasonlimantoro

Any news on this feature?

emahuni avatar May 28 '19 11:05 emahuni

Chrome 75 has now been "fixed" where it will never timeout as long as this prompt is there. https://bugs.chromium.org/p/chromium/issues/detail?id=957596

Therefore this no longer works even with stubbing the location.

We have had to rollback to Chrome 74 for now, but we need to fix this asap

tymondesigns avatar Jun 05 '19 08:06 tymondesigns

Edit: it seems that calling the command directly inside cy.visit() messes up the request recognition? cy.wait() just stopped working when I did that (something to do with promisses). As a workaround, I still added the stub as a custom command and did as follows:

cy.visit('url')
cy.on('window:before:load', (win) => { cy.mockGeolocation(win, lat, long) })

And it seems to be working properly now, logging every XHR request on the console and cy.wait() works again.


I managed to pull it out by mixing up @nico2che's answer a bit also: I first added it as a Cypress Command

Cypress.Commands.add("mockGeolocation", (windowObj, lat, long) => {
    cy.stub(windowObj.navigator.geolocation, "getCurrentPosition", (cb) => {
        return cb({ coords: { "latitude": lat, "longitude": long } });
    })
})

And implemented in the spec as:

cy.visit('url', { onBeforeLoad: (win) => { cy.mockGeolocation(win, lat, long) } })

ravihlb avatar Aug 19 '19 21:08 ravihlb

We have implemented the above and it works fine for triggering and mocking out the getCurrentLocation response, however, one thing we are having trouble with is testing the state of the DOM while the getCurrentLocation stub runs.

yes-mam avatar Sep 17 '19 14:09 yes-mam

UPDATED: I could start my test with following custom command:

Cypress.Commands.add('visitWithMockGeolocation', (url, latitude = 54, longitude = 39) => {
  const mockGeolocation = (win, latitude, longitude) => {
    cy.stub(win.navigator.geolocation, 'getCurrentPosition', cb => {
      return cb({ coords: { latitude, longitude } });
    });
  };
  cy.visit(url, {
    onBeforeLoad: win => {
      mockGeolocation(win, latitude, longitude);
    }
  });
});

@ravihlb Hi, I have tried your both approaches and got error:

Uncaught CypressError: Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.

The command that returned the promise was:

 > cy.visit()

The cy command you invoked inside the promise was:

 > cy.mockGeolocation()

Because Cypress commands are already promise-like, you don't need to wrap them or return your own promise.

smlkA avatar Sep 18 '19 15:09 smlkA

Hello guys, I'm also in with this same problem. Any updates about a solution for cypress interact with chrome alerts and notifications? I tried all the solutions proposed by those guys above and none worked for my case. :cry:

victor-battestin avatar Oct 02 '19 21:10 victor-battestin

I have a slightly other question. As I also got the permission popup (along with a write files one) and that is actually expected by me - I wonder if there is (or will be) a way to either control those popups or simulate behavior with config/mock to either enable or disable particular feature during test execution.

I also found those popups to appear only on first execution thus I assume the answer is being stored in Cypress browser profile.

ciekawy avatar Oct 09 '19 12:10 ciekawy

I managed to simplify the solution suggested by @nico2che and @jasonlimantoro by using cy.window() cypress method.

Cypress.Commands.add('mockGeolocation', (latitude = 30, longitude = -98) => {
	cy.window().then(($window) =>  {
		cy.stub($window.navigator.geolocation, 'getCurrentPosition', (callback) => {
	   		return callback({ coords: { latitude, longitude } });
		});
	});
});

And this way it can be used anywhere without using an onBeforeLoad promise if it's needed in a specific test rather than the whole suite.

cy.mockGeolocation();

eerkkk avatar Dec 12 '19 00:12 eerkkk

This is beautiful solution

Sent from my iPhone

On Dec 11, 2019, at 19:37, Erk Eyupgiller [email protected] wrote:

 I managed to simplify the solution suggested by @nico2che and @jasonlimantoro by using cy.window() cypress method.

Cypress.Commands.add('mockGeolocation', (latitude = 30, longitude = -98) => { cy.window().then(($window) => { cy.stub($window.navigator.geolocation, 'getCurrentPosition', (callback) => { return callback({ coords: { latitude, longitude } }); }); }); }); And this way it can be used anywhere without using an onBeforeLoad promise if it's needed in a specific test rather than the whole suite.

cy.mockGeolocation(); — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or unsubscribe.

bahmutov avatar Dec 12 '19 00:12 bahmutov

Mentioned in https://github.com/cypress-io/cypress/issues/5973 - the Chrome Debugger Protocol has a way to set geolocation now and we may be able to tap into that - caveat, as always, being that this would only work in chrome browsers. https://developers.google.com/web/tools/chrome-devtools/device-mode/geolocation

jennifer-shehane avatar Dec 17 '19 09:12 jennifer-shehane

@jennifer-shehane, any news on this?

marcospassos avatar Apr 20 '20 11:04 marcospassos

Hey all! I've just released a package to control browser permissions in Cypress which should address this issue! 🎉

In short, it uses the before:browser:launch event to handle sending the right preference settings/values to Chrome, Edge, and Firefox.

The package is cypress-browser-permissions and you can read the introduction post here!

https://dev.to/kamranayub/controlling-browser-permissions-in-cypress-end-to-end-tests-5d7b

@jennifer-shehane It would be amazing to see this built into the core.

kamranayub avatar Jul 17 '20 03:07 kamranayub

I managed to simplify the solution suggested by @nico2che and @jasonlimantoro by using cy.window() cypress method.

Cypress.Commands.add('mockGeolocation', (latitude = 30, longitude = -98) => {
	cy.window().then(($window) =>  {
		cy.stub($window.navigator.geolocation, 'getCurrentPosition', (callback) => {
	   		return callback({ coords: { latitude, longitude } });
		});
	});
});

And this way it can be used anywhere without using an onBeforeLoad promise if it's needed in a specific test rather than the whole suite.

cy.mockGeolocation();

I have tried out this work around and have done a cy.visit() to a page after cy.mockGeolocation() command but the visit commands doesn't seems to use the mocked geo location and still shows the page based on the current location.

PriyaKR avatar Aug 21 '20 11:08 PriyaKR

Tried the above commands to mock the geo location as well but it doesn't work for me too. :( The page I am currently testing still behave as though the user is on current location

ThaiChingChong avatar Dec 02 '20 22:12 ThaiChingChong

@eerkkk 's solution works fine for me if I call cy.mockGeolocation() after cy.visit().

mpelzsherman avatar Feb 17 '21 17:02 mpelzsherman

For future reference, another solution (I'm using this as I'm working on the new component testing and there isn't a visit method), I use this to mock/stub/etc the geolocation inside the test:

//*.spec.js
    it("foo", () => {
        cy.window().then(win => {
            win.navigator.geolocation.getCurrentPosition = function() {
                
            };
        });
    });

IpelaTech avatar Apr 16 '21 08:04 IpelaTech

Hi, I was facing the same problem, but I found this plugin and it works great 🚀

I hope this helps someone else.

alastorohlin avatar Jul 09 '21 17:07 alastorohlin

I am also using the cypress-browser-permissions plugin, but it would be great if Cypress could support this without an additional plugin. It is not ideal especially when there are limitations like this https://github.com/cypress-io/cypress/issues/5240 when using multiple plugins that modify the 'on' event

Also, as of 8.0.0 the default behavior for cypress run is headless mode, and plugins do not work there. image

mmitchell3590 avatar Jul 23 '21 13:07 mmitchell3590

Hi, I was facing the same problem, but I found this plugin and it works great 🚀

I hope this helps someone else.

It's @kamranayub's. He posted about it a few posts above.

owenashurst avatar Aug 02 '21 13:08 owenashurst

I wonder if relying on location state violates the recommendations in Cypress' docs on Conditional Testing.

If we don't have a way to override chrome's default behavior in a reliable way, abstracting geolocation as a service in our applications and providing an override via localStorage in the service might be a way to avoid flaky tests.

Something like:

if (locationOverrideExists()) {
 // return location from local storage...
} else {
 // get live location data
}

prodrammer avatar Dec 23 '21 01:12 prodrammer