cypress-io / cypress

Fast, easy and reliable testing for anything that runs in a browser.
https://cypress.io
MIT License
46.44k stars 3.14k forks source link

Automatically accept geolocation permission / mock geolocation for tests #2671

Open akozmanyan1 opened 5 years ago

akozmanyan1 commented 5 years ago

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

jennifer-shehane commented 5 years ago

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.

akozmanyan1 commented 5 years ago

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.

jennifer-shehane commented 5 years ago

@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.

swapab commented 5 years ago

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

nico2che commented 5 years ago

@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

attilavago commented 5 years ago

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.

andrewhl commented 5 years ago

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.

jasonlimantoro commented 5 years ago

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.

attilavago commented 5 years ago

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.

jasonlimantoro commented 5 years ago

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.

emahuni commented 5 years ago

Any news on this feature?

tymondesigns commented 5 years ago

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

ravihlb commented 4 years ago

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) } })
yes-mam commented 4 years ago

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.

smlkA commented 4 years ago

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.
victor-battestin commented 4 years ago

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:

ciekawy commented 4 years ago

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.

eerkkk commented 4 years ago

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();
bahmutov commented 4 years ago

This is beautiful solution

Sent from my iPhone

On Dec 11, 2019, at 19:37, Erk Eyupgiller notifications@github.com 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.

jennifer-shehane commented 4 years ago

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

marcospassos commented 4 years ago

@jennifer-shehane, any news on this?

kamranayub commented 3 years ago

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.

PriyaKR commented 3 years ago

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.

ThaiChingChong commented 3 years ago

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

mpelzsherman commented 3 years ago

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

IpelaTech commented 3 years ago

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() {

            };
        });
    });
alastorohlin commented 3 years ago

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

I hope this helps someone else.

mmitchell3590 commented 2 years ago

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

owenashurst commented 2 years ago

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.

prodrammer commented 2 years ago

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
}
gaikwadamolraj commented 2 years ago

I encountered with same problem but mocking was not worked for me. My case: Application use geo location for the login purpose. So with mock it not worked for me.

Solution: I used below package and followed the steps and its works for me.

https://www.npmjs.com/package/cypress-visit-with-custom-geolocation

Working sample example: https://github.com/gaikwadamolraj/cypress-visit-with-custom-geolocation

ArCiGo commented 1 year ago

@eerkkk Do you have a sample using your solution?

gaikwadamolraj commented 1 year ago

@eerkkk Do you have a sample using your solution?

Yes you can check this repo: https://github.com/gaikwadamolraj/custom-geo-location-cypress

I created a sample working html file and currently pkg is using in cypress.

marijana-rukavina commented 1 year ago

Why did mocking it like this stopped working? I am curious, it was working fine in our tests until recently 🤔

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.

ter1203 commented 1 year ago

I am now working on a test coverage that should check if the modal is displayed when the user location is disabled. The problem is how to check the user location is disabled or not with Cypress. Is there anyway for that? Or how to mock location disable status with Cypress?

janswist commented 1 year ago

4 years and still it's not implemented despite interest?

Khalester commented 1 year ago

I came up with this custom cypress command, it seems to work and doesn't show the popup.

Cypress.Commands.add(
  "mockGeolocation",
  (coords: { latitude: number; longitude: number }) => {
    cy.window().then((win) => {
      cy.wrap(
        Cypress.automation("remote:debugger:protocol", {
          command: "Browser.grantPermissions",
          params: {
            permissions: ["geolocation"],
            origin: win.location.origin,
          },
        }),
      );
    });

    console.debug(
      `cypress::setGeolocationOverride with position ${JSON.stringify(coords)}`,
    );

    cy.log("**setGeolocationOverride**").then(() =>
      Cypress.automation("remote:debugger:protocol", {
        command: "Emulation.setGeolocationOverride",
        params: {
          latitude: coords.latitude,
          longitude: coords.longitude,
          accuracy: 50,
        },
      }),
    );
  },
);

Then use it like this cy.mockGeolocation({ latitude: 0, longitude: 0 })

EDIT

Versions

EDIT 2

This is the command for mocking denied Geolocation permissions

Cypress.Commands.add("mockGeolocationDenied", () => {
  cy.window().then((win) => {
    cy.wrap(
      Cypress.automation("remote:debugger:protocol", {
        command: "Browser.grantPermissions",
        params: {
          permissions: [],
          origin: win.location.origin,
        },
      }),
    );
  });
});
PrabhurajGMastery commented 1 year ago

@Khalester Hi.. if i launch cypress with your code i get "permission can't be granted to opaque origins" Was really hoping for your code to work May i get your repo for analysis ?

muralinaidud commented 1 year ago

HI All is there a way to click allow when the test runs ?

agospranzetti commented 6 months ago

@jennifer-shehane whats the update here ? there is still not a viable solution ? with the above Im also getting "permission can't be granted to opaque origins"

Zach-Costa commented 3 weeks ago

@PrabhurajGMastery @muralinaidud @agospranzetti

Got this working - Cypress no longer likes you setting origin to win.location.origin, it wants to know exactly what site is getting the location permission. If I explicitly include the expected origin, it passes without issue.