node selenium_如何使用Selenium和Node.js编写可靠的浏览器测试

node selenium

by Todd Chaffee

托德·查菲(Todd Chaffee)

如何使用Selenium和Node.js编写可靠的浏览器测试 (How to write reliable browser tests using Selenium and Node.js)

There are many good articles on how to get started with automated browser testing using the NodeJS version of Selenium.

关于如何使用NodeJS版本的Selenium进行自动浏览器测试,有很多不错的文章。

Some wrap the tests in Mocha or Jasmine, and some automate everything with npm or Grunt or Gulp. All of them describe how to install what you need, along with giving a basic working code example. This is very helpful because getting all the different pieces working for the first time can be a challenge.

有些将测试包装在Mocha或Jasmine中,而另一些则通过npm或Grunt或Gulp自动化。 所有这些都描述了如何安装所需的组件,并给出了一个基本的工作代码示例。 这非常有帮助,因为第一次使所有不同的组件都可以工作是一个挑战。

But they fall short of digging into the details of the many gotchas and best practice of automating your browser testing when using Selenium.

但是,他们没有深入研究许多陷阱的细节以及使用Selenium时自动化浏览器测试的最佳实践。

This article continues where those other articles leave off, and will help you to write automated browser tests that are far more reliable and maintainable with the NodeJS Selenium API.

本文将继续其他文章,并帮助您编写使用NodeJS Selenium API更加可靠和可维护的自动浏览器测试。

避免睡觉 (Avoid sleeping)

The Selenium driver.sleep method is your worst enemy. And everyone uses it. This may be because the documentation for the Node.js version of Selenium is terse and only covers the syntax of API. It lacks real life examples.

Selenium driver.sleep方法是您最大的敌人。 每个人都使用它。 这可能是因为Selenium的Node.js版本的文档很简洁,仅涵盖API的语法。 它缺少现实生活中的例子。

Or it may be because a lot of example code in blog articles and on Q&A sites like StackOverflow make use of it.

或可能是因为博客文章和Stack&Overflow之类的问答网站中的许多示例代码都在使用它。

Let’s say an panel animates from a size of zero, to full size. Let’s look.

假设面板从零大小动画化为完整大小。 我们看看吧。

It happens so quickly you may not notice that the buttons and controls inside the panel are constantly changing size and position.

它发生得如此之快,以至于您可能没有注意到面板内的按钮和控件会不断变化的大小和位置。

Here’s a slowed down version. Pay attention to the green close button and you can see the changing size and position of the panel.

这是一个慢下来的版本。 注意绿色的关闭按钮,您会看到面板尺寸和位置的变化。

This is hardly ever a problem for real users because the animation happens so fast. If it were slow enough, like in the second video, and you tried to manually click on the close button while this was happening, you might click the wrong button, or miss the button altogether.

对于真正的用户来说,这几乎不是问题,因为动画发生得如此之快。 如果像第二个视频中那样足够慢,并且您试图在这种情况下手动单击关闭按钮,则可能单击了错误的按钮,或者完全错过了该按钮。

But these animations usually happen so quickly, you never have a chance to do that. Humans just wait for the animation to complete. Not true with Selenium. It’s so quick that it can try to click on elements that are still being animated, and you might get an error message like:

但是这些动画通常发生得如此之快,因此您再也没有机会这么做。 人类只是在等待动画完成。 Selenium不正确。 它是如此之快,以至于它可以尝试单击仍处于动画状态的元素,并且您可能会收到以下错误消息:

System.InvalidOperationException : Element is not clickable at point (326, 792.5)

This is when many programmers will say “Aha! I’ve got to wait for the animation to finish so I’ll just use driver.sleep(1000) to wait for the panel to be usable.”

这是许多程序员说“啊哈! 我必须等待动画完成,所以我只使用driver.sleep(1000)来等待面板可用。”

所以有什么问题? (So what’s the problem?)

