在JavaScript中模拟用户代理?

80

我正在寻找一种在程序中动态更改 navigator.userAgent 的方法。 我曾尝试获取自动化的JavaScript单元测试器,但失败了,并试图开始使用fireunit。 立即,我遇到了使用实际浏览器进行JavaScript测试的障碍之一。

具体而言,我需要更改 navigator.userAgent 以模拟几百个userAgent字符串,以确保在给定函数上正确检测和覆盖。 navigator.userAgent 是只读的,所以我似乎陷入了困境! 如何模拟 navigator.userAgent? 用户代理切换器(插件)可以切换FF的用户代理,但我能否在 JavaScript 中执行此操作?


你看过env.js了吗?(http://groups.google.com/group/envjs) - Aaron Digulla
15个回答

121

尝试:

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

navigator.userAgent; // 'foo'

我已经在Firefox 2和Firefox 3中尝试过了。


4
这个解决方案可以用来设置iframe的用户代理吗? - MANnDAaR
4
“__defineGetter__”已过时,请使用“defineProperty”代替。请查看我下面的答案。 - Tyler Liu
如果使用Angular,如何在提供程序的私有函数中手动模拟注入的窗口? - danday74
我最近创建了一个代码片段,使只读属性变为可写(您可以找到一个覆盖navigator.userAgent的示例)。这很丑陋,不建议使用,但我在单元测试中使用它来动态更改用户代理,所以请谨慎使用。代码片段链接:https://gist.github.com/moehlone/bed7dd6cb38fc55bd640 - Philipp Möhler
在Chrome控制台中可以运行。 但在PhantomJS中对我无效。 - theUtherSide
显示剩余5条评论

25

Crescent Fresh的解决方案基础上,重新定义navigator.userAgent getter 在Safari 5.0.5(Windows 7和Mac OS X 10.6.7)中似乎不起作用。

需要创建一个继承自navigator对象的新对象,并定义一个新的userAgent getter来隐藏navigator中原有的userAgent getter:

var __originalNavigator = navigator;
navigator = new Object();
navigator.__proto__ = __originalNavigator;
navigator.__defineGetter__('userAgent', function () { return 'Custom'; });

我无法覆盖window.navigator对象,所以你的解决方案对我不起作用。'Crescent Fresh'的那个可以。 - cburgmer
6
这也适用于phantomjs……需要一个新的Object。我猜这是一个WebKit的问题。 - Pykler
在Safari版本12.1.2(14607.3.9)中无法工作。在navigator.__proto__ = __originalNavigator;这一行上出现了Javascript错误,它说:“循环__proto__值”或类似的内容。删除该行将使用户代理“在本地工作”,这意味着navigator.userAgent将返回您设置的内容,但对于XMLHttpRequest调用,它将仅使用默认UA。 - Jonny

24
以下解决方案可在Chrome、Firefox、Safari、IE9+以及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
            });
        }
    }
}

例子:

setUserAgent(window, 'new user agent');
setUserAgent(document.querySelector('iframe').contentWindow, 'new user agent');

我认为可能是因为在更改用户代理之前,我有console.log。 - Aero Wang
@AeroWindwalker 我刚在 Safari 8 中测试了一下,它可以正常工作。 - Joel
1
@AeroWang,你是如何解决这个问题的?“iframe在脚本应用新user agent之前向服务器发送了默认user agent。” - LIGHT
@Prakash 你无法修改发送到服务器的用户代理(除非更改浏览器设置)。此更改仅是临时的,并且仅适用于JavaScript环境。 - Joel
@prakash 没有解决方案。我最终编写了自己的node.js脚本来测试代理。 - Aero Wang
显示剩余7条评论

13

如果您在这里是因为需要在单元测试中更改userAgent值,那么Tyler Long的解决方案是有效的,但是如果你想要恢复初始的userAgent或者多次更改它,那么您可能需要将该属性设置为可配置:

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);
否则可能会遇到“TypeError: Cannot redefine property”错误。在我的 Chrome Headless 上可以正常工作。

