.netcore持续集成测试篇之web项目验收测试

系列目录css

经过前面的单元测试,咱们可以保证项目的基本模块功能逻辑是正常的,经过集成测试可以保证接口的请求是正常的.然而最终项目交付咱们还须要对项目进行页面的行为进行测试,好比页面布局是否正常,按钮是否能点击,点击后执行的动做是否正确,连接是否正常等功能进行测试,表单提交是否返回正确结果等.这些都是一些墨盒测试,通常是由专门测试人员来完成,然而随着web的发展,各类自动化工具愈来愈完善,有一些页面功能的测试也能够由程序员来编写自动测试代码完成.这里主要结合Selenium来介绍如何完成页面行为的测试.html

点击按钮

前面咱们已经讲到如何安装和简单使用Selenium,这里再也不介绍.下面介绍一下如何使用Selenium来触发一个按钮点击事件.jquery

首先咱们在HelloWorldController里新建Action FormTest(也能够在其它控制器里建立,这里随意),代码以下程序员

public IActionResult FormTest()
        {
            return View();
        }
        [HttpPost]
        public IActionResult FormTest(string name)
        {
            return Content(name);
        }

以上代码很是简单,咱们建立FormTest并请求本身,而后把请求的数据返回web

咱们为这个Action新建一个页面,而且引入jquery.ajax

页面代码以下api

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <script src="~/js/JQueryt.js"></script>
    <title>FormTest</title>
</head>
<body>
<form method="post" id="frm1">
    <input id="btn1" type="button" value="点我"/>
</form>
<script>
    $("#btn1").click(function() {
        alert("hello,world");
    });
</script>
</body>
</html>

这个页面里有一个btn1,若是咱们点击它就会弹出一个alert框.
测试代码以下浏览器

[Fact]
        public void ClickTest()
        {
            IWebDriver driver =
                new ChromeDriver(Environment.CurrentDirectory);

            driver.Url = "http://localhost:28614/HelloWorld/FormTest";
            driver.Navigate();
            var element = driver.FindElement(By.Id("btn1"));
            element.Click();
        }

咱们先经过id找到这个按钮,而后令它触发一个click事件.咱们运行测试服务器

avatar

咱们并无手动点击按钮,可是弹出了上面的弹框,说明点击事件正确触发了.cookie

自动填写表单

经过以上代码咱们能够看到,触发一个按钮点击事件在Selenium是很是容易的,这对咱们自动模拟表单提交提供了大大的便利.Selenium还能够模拟自动填写表单,思路和上面是同样的,首先获取到要填写的表单,而后模拟填写内容.下面咱们改动一下网页代码,在form里面添加一个简单的表单

<input type="text" name="name"/>

测试代码改成以下:

[Fact]
        public void ClickTest()
        {
            IWebDriver driver =
                new ChromeDriver(Environment.CurrentDirectory);

            driver.Url = "http://localhost:28614/HelloWorld/FormTest";
            driver.Navigate();
            var input = driver.FindElement(By.Name("name"));
            input.SendKeys("hello,world");
        }

以上代经过FindElement By.Name获取到页面里name为name的元素(听起来有点绕),而后经过SendKeys方法模拟向指定元素填写内容

avatar

页面的开之后便会自动填写以上内容.这样咱们就能够自动填写内容,而后点击点我按钮提交表单了.

自动填写表单,而后提交

综合以上咱们模拟一次自动填写表单,而后提交的动做.

下面贴出修改后的完整代码.

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <script src="~/js/JQueryt.js"></script>
    <title>FormTest</title>
</head>
<body>
<form method="post" id="frm1">
    <input type="text" name="name"/>
    <input id="btn1" type="button" value="点我"/>
</form>
<script>
    $("#btn1").click(function() {
        $.ajax({
            type: "POST",
            url: "/HelloWorld/FormTest",
            data: $("#frm1").serialize(),
            dataType: "text",
            success: function (response) {
                alert("返回的数据是:"+response);
            }
        });
    });