The driver.sleep(1000) statement does what it looks like. It stops execution of your program for 1000 milliseconds, and allows the browser to continue working. Doing layout, fading in or animating elements, loading the page, or whatever.

driver.sleep(1000)语句看起来像样子。 它会在1000毫秒内停止执行程序,并允许浏览器继续运行。 进行布局,淡入元素或为元素添加动画,加载页面等。

Using the example from above, if the panel faded in over a period of 800 milliseconds the driver.sleep(1000)would usually accomplish what you want. So why not use it?

使用上面的示例,如果面板在800毫秒内逐渐消失, driver.sleep(1000) 通常会完成您想要的操作。 那么为什么不使用它呢?

The most important reason is that it is not deterministic. Meaning it will only work some of the time. Since it works only some of the time, we end up with fragile tests that break under certain conditions. This gives automated browser testing a bad name.

最重要的原因是它不是确定性的 。 意味着它只会在某些时候起作用。 由于它仅在某些时候起作用,因此我们最终得到了在某些条件下会破裂的易碎测试。 这给自动浏览器测试起了一个不好的名字。

Why does it work only some of the time? In other words, why isn’t it deterministic?

为什么它仅在某些时间起作用? 换句话说,为什么它不是确定性的?

What you notice with your eyes isn’t often the only thing that happening on a website. An element fade-in or animation is a perfect example. We are not supposed to notice these things if they are done well.

您眼中所见通常不是网站上发生的唯一事情。 元素淡入或动画是一个很好的例子。 如果做得好,我们不应该注意到这些事情。

If you tell Selenium to first find an element and then click it, there might only be a few milliseconds between those two operations. Selenium can be far faster than a human.

如果您告诉Selenium首先找到一个元素然后单击它,那么这两个操作之间可能只有几毫秒的时间。 Selenium可能比人类快得多。

When a human uses the website, we wait for the element to fade in before clicking on it. And when that fade-in takes less than a second, we probably don’t even notice we are doing that “waiting”. Selenium is not only faster and less forgiving, your automated tests have to deal with all sorts of other unpredictable factors:

当人们使用该网站时,我们等待该元素淡入,然后单击它。 当淡入时间不到一秒钟时,我们甚至可能没有注意到我们正在“等待”。 Selenium不仅更快,更容易理解,您的自动化测试还必须处理其他各种不可预测的因素:

  1. The designer of your web page might change the animation time from 800 milliseconds to 1200 milliseconds. Your test just broke.

    您的网页设计者可能会将动画时间从800毫秒更改为1200毫秒。 您的测试刚刚失败。

  2. Browsers don’t always do exactly what you ask for. Due to system load the animation might actually stall and take longer than 800 milliseconds, maybe even longer than your sleep of 1000 milliseconds. Your test just broke.

    浏览器并不总是能够完全满足您的要求。 由于系统负载,动画实际上可能会停顿,并花费超过800毫秒的时间,甚至可能长于您睡眠1000毫秒的时间。 您的测试刚刚失败

  3. Different browsers have different layout engines and prioritize the layout operations differently. Add a new browser to your test suite and your tests just broke.

    不同的浏览器具有不同的布局引擎,并且对布局操作的优先级不同。 在您的测试套件中添加一个新的浏览器,您的测试就坏了

  4. Browsers and the JavaScript that controls a page are asynchronous by nature. If the animation in our example changes functionality that needs information from the back-end, the programmer might add an AJAX call and wait for the result before firing the animation.

    浏览器和控制页面JavaScript本质上是异步的。 如果示例中的动画更改了需要后端提供信息的功能,则程序员可以添加AJAX调用并等待结果,然后再触发动画。

    We are now dealing with network latency and zero guarantee of how long it will take for the panel to display.

    现在,我们正在处理网络延迟,并且零保证面板将显示多长时间。

    Your test just broke.

    您的测试刚刚失败

  5. There are surely other reasons I don’t know of.

    当然还有其他我不知道的原因。

    Even

    甚至

    one browser on its own is a complex beast and all of them have bugs. So we are talking about trying to make the same thing work over several different browsers, several different browser versions, several different operating systems, and several different operating system versions.

    一个浏览器本身就是一个复杂的野兽,并且所有浏览器都有错误。 因此,我们正在谈论试图使同一件事在多个方面起作用 不同的浏览器,几种不同的浏览器版本,几种不同的操作系统以及几种不同的操作系统版本。

    At some point

    在某一点

    your tests just break if they are not deterministic. No wonder programmers give up on automated browser testing and complain about how fragile the tests are.

    如果测试不是确定性的,则测试只会中断 。 难怪程序员会放弃自动浏览器测试,而抱怨测试的脆弱性。

