Stefan Kendall
Stefan Kendall

Reputation: 67892

Mocking a useragent in javascript?

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?

Upvotes: 84

Views: 84036

Answers (16)

Anderson Laverde
Anderson Laverde

Reputation: 395

This works for me without extra files

// yourTest.spec.js

let windowSpy: jest.SpyInstance
describe('string', () => {
  beforeEach(() => {
    windowSpy = jest.spyOn(globalThis, 'window', 'get')
    windowSpy.mockImplementation(() => ({
      navigator: {userAgent: PUT_YOUR_USER_AGENT_HERE},
    }))
  })

  // optional
  afterEach(() => {
    windowSpy.mockClear()
  })

  ...your tests here
})

Upvotes: 0

vnxyz
vnxyz

Reputation: 466

Try this it worked for me without lint issues

Object.defineProperty(global.navigator, 'userAgent', { get: () => 'iPhone' });

Upvotes: 1

So6
So6

Reputation: 141

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.

Upvotes: 14

Avram Virgil
Avram Virgil

Reputation: 1210

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

Upvotes: 1

blackspacer
blackspacer

Reputation: 363

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'; });

Upvotes: 1

Miroslav Jonas
Miroslav Jonas

Reputation: 6657

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';
});

Upvotes: 3

Fernando De Vega
Fernando De Vega

Reputation: 76

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

Upvotes: 4

Joel
Joel

Reputation: 15752

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');

Upvotes: 24

Bundyo
Bundyo

Reputation: 2193

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.

Upvotes: 9

Tyler Liu
Tyler Liu

Reputation: 20396

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'

Upvotes: 5

Priince paul Gates
Priince paul Gates

Reputation: 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

Upvotes: -1

maxyfc
maxyfc

Reputation: 11337

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'; });

Upvotes: 25

NakedLuchador
NakedLuchador

Reputation: 991

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

Upvotes: 0

Grant Wagner
Grant Wagner

Reputation: 25941

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.

Upvotes: 2

Crescent Fresh
Crescent Fresh

Reputation: 117028

Try:

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

navigator.userAgent; // 'foo'

Tried it in FF2 and FF3.

Upvotes: 123

Marius
Marius

Reputation: 58999

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?

Upvotes: -3

Related Questions