2
在反复尝试后,我得出了相同的结论,但真希望我先看了这个答案。 - MDahlke
这在我当前使用的Windows Google Chrome版本78.0.3904.108(官方构建)(64位)中无法正常工作。 - Ryan
谢谢,非常顺利。 const setUserAgent = (userAgent: 'Chrome' | 'Android') => { Object.defineProperty(navigator, 'userAgent', { get: () => userAgent, configurable: true, }); }; - Aamir Afridi

9
使用Object.defineProperty应该能让更多的浏览器支持:
if (navigator.__defineGetter__) {
    navigator.__defineGetter__("userAgent", function () { 
        return "ua"; 
    });
} else if (Object.defineProperty) { 
    Object.defineProperty(navigator, "userAgent", { 
        get: function () { 
            return "ua";
        }
    });
}

这段代码应该可以在以下浏览器中正常工作(已测试):Firefox 1.5+Chrome 6+Opera 10.5+IE9+。不幸的是,任何平台上的Safari都不允许更改userAgent。

编辑:如上另一个解决方案所指出的,虽然Safari不允许更改userAgent,但可以替换整个navigator对象。


嘿,Bundyo,有没有办法在IE8中做同样的事情? - Pratik Pattanayak
很遗憾,我不知道任何相关的信息。 - Bundyo

5
新月鲜啤的答案是正确的。但是有一个问题:__defineGetter__已经被弃用: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineGetter 已弃用 该特性已从 Web 标准中删除。虽然某些浏览器仍可能支持它,但正在逐步停止支持。不要在旧项目或新项目中使用它。使用它的页面或 Web 应用程序可能随时会崩溃。
您应该使用defineProperty代替。
Object.defineProperty(navigator, "userAgent", { 
    get: function () { 
        return "foo"; // customized user agent
    }
});

navigator.userAgent; // 'foo'

1
类型错误:试图更改不可配置属性的getter。 - uchuugaka
@uchuugaka,我完全没有测试Safari。对此我很抱歉。 - Tyler Liu
没问题,我已经做了。这是最严肃的安全锁定问题。这些都不起作用。 - uchuugaka
我相信这个解决方案在Firefox中不再有效,因为navigator.useragent属性是只读的。 - Olivier

4

更新本线程,defineGetter在Jasmine中已经被弃用,不再起作用。然而,我发现以下方法可以修改jasmine中navigator.userAgent的getter:

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

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

请记得在Jasmine测试完成后重置导航器对象。


在NodeJS 6.1上(使用Jasmine 2),使用__defineGetter__对我来说似乎非常有效。 - Stephan Bijzitter
__defineGetter__ 在 Jasmine 2.4.1 和 node 6.8.1 上对我无效,但使用这个方法完美解决了问题。 - karl
你怎么重置它? - joacoleza

3

对于那些尝试在TypeScript中做同样事情的人,这是解决方案:

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

navigator.userAgent; // 'foo'

或者对于语言,也是同样的道理:

(<any>navigator)['__defineGetter__']('language', function(){
    return 'de-DE';
});

2
我会采用依赖注入的方式进行处理。与其说:
function myFunction() {
    var userAgent = navigator.userAgent;
    // do stuff with userAgent
}

也许可以做一些类似这样的事情:
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);
}

接下来,您可以通过以下方式“模拟”getUserAgent()

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

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

这个设计仍然没有完全自动化(您必须手动重命名getter以进行测试),并且它为像操作navigator.userAgent这样简单的事情添加了很多复杂性,我不确定您实际上如何识别出myFunction中的任何错误,但我只是想提供一些思路来解决这个问题。

也许这里介绍的“依赖注入”的想法可以与FireUnit集成。


当然,可以将navigator.userAgent更改为getUserAgent,然后在运行时重新定义getUserAgent。只要我的定义是最后的,模拟就成为了真相。但这会使实际的JavaScript变得更大、更笨重和更糟糕,所以我试图避免这种情况。 - Stefan Kendall

1

以上的答案对于PhantomJS + TypeScript并没有起作用。以下代码对我有效:

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

帮了很多忙。最佳解决方案。谢谢。 - Dumbo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接