【问题标题】:Mocking a useragent in javascript?在javascript中模拟用户代理?
【发布时间】:2010-11-21 09:02:54
【问题描述】:

我正在寻找一种以编程方式即时更改 navigator.userAgent 的方法。在我尝试获得自动化 javascript 单元测试器的失败中,我放弃了并尝试开始使用 fireunit。立即,我撞上了使用实际浏览器进行 javascript 测试的一堵墙。

具体来说,我需要更改 navigator.userAgent 以模拟数百个 userAgent 字符串,以确保对给定函数的正确检测和覆盖。 navigator.userAgent 是只读的,所以我似乎卡住了!如何模拟 navigator.userAgent? User Agent Switcher(插件)可以切换FF的useragent,但是我可以在javascript里面做吗?

【问题讨论】:

标签: javascript unit-testing user-agent


【解决方案1】:

试试:

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

navigator.userAgent; // 'foo'

在 FF2 和 FF3 中尝试过。

【讨论】:

  • 这个方案可以用来设置iframe的用户代理吗?
  • 为我工作。 (Windows 上的 IE)
  • __defineGetter__ 已弃用,请改用defineProperty 。在下面检查我的答案。
  • 我最近创建了一个 gist 以使只读属性可写(您可以找到一个示例来覆盖 navigator.userAgent)。它很难看,不推荐,但我将它用于单元测试以动态更改用户代理,所以要小心。要点:gist.github.com/moehlone/bed7dd6cb38fc55bd640
【解决方案2】:

添加到Crescent Fresh's solution,重新定义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' 的那个。
  • 这也适用于 phantomjs ......需要新的对象。我猜它是一个 webkit 的东西。
  • 在 Safari 版本 12.1.2 (14607.3.9) 中不起作用。 navigator.__proto__ = __originalNavigator; 线上的 Javascript 错误,上面写着:cyclic __proto__ value 或类似的。删除该行会使用户代理“在本地工作”,这意味着 navigator.userAgent 将取回您设置的内容,但对于 XMLHttpRequest 调用,它只会使用默认的 UA。
【解决方案3】:

以下解决方案适用于 Chrome、Firefox、Safari、IE9+ 以及 iframe:

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

【讨论】:

  • 我在 Safari 8 上进行了测试,但它似乎不起作用。我使用 console.log(navigator.userAgent) 并且控制台记录了默认的用户代理字符串。
  • 我认为可能是在脚本更改用户代理之前我有 console.log。
  • @AeroWindwalker 我刚刚在 Safari 8 中再次对其进行了测试,并且可以正常工作。
  • 是的,我意识到脚本有效。只是 iframe 在脚本应用新的用户代理之前将默认用户代理发送到服务器。
  • @AeroWang 你是怎么解决这个问题的? the iframe sent the default user agent to the server before the script applies a new user agent to it.
【解决方案4】:

对于那些因为需要在单元测试中更改 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 上为我工作。

【讨论】:

  • 经过反复试验,我得出了同样的结论,希望我先阅读这个答案。
  • 这在 Windows Google Chrome 版本 78.0.3904.108(官方版本)(64 位)中现在不适合我
  • 谢谢,像魅力一样工作const setUserAgent = (userAgent: 'Chrome' | 'Android') => { Object.defineProperty(navigator, 'userAgent', { get: () => userAgent, configurable: true, }); };
【解决方案5】:

使用 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 中做同样的事情?
  • 很遗憾我什么都不知道。
【解决方案6】:

Crescent Fresh 的回答是正确的。但有一个问题:__defineGetter__ 已弃用:

https://developer.mozilla.org/en-US/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'

【讨论】:

  • TypeError: Attempting to change the getter of an unconfigurable property.
  • @uchuugaka,我根本没有测试 Safari。很抱歉。
  • 不用担心。我做到了。最严重的是锁定安全性。这些都不起作用。
  • 我相信这个解决方案不再适用于 Firefox,因为 navigator.useragent 属性是只读的。
【解决方案7】:

要更新此线程,defineGetter 在 Jasmine 中不再工作,因为它已被弃用。但是我发现这允许我在 jasmine 中修改 navigator.userAgent 的 getter:

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

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

只要记住在 jasmine 中完成测试后重置导航器对象

【讨论】:

  • 在 NodeJS 6.1 上使用 __defineGetter__(使用 Jasmine 2)似乎对我来说效果很好。
  • __defineGetter__ 在 Jasmine 2.4.1 和节点 6.8.1 上对我不起作用,但使用它非常有效。
  • 如何重置?
【解决方案8】:

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

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

navigator.userAgent; // 'foo'

语言也一样:

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

【讨论】:

    【解决方案9】:

    我想我会采用依赖注入方法。而不是:

    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 更大、更笨重、更讨厌,所以我试图避免这种情况。
    【解决方案10】:

    以上答案不适用于 PhantomJS + TypeScript。下面的代码对我有用:

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

    【讨论】:

    • 帮助很大。最佳解决方案。谢谢
    【解决方案11】:

    这个话题晚了,但是对于 Karma + Jasmin 和 Typescript 并且想要设置 userAgent 属性,这将做到这一点:

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

    这篇文章有帮助:https://www.codeproject.com/Tips/1036762/Mocking-userAgent-with-JavaScript

    【讨论】:

      【解决方案12】:

      试试这个,它对我有用,没有 lint 问题

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

      【讨论】:

        【解决方案13】:

        navigator.userAgent 是一个只读字符串属性,所以不能编辑它

        【讨论】:

        • navigator.userAgent 是一个 getter,而不是“只读字符串属性”。
        • @Elijah Grey,在您看来,只有 getter 和只读属性有什么区别?
        【解决方案14】:

        通过 defineGetter 在 Firefox 和 Opera 上更改 navigator.userAgent

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

        通过对象实例更改 IE 和 Opera 上的 navigator.userAgent

        var navigator = new Object; 
        navigator.userAgent = 'iPhone 5';
        
        alert( navigator.userAgent ); //iPhone5
        

        好消息是,如果您使用 IE 浏览器控件,您可以通过 execScript 双重欺骗 HTTP 请求和 JavaScript navigator.userAgent

        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
        

        【讨论】:

        • 我认为你提到的将导航器设为新对象的IE方法不起作用。请验证
        【解决方案15】:

        不,我怀疑你可以在 javascript 中做到这一点。但是使用 Firefox 的用户代理切换器,您可以测试任何您想要的用户代理,那么为什么不直接使用呢?

        【讨论】:

        • 你没看到我说的“数百个用户代理字符串”的部分吗?
        • 我认为您不太了解单元测试的目的。 “我只有 30 个测试用例,为什么不每次做最小的更改时手动运行一遍?”
        • 使用用户代理测试器并修改代码以自动使用所有“数百个用户代理字符串”进行测试。如果你懂javascript,那应该很简单
        • 似乎与用户的 JS 知识无关。您的答案不包含有关海报如何实际实现此结果的任何有效信息。这更像是一个评论而不是一个答案。
        猜你喜欢
        • 1970-01-01
        • 2014-11-29
        • 1970-01-01
        • 2011-05-29
        • 2012-10-14
        • 1970-01-01
        • 1970-01-01
        • 2021-12-16
        • 2011-02-18
        相关资源
        最近更新 更多