</script>
</body>
</html>

此次当按钮点击之后咱们触发一次ajax提交,而后alert服务器返回的数据

测试代码以下:

[Fact]
        public void ClickTest()
        {
            IWebDriver driver =
                new ChromeDriver(Environment.CurrentDirectory);

            driver.Url = "http://localhost:28614/HelloWorld/FormTest";
            driver.Navigate();
            var input = driver.FindElement(By.Name("name"));
            input.SendKeys("hello,world");
            var btn = driver.FindElement(By.Id("btn1"));
            btn.Click();
        }

上面的代码执行了两个动做,第一是模拟填写表单数据,第二是点击按钮,提交表单.
咱们运行测试代码,看一下结果

avatar

能够看到表单自动提交了.

获取Alert框

咱们前面都是经过截图来看指定的行为是否产生了正确的结果,然而在自动化环境中这是不能接受的,更多的时候咱们是在无头模式下进行测试,而后自动获取行为产生的结果,而后断言此结果是不是期待的一个值.下面咱们改造以上代码,自动获致到Alert框并取得它里面的值,而后断言这个值是咱们想要的值.

[Fact]
        public void ClickTest()
        {
            IWebDriver driver =
                new ChromeDriver(Environment.CurrentDirectory);

            driver.Url = "http://localhost:28614/HelloWorld/FormTest";
            driver.Navigate();
            var input = driver.FindElement(By.Name("name"));
            input.SendKeys("hello,world");
            var btn = driver.FindElement(By.Id("btn1"));
            btn.Click();
            Thread.Sleep(3000);
            var alert = driver.SwitchTo().Alert();
            var txt = alert.Text;
            Assert.Equal("返回的数据是:hello,world", txt);
        }

以上代码的关键是经过SwitchTo获取到Alert框,进而获取到它的Text值,咱们在ajax请求成功的处理是"返回的数据是:"+提交的值,所以若是正常则以上代码会执行成功.这样咱们就不用守着页面查看结果了.

获取自定义弹出层

作到以上并无成事大吉,实际业务中咱们不多使用浏览器自带的Alert,而是使用一些第三方的组件,由于原生Alert用户体验实在不是太好,只能在测试的时候玩一玩还能够.因为第三方组件实现方式不一样,这就致使获取的方法也不同,咱们还要根据具体状况而定.下面咱们结合layui的alert框来介绍一下如何来获取它里面的内容.

咱们在项目中引入layui,而后把ajax请求成功后的alert换成layui的alert,代码以下

<html>
<head>
    <meta name="viewport" content="width=device-width" />

    <link href="~/lib/layui/css/layui.css" rel="stylesheet" />
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/layui/layui.all.js"></script>
    <title>FormTest</title>
</head>
<body>
<form method="post" id="frm1">
    <input type="text" name="name"/>
    <input id="btn1" type="button" value="点我"/>
</form>
<script>
    var layer = layui.layer;
    $("#btn1").click(function() {
        $.ajax({
            type: "POST",
            url: "/HelloWorld/FormTest",
            data: $("#frm1").serialize(),
            dataType: "text",
            success: function (response) {
                layer.alert("返回的数据是:" + response);
            }
        });
    });
</script>
</body>
</html>

因为这是一个自定义alert,咱们先运行一下项目,而后手动点击下按钮,等alert框出来之后咱们分析一下它的结构:

avatar

咱们能够看到,layui的这个alert框其实是一个div层,因为id是动态生成的,所以咱们不能使用,可是它的class是固定的,它包含了两个class元素,内部弹出的具体内容则是蓝色高亮的那个div里面的内容,它的class也是固定的,咱们这里可使用class获取到它们.

下面看测试代码:

[Fact]
        public void ClickTest()
        {
            IWebDriver driver =
                new ChromeDriver(Environment.CurrentDirectory);

            driver.Url = "http://localhost:28614/HelloWorld/FormTest";
            driver.Navigate();
            var input = driver.FindElement(By.Name("name"));
            input.SendKeys("hello,world");
            var btn = driver.FindElement(By.Id("btn1"));
            btn.Click();
            Thread.Sleep(3000);
            var layer = driver.FindElement(By.ClassName("layui-layer-dialog"));
            var content = layer.FindElement(By.ClassName("layui-layer-content"));
            var text = content.Text;
            Assert.Equal("返回的数据是:hello,world", text);
        }

产生咱们经过class获取到这个弹出层元素,而后再经过它找到它的子元素(包含弹出信息文字的div).这里的sleep前面说过,因为js是异步执行的,所以点击后并不能立刻获取到结果,这里咱们sleep一下.

须要特别注意的是,经过By.ClassName获取到的元素可能不止一下,默认取得的是获取到的第一个,这在有些时候可能并不能知足咱们的要求(这里代码比较少,发生冲突的机率比较小),实际工做中咱们必定要想办法保证元素的唯一性,也就是获取到的元素肯定是咱们所须要的.

还有一点须要注意的是第三方的组件实现方式可能会改变致使获取不到内容,这确实没有比较好的解决方案.

实际工做中可能还会有更为复杂的行为要去模拟,好比说弹出层是一个带有tab的面板,咱们须要切换到特定的tab去寻找想要的内容,因为这些内容都是非标准实现,所以模拟的难度根据采用框架的复杂度而定,有时候可能特别复杂,可是只要静下心来分析分析,老是能找到解决方案的.

前面讲到了如何填写表单,点击按钮提交表单以及获取弹出层内容.下面讲解一下如何获取连接,弹出页面,iframe以及高级行为.这里仍然是以实际应该为主导讲解一些最基本最经常使用的功能,并不求面面俱到,有兴趣的同事能够查看官方文档,第三方博客,书籍等获取更多知识.

连接行为测试

连接不少时候能够完成按钮的功能,可是最经常使用的是跳到一个新的页面,下面讲一下如何获取到新的页面

咱们在上节的页面中添加一个a连接,代码以下:

<a id="clk" href="http://www.baidu.com" target="_blank">飞往百度</a>

以上代码很简单,点击一下a标签就会出现一个新的百度页面,咱们想要判断一下是否正确打开了百度页面,测试代码以下:

[Fact]
        public void LinkClick()
        {
            IWebDriver driver =
                new ChromeDriver(Environment.CurrentDirectory);

            driver.Url = "http://localhost:28614/HelloWorld/FormTest";
            driver.Navigate();
            var link = driver.FindElement(By.Id("clk"));
            link.Click();
            var hands = driver.WindowHandles;
            var wind = driver.SwitchTo().Window(hands[1]);
            var title = wind.Title;
            Assert.Equal("百度一下,你就知道", title);
        }

上面代码主要的功能在于当点击连接之后经过driver.WindowHandles获取到容器的句柄,须要说明的是这里的句柄并非指针类型的句柄,而是一个字符串类类型的变量,咱们能够经过它找到指定的窗口,下面部分经过SwitchTo切换到一个窗口(SwitchTo咱们前面讲到过),Window接收一个字符串类开的参数,虽然提示字符说是窗口的标题,实际上并非,而是咱们刚才获取到的句柄,咱们知道如今共有两个窗口,百度窗口是后打开的,所以它的索引是是1.而后咱们再获取它的标题,看看是否是"百度一下, 你就知道"

须要说明的是,以上咱们虽然是经过索引获取的百度窗口,这样可能会由于位置切换形成问题(这里强烈不建议手动修改自动过程当中的行为,实际上真实的测试环境是无头环境,所以这其实不是一个很大的问题),就上面的例子咱们确实有办法能唯一肯定百度窗口,可是若是窗口过多想要不使用索引获取到指定的窗口仍是很困难的,这里强烈建议若是有打开很是多的窗口的复杂行为时,把测试分红若干个测试,每一个测试里的逻辑只打开少许窗口,这样出现问题也更容易排查.

