BDD - SpecFlow SpecRun Web UI 多浏览器测试
创始人
2024-02-21 15:53:46

BDD - SpecFlow & SpecRun 一个 Cases 匹配多个浏览器

  • 引言
  • 方案
    • SpecFlow+ Runner profiles
  • 实现
    • 被测 Web Application
    • 创建一个 Class Libary 项目
    • 添加 NuGet Packages
      • SpecFlow & SpecRun 包
      • 添加 Selenium包
      • 其它包
    • 创建 Feature 文件
    • 配置 Default.srprofile
      • Default.srprofile 添加 Targets
      • 添加 DeploymentTransformation
    • 配置 App.config
    • 设计 Driver 层
      • ConfigurationDriver.cs
      • BrowserSeleniumDriverFactory.cs
      • WebDriver.cs
      • CalculatorPageDriver.cs
    • 实现 Step Definition
    • 添加 Hooks
    • 执行测试

引言

在进行 Web UI 测试,通常需要在多个浏览器上进行兼容性测试,例如:Chrome,IE,Edge 和 Firefox。但是为所有浏览器都分别写 Cases 似乎是费时,也是没有必要的事。今天我们就来介绍一种方案,一套 Cases 可以在所有浏览器上运行。如果你不太了解 BDD SpecFlow Web UI 测试,请先阅读之前的文章 《 BDD - SpecFlow Web UI 测试实践 》

方案

SpecFlow+ Runner 可通过 Targets 来实现,Targets 定义在 SpecFlow+ Runner profile 文件中,可以定义不同环境的设置,filters 和 为每个 target 部署转换步骤 (deployment transformation steps)

为每个浏览器定义 targets 使得一个 cases 可以在所有的浏览器上执行。

SpecFlow+ Runner profiles

SpecFlow+ Runner profiles (.srprofile 扩展名文件) 是 XML 结构文件,用来决定 SpecFlow+ Runner 如何运行测试用例,例如:失败的测试是可以再执行 1 次或多次, 定义测试环境的不同 target(定义不同的浏览器或 x64/x86),启动多线程,配置文件及文件夹路径,应用 transformations 规则为不同的 target 环境来改变配置变量等。

默认,SpecFlow+ Runner 会有一个名为 Default.srprofile 的文件。注意:对于 SpecFlow 2,当添加 SpecFlow.SpecRun NuGet 包时,这个 Default.srprofile 文件会自动加到项目中。对于 SpecFlow 3,需要后动添加这个文件,但是我试过,SpecFlow 3 没法手动添加这个文件。 出于这个考虑,所以本文将采用 SpecFlow 2.

实现

被测 Web Application

我们的实践测试项目是一个 Web 版的简单的计算器实现,Web Calculator, 实现了两个数相加的功能。

在这里插入图片描述

创建一个 Class Libary 项目

在这里插入图片描述

添加 NuGet Packages

SpecFlow & SpecRun 包

需要注意匹配的版本
SpecFlow 2.4
SpecRun.SpecFlow 1.8.5

安装成功后,项目中会自动添加这些文件,重点关注 Default.srprofile 文件
在这里插入图片描述

添加 Selenium包

下面这些 Selenium 包只需最新版本就可以了

Selenium.Support
Selenium.Firefox.WebDriver
Selenium.WebDriver.ChromeDriver
Selenium.WebDriver.IEDriver
Selenium.WebDriver.MSEdgeDriver

添加成功后,编译一下整个 Solution,Bin 目录下会下载各个浏览器的 Web Dirver,用来启动浏览器,并进行元素定位操作。
在这里插入图片描述

其它包

可根据代码需要添加一些依赖包,例如 FluentAssertions 包用来断言的。

创建 Feature 文件

在这里插入图片描述

Feature: CalculatorFeatureIn order to avoid silly mistakes As a math idiotI want to be told the sum of two numbers@Browser_Chrome
@Browser_IE
@Browser_Firefox
@Browser_Edge
Scenario: Add two numbersGiven the first number is 50And the second number is 70When the two numbers are addedThen the result should be 120@Browser_IE 
@Browser_Chrome
@Browser_Firefox
@Browser_Edge
Scenario Outline: Add two numbers permutationsGiven the first number is And the second number is When the two numbers are addedThen the result should be Examples:| First number | Second number | Expected result || 0            | 0             | 0               || -1           | 10            | 9               || 6            | 9             | 15              |

