|
E2E
业务系统的历史债务往往是 RD 最大的痛点,混乱且没有单测的代码,连重构都没有信心。这件事很多时候就是恶性循环,没有单测,不敢重构,继续在坏掉的代码上添砖加瓦。直到某一天跑路。
但坦率的讲,好的单测是很高的要求,这个时候我们可以利用 End To End 测试来确认重构代码是否符合预期。这样也能给 RD 真实的自信。
Rod 就是web页面 E2E 测试的好帮手,今天我们来看看它能做什么。大家只需要安装好 Golang 环境就可以体验了。
Rod
Rod is a high-level driver directly based on?DevTools Protocol. It's designed for web automation and scraping for both high-level and low-level use, senior developers can use the low-
level packages and functions to easily customize or build up their own version of Rod, the high-level functions are just examples to build a default version of Rod.
go-rod 是一款支持 golang 的 web automation工具,基于DevTools Protocol协议实现,Chrome DevTools Protocol 协议支持与浏览器进行通信,允许使用工具来检测、检查、调试和分
析 Chromium、Chrome 和其他基于 Blink 的浏览器。一些需要页面端到端测试的case,使用自动化可以大幅减少手工操作时间。
它具有以下优势:
链式上下文设计;
自动等待网页的元素加载完毕;
对调试很友好,自动跟踪输入,远程监控浏览器;
线程安全;
自动查找,并下载浏览器,参照 launcher;
高层次的 helper 方法,如 WaitStable, WaitRequestIdle, HijackRequests, WaitDownload;
两步的 WaitEvent 设计,保障不会丢失 event,参照 goob;
正确处理嵌入 iframe;
进程 crash 后不会再有浏览器进程运行,防止泄露,参照 leakless;
100% 的测试覆盖率,安全可靠。
作为优秀的开源库,我们同样可以从 Rod 的单测来一窥究竟,看看到底提供了哪些能力。感兴趣的同学可以直接看 example_test.go,以及官方提供的各个案例库。
对于对 rod 不熟悉的同学,我们下来一起看一看它的用法,希望能够带大家感受到它的魔力。
这里需要注意,rod 只要求大家有 Golang 的环境,请确保已经安装,不用担心 HTML 不熟悉。
用法入门
用 rod 打开页面截屏需要几行代码?
下面这个程序就是答案:
package main
import "github.com/go-rod/rod"
func main() {
page := rod.New().MustConnect().MustPage("https://www.wikipedia.org/")
page.MustWaitLoad().MustScreenshot("a.png")
}
我们新建一个 main.go 文件,复制上面的内容后保存,运行 go run main.go,就会发现在你的项目目录下多了一张图片 a.png,这就是对指定网页的截图:
rod.New
回到代码,rod.New 创建了一个浏览器对象:
// New creates a controller.
// DefaultDevice to emulate is set to devices.LaptopWithMDPIScreen.Landscape(), it can make the actual view area
// smaller than the browser window on headful mode, you can use NoDefaultDevice to disable it.
func New() *Browser {
return (&Browser{
ctx: context.Background(),
sleeper: DefaultSleeper,
controlURL: defaults.URL,
slowMotion: defaults.Slow,
trace: defaults.Trace,
monitor: defaults.Monitor,
logger: DefaultLogger,
defaultDevice: devices.LaptopWithMDPIScreen.Landescape(),
targetsLock: &sync.Mutex{},
states: &sync.Map{},
}).WithPanic(utils.Panic)
}
事实上我们还可以设置为无痕模式,通过链式的 Incognito 方法即可:
// Incognito creates a new incognito browser
func (b *Browser) Incognito() (*Browser, error) {
res, err := proto.TargetCreateBrowserContext{}.Call(b)
if err != nil {
return nil, err
}
incognito := *b
incognito.BrowserContextID = res.BrowserContextID
return &incognito, nil
}
MustConnect
拿到了一个 Browser 对象后,通过 MustConnect 来启动并连接到浏览器:
// MustConnect is similar to Browser.Connect
func (b *Browser) MustConnect() *Browser {
b.e(b.Connect())
return b
}
// Connect to the browser and start to control it.
// If fails to connect, try to launch a local browser, if local browser not found try to download one.
func (b *Browser) Connect() error
MustPage
连接到浏览器后,我们给出希望打开的地址,通过调用 MustPage 来创建出一个页面对象。对应到下面的 *Page。
// MustPage is similar to Browser.Page.
// The url list will be joined by "/".
func (b *Browser) MustPage(url ...string) *Page {
p, err := b.Page(proto.TargetCreateTarget{URL: strings.Join(url, "/")})
b.e(err)
return p
}
大家可以把 Page 想象成浏览器里的一个 tab。这就是我们这一行做的事情:
page := rod.New().MustConnect().MustPage("https://www.wikipedia.org/")
创建一个 rod 浏览器实例,连接上去,打开指定地址的一个页面 tab。
MustWaitLoad
最后我们调用 Page 对象的 MustWaitLoad 方法,参照下面 WaitLoad 注释,这里会等待 window.onload 事件,也就是等页面完全加载完成。此处返回值还是 Page 对象,继续链式调用。
// MustWaitLoad is similar to Page.WaitLoad
func (p *Page) MustWaitLoad() *Page {
p.e(p.WaitLoad())
return p
}
// WaitLoad waits for the `window.onload` event, it returns immediately if the event is already fired.
func (p *Page) WaitLoad() error {
defer p.tryTrace(TraceTypeWait, "load")()
_, err := p.Evaluate(evalHelper(js.WaitLoad).ByPromise())
return err
}
MustScreenshot
最后,我们调用 Page 对象的 MustScreenshot 来截屏:
// MustScreenshot is similar to Screenshot.
// If the toFile is "", it Page.will save output to "tmp/screenshots" folder, time as the file name.
func (p *Page) MustScreenshot(toFile ...string) []byte {
bin, err := p.Screenshot(false, nil)
p.e(err)
p.e(saveFile(saveFileTypeScreenshot, bin, toFile))
return bin
}
// Screenshot captures the screenshot of current page.
func (p *Page) Screenshot(fullpage bool, req *proto.PageCaptureScreenshot) ([]byte, error)
// PDF prints page as PDF
func (p *Page) PDF(req *proto.PagePrintToPDF) (*StreamReader, error)
事实上,我们可以看到这里分为两步:
截屏,拿到图片的数据,一个字节数组;
将图片数据写入我们指定的文件中。
除了图片,这里还支持 PDF,相当强大。这就是这一行的意义:
page.MustWaitLoad().MustScreenshot("a.png")
|
|