End-to-end tests for UI are notoriously work-intensive and brittle. But my team has developed a way to tackle this problem: a tool that automatically visits all our UI pages and looks for errors on them.
UI的端到端测试非常耗时且脆弱。 但是我的团队已经开发出一种解决此问题的方法:一种工具,该工具可自动访问我们所有的UI页面并查找错误。
大想法 (The Big Idea)
Historically, my team’s automated tests have mostly consisted of function-level unit and integration tests. These give us great code coverage, but have a few drawbacks. The biggest is that each test has limited scope and requires using mock data. In addition, creating these tests is time-consuming as each needs to be written by hand.
从历史上看,我团队的自动化测试主要由功能级别的单元测试和集成测试组成。 这些为我们提供了很好的代码覆盖范围,但也有一些缺点。 最大的是,每个测试的范围都有限,需要使用模拟数据。 另外,创建这些测试非常耗时,因为每个测试都需要手工编写。
We decided to augment these small-scale tests with automated tools that:
我们决定使用以下自动化工具来扩充这些小型测试:
- test whole systems rather than just individual functions or components 测试整个系统,而不仅仅是单个功能或组件
- automatically cover most of our code with a single generic function, and automatically test new code without having to update the tool 使用单个通用函数自动覆盖我们的大多数代码,并且无需更新工具即可自动测试新代码
- test using a variety of accounts (which each have different permissions and feature flags) 使用各种帐户(每个帐户具有不同的权限和功能标志)进行测试
- record how long the code takes to execute 记录代码执行所需的时间
The first tool we created to achieve these goals was called Jeffbot. Jeffbot iterates through a set of accounts, then for each one makes a GET call to each of our API endpoints, recording errors and response time. These calls test the whole backend, from API endpoint to database. As new APIs are added, they’re automatically picked up and called by Jeffbot.
我们创建的用于实现这些目标的第一个工具称为Jeffbot。 Jeffbot遍历一组帐户,然后针对每个帐户对我们的每个API端点进行GET调用,记录错误和响应时间。 这些调用测试从API端点到数据库的整个后端。 添加新的API后,Jeffbot会自动选择并调用它们。
Jeffbot was a great start, but it doesn’t test the interface between the frontend and backend. We had tests for our APIs, and tests for our frontend code using mock API data, but we needed another tool to make sure that the data that our APIs provided matched what the frontend expected.
Jeffbot是一个很好的开始,但是它没有测试前端和后端之间的接口。 我们已经对API进行了测试,并使用模拟API数据对我们的前端代码进行了测试,但是我们需要另一个工具来确保API提供的数据与前端期望的数据匹配。
解决方案 (The Solution)
Enter Dragoon. Historically, dragoons were mounted soldiers who sometimes dismounted and walked. We named our tool after them because it “walks” our pages and “mounts” each one. Dragoon is a lot like Jeffbot, but instead of calling the APIs directly, it loads pages in our UI. Like Jeffbot, it uses several different accounts, and for each one iterates through our site’s pages using that account’s permissions and feature flags. The data for the pages comes from APIs running on a test environment, and is very similar to our production data. Dragoon records how long each page takes to load and any errors or warnings it encounters.
输入龙骑。 从历史上看,龙骑兵是骑兵 ,有时下马又走路。 我们以它们命名我们的工具,因为它“遍历”我们的页面并“装载”每个页面。 Dragoon与Jeffbot非常相似,但是它不直接调用API,而是在我们的UI中加载页面。 与Jeffbot一样,它使用几个不同的帐户,并且每个帐户都使用该帐户的权限和功能标记来遍历我们网站的页面。 页面数据来自在测试环境中运行的API,与我们的生产数据非常相似。 Dragoon记录每页加载所需的时间,以及遇到的任何错误或警告。
首次实施(酶) (First Implementation (Enzyme))
Our initial implementation of Dragoon used much of the same tooling we already had in place for our frontend unit tests. Karma connected the tests to an environment, ran them, and collected output. Enzyme rendered the frontend code locally in headless chrome and connected it to the test environment APIs. Jasmine was used for the test cases and assertions.
Dragoon的最初实现使用了许多与前端单元测试相同的工具。 Karma将测试连接到环境,运行它们并收集输出。 酶以无头chrome本地呈现前端代码,并将其连接到测试环境API。 茉莉花用于测试用例和断言。
This mostly worked, but we ran into issues with the test environment denying requests due to CORS on certain versions of chromium. When we initially built Dragoon, the current version of chromium denied requests, but a new version fixed that. Later, an even newer version broke it again.
这通常是可行的,但是我们遇到了测试环境遇到的问题,即由于某些版本的Chrome的CORS而导致拒绝请求。 当我们最初构建Dragoon时,当前版本的Chrome拒绝了请求,但是新版本修复了该问题。 后来,一个更新的版本再次将其破坏。
第二次实施(赛普拉斯) (Second Implementation (Cypress))
Fortunately, around the second time that Dragoon broke we discovered a new technology called Cypress. Crucially, Cypress can bypass CORS, so we decided to scrap our initial implementation and rewrite Dragoon to run on Cypress instead.
幸运的是,在龙骑兵第二次破产的时候,我们发现了一种名为赛普拉斯的新技术。 至关重要的是,赛普拉斯可以绕开CORS ,因此我们决定取消最初的实现,然后重写Dragoon使其在赛普拉斯上运行。
Switching to Cypress required a significant change in tooling. Our old implementation ran the frontend code in the same process that tested it. However, Cypress needs a separate process to run the frontend code. So, we decided to use webpack-dev-server to run it since we were already using Webpack to bundle our code. We also added start-server-and-test, which let us write a one-line command to start webpack-dev-server locally, wait until it’s running, and then run Dragoon.
切换到赛普拉斯需要对工具进行重大更改。 我们的旧实现在测试前端代码的同一过程中运行了前端代码。 但是,赛普拉斯需要一个单独的过程来运行前端代码。 因此,我们决定使用webpack-dev-server来运行它,因为我们已经在使用Webpack来捆绑我们的代码。 我们还添加了start-server-and-test ,它使我们可以编写一个单行命令来在本地启动webpack-dev-server,等待其运行,然后运行Dragoon。
Of course, there were some obstacles that we had to work through:
当然,我们必须克服一些障碍:
- Even though Cypress can bypass CORS, we don’t get that benefit unless Cypress is actually making the request. In our case it was the frontend code making the request, not Cypress, so we still ran into CORS issues for POST requests. We solved this by intercepting all POST requests from our frontend code and making those requests from Cypress instead. 即使赛普拉斯可以绕过CORS,但除非赛普拉斯实际提出要求,否则我们不会获得该好处。 在我们的例子中,它是发出请求的前端代码,而不是赛普拉斯,因此我们仍然遇到POST请求的CORS问题。 我们通过拦截来自前端代码的所有POST请求并改为从Cypress发出这些请求来解决了这一问题。
- Dragoon needs to know the list of routes to iterate through, but that list comes from a helper function in our code that Cypress doesn’t have access to. We fixed this by bundling our code in with Cypress using webpack. Dragoon需要知道要迭代的路由列表,但是该列表来自赛普拉斯无法访问的代码中的帮助器函数。 我们通过使用webpack将代码与Cypress捆绑在一起来解决此问题。
- We want console errors to count as test failures, but, by default, Cypress ignores them. We resolved this by having Cypress intercept calls to ‘console.error’ and fail the test if there are any. 我们希望将控制台错误计为测试失败,但默认情况下,赛普拉斯会忽略这些错误。 我们通过让Cypress拦截对“ console.error”的调用并通过测试(如果有的话)而使问题得以解决。
Even after adding solutions for these issues we still found that using Cypress to drive Dragoon drastically reduced the amount of code required. In the initial implementation we hand-built functions to visit a page, wait until it loads, and check for errors. Cypress is made for running tests like Dragoon, so it has built-in commands to handle all of those tasks. Using Cypress as our driver also provides significant opportunity for future enhancements using its rich feature suite.
即使为这些问题添加了解决方案之后,我们仍然发现使用赛普拉斯驱动Dragoon可以大大减少所需的代码量。 在最初的实现中,我们手工构建了函数来访问页面,等待页面加载,然后检查错误。 赛普拉斯专门用于运行诸如Dragoon之类的测试,因此它具有内置命令来处理所有这些任务。 使用赛普拉斯作为我们的驱动程序,还可以通过其丰富的功能套件为未来的增强提供大量机会。
结论 (Conclusion)
Despite some challenges along the way, Dragoon is now consistent and stable. It automatically provides broad coverage of our UI, acting as a smoke test that gives us increased confidence in the code we ship. It also provides consistent timing metrics so that we can track the performance of our pages, either across all users or just for a specific account. We’re excited to take advantage of these features and explore how we can use Cypress to continue improving Dragoon.
尽管在此过程中遇到了一些挑战,但龙骑兵现在始终保持稳定。 它会自动提供我们UI的广泛覆盖范围,作为冒烟测试,使我们对所发布的代码更有信心。 它还提供了一致的计时指标,因此我们可以跨所有用户或仅针对特定帐户跟踪页面的性能。 我们很高兴能够利用这些功能,并探索如何使用赛普拉斯来继续改进Dragoon。
翻译自: https://medium.com/xandr-tech/dragoon-an-automated-ui-testing-tool-190f6cd62960