脆弱的Playwright测试?循环来解决

2024-11-03   出处: themodernqa  作/译者:Steven Boutcher/空山新雨

遇到的问题

工作中,当试着为包含无头(headless)UI 单选按钮组功能的用户流编写Playwright测试时,我遇到了一个问题。
某个测试偶尔总会失败,原因是没有成功点击到某个单选按钮。
但可操作性验证步骤是成功的,然后Playwrite测试就尝试点击这个单选按钮。在此期间,Playwright没有抛出任何错误。
但整个测试最后失败了,这是因为随后的断言部分依赖该单选按钮的选择状态。
当我检查相关运行追踪信息时,我可以在单选按钮组下面看到红色的验证错误信息,这个错误只有在没有任何选择,并且用户试图进入下一页时才会出现。

试过的不同解决方案

找到问题的根本原因是容易的部分。
如何修复可操作性检查步骤依然发现不了的脆弱(或不稳定)的点击事件?
这个问题不正是我们先一步做可操作性验证的原因吗?

为了让点击那一步工作,我试了下面几种方法:

  • 除了getByTestId之外,使用了其他不同的定位策略
  • 在单选按钮的可点击区域内寻找不同HTML元素
  • 连续点击20次
  • 在试着单击之前,等待某个特定的条件,比如正确的页面URL。总体来说就是添加智能waitFor语句,这些语句不会重复已存在的可操作性验证步骤。

然而这些效果都不好,或者根本不起作用。因此,我卷起“LeetCoder”袖子,尝试了自从freeCodeCamp学习算法以来从未使用过的东西:一段时间的循环。

设计while循环

首先,我需要分析用户场景,以确定“点击失败”和“点击成功”如何影响用户界面。这听起来像是常识,但我们一般并不习惯考虑在失败或成功场景中出现的具体UI元素。
然后我就发现,如果我尝试在不点击单选按钮的情况下继续运行,验证错误将会出现在单选组下。如果我成功点击单选按钮,测试将单击表单上的“下一步”按钮,然后导航到新页面。但即使在单击“下一步”按钮之前,我们也会知道这个,因为isChecked()函数会告诉我们是否选择了单选按钮。这个“下一步”按钮就是我所说的“错误触发器”:因为如果单选按钮点击失败,单击“下一步”按钮将“触发”错误信息。有了这些信息,我就能够创建一个条件语句来确定单选按钮单击是成功还是失败:

let buttonWasClicked = await radioButton.isChecked();

if (buttonWasClicked) {
    await errorTextTrigger.click();
    errorTextIsVisible = await page.getByTestId("ErrorText").isVisible();
}

但这还不够。如果我们在第一次尝试点击时没有成功,我们需要能多次重试此条件。
这就是为什么我选择使用while循环。我可以说“如果我们看到失败的情况,就再试一次”。
如果我看到成功的情况,我也可以退出循环,从而尽可能少地重試次数。
我还必须精心设计这个while循环,使其不会变成无限循环。
所以我设置最多尝试5次。这是确保此测试不再脆弱(或不稳定)所需的最小循环次数。

while (errorTextIsVisible || !buttonWasClicked) {
    if (iterationCounter === 5) {
        break;
    } else {
        iterationCounter++;
    }
...

但那一步是我在看到循环方案正在起作用后做的最后一件事。
我之所以从无限迭代开始尝试,是因为如果出现无限循环我就会知道循环没有解决问题。
当循环开始成功时,我就把计数器设置为20,然后是10,然后是5。
我把测试又在本地和CI(持续集成)工具中用多个浏览器运行了多次,以确保此解决方案是无懈可击的。

最终解决方案

您会注意到最终解决方案包含一个waitForURL检查。
这是为了给页面足够的时间加载,以确保我们没有试图与错误的页面进行交互。
我们可能并不很需要它,但它是一层额外的保证。
如果此检查失败,我们甚至不会进入循环,这使我们能够更快地获得反馈。

export async function radioButtonGroupClick(
    page: Page,
    urlToWaitFor: string, // The URL where the radio button is
    radioButton: Locator,
    errorTextTrigger: Locator // A button you can click that will throw error text if the radio button wasn't clicked
) {
    await page.waitForURL(urlToWaitFor);

    let iterationCounter = 0;
    let errorTextIsVisible = await page.getByTestId("ErrorText").isVisible();
    let buttonWasClicked = await radioButton.isChecked();

    while (errorTextIsVisible || !buttonWasClicked) {
        if (iterationCounter === 5) {
            break;
        } else {
            iterationCounter++;
        }

        await radioButton.click();
        buttonWasClicked = await radioButton.isChecked();

        if (buttonWasClicked) {
            await errorTextTrigger.click();
            errorTextIsVisible = await page.getByTestId("ErrorText").isVisible();
        }
    }
}

最后一些想法

这不是我第一次遇到与第三方UI库生成的HTML交互的Playwright测试问题。
例如,您不能在React Select组件上使用Playwright的selectOption。
相反,您必须单击下拉框,并通过其标签文本找到一个选项。
虽然使用这些库中的组件能显著地提高开发人员的生产力,但它们可能会因为脆弱(或不稳定)测试而造成另外的生产力损失。
我们应该记下可以追溯到某个库的脆弱(或不稳定)测试。等到足够长的时间后,可能会发现一些规律,并特别指向特定的某一个库。
市场上有很多UI组件的选择。
如果一个UI 组件没有提供无缝的测试体验,我们也许可以把它换成另一个可以做到的组件。

这是QA在团队内“向左移动”的一种实践。
下次如果您有一个脆弱(或不稳定)测试,并且认为这可能与UI实现有关时,请看看该UI组件的代码。
找到导入语句。在某个地方把用于该UI组件的库都记下来。
如果您觉得合适的话,那么可以做一个实验:将有问题的组件替换为不同UI库的同一组件。
这样也许会减少您的团队在脆弱(或不稳定)测试上花费的时间。

更新:我的解决方案实际上有点糟糕

一位名叫Thananjayan Rajasekaran的读者在我LinkedIn(领英)帖子的评论中提出了更合理的替代方案。
那是我完全忘记了Playwright的expect.toPass() 功能!
我禁不住想立即试试。我太好奇了,不知道这是否能解决我的问题。
然后我就试了。瞧,瞧……

await expect(async () => {
     await radioButton.click(); // locator defined outside this block
     await expect(radioButton).toBeChecked();
 }).toPass();

看看,只有简单的4 行代码,伙计。我当时在做什么?
这种expect.toPass() 函数也修复了我正在处理的另一个脆弱(或不稳定)测试。
(对于那个,我设置了一些间隔时间,以决定重试的频率和重试的次数)
无论如何,希望你也会喜欢我之前过度设计的解决方案。
当时开发它的时候很有趣,即使现在我已经把它从代码库中撤下来了。
干杯,Thananjayan!


声明:本文为本站编辑转载,文章版权归原作者所有。文章内容为作者个人观点,本站只提供转载参考(依行业惯例严格标明出处和作译者),目的在于传递更多专业信息,普惠测试相关从业者,开源分享,推动行业交流和进步。 如涉及作品内容、版权和其它问题,请原作者及时与本站联系(QQ:1017718740),我们将第一时间进行处理。本站拥有对此声明的最终解释权!欢迎大家通过新浪微博(@测试窝)或微信公众号(测试窝)关注我们,与我们的编辑和其他窝友交流。
151° /1514 人阅读/0 条评论 发表评论

登录 后发表评论