Mocking a useragent in javascript?
Asked Answered
E

15

81

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?

Elviaelvie answered 20/8, 2009 at 15:34 Comment(1)
Did you look at env.js? (groups.google.com/group/envjs)Crotch
E
122

Try:

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

navigator.userAgent; // 'foo'

Tried it in FF2 and FF3.

Eugenides answered 20/8, 2009 at 15:46 Comment(10)
Can this solution be used to set iframe's user agent ?Radom
Works for me. (IE on Windows)Samuels
__defineGetter__ is deprecated, use defineProperty instead. Check my answer below.Samuels
if using angular .. #24702503Menke
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: gist.github.com/moehlone/bed7dd6cb38fc55bd640Distributive
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?Hartsell
Works in Chrome console. Did not work for me in PhantomJS.Indefectible
Outdated answer!Liquidity
Object.defineProperty(navigator, 'userAgent', { get: () => 'foo' });Anticipatory
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'Fenny
M
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'; });
Mehala answered 10/5, 2011 at 12:29 Comment(3)
I cannot override the window.navigator object, so your solution does not work for me. The one by 'Crescent Fresh' does.Laundromat
This works with phantomjs too ... the new Object is needed. I guess its a webkit thing.Cinderellacindi
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.Foilsman
C
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');
Colenecoleopteran answered 12/11, 2014 at 13:36 Comment(12)
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.Spectroscope
I think it could be that I have console.log before the script changing the user agent, however.Spectroscope
@AeroWindwalker I have just tested it in Safari 8 again and it works.Colenecoleopteran
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.Spectroscope
I am also getting error saying, "null is not an object (evaluating 'document.querySelector('iframe').contentWindow')"Spectroscope
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.Spectroscope
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.Spectroscope
@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).Colenecoleopteran
@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.Raposa
@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.Colenecoleopteran
@prakash There is no solution to this. I ended up writing my own node.js script to test proxies.Spectroscope
This worked with Jasmine/Karma//Phantom stack. Thank you!Indefectible
C
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.

Credendum answered 8/1, 2019 at 16:39 Comment(3)
I came to the same conclusion after trial and error, wish I read this answer first.Stepp
This is not working for me right now in Windows Google Chrome Version 78.0.3904.108 (Official Build) (64-bit)Vookles
Thanks, worked like charm const setUserAgent = (userAgent: 'Chrome' | 'Android') => { Object.defineProperty(navigator, 'userAgent', { get: () => userAgent, configurable: true, }); };Aile
O
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.

Oedipus answered 24/3, 2014 at 16:40 Comment(2)
Hey Bundyo, is there any way to do the same in IE8?Chaunceychaunt
Unfortunately I don't know of any.Oedipus
S
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'
Samuels answered 30/1, 2015 at 11:43 Comment(4)
TypeError: Attempting to change the getter of an unconfigurable property.Feud
@uchuugaka, I didn't test Safari at all. Sorry for that.Samuels
No worries. I did. It's most serious about locking down security. None of these work.Feud
I believe this solution no longer works in Firefox because the navigator.useragent property is read only.Liquidity
E
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

Engulf answered 23/6, 2015 at 15:43 Comment(3)
Using __defineGetter__ on NodeJS 6.1 (using Jasmine 2) seems to work pretty well for me.Again
__defineGetter__ didn't work for me on Jasmine 2.4.1 and node 6.8.1, but using this worked perfectly.Armstead
How do you reset it?Elna
H
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';
});
Hoar answered 20/10, 2016 at 11:32 Comment(0)
N
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.

Nowlin answered 20/8, 2009 at 16:43 Comment(1)
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.Elviaelvie
F
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'; });
Faubourg answered 22/8, 2017 at 13:29 Comment(1)
Helped a lot. Best solution. Thank youSidon
R
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

Roos answered 6/8, 2018 at 14:15 Comment(0)
A
1

Try this it worked for me without lint issues

Object.defineProperty(global.navigator, 'userAgent', { get: () => 'iPhone' });
Anticipatory answered 23/1, 2020 at 10:13 Comment(0)
O
0

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

Operand answered 20/8, 2009 at 15:39 Comment(2)
navigator.userAgent is a getter, not a "read-only string property".Pyongyang
@Elijah Grey, what's the difference between a getter only and a read-only property at your point of view ?Klina
S
-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
Sandy answered 6/5, 2014 at 22:45 Comment(1)
I think the IE method u mentioned by making navigator a new Object doesn't work. please do verifyChaunceychaunt
D
-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?

Derby answered 20/8, 2009 at 15:38 Comment(4)
Did you not see the part where I said "hundreds of user agent strings?"Elviaelvie
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?"Elviaelvie
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 simpleDerby
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.Cauliflower

© 2022 - 2024 — McMap. All rights reserved.