80

I'm looking for a way to programmatically change navigator.userAgent on the fly. In my failed attempt to get an automated javascript unit tester, I gave up and attempted to begin using fireunit. Immediately, I've slammed into one of the walls of using an actual browser for javascript testing.

Specifically, I need to change navigator.userAgent to simulate a few hundred userAgent strings to ensure proper detection and coverage on a given function. navigator.userAgent is readonly, so I seem stuck! How can I mock navigator.userAgent? User Agent Switcher (plugin) can switch FF's useragent, but can I do it within javascript?

Stefan Kendall
  • 66,414
  • 68
  • 253
  • 406

15 Answers15

122

Try:

navigator.__defineGetter__('userAgent', function(){
    return 'foo' // customized user agent
});

navigator.userAgent; // 'foo'

Tried it in FF2 and FF3.

Crescent Fresh
  • 115,249
  • 25
  • 154
  • 140
  • 4
    Can this solution be used to set iframe's user agent ? – MANnDAaR Aug 22 '12 at 10:24
  • Works for me. (IE on Windows) – Tyler Liu Jan 27 '15 at 03:00
  • 4
    `__defineGetter__ ` is deprecated, use `defineProperty ` instead. Check my answer below. – Tyler Liu Jan 30 '15 at 11:46
  • if using angular .. http://stackoverflow.com/questions/24702003/how-do-i-mock-window-injected-manually-in-provider-private-function – danday74 Mar 04 '16 at 03:55
  • I recently created a gist to make readonly propertys writable (You can find an example to overwrite the navigator.userAgent). It is ugly and not recommended but I used it for unit tests to change user agents on the fly, so be careful. Gist: https://gist.github.com/moehlone/bed7dd6cb38fc55bd640 – Philipp Mar 17 '16 at 08:40
  • I tried putting this directly into the browser dev tools and it just returns the agent as a string. The browser it self still renders as if it is the original browser it self. Do I need to do a refresh for it to work? – mogoli Sep 14 '16 at 16:34
  • Works in Chrome console. Did not work for me in PhantomJS. – theUtherSide Jun 21 '17 at 18:30
  • Outdated answer! – Olivier Mar 19 '19 at 19:03
  • `Object.defineProperty(navigator, 'userAgent', { get: () => 'foo' });` – vnxyz Jan 23 '20 at 10:13
  • As advice: it's nice to add `writable: true` option to have the ability to redefine userAgent later, like this: `Object.defineProperty(window.navigator, 'userAgent', { value: 'Mobile', writable: true });` or reassign, like this: `navigator.userAgent = 'Opera mini'` – Alex Strizhak Jan 12 '22 at 17:24
25

Adding on to Crescent Fresh's solution, redefining the navigator.userAgent getter doesn't seem to work in Safari 5.0.5 (on Windows 7 & Mac OS X 10.6.7).

Need to create a new object that inherits from the navigator object and define a new userAgent getter to hide the original userAgent getter in navigator:

var __originalNavigator = navigator;
navigator = new Object();
navigator.__proto__ = __originalNavigator;
navigator.__defineGetter__('userAgent', function () { return 'Custom'; });
Community
  • 1
  • 1
maxyfc
  • 11,167
  • 7
  • 37
  • 46
  • I cannot override the window.navigator object, so your solution does not work for me. The one by 'Crescent Fresh' does. – cburgmer Dec 10 '12 at 10:22
  • 6
    This works with phantomjs too ... the new Object is needed. I guess its a webkit thing. – Pykler May 14 '13 at 02:42
  • Not working in Safari Version 12.1.2 (14607.3.9). Javascript error on line `navigator.__proto__ = __originalNavigator;`, it says: `cyclic __proto__ value` or such. Removing that line makes the user agent "work locally", meaning `navigator.userAgent` will get back what you set, but for XMLHttpRequest calls it'll just use the default UA. – Jonny Aug 28 '19 at 01:51
24

The following solution works in Chrome, Firefox, Safari, IE9+ and also with iframes:

function setUserAgent(window, userAgent) {
    if (window.navigator.userAgent != userAgent) {
        var userAgentProp = { get: function () { return userAgent; } };
        try {
            Object.defineProperty(window.navigator, 'userAgent', userAgentProp);
        } catch (e) {
            window.navigator = Object.create(navigator, {
                userAgent: userAgentProp
            });
        }
    }
}

Examples:

setUserAgent(window, 'new user agent');
setUserAgent(document.querySelector('iframe').contentWindow, 'new user agent');
Joel
  • 15,496
  • 7
  • 52
  • 40
  • I tested on Safari 8 and it didn't seem to work. I use console.log(navigator.userAgent) and the console logs the default user agent string. – Aero Wang Feb 27 '15 at 12:10
  • I think it could be that I have console.log before the script changing the user agent, however. – Aero Wang Feb 27 '15 at 12:14
  • @AeroWindwalker I have just tested it in Safari 8 again and it works. – Joel Feb 27 '15 at 18:55
  • Yeah I realized that the script works. It's just that the iframe sent the default user agent to the server before the script applies a new user agent to it. – Aero Wang Feb 28 '15 at 05:35
  • I am also getting error saying, "null is not an object (evaluating 'document.querySelector('iframe').contentWindow')" – Aero Wang Feb 28 '15 at 06:07
  • I realized that I am using the backend to detect user agent while this script only change the user agent in the front end. That is why the backend will ignore the javascript mobile user agent and send desktop content back. – Aero Wang Feb 28 '15 at 07:11
  • Tested on Chrome 41.0.2272.64, it also does not work. It only works if I have a front end javascript function that detects user agent string. My back end receives default user agent string at all time. – Aero Wang Feb 28 '15 at 07:35
  • @AeroWindwalker There is no JavaScript solution to change the user agent which is sent in the HTTP headers. You cannot even override it with setRequestHeader for AJAX requests. However, some browser allow to override it with a setting (which is probably not what you want). – Joel Mar 01 '15 at 12:00
  • 1
    @AeroWang How did you solve this? `the iframe sent the default user agent to the server before the script applies a new user agent to it.` – LIGHT Apr 28 '15 at 14:14
  • @Prakash You cannot modify the user agent which is sent to the server (unless you change your browser settings). This change is only temporary and only applies to the JavaScript environment. – Joel Apr 28 '15 at 17:49
  • @prakash There is no solution to this. I ended up writing my own node.js script to test proxies. – Aero Wang Apr 29 '15 at 21:42
  • This worked with Jasmine/Karma//Phantom stack. Thank you! – theUtherSide Jun 21 '17 at 18:40
13

For those here because they need to change the userAgent value in unit tests, Tyler Long's solution works, but if you want to restore the initial userAgent or change it more than once, you will probably need to set the property as configurable:

function setUserAgent(userAgent) {
    Object.defineProperty(navigator, "userAgent", { 
        get: function () { 
            return userAgent; // customized user agent
        },
        configurable: true
    });
}

// Now in your setup phase:
// Keep the initial value
var initialUserAgent = navigator.userAgent;
setUserAgent('foo');

// In your tearDown:
// Restore the initial value
setUserAgent(initialUserAgent);

Otherwise you might run into a TypeError: Cannot redefine property error. Works for me on Chrome Headless.

So6
  • 131
  • 1
  • 5
  • 2
    I came to the same conclusion after trial and error, wish I read this answer first. – MDahlke Mar 21 '19 at 16:34
  • This is not working for me right now in Windows Google Chrome Version 78.0.3904.108 (Official Build) (64-bit) – Ryan Dec 16 '19 at 17:56
  • Thanks, worked like charm `const setUserAgent = (userAgent: 'Chrome' | 'Android') => { Object.defineProperty(navigator, 'userAgent', { get: () => userAgent, configurable: true, }); };` – Aamir Afridi Mar 09 '20 at 17:04
9

Using Object.defineProperty should add several more browsers to the mix:

if (navigator.__defineGetter__) {
    navigator.__defineGetter__("userAgent", function () { 
        return "ua"; 
    });
} else if (Object.defineProperty) { 
    Object.defineProperty(navigator, "userAgent", { 
        get: function () { 
            return "ua";
        }
    });
}

This code should work (and was tested) in Firefox 1.5+, Chrome 6+, Opera 10.5+ and IE9+. Unfortunately Safari on any platform doesn't allow changing the userAgent.

Edit: Safari doesn't allow changing the userAgent, but one can replace the whole navigator object, as pointed out in another solution above.

Bundyo
  • 2,195
  • 13
  • 13
5

Crescent Fresh's answer is correct. But there is an issue: __defineGetter__ is deprecated:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineGetter

Deprecated This feature has been removed from the Web standards. Though some browsers may still support it, it is in the process of being dropped. Do not use it in old or new projects. Pages or Web apps using it may break at any time.

You should use defineProperty instead:

Object.defineProperty(navigator, "userAgent", { 
    get: function () { 
        return "foo"; // customized user agent
    }
});

navigator.userAgent; // 'foo'
Tyler Liu
  • 19,552
  • 11
  • 100
  • 84
4

To update this thread, defineGetter does not work anymore in Jasmine as it was deprecated. However I found this allows me to modify the getter for navigator.userAgent in jasmine:

navigator = {
  get userAgent() {
    return 'agent';
  }
}

console.log(navigator.userAgent); // returns 'agent'

Just remember resetting the navigator object once you are done testing in jasmine

3

For those trying to do the same thing in TypeScript here's the solution:

(<any>navigator)['__defineGetter__']('userAgent', function(){
    return 'foo';
});

navigator.userAgent; // 'foo'

Or same thing for language:

(<any>navigator)['__defineGetter__']('language', function(){
    return 'de-DE';
});
Miroslav Jonas
  • 5,407
  • 1
  • 27
  • 41
2

I guess I'd take a dependency injection approach. Instead of:

function myFunction() {
    var userAgent = navigator.userAgent;
    // do stuff with userAgent
}

Maybe do something like:

function myFunction(userAgent) {
    // do stuff with userAgent
}

function getUserAgent() {
    window.userAgentReal = +window.userAgentReal || 0;
    return [ navigator.userAgent ][window.userAgentReal++];
}

function getUserAgentMock() {
    window.nextUserAgentMock = +window.nextUserAgentMock || 0;
    return [
        'test user agent1',
        'test user agent2',
        'test user agent3'
    ][window.nextUserAgentMock++];
}

var userAgent;
while (userAgent = getUserAgent()) {
    myFunction(userAgent);
}

Then you can "mock out" getUserAgent() by doing:

function getUserAgentReal() { // formerly not 'Real'
    // ...
}

function getUserAgent() { // formerly 'Mock'
    // ...
}

This design still isn't completely automated (you have to manually rename the getter to perform your testing), and it adds a bunch of complexity to something as simple as operating on navigator.userAgent, and I'm not sure how you'd actually identify any bugs in myFunction, but I just figured I'd throw it out there to give you some ideas how this might be dealt with.

Maybe the idea of "dependency injection" presented here can somehow be integrated with FireUnit.

Grant Wagner
  • 25,263
  • 7
  • 54
  • 64
  • Sure, could just make navigator.userAgent be getUserAgent, then in the running I could just redefine getUserAgent. So long as my definitions are last, the mock becomes the truth. It makes the actual javascript larger, clunkier, and nastier, though, so I'm trying to avoid that. – Stefan Kendall Aug 20 '09 at 17:23
1

Above answers were not working for PhantomJS + TypeScript. Below code worked for me:

var __originalNavigator = navigator;
(window as any).navigator = new Object();
navigator["__proto__"] = __originalNavigator["__proto__"];
navigator["__defineGetter__"]('userAgent', function () { return 'Custom'; });
blackspacer
  • 343
  • 6
  • 15
1

Late to this topic but for Karma + Jasmin and Typescript and want to set the userAgent property this will do it:

describe('should validate YYYY-MM-dd format only on IE browser', () => {
    // this validator has a specific condition to work only in IE11 and down
    (window as any).navigator.__defineGetter__('userAgent', function () {
      return 'MSIE';
    });

...
// rest of the test

});

This article helped: https://www.codeproject.com/Tips/1036762/Mocking-userAgent-with-JavaScript

Avram Virgil
  • 1,170
  • 11
  • 22
1

Try this it worked for me without lint issues

Object.defineProperty(global.navigator, 'userAgent', { get: () => 'iPhone' });
vnxyz
  • 386
  • 3
  • 10
0

navigator.userAgent is a read-only string property, so its not possible to edit it

Yi Jiang
  • 49,435
  • 16
  • 136
  • 136
Lil'Monkey
  • 991
  • 6
  • 14
-1

Change navigator.userAgent on Firefox and Opera via defineGetter

navigator.__defineGetter__('userAgent', function(){
    return( "iPhone 5" );
});

alert( navigator.userAgent ); //iPhone 5

Change navigator.userAgent on IE and Opera via object instance

var navigator = new Object; 
navigator.userAgent = 'iPhone 5';

alert( navigator.userAgent ); //iPhone5

Good thing is, if you work on IE webbrowser control, you can double spoof both HTTP request and JavaScript navigator.userAgent via execScript

WebBrowser1.Navigate "http://example.com", , , , "User-Agent: iPhone 5" & vbCrLf

WebBrowser1.Document.parentWindow.execScript ("var navigator=new Object;navigator.userAgent='iPhone 5';")
WebBrowser1.Document.parentWindow.execScript ("alert(navigator.userAgent);") 'iPhone 5
-3

No, i doubt you can do it within javascript. But with Firefox's User Agent Switcher you can test whatever useragent you want, so why not just use that?

Marius
  • 57,995
  • 32
  • 132
  • 151
  • 1
    Did you not see the part where I said "hundreds of user agent strings?" – Stefan Kendall Aug 20 '09 at 15:39
  • 1
    I don't think you quite understand the purpose of unit testing. "I only have 30 test cases, so why not run through manually each time I make the smallest change?" – Stefan Kendall Aug 20 '09 at 15:39
  • Take the User Agent Tester and modify the code to automatically test with all "hundreds of user agent strings". If you know javascript, that should be very simple – Marius Aug 20 '09 at 15:47
  • Doesn't seem relevant about the users knowledge of JS. Your answer does not contain any valid information about how the poster would actually achieve this result. This is more of a comment than an answer. – perry Nov 28 '16 at 05:15