点击时按下修改键

前面咱们屡次用到了模拟点击事件,其实这也是实际项目中用的最多的,可是也不排除少数状况下会用到其它的按键,好比说拖拽,双击,ctrl+点击等,

下面咱们演示如何在百度首页点击百度新闻并在新页面打开,咱们知道百度首页的新闻默认是在本页打开的,若是点击连接时按下ctrl键则会在新页面中打开.下面咱们模拟ctrl+点击这个行为
这里其实也很简单,主要经过Actions封装对象来触发一系列动做来达到咱们的目的.

下面看测试代码:

[Fact]
        public void LinkClick()
        {
            IWebDriver driver =
                new ChromeDriver(Environment.CurrentDirectory);

            driver.Url = "http://www.baidu.com";
            driver.Navigate();
            var link = driver.FindElement(By.LinkText("新闻"));
            Actions actions = new Actions(driver);
            actions.KeyDown(Keys.LeftControl).Click(link).Build().Perform();
        }

以上代码主要是经过传入driver对象构造一个Actions类型对象,这个对象在调用build以前会一直返回自身,相似是是jQuery里的链式操做,这样咱们就能够连续执行多个动做.

下面的代码咱们先是调用actions对象的keyDown方法,而后传入要按下的键,而后再调用点击事件,最后调用Build方法终止链式调用,最后再执行Perform执行前面的操做.启动测试就会发现浏览器在新的页面打开了百度新闻页

上面用到了一个之前没用到的选择方法那就是By.LinkText,语义很是明确那就是根据连接的文本找到连接对象.

Iframe对象的获取

咱们知道Iframe对象的处理比较麻烦,里面是一个比较封装的区域与外面通讯过程比较麻烦,在selenium里它的处理也比较特殊,直接按照id或者其它特征获取到它几乎没有任何做用,由于没法获取到内容元素,selenium是经过switchTo.Frame传入获取到的iframe对象对它进行一层封装,而后就可以正常获取到它内部的元素了.

咱们在页面添加一个简单的iframe页面,代码以下

<iframe id="ifrm1" src="http://www.baidu.com"></iframe>

测试代码以下

[Fact]
        public void FindIframe()
        {
            IWebDriver driver =
                new ChromeDriver(Environment.CurrentDirectory);
            driver.Url = "http://localhost:28614/HelloWorld/FormTest";
            driver.Navigate();
            var ele = driver.FindElement(By.Id("ifrm1"));
            var frm = driver.SwitchTo().Frame(ele);
            var txt = frm.FindElement(By.LinkText("新闻")).Text;
            Assert.Equal("新闻", txt);
        }

以上代码首先像获取普通元素同样获取到这个iframe对象,而后经过SwitchTo.Frame把它传入封装成个frame对象,后面就能够获取到它内部的元素了.(iframe指向百度首页,咱们获取到新闻连接)

然而实际项目中,每每咱们并不本身去建立iframe,而是由一些第三方ui框架自动建立的,框架生成的iframe要么没有id,要么是动态的,所以使用自动生成元素的id要很是慎重,可是笔者见过很多在生成iframe时能够给iframe指定name的,因为页面中iframe通常都不会太多,咱们能够给它命一个唯一的名字,经过名字找到它.若是没有名字,还能够根据它的class找到它,通常iframe样式class都是固定的,可是这时候要想办法确保选择到的对象是唯一的,这样才能保证测试结果的稳定性.

获取下拉列表

在一些查询功能中,每每全有下拉列表,经过js获取或者设置下拉项并非一件很困难的事,然而咱们并不想不了测试而增长无关的js代码,这样用完还要删除很是麻烦,其实Selenium也提供了设置下拉列表选项的功能,这样极大方便了咱们的测试.

