构建 Selenium

Selenium 团队如何构建 Selenium 本身,以及我们为什么选择我们选择的工具?

我们在 Selenium 项目一开始就知道的一件事是,人们喜欢用多种语言进行编码。有些人喜欢 JavaScript,有些人喜欢 Ruby,还有些人更喜欢 C# 或 Java。

更复杂的是,我们想在您将使用的语言绑定之间共享很多东西。例如,“原子”(可重用的 JavaScript 代码片段,用于执行常见功能,例如“isDisplayed”或“getAttribute”,我们希望无论您喜欢用哪种语言编写测试,它们的行为方式都相同)、CDP 支持(使用描述我们可以调用的所有可用函数的共享文件)以及新的 Selenium Manager(用 Rust 编写,但我们将其与每个语言绑定捆绑在一起)。

将源代码和其他工件(例如原子)一起转换为我们分发的工件(例如 Selenium Server 或语言绑定)的过程称为“构建”。有很多构建工具。如果您是 Java 开发人员,您可能遇到过 Maven 或 Gradle。如果您是 JS 黑客,那么您可能使用过 npm 或 yarn 之类的工具。如果您是 C 开发人员(现在仍然有很多!),那么您可能正在使用 make 或 CMake。

许多构建工具的问题在于它们专注于一种语言。Npm 很棒,但对于 Java 项目来说是一个糟糕的选择。Gradle 还可以,但如果您使用 Ruby,则不行。

为什么这是一个问题?因为在 Selenium 代码库中,我们希望支持多种不同的语言,并且我们希望能够将它们“缝合”成一个单一的整体。例如,Selenium jar 包含相当大量的 JS。Ruby gem 也是如此。

我们想要的是一个可以处理多种不同语言的构建工具,这样我们就可以将我们的构建编织成只需要一个工具的东西。

引入 Bazel。这是一个最初由 Google 开发的构建工具,但现在是开源的,并且使用越来越广泛。Bazel 本身的功能相对有限,但可以使用“规则集”轻松扩展它,使其支持我们需要的一切,甚至更多!

Bazel 是新一代构建工具之一,它专注于展示构建过程的每个部分如何与其他部分相关联。您可以想象绘制一个图表,其中我们需要编译的每个内容(例如 Selenium Manager 或原子或我们发布的 jar 之一)通过线连接到它们所依赖的其他部分。在计算机科学中,该图表称为“图”,并且因为每条线都有一个方向(“这个东西依赖于那个东西”),所以我们称之为有向图。因为我们不能依赖于依赖于自身的东西,所以我们可以引入一个“循环”。Bazel 是一种旨在处理这些“有向无环图”的构建工具。

关于这些图的一个好处是,有很多众所周知的方法可以找出构建的哪些部分可以并行执行。现代计算机的 CPU 有多个(4、8、16!)核心、大量的内存和快速 SSD:它可以轻松地同时执行多项任务。因此,Bazel 利用了这一点,尽可能多地同时运行构建的各个部分。这使得我们的构建速度比以前快得多!

更好的是,Bazel 使我们列出构建的每个部分所依赖的所有内容。不仅包括源代码,还包括我们正在使用的工具的哪个版本。这使得项目的新开发人员更容易上手:他们只需要克隆我们的存储库,确保他们安装了 Bazel,并且构建过程将负责确保他们拥有他们需要的一切(尽管第一次构建可能会非常慢,因为所有需要的东西都将从网上下载)。这不仅对项目的新手有好处,对现有开发人员也有好处。他们不再需要知道如何安装和设置他们可能不熟悉的工具链——他们只需运行构建即可。

使用“构建图”,Bazel 可以知道 Selenium 源代码中的哪些代码片段依赖于其他哪些部分。这意味着,尽管我们可以告诉 Bazel 在我们进行更改时“运行所有测试”,但它足够智能,可以知道它只需要运行那些实际受更改影响的测试。您可以在此视频中看到它的实际效果,但不用说,这可以为我们节省大量时间!我们还可以要求 Bazel 重新运行不稳定的测试

但描述构建所需的所有内容还有另一个好处。既然我们已经向 Bazel 描述了所有我们需要的东西,以及所有部件如何组合在一起,就没有必要仅仅在我们自己的机器上运行构建。我们正在与 EngFlow 合作,以使用他们的构建网格。与其仅仅在我们的机器上同时运行少量任务,我们可以在他们的构建网格上运行多倍的任务。我们在那里的构建速度非常快!

所以,这就是为什么我们在项目中使用 Bazel 的原因:它在一个工具中支持我们想要使用的所有语言,使我们不必考虑如何设置开发机器,构建运行速度非常快,而且我们可以利用构建网格来更快地构建东西。