配置 Default.srprofile

Default.srprofile 添加 Targets

定义各种浏览器 Target,用 Scenarios 的 tag 做为 filter。
例如:打上 @Browser_IE tag 的 Scenario target 为 IE 浏览器。

Browser_IEBrowser_ChromeBrowser_FirefoxBrowser_Edge

但是官网例子中下面这种方式,EnvironmentVariable tag 识别不了,很是奇怪。

    Browser_IE

添加 DeploymentTransformation

用来解析 target 中的值,转换 app.config 文件,将 browser key 的值 设置成 target 的 name,用 {Target} 作为占位符。在转换过程中用当前 target 的 name 来替换这个占位符。

 ]]>

在这里插入图片描述

配置 App.config

添加下面这个配置,用来配置被测 APP 的 URL,以及 browser 变量,这个变量的值不用赋值,通过 Default.srprofile 中添加的 DeploymentTransformation 来动态转换成对应的 target。

  

在这里插入图片描述

设计 Driver 层

整个设计都设计到 Context Injection,具体细节可以参考 《 BDD - SpecFlow Context Injection 上下文依赖注入 》

ConfigurationDriver.cs

用来读取 App.config 中的配置的被测 APP URL 及 browser 信息。

为了读取 App.config 中的配置,需要添加 Add Reference “System.Configuration

在这里插入图片描述

using System;
using System.Configuration;namespace SpecflowSpecRunMutiBrowser.Drivers
{public class ConfigurationDriver{private const string SeleniumBaseUrlConfigFieldName = "seleniumBaseUrl";private const string BrowserName = "browser";public ConfigurationDriver(){Console.WriteLine("ConfigurationDriver construct begin");Console.WriteLine("ConfigurationDriver construct end");}public string SeleniumBaseUrl = ConfigurationManager.AppSettings[SeleniumBaseUrlConfigFieldName];public string Browser => ConfigurationManager.AppSettings[BrowserName];}
}

在这里插入图片描述

BrowserSeleniumDriverFactory.cs