下面看示例代码

咱们首先 在页面中添加以下代码

<select name="China" id="zhengzhouDistrict">
    <option value="">--请选择区域--</option>
    <option value="pdditrict">中原区</option>
    <option value="hpditrict">二七区</option>
    <option value="xhditrict">管城区</option>
    <option value="cnditrict">高新区</option>
    <option value="sjditrict">开发区</option>
</select>

测试代码以下:

[Fact]
        public void DropDownListTest()
        {
            IWebDriver driver =
                new ChromeDriver(Environment.CurrentDirectory);
            driver.Url = "http://localhost:28614/HelloWorld/FormTest";
            driver.Navigate();
            var element = driver.FindElement(By.Id("zhengzhouDistrict"));
            SelectElement dropdownList = new SelectElement(element);
            dropdownList.SelectByIndex(3);
            var select = dropdownList.SelectedOption.Text;
            Assert.Equal("管城区", select);
        }

以上代码中咱们先是经过普通方法获取到了这个下拉列表,而后把它封装成一个SelectElement对象,而后调用它的SelectByIndex设置选中的项,这样选中的项就是不默认值了,而是咱们想要选择的值.

select还有按索引,键,值等设置选择项的方法,而且能够取消选择,你们本身尝试一下,这里再也不介绍.

然而以上方法并无什么太大做用,因为浏览器自带的select界面每每都不太美观,而且动态交互性不是很是好,实际项目中咱们不多使用原生的select,而是使用第三方ui框架带的select,而第三方框架每每都是把select隐藏起来,而后把它的值赋值给一个input元素,它设置和获取值都是经过第三方框架提供的api而非原生select自带的方法.若是这时候使用以上方法获取select元素就会致使失败,selenium会提交元素被隐藏没法交互.针对这个问题笔者采用了一种比较笨的方法那就是模拟按键,固然这里模拟按键并不引入第三方按键类框架,而是使用selenium自己的功能.

下面仍然以layui为例说明如何设置下拉列表值.

页面代码以下

<html>
<head>
    <meta name="viewport" content="width=device-width" />

    <link href="~/lib/layui/css/layui.css" rel="stylesheet" />
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/layui/layui.all.js"></script>
    <title>FormTest</title>
</head>
<body>
<form class="layui-form" action="">
    <div class="layui-form-item">
        <div class="layui-input-inline">
            <select name="China" id="zhengzhouDistrict">
                <option value="">--请选择区域--</option>
                <option value="pdditrict">中原区</option>
                <option value="hpditrict">二七区</option>
                <option value="xhditrict">管城区</option>
                <option value="cnditrict">高新区</option>
                <option value="sjditrict">开发区</option>
            </select>
        </div>
    </div>
</form>
        <script>
            var layer = layui.layer;
            var form = layui.form;
            form.render();
        </script>
</body>
</html>

以上代码和上面的相似,只是这里把它封装成layui的select,咱们的思路是先获取到layui的显示select的元素,也就是最终渲染的input元素,通过观察发现这个元素有一个这样的样式layui-input,咱们能够经过这个关键信息找到它,而后点击一下,这时候下拉列表就出来了,此时再点击向下按钮,在想要的位置处click一下就能够获得想要的结果了.

测试代码以下:

[Fact]
        public void DropDownListTest()
        {
            IWebDriver driver =
                new ChromeDriver(Environment.CurrentDirectory);
            driver.Url = "http://localhost:28614/HelloWorld/FormTest";
            driver.Navigate();
            var input = driver.FindElement(By.ClassName("layui-input"));
            input.Click();
            Actions action = new Actions(driver);
            action.SendKeys(Keys.ArrowDown+Keys.ArrowDown+Keys.ArrowDown).Click().Build().Perform();
        }

以上主要是使用的Actions连续点击两次向下,就能够选择到指定的元素了.

日期框处理

