谈谈小程序自动化
浏览器领域,我们有如selenium和puppeteer这样的库,可以自动化控制浏览器执行自动化脚本,以完成自动化端对端测试、定时自动化任务等。随着持续集成、持续部署也就是CI/CD的需求日益增长,自动化也成为必不可少的一环。
对于日益增长的小程序开发需求,我们能不能自动化控制小程序,进而达成自动测试、自动发布等任务呢?
浏览器领域,我们有如selenium和puppeteer这样的库,可以自动化控制浏览器执行自动化脚本,以完成自动化端对端测试、定时自动化任务等。随着持续集成、持续部署也就是CI/CD的需求日益增长,自动化也成为必不可少的一环。
对于日益增长的小程序开发需求,我们能不能自动化控制小程序,进而达成自动测试、自动发布等任务呢?
转眼年关切近,很多业务都会考虑做一些抽奖活动,而各大公司在年底也会考虑开办年会。年会中,一般会安排抽奖环节。抽奖程序一般接收一个抽奖人员列表,通过算法产生一个随机数,再通过某种对应关系,将列表中的某个人与这个随机数匹配。这个匹配的人就是中奖的幸运儿了。
不出意外的话,年底这一个多月,通常是一年中随机函数被调用最频繁的一段时间。
标题很绕口,不过确实是一个很常见的需求。
众所周知,NodeJS程序开发简便且容易实现跨平台。但是,当你开发了一个NodeJS程序,想要分发给其他人运行的时候,你会发现,你往往需要对方也来安装一个NodeJS环境。理想的方式是,我们可以把我们的程序打包成一个可执行文件,这样,就可以直接在对方的电脑上运行你的程序了;同时,我们将代码打成二进制形式,可以在一定程度上保护源代码以及API等处理的逻辑。
你在实际的开发中很可能遇到过这样的需求:实现一个可以拖拽的滑块,以实现范围选择、音量控制等需求。
除了直接用组件库,聪明的你肯定已经想到了多种解决办法。如在数据驱动框架React/Vue/Angular下,你可能会找到或编写对应的组件,通过相应数据状态的变更,完成相对复杂的交互;如在小快灵的项目下,用jQuery的Widget也是一个不错的选择;再或者,你可以点开你的HTML+JavaScript+CSS技能树,纯手工打造一个。这都是不难完成的任务。
当然,在完成之后,你可能会考虑对组件做一些提炼,下次再遇到同样的需求,你就可以气定神闲地“开箱即用”。
这里是Clair组件库对这个需求的封装。
我们不妨从这个层面再多想一步。其实由于HTML和CSS默认都是全局可见的,因此,尤其是纯手工打造的组件,其样式是很容易受到所在环境的干扰的;由于选择器在组件层没有统一的保护手段,也会造成撰写时候的规则可以被随意修改;事件的捕获和冒泡过程会和所在环境密切相关,也可能会引起事件管理的混乱。
根据一般意义上“封装”的概念,我们希望相对组件来讲,DOM和CSS有一定的隐藏性;如非必要,外部的变化对于内部的有一定的隔离;同时,外界可以通过且仅可以通过一些可控的方法来影响内部,反之亦然。
针对这些问题,其实浏览器提供了一种名叫Shadow DOM的解决方案。这个方案目前与 Custom Elements、HTML Templates、CSS changes和JSON, CSS, HTML Modules并列为Web Components标准。
在开发需要多个密切协作的软件包时候,我们往往将独立的功能块进行划分,使得各个功能独立的模块分别完成,以减少相互影响,完成有效的多人合作。但是,在模块协作时,经常会遇到一些问题:
一直以来,Windows的命令行的体验都不是特别的友好。由于Windows以图形界面交互为主,同时微软在一段时间内对命令行程序发展并不积极,以及Windows系统底层与*nix系列的不一致。造成Windows下的命令行开发与图形开发体验相去甚远:一方面工具链的不完整,一方面终端字体不甚美观,甚至默认的终端色域相对于Mac都是精简的。
纷纷红紫已成尘,布谷声中夏令新。夹路桑麻行不尽,始知身是太平人。 ——宋.陆游 《初夏绝句》
我们在开发多Tab应用时候,常常会遇到多个Tab状态同步的问题。
想象如下场景:用户主界面,显示用户购物车内待结算的商品总数。此时,用户可能打开多个Tab。当用户添加新商品到购物车的时候,需要更新购物车的数量。
此时,当前页面需要向服务器发起请求,在得到添加成功响应的时候,可以更新用户界面。为了兼顾体验和可靠性,如果确信添加成功概率比较高的时候,也可以先更新界面,当多数返回错误的时候,可以给用户界面做状态回滚。为了下次展示方便,我们还会把这个数据写到LocalStorage里面。用户再次打开时候,可以优先从localStorage中取值。
当前页面解决了,那么如果同时打开多个Tab该如何解决呢?这里使用StorageEvent可能是一种代价较小的解决方案。
StorageEvent是什么呢?
这里有一个简单的示例可以展示这个API的用法。
const STORAGE_KEY = "cartlist"
const getStorage = () => {
try {
let rets = window.localStorage.getItem(STORAGE_KEY)
if (rets === null) {
return []
}
return JSON.parse(rets)
}
catch(e){
return []
}
}
const addCart = (value) => {
let rets = getStorage()
rets.push(value)
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(rets))
return rets
}
const minusCart = (value) => {
let rets = getStorage()
let idx = rets.indexOf(value)
if (idx !== -1){
rets.splice(idx, 1)
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(rets))
}
return rets
}
const render = () => {
let rets = getStorage()
if (rets.length){
$("#num").html(rets.length).show()
}
else {
$("#num").hide()
}
$(".list li").each((i,el) => {
if (rets.includes(i)){
$(el).find("a:nth-child(1)").css("visibility", "hidden")
$(el).find("a:nth-child(2)").css("visibility", "visible")
}
else {
$(el).find("a:nth-child(1)").css("visibility", "visible")
$(el).find("a:nth-child(2)").css("visibility", "hidden")
}
})
}
$(".list a").on("click", (e)=> {
let opIdx = $(e.target).parent().find("a").index(e.target)
let line = $(e.target).parent().parent()
let idx = $(".list li").index(line)
opIdx === 0 ? addCart(idx) : minusCart(idx)
render()
return false
})
window.addEventListener('storage', (e) => {
render()
})
render()
其中,下面这行代码是实现的关键:
window.addEventListener('storage', (e) => {
render()
})
当我们注释掉这个语句,我们的页面同步就不能运行了。
读者可以打开多个Tab并观察页面的变化https://jsbin.com/radekilosu/1/edit?html,css,js,output。
实际上,这个事件e上还带有很多信息,方便编程时,对于事件做精确的控制。
字段 | 含义 |
---|---|
key | 发生变化的storageKey |
newValue | 变换后新值 |
oldValue | 变换前原值 |
storageArea | 相关的变化对象 |
url | 触发变化的URL,如果是frameset内,则是触发帧的URL |
上述各值都是只读的。
还有一点没有解决掉,就是触发storage变化的本页面,不能接收这个值,这个一般情况下是没问题。当然,为了一致性,我们可以自行new一个事件,在发生时候主动触发它。
此时我们可以包装一个新的Storage对象:
var Storage = {
setItem : function(k,v){
localStorage.setItem(k,v);
},
removeItem : function(k){
localStorage.removeItem(k);
},
clear: function (){},
getItem: function(k){}
}
此时,我们再包装一个函数:
function dispatchMe(key, oldval, newval, url, storage){
var se = document.createEvent("StorageEvent");
se.initStorageEvent('storage', false, false, key, oldval, newval, url, storage);
window.dispatchEvent(se);
}
此时,我们只需要在setItem、removeItem、clear中获取对应的值,并手动调用一下dispatchMe,同时把和localStorage打交道的地方改为调用我们包装的Storage即可。
2019年5月20日,科技界最火爆的消息莫过于谷歌暂停支持华为部分业务。关于这个事件将产生的影响,大家可以从相关的科技媒体上找到详细的分析。比较一致的看法是:谷歌的这个做法,对于国内的华为用户,影响不大;对于海外的华为用户,尤其是在上游应用生态环境上,会有一定的影响。
这是由于,谷歌暂停合作的服务,都是在用户层面的服务GMS。用户层与安卓底层内核遵从不同的协议,因此不必开源,进而导致替换成本高企。基于现有生态,在海外市场构建应用生态环境难度非常大。
这篇文章将尝试从这则新闻涉及的开源协议来分析一下,这个做法背后相关的逻辑,并籍此,跟各位读者聊一聊主流的开源协议。
天街小雨润如酥,草色遥看近却无。最是一年春好处,绝胜烟柳满皇都。 —— 唐.韩愈 《早春呈水部张十八员外二首》
几个月之前,chunpu小编曾经在《震惊! 滑动验证码竟然能这样破解》一文中给大家展示了使用支持WebDriver标准的Firefox破解滑动验证码的示例,脑洞之大,构思之巧,笔法幽默,叹为观止,也极大的燃起了笔者对此的兴趣。
这篇文章就带大家使用目前较为流行的Puppeteer完成这件事情。