前端中的e2e测试

Posted on

对于单页面应用,我们需要由前端来控制每个页面的路由。但是在开发过程中,我们经常要对路由进行调整。每次调整都要手动检查所有页面是否正常显示。这个过程实在是太浪费生命了, 所以我们需要选择一个测试框架来自动完成这个过程,结合CI系统,在每次PR的时候都自动测试一遍,只有通过测试才会进行下一步的review。

Nightwatch

Nightwatch是基于W3C WebDriver API。WebDrive主要是为了满足浏览器的自动化测试需求,把最终的接口统一成HTTP协议。 所以可以统一不同浏览器的自动化测试接口。我们只要安装每个浏览器的WebDriver实现,就可以同一套代码在不同的浏览器中进行测试。老版本的NightWatch需要使用Selenium来管理各个浏览器,但是从1.0版本开始便不需要也不推荐。所以我现在的项目中并没有安装Selenium。

依赖

由于不需要安装Selenium,所以并不需要安装Java依赖。我使用Chrome浏览器来进行自动化测试,所以需要测试环境安装Chrome,注意安装的必须是Chrome,Chromium不可以。 在Travis CI中可以很方便的添加Chrome。还需要安装ChromeDriver依赖来驱动Chrome完成自动化测试。

配置

在Linux测试环境下,基本上都是没有图形化界面的,所以我们在配置Chrome的启动参数中需要加入--headless选项,这样就可以不启动UI。当我们以root权限启动的时候还需要添加--no-sandbox选项,这两个选项基本上是必须的。在我的实际情况下,需要测试不同UA下的展示效果,所以还需要添加--user-agent=Mozilla/5.0 (Macintosh; Test自定义的UA。 下面给出一个配置的例子供大家参考:

// http://nightwatchjs.org/gettingstarted#settings-file

module.exports = {
  output_folder: "tests/e2e/reports",
  custom_assertions_path: ["tests/e2e/custom-assertions"],
  globals_path: "globalsModule.js",
  src_folders: ["tests/e2e/specs/web"]

  webdriver: {
    start_process: true,
    server_path: require("chromedriver").path,
    cli_args: ["--verbose"],
    port: 9515
  },

  test_settings: {
    default: {
      desiredCapabilities: {
        browserName: "chrome",
        javascriptEnabled: true,
        chromeOptions: {
          args: ["--headless", "--no-sandbox"]
        },
        acceptSslCerts: true
      }
    }
  }
};

每个参数的详细含义可以参考官方的说明文档。

总结

在集成Nightwatch的时候,我是卡在了安装浏览器上。在Mac系统上可以正常执行,在CentOS的docker里面却不行。找了半天发现是自己用了Chromium,之前对Chromium能代替Chrome深信不疑。。。 在遇到问题的时候,还是多怀疑,多找找可能性比较好。

localStorage互斥锁的使用

Posted on

JavaScript是单线程语言,所以我们在写代码的时候根本不会遇到互斥锁的问题。但是当用户打开多个Tab页面的时候,这些页面却是共享同一个localStorage的。当我们试图修改localStorage的时候就会遇到竞争问题,如果两个页面同时修改了localStorage,程序的可靠性就无法保证。

多Tab互斥

这个问题查了不少时间,目前有的解决方案:Shared Web Workersfast mutex。由于想尽可能的支持更多浏览器,所以我们选择了后者。

实现localStorage互斥

fast mutex的源码也不是很多,所以读起来也没有很复杂。当执行lock函数的时候,会为一个key存储 _MUTEX_LOCK_X_KEY_MUTEX_LOCK_Y_KEY。以下简称X和Y。 首先存X,然后读Y。如果Y存在说明别人已经拿到互斥锁了,所以重新执行函数。直到获取到互斥锁。如果Y不存在,说明没有人竞争锁。所以往下继续执行存储Y。 但是我们不能保证这段时间没有别的tab来存储X或者Y。所以继续读取X,如果X没有发生变化,说明没有人来竞争锁。我们就可以resolve传入的回调函数了。 如果X发生了变化,说明有人来竞争互斥锁,这时候的函数设置了一个50ms的延迟执行,就是保证检测的足够晚。竞争的tab能够执行完自己的lock函数。50ms之后再去读取Y,如果发现Y没有发生变化,则自己还拥有这个互斥锁,可以顺利执行resolve。否则自己丢失了互斥锁,重新执行lock函数。

总结

fast mutex的实现确实很巧妙,通过添加两个localStorage值和setTimeout完成互斥锁的实现。看到这个项目之前,一直以为想做到localStorage的互斥是不现实的事情。

tagged: mutex

亚马逊S3服务简单介绍

Posted on

最近在做后端的开发,需要一些二进制数据保存在服务器云端。团队决定调查AWS的S3服务是否满足需求,所以就做了一些调查工作。不过也遇到很多坑的地方。所以记录下来,防止以后再遇到。

基本需求

需要云服务有稳定性保证,并且可以批量上传文件。可以设置上传和下载链接的有效期。

AWS试用

AWS提供免费试用,但是注册的时候需要填写信用卡信息。确实有点不安,万一不小心被收费了就不好了。

生成上传凭证

我使用的是AWS的 JavaScript SDK。使用createPresignedPost API可以创建用于上传的凭证。这个凭证是根据用户的AccessId,AccessKey和Policy策略等计算生成的,并没有和AWS服务器直接进行交互。所以不用担心这个接口和AWS直接的流量费用问题。

var params = {
  Bucket: 'bucket',
  Conditions: [
    ['starts-with', '$key', 'path/to/uploads/']
  ]
};
s3.createPresignedPost(params, function(err, data) {
  if (err) {
    console.error('Presigning post data encountered an error', err);
  } else {
    data.Fields.key = 'path/to/uploads/${filename}';
    console.log('The post data is', data);
  }
});

官方提供的例子中,可以使用starts-with的方式来指定上传文件的key必须是以什么开头的,这样就可以指定上传的文件夹。很多文件也可以使用这一个上传凭证来完成上传。

上传Policy构造

AWS提供了一个详细文档说明如何构造合法的Policy:Creating a POST Policy。例如常见的需求就是在上传的时候添加meta信息声明文件的格式或者MD5值。 Policy的Conditions数组里面可以添加["starts-with", "$x-amz-meta-md5checksum", ""]。最后一个参数为空字符串代表可以上传任何数值。

构造POST表单

AWS也有文档说明了如何构造一个上传的表单。这个表单中最重要的是一句注释:The elements after this will be ignored。在file字段之后的所有信息都会被忽略掉,我测试的时候一直把x-amz-meta-md5checksum字段放在file字段之后导致上传一直报错。直到Stack Overflow上面有人解释了才恍然大悟。

构造下载链接

我们使用getSignedUrl API来生成下载链接,下载链接也是根据自己的AccessId和AccessKey生成链接的凭证,也没有和AWS服务器直接进行交互。当请求文件的时候,AWS再计算凭证是否有效。所以后端无需和AWS交互就可以返回客户端有效的AWS下载链接。针对需要返回实际文件的API接口,可以采用返回302的跳转链接来完成需求。示例代码如下:

var params = {Bucket: 'bucket', Key: 'key'};
var url = s3.getSignedUrl('getObject', params);
console.log('The URL is', url);

可能有人会问,这个API也可以用来上传啊。但是这个API接口必须指定key值,这样我们就需要为每个文件来生成一个独立的上传URL。这样太麻烦了。

总结

我们使用pre-sign的方式来生成URL主要是为了对客户端透明。虽然我们可以设置最小权限的IAM User给客户端,但是客户端很容易被逆向拿到敏感数据。这样难免会有风险,所以生成一个单纯的URL供客户端使用一定程度上保证了安全性也减少了客户端的复杂性。毕竟我也不想引入一个AWS的SDK进来。

tagged: AWS