这里结合layui来说解如何处理日期框的问题

首先咱们来观察一下layui日期输入框的特色,它实际上是一个input,而且是能够接受用户输入的,这就跟咱们模拟手动输入带来了方便,可是事情并无这么简单,咱们能够看到手输内容以后还要点击那个弹出层的肯定按钮来确认输入,一旦点击了肯定则会把弹出层默认选中的日期输入到input框中,覆盖了刚才的选择.然而它却有如下一个特色:若是咱们输入之后不点击确认,而是把光标移到空白色点击或者光标焦点移到其它可输入元素内,则也能够确认输入.这样咱们就能够在日期输入框输入内容之后再把焦点移到其它输入框就可以确认输入了.

页面代码以下:

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <link href="~/lib/layui/css/layui.css" rel="stylesheet" />
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/layui/layui.all.js"></script>
    <title>FormTest</title>
</head>
<body>
<div class="layui-inline">
    <input type="text" class="layui-input" id="test1">
    <input type="text" id="input1"/>
</div>
<script>
    var laydate = layui.laydate;
    laydate.render({
        elem: '#test1'
    });
</script>
</body>
</html>

以上有两个input框,一个是普通input,一个是日期框,咱们模拟在日期输入框输入内容后把焦点移动它右边空白处点击,而后看看上面日期输入框里的值是否是咱们赋予的.

测试代码以下:

[Fact]
        public void DropDownListTest()
        {
            IWebDriver driver =
                new ChromeDriver(Environment.CurrentDirectory);
            driver.Url = "http://localhost:28614/HelloWorld/FormTest";
            driver.Navigate();
            var input = driver.FindElement(By.Id("test1"));
            input.SendKeys("2008-5-3");
            var position = input.Location;
            var clickposition = position.X + 200;
            Actions actions = new Actions(driver);
            actions.MoveByOffset(clickposition, 0).Click().Build().Perform();
            var txt = input.GetAttribute("value");
            Assert.Equal("2008-05-03", txt);
        }

以上代码咱们首先获取到这个日期选择框,而后给它输入值.下面咱们获取它的位置主要是为了让它关闭(若是点击了空白处或者其它可点击控件,则日期选择框就会消失,这里只因此要关闭它是由于它当前处于激活状态,若是不关闭则会影响其它操做).咱们获取到它的位置后向右移动鼠标到空白处,而后点击空白处日期选择框就消失了.咱们输入的是'2008-5-3'而断言它是'2008-05-03'是由于layui有格式纠正功能,自动把一位的数据前面补零.

这里获取input的值是经过GetAttribute获取的,而不是经过Text,text是获取元素内部的文本(也就是文本包含在标签里),而input的值是它的value属性的一个值,所以使用Text获取不到.
必定要注意点击位置,若是点击位置位于连接或者提交按钮上则可能触发不可预期的效果.

前面咱们介绍了如何经过普通的方法给元素设置值以及模拟特定的行为,本篇主要介绍如何获取页面cookie,如何模拟手机测试.

获取页面cookie

有些比较复杂的测试可能会用到cookie,在Selenium里经过driver.Manage().Cookies就能够获取到页面全部的cookie对象了.

模拟手机浏览器

因为目前没有手机项目,这里并不详细介绍,只是做为一个知识点简单介绍一下.

看如下测试代码

[Fact]
        public void DropDownListTest()
        {
            ChromeOptions opts = new ChromeOptions();
            opts.EnableMobileEmulation("iPhone X");
            IWebDriver driver =
                new ChromeDriver(Environment.CurrentDirectory,opts);
            driver.Url = "http://www.baidu.com";
            driver.Navigate();

        }

这里主要是增长一个谷歌浏览器启用模拟手机浏览器选项,并指定一个模拟器的名字(这些名字能够经过谷歌浏览器的手机模式查看).而后再启动页面就会在指定的手机模拟器运行了.

以上运行结果截图以下:

avatar