What do programmers typically do to fix things when any of the above happens? They trace things back to timing problems so the obvious answer is to increase the time in the driver.sleep statement. Then cross their fingers that it covers all possible future scenarios of system load, layout engine differences, and so on. It’s not deterministic and it breaks, so don’t do this!

当上述任何一种情况发生时,程序员通常会做什么来解决问题? 它们可以追溯到时序问题,因此显而易见的答案是增加driver.sleep语句中的时间。 然后动动手指,它涵盖了系统负载,布局引擎差异等所有未来可能的情况。 它不是确定性的它会中断 ,所以请不要这样做!

If you’re not convinced yet, here’s one more reason: your tests will run much faster. The animation from our example only takes 800 milliseconds, we hope. To deal with the “we hope” and make the tests work under all conditions you’ll probably see something like driver.sleep(2000) in the real world.

如果您还不确定,那么还有另一个原因:您的测试将运行得更快。 我们希望示例中的动画仅花费800毫秒。 为了处理“我们希望”并使测试在所有条件下都能工作,您可能会在现实世界中看到诸如driver.sleep(2000)类的东西。

That’s more than one full second lost for just one step of your automated tests. Over many steps, it adds up fast. A recently refactored test for one of our web pages that took several minutes due to overuse of driver.sleep now takes less than fifteen seconds.

损失了整整一秒钟的时间 进行自动化测试的一个步骤 。 经过许多步骤,它的累加速度很快。 由于过度使用driver.sleep,最近对我们的网页之一进行的重构测试花费了几分钟,现在睡眠时间不到15秒。

Most of the rest of this article gives specific examples on how you can make your tests fully deterministic, and avoid the use of driver.sleep.

本文的其余大部分内容都提供了有关如何使测试完全确定性并避免使用driver.sleep.特定示例driver.sleep.

关于承诺的说明 (A note about promises)

The JavaScript API for Selenium makes heavy use of promises, and it also does a good job of hiding that by using a built-in promise manager. This is changing and will be deprecated.

用于SeleniumJavaScript API大量使用了Promise,并且还通过使用内置的Promise Manager很好地隐藏了Promise。 这正在发生变化, 将不建议使用

In the future you will either need to learn how to use promise chaining yourself, or use the new JavaScript async functions like await.