基于 App.config 配置的 browser 来创建对应的 WebDriver

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Edge;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.IE;
using System;
using TechTalk.SpecRun;namespace SpecflowSpecRunMutiBrowser.Drivers
{public class BrowserSeleniumDriverFactory{private readonly ConfigurationDriver _configurationDriver;private readonly TestRunContext _testRunContext;public BrowserSeleniumDriverFactory(ConfigurationDriver configurationDriver, TestRunContext testRunContext){Console.WriteLine("BrowserSeleniumDriverFactory construct begin");_configurationDriver = configurationDriver;_testRunContext = testRunContext;Console.WriteLine("BrowserSeleniumDriverFactory construct End");}public IWebDriver GetForBrowser(){string lowerBrowserId = _configurationDriver.Browser.ToUpper();Console.WriteLine($"browser is {lowerBrowserId}");switch (lowerBrowserId){case "IE": return GetInternetExplorerDriver();case "CHROME": return GetChromeDriver();case "FIREFOX": return GetFirefoxDriver();case "EDGE": return GetEdgeDriver();case string browser: throw new NotSupportedException($"{browser} is not a supported browser");default: throw new NotSupportedException("not supported browser: ");}}private IWebDriver GetFirefoxDriver(){return new FirefoxDriver(FirefoxDriverService.CreateDefaultService(_testRunContext.TestDirectory)){Url = _configurationDriver.SeleniumBaseUrl,};}private IWebDriver GetChromeDriver(){return new ChromeDriver(ChromeDriverService.CreateDefaultService(_testRunContext.TestDirectory)){Url = _configurationDriver.SeleniumBaseUrl};}private IWebDriver GetEdgeDriver(){return new EdgeDriver(EdgeDriverService.CreateDefaultService(_testRunContext.TestDirectory));//{//    Url = _configurationDriver.SeleniumBaseUrl//};}private IWebDriver GetInternetExplorerDriver(){var internetExplorerOptions = new InternetExplorerOptions{InitialBrowserUrl = null,IntroduceInstabilityByIgnoringProtectedModeSettings = true,IgnoreZoomLevel = true,EnableNativeEvents = true,RequireWindowFocus = true,EnablePersistentHover = true};return new InternetExplorerDriver(InternetExplorerDriverService.CreateDefaultService(_testRunContext.TestDirectory), internetExplorerOptions){Url = _configurationDriver.SeleniumBaseUrl,};}}
}

在这里插入图片描述

WebDriver.cs

用来得到当前 WebDriver 实例

using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;namespace SpecflowSpecRunMutiBrowser.Drivers
{public class WebDriver : IDisposable{private readonly BrowserSeleniumDriverFactory _browserSeleniumDriverFactory;private readonly Lazy _currentWebDriverLazy;private readonly Lazy _waitLazy;private readonly TimeSpan _waitDuration = TimeSpan.FromSeconds(10);private bool _isDisposed;public WebDriver(BrowserSeleniumDriverFactory browserSeleniumDriverFactory){Console.WriteLine("WebDriver construct begin");_browserSeleniumDriverFactory = browserSeleniumDriverFactory;_currentWebDriverLazy = new Lazy(GetWebDriver);_waitLazy = new Lazy(GetWebDriverWait);Console.WriteLine("WebDriver construct end");}public IWebDriver Current => _currentWebDriverLazy.Value;public WebDriverWait Wait => _waitLazy.Value;private WebDriverWait GetWebDriverWait(){return new WebDriverWait(Current, _waitDuration);}private IWebDriver GetWebDriver(){return _browserSeleniumDriverFactory.GetForBrowser();}public void Dispose(){if (_isDisposed){return;}if (_currentWebDriverLazy.IsValueCreated){Current.Quit();}_isDisposed = true;}}
}

在这里插入图片描述

CalculatorPageDriver.cs

用来封装被测 APP 的页面元素和行为

using OpenQA.Selenium;
using System;namespace SpecflowSpecRunMutiBrowser.Drivers
{public class CalculatorPageDriver{private readonly WebDriver _webDriver;private readonly ConfigurationDriver _configurationDriver;public CalculatorPageDriver(WebDriver webDriver, ConfigurationDriver configurationDriver){Console.WriteLine("CalculatorPageDriver construct begin");_webDriver = webDriver;_configurationDriver = configurationDriver;Console.WriteLine("CalculatorPageDriver construct end");}public void GoToCalculatorPage(){string baseUrl = _configurationDriver.SeleniumBaseUrl;_webDriver.Current.Manage().Window.Maximize();_webDriver.Current.Navigate().GoToUrl($"{baseUrl}");}//Finding elements by IDprivate IWebElement FirstNumberElement => _webDriver.Current.FindElement(By.Id("first-number"));private IWebElement SecondNumberElement => _webDriver.Current.FindElement(By.Id("second-number"));private IWebElement AddButtonElement => _webDriver.Current.FindElement(By.Id("add-button"));private IWebElement ResultElement => _webDriver.Current.FindElement(By.Id("result"));private IWebElement ResetButtonElement => _webDriver.Current.FindElement(By.Id("reset-button"));public void EnterFirstNumber(string number){//Clear text boxFirstNumberElement.Clear();//Enter textFirstNumberElement.SendKeys(number);}public void EnterSecondNumber(string number){//Clear text boxSecondNumberElement.Clear();//Enter textSecondNumberElement.SendKeys(number);}public void ClickAdd(){//Click the add buttonAddButtonElement.Click();}public string WaitForNonEmptyResult(){//Wait for the result to be not emptyreturn WaitUntil(() => ResultElement.GetAttribute("value"),result => !string.IsNullOrEmpty(result));}/// /// Helper method to wait until the expected result is available on the UI/// /// The type of result to retrieve/// The function to poll the result from the UI/// The function to decide if the polled result is accepted/// An accepted result returned from the UI. If the UI does not return an accepted result within the timeout an exception is thrown.private T WaitUntil(Func getResult, Func isResultAccepted) where T : class{return _webDriver.Wait.Until(driver =>{var result = getResult();if (!isResultAccepted(result))return default;return result;});}}
}

在这里插入图片描述

实现 Step Definition

CalculatorFeatureSteps.cs 通过调用 CalculatorPageDriver 封装的 UI 元素和方法来实现 Feature 文件中的 Steps。

using SpecflowSpecRunMutiBrowser.Drivers;
using TechTalk.SpecFlow;
using FluentAssertions;
using System;namespace SpecflowSpecRunMutiBrowser.Steps
{[Binding]public class CalculatorFeatureSteps{private readonly CalculatorPageDriver _calculatorPageDriver;public CalculatorFeatureSteps(CalculatorPageDriver calculatorPageDriver){Console.WriteLine("CalculatorFeatureSteps construct end");_calculatorPageDriver = calculatorPageDriver;Console.WriteLine("CalculatorFeatureSteps construct end");}[Given("the first number is (.*)")]public void GivenTheFirstNumberIs(int number){//delegate to Page Object_calculatorPageDriver.EnterFirstNumber(number.ToString());}[Given("the second number is (.*)")]public void GivenTheSecondNumberIs(int number){//delegate to Page Object_calculatorPageDriver.EnterSecondNumber(number.ToString());}[When("the two numbers are added")]public void WhenTheTwoNumbersAreAdded(){//delegate to Page Object_calculatorPageDriver.ClickAdd();}[Then("the result should be (.*)")]public void ThenTheResultShouldBe(int expectedResult){//delegate to Page Objectvar actualResult = _calculatorPageDriver.WaitForNonEmptyResult();actualResult.Should().Be(expectedResult.ToString());}}
}

在这里插入图片描述

添加 Hooks

Hooks.cs 用于添加 BeforeScenario Hook,Scenario 执行前先导航到被测 APP 的 Home page。当然如果不添加 Hook,就得在每个 Scenario 中添加一个前置 Step 也是可以的,例如:Given I navigated to the Calculator page

using SpecflowSpecRunMutiBrowser.Drivers;
using System;
using TechTalk.SpecFlow;namespace SpecflowSpecRunMutiBrowser.Hooks
{[Binding]public class Hooks{//////  Reset the calculator before each scenario tagged with "Calculator"/// [BeforeScenario()]public static void BeforeScenario(WebDriver webDriver, ConfigurationDriver configurationDriver){Console.WriteLine("BeforeScenario begin");var pageDriver = new CalculatorPageDriver(webDriver, configurationDriver);pageDriver.GoToCalculatorPage();Console.WriteLine("BeforeScenario end");}}
}

在这里插入图片描述

执行测试

Build 整个 Solution,打开 Test Explore,会发现每个 Scenario 都会自动生成了 4 个 测试用例,因为分别打上了 4 个浏览器标签,就会 target 到对应的浏览器上运行。
在这里插入图片描述

为了更好的理解 Context Injection,在各个 Class 的 Public 的构造函数中都加了一些输出日志。

我们来运行一个 target chrome 的测试用例,会启动 Chrome 浏览器来执行

在这里插入图片描述

日志细节:

 Add two numbers in CalculatorFeature (target: Chrome)Source: CalculatorFeature.feature line 10Duration: 10.2 secStandard Output: 
SpecRun Evaluation Mode: Please purchase at http://www.specflow.org/plus to remove test execution delay.-> -> Using app.config-> ConfigurationDriver construct begin
-> ConfigurationDriver construct end
-> BrowserSeleniumDriverFactory construct begin
-> BrowserSeleniumDriverFactory construct End
-> WebDriver construct begin
-> WebDriver construct end
-> BeforeScenario begin
-> CalculatorPageDriver construct begin
-> CalculatorPageDriver construct end
-> browser is CHROME
-> BeforeScenario endGiven the first number is 50
-> CalculatorPageDriver construct begin
-> CalculatorPageDriver construct end
-> CalculatorFeatureSteps construct end
-> CalculatorFeatureSteps construct end
-> done: CalculatorFeatureSteps.GivenTheFirstNumberIs(50) (0.1s)And the second number is 70
-> done: CalculatorFeatureSteps.GivenTheSecondNumberIs(70) (0.1s)When the two numbers are added
-> done: CalculatorFeatureSteps.WhenTheTwoNumbersAreAdded() (0.1s)Then the result should be 120
-> done: CalculatorFeatureSteps.ThenTheResultShouldBe(120) (0.7s)

再运行一个 target Edge 的用例,会启动 Edge 浏览器运行
在这里插入图片描述
对于IE, FireFox 就不一一运行了,提醒一下 FireFox 有必要需要设置环境变量,将 geckodriver.exe 所在路径添加到 Path 中,例如:

在这里插入图片描述

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...