将来,您将需要学习如何自己使用Promise链,或者使用新JavaScript异步函数(如await

In this article, the examples still make use of the traditional built-in Selenium promise manager and take advantage of promise chaining. The code examples here will make more sense if you understand how promises work. But you can still get a lot out of this article if you want to skip learning promises for the moment.

在本文中,示例仍使用传统的内置Selenium Promise Manager并利用Promise链。 如果您了解promise的工作原理,那么这里的代码示例将更有意义。 但是,如果您想暂时跳过学习承诺,您仍然可以从本文中学到很多东西。

让我们开始吧 (Let’s get started)

Continuing with our example of a button that we want to click on a panel that animates, let’s look at several specific gotchas that could break our tests.

继续我们要在动画面板上单击按钮的示例,让我们看一下可能破坏测试的几个特定陷阱。

How about an element that is dynamically added to the page and does not even exist yet after the page is finished loading?

在页面完成加载后动态添加到页面甚至不存在的元素怎么样?

等待元素出现在DOM中 (Waiting for an element to be present in the DOM)

The following code would not work if an element with a CSS id of ‘my-button’ was added to the DOM after page load:

如果在页面加载后将CSS ID为“ my-button”的元素添加到DOM,则以下代码将不起作用:

// Selenium initialization code left out for clarity
// Load the page.driver.get('https:/foobar.baz');
// Find the element.const button = driver.findElement(By.id('my-button'));
button.click();

The driver.findElement method expects the element to already be present in the DOM. It will error out if the element cannot be found immediately. In this case, immediately means “after page load is complete” due to the prior driver.get statement.

driver.findElement方法期望该元素已经存在于DOM中。 如果无法立即找到该元素,它将出错。 在这种情况下,由于先前的driver.get statement ,立即表示“页面加载完成后”。

Remember that the current version of JavaScript Selenium manages the promises for you. So each statement will fully complete before moving on to the next statement.

请记住,当前版本JavaScript Selenium为您管理了承诺。 因此,在继续进行下一个语句之前,每个语句将完全完成。

Note: The above behavior isn’t always undesirable. driver.findElement on its own might be actually be handy if you are sure the element should already be there.

注意:上述行为并不总是不希望的。 如果您确定该元素应该已经在其中,则driver.findElement本身可能实际上很方便。

First let’s look at the wrong way of fixing this. Having been told it might take a few seconds for the element to be added to the DOM:

首先,让我们看一下解决此问题的错误方法。 有人告诉您,将元素添加到DOM可能需要几秒钟的时间:

driver.get('https:/foobar.baz');
// Page has been loaded, now go to sleep for a few seconds.driver.sleep(3000);
// Pray that three seconds is enough and find the element.const button = driver.findElement(By.id('my-button'));
button.click();

For all the reasons mentioned earlier, this can break, and probably will. We need to learn how to wait for an element to be located. This is fairly easy, and you’ll see this often in examples from around the web. In the example below, we use the well documented driver.wait method to wait for up to twenty seconds for the element to be found in the DOM:

由于前面提到的所有原因,这可能会中断,甚至可能会中断。 我们需要学习如何等待元素被定位。 这相当容易,并且您会在网络上的示例中经常看到这一点。 在下面的示例中,我们使用有据可查的 driver.wait方法来等待长达20秒的时间,以便在DOM中找到该元素:

const button = driver.wait(  until.elementLocated(By.id('my-button')),   20000);
button.click();

There are immediate advantages to this. For example, if the element is added to the DOM in one second, the driver.wait method will complete in one second. It will not wait the full twenty seconds specified.

这有直接的好处。 例如,如果在一秒钟内将元素添加到DOM,则driver.wait方法将在一秒钟内完成。 它不会等待指定的整整二十秒。

Because of this behavior, we can put loads of padding in our timeout without worrying about the timeout slowing down our tests. Unlike the driver.sleep which will always wait the entire time specified.

由于这种行为,我们可以在超时中放入填充的负载,而不必担心超时会使测试变慢。 与driver.sleep不同, driver.sleep将始终等待指定的整个时间。

This works in a lot of cases. But one case it doesn’t work in is trying to click an element that is present in the DOM, but is not yet visible.

这在很多情况下都有效。 但是它无法解决的一种情况是尝试单击DOM中存在但尚不可见的元素。

Selenium is smart enough to not click an element that is not visible. This is good, because users cannot click invisible elements, but it does make us work harder at creating reliable automated tests.

Selenium足够聪明,不会单击不可见的元素。 这很好,因为用户无法单击不可见的元素,但这确实使我们更加努力地创建可靠的自动化测试。

等到元素可见 (Waiting until an element is visible)

We will build on the above example because it makes sense to wait for an element to be located before it becomes visible.

我们将在上面的示例的基础上进行构建,因为在元素可见之前先等待它的定位是有意义的。

You’ll also find our first use of promise chaining below:

您还会在下面找到我们对诺言链的首次使用:

const button = driver.wait(  until.elementLocated(By.id('my-button')),   20000).then(element => {   return driver.wait(     until.elementIsVisible(element),     20000   );});
button.click();

We could almost stop here and you would already be far better off. With the code above, you will eliminate loads of test cases that would otherwise break because an element is not immediately present in the DOM. Or because it is not immediately visible due to things like animation. Or even for both reasons.

我们几乎可以在这里停下来,您的境况会好得多。 使用上面的代码,您将消除测试用例的负载,否则这些负载可能会因为DOM中没有立即出现的元素而中断。 或由于动画等原因无法立即看到它。 甚至出于两个原因。

Now that you understand the technique, there should never be a reason to write Selenium code that is not deterministic. That’s not to say this is always easy.

既然您已经了解了该技术,那么就永远没有理由编写不确定的Selenium代码了。 这并不是说这总是很容易。

When things become more difficult, developers often give up again and resort to driver.sleep. I hope by giving even more examples, I can encourage you to make your tests deterministic.

当事情变得更加困难时,开发人员通常会再次放弃并诉诸driver.sleep 。 我希望通过举更多的例子,可以鼓励您确定性地进行测试。

编写自己的条件 (Writing your own conditions)

Thanks to the until method, the JavaScript Selenium API already has a handful of convenience methods you can use with driver.wait. You can also wait until an element no longer exists, for an element that contains specific text, for an alert to be present, or many other conditions.

多亏了until方法,JavaScript Selenium API已经有了一些可以与driver.wait使用的便捷方法 。 您也可以等到元素不再存在,包含特定文本的元素,出现警报或许多其他情况为止。

If you can’t find what you need in the supplied convenience methods you will need to write your own conditions. This is actually pretty easy, but it’s hard to find examples. And there is one gotcha — which we will get to.

如果在提供的便捷方法中找不到所需的内容,则需要编写自己的条件。 这实际上很容易,但是很难找到例子。 有一个陷阱,我们将去解决。

According to the documentation, you can provide driver.wait with a function that returns true or false.

根据文档 ,可以为driver.wait提供返回truefalse的函数。

Let’s say we wanted to wait for an element to be full opacity:

假设我们要等待元素完全不透明:

// Get the element.const element = driver.wait(  until.elementLocated(By.id('some-id')),  20000);
// driver.wait just needs a function that returns true of false.driver.wait(() => {   return element.getCssValue('opacity')          .then(opacity => opacity === '1');});

That seems useful and reusable, so let’s put it in a function:

这似乎有用且可重用,所以让我们将其放在函数中:

const waitForOpacity = function(element) {  return driver.wait(element => element.getCssValue('opacity')          .then(opacity => opacity === '1');  );};

And then we can use our function:

然后我们可以使用我们的函数:

driver.wait(  until.elementLocated(By.id('some-id')),  20000).then(waitForOpacity);

Here comes the gotcha. What if we want to click the element after it reaches full opacity? If we try to assign the value returned by the above, we would not get what we want:

陷阱来了。 如果要在完全不透明后单击该元素怎么办? 如果尝试分配上面返回的值,将无法获得所需的结果:

const element = driver.wait(  until.elementLocated(By.id('some-id')),  20000).then(waitForOpacity);
// Oops, element is true or false, not an element.element.click();

We cannot use promise chaining either, for the same reason.

出于相同的原因,我们也不能使用承诺链。

const element = driver.wait(  until.elementLocated(By.id('some-id')),  20000).then(waitForOpacity).then(element => {  // Nope, element is a boolean here too.  element.click();});

This is easy to fix. Here is our improved method:

这很容易解决。 这是我们改进的方法:

const waitForOpacity = function(element) {  return driver.wait(element => element.getCssValue('opacity')          .then(opacity => {      if (opacity === '1') {        return element;      } else {        return false;    });  );};

The above pattern, which returns the element when the condition is true, and returns false otherwise, is a reusable pattern you can use when writing your own conditions.

上面的模式( 当条件为true时返回元素 ,否则返回false)是可重用的模式,您可以在编写自己的条件时使用。

Here is how we can use it with promise chaining:

这是我们如何在诺言链中使用它:

driver.wait(  until.elementLocated(By.id('some-id')),  20000).then(waitForOpacity).then(element => element.click());

Or even:

甚至:

const element = driver.wait(  until.elementLocated(By.id('some-id')),  20000).then(waitForOpacity);
element.click();

By writing your own simple conditions, you can expand your options for making your tests deterministic. But that’s not always enough.

通过编写自己的简单条件,您可以扩展选择范围以使测试具有确定性。 但这并不总是足够的。

去消极 (Go negative)

That’s right, sometimes you need to be negative instead of positive. What I mean by this is to test for something to no longer exist or for something to not be visible anymore.

是的,有时候您需要否定而不是肯定。 我的意思是测试不再存在的东西或不再可见的东西。

Let’s say an element exists in the DOM already, but you shouldn’t interact with it until some data is loaded via AJAX. The element could be covered with a “loading…” panel.

假设DOM中已经存在一个元素,但是在通过AJAX加载某些数据之前,您不应与该元素进行交互。 该元素可能被“正在加载...”面板覆盖。

If you paid close attention to the conditions offered by the until method, you might have noticed methods like elementIsNotVisible or elementIsDisabled or the not so obvious stalenessOfElement.

如果您密切关注由所提供的条件until方法,你可能已经注意到方法,如elementIsNotVisibleelementIsDisabled或不那么明显stalenessOfElement

You could test for a “loading…” panel to no longer be visible:

您可以测试“正在加载...”面板,使其不再可见:

// Already added to the DOM, so this will return immediately.const desiredElement = driver.wait(  until.elementLocated(By.id('some-id')),  20000);
// But the element isn't really ready until the loading panel// is gone.driver.wait(  until.elementIsNotVisible(By.id('loading-panel')),  20000);
// Loading panel is no longer visible, safe to interact now.desiredElement.click();

I find the stalenessOfElement to be particularly useful. It waits until an element has been removed from the DOM, which could also happen from page refresh.

我发现stalenessOfElement特别有用。 它等待直到从DOM中删除了一个元素,这也可能发生在页面刷新中。

Here is an example of waiting for the contents of an iframe to refresh before continuing:

这是在继续操作之前等待iframe内容刷新的示例:

let iframeElem = driver.wait(  until.elementLocated(By.className('result-iframe')),  20000  );
// Now we do something that causes the iframe to refresh.someElement.click();
// Wait for the previous iframe to no longer exist:driver.wait(  until.stalenessOf(iframeElem),  20000);
// Switch to the new iframe. driver.wait(  until.ableToSwitchToFrame(By.className('result-iframe')),  20000);
// Any following code will be relative to the new iframe.

始终保持确定性,不要睡觉 (Always be deterministic, and don’t sleep)

I hope these examples have helped you better understand how to make your Selenium tests deterministic. Do not rely on driver.sleep with an arbitrary guess.

我希望这些例子可以帮助您更好地了解如何确定Selenium测试。 不要随意猜测driver.sleep

If you have questions or your own techniques for making Selenium testing deterministic, please leave a comment.

如果您有疑问或您自己的确定性Selenium测试技术,请发表评论。

翻译自: https://www.freecodecamp.org/news/how-to-write-reliable-browser-tests-using-selenium-and-node-js-c3fdafdca2a9/

node selenium

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值