最常见的测试自动化挑战之一是我们如何修改Selenium WebDriver
中的请求标头。作为一名自动化测试人员,你会遇到任何编程语言(包括 Java)的这一挑战。在提出解决方案之前,我们需要更好地理解问题陈述,并在使用 Selenium WebDriver
的同时,在 Java 中修改头部请求的不同可能性。在接下来的文章里,我们将学习如何使用 Selenium WebDriver 和不同的可用选项在 Java 中修改 HTTP 请求标头。
什么是 HTTP 标头
HTTP 标头是 HTTP 协议的重要组成部分。它们定义了 HTTP 消息(请求或响应)并允许客户端和服务器与消息交换可选的元数据。它们由不区分大小写的头字段名称后跟一个冒号,然后是头字段值组成。标题字段可以扩展到多行,方法是在每一额外行前至少有一个空格或水平制表符。
标题可以根据其上下文进行分组:
- 请求标头:HTTP 请求标头用于提供有关正在获取的资源和发出请求的客户端的附加信息。
- 响应头:HTTP 响应头提供有关响应的信息。Location 标头指定资源的位置,服务器标头提供有关提供资源的服务器的信息。
- 表示头:HTTP 表示头是任何 HTTP 响应的重要组成部分。它们提供有关协议元素的信息,如 MIME 类型、字符编码等。这使它们成为通过 Internet 处理资源的重要组成部分。
- 有效载荷标头:HTTP 有效载荷标头包含有关 HTTP 消息有效载荷的数据(例如其长度和编码),但与表示无关。
深入研究 HTTP 请求标头
HTTP 请求标头是一种通信机制,使浏览器或客户端能够从(Web)服务器请求特定网页或数据。在 Web 通信或 Internet 浏览中使用时,HTTP 请求标头使浏览器和客户端能够通过发送请求与适当的 Web 服务器进行通信。
HTTP 请求标头描述了 Web 浏览器发送的加载页面的请求。它也被称为客户端到服务器协议。标头包括客户端请求的详细信息,例如用户使用的浏览器类型和操作系统以及在屏幕上正确显示请求内容所需的其他参数。
以下是 HTTP 请求标头中包含的主要信息:
- IP 地址(来源)和端口号。
- 请求的网页的 URL。
- Web 服务器或目标网站(主机)。
- 浏览器将接受的数据类型(文本、html、xml 等)。
- 发送兼容数据的浏览器类型(Mozilla、Chrome、IE)。
作为响应,包含请求数据的 HTTP 响应标头由 发回。
需要更改 HTTP 请求标头
你能猜到为什么我们甚至需要在已经设置到脚本中后更改请求头吗?
以下是你可能需要更改 HTTP 请求标头的一些场景:
- 通过建立适当的 HTTP 标头来测试控制和/或测试不同的变体。
- 需要对 Web 应用程序的不同方面甚至服务器逻辑进行彻底测试的情况。
- 由于 HTTP 请求标头用于启用 Web 应用程序逻辑的某些特定部分,通常在正常模式下会禁用这些部分,因此根据测试场景,可能需要不时修改 HTTP 请求标头。
在被测 Web 应用程序上测试访客模式是你可能需要修改 HTTP 请求标头的理想情况。
但是Selenium RC
曾经支持的修改HTTP请求头的功能,现在Selenium Webdriver
不处理了。
这就是为什么在使用Selenium
框架和 Java
编写测试自动化项目时,我们如何更改 header
请求的问题。
如何在 Selenium Java 项目中修改头请求
在 Selenium Java 教程的这一部分中,我们将了解在 Java 中修改标头请求的多种方法。大体上,有几种可能,接下来可以修改 Java-Selenium
项目中的头请求。
- 使用像
REST Assured
这样的驱动程序/库而不是 Selenium
。 - 使用反向代理,例如浏览器
mob-proxy
或其他一些代理机制。 - 使用
Firefox
浏览器扩展,这将有助于修改请求的标头。
让我们一一探讨每一种可能性:
使用 REST Assured 库修改 HTTP 请求标头
与 Selenium 一起,我们可以使用 REST Assured,它是一种以简单方式使用 REST 服务的绝佳工具。
在任何 IDE(例如 Eclipse)中为你的项目配置 REST Assured 的先决条件非常简单。设置 Java
、Eclipse
和TestNG
后,你需要下载所需的REST Assured jar 文件。
jar 文件下载后,你必须在 Eclipse 中创建一个项目,并将下载的 jar 文件作为外部 jar 添加到 Properties
部分。这再次类似于我们将 Selenium jar
文件添加到项目的方式。使用 REST Assured 库成功设置 Java 项目后,就可以开始了。
我们打算创建一种机制,以便可以自定义请求标头。为了以上述可能性实现这一点,我们首先需要了解创建请求标头的常规方法。
让我们考虑以下场景:
- 我们有一个名为
RequestHeaderChangeDemo
的 Java 类,我们在其中维护基本配置 - 我们有一个名为
TestSteps
的测试步骤文件,我们将在其中调用 RequestHeaderChangeDemo
Java 类中的方法,通过这些方法我们将执行我们的测试。
观察下面名为 RequestHeaderChangeDemo
的 Java 类。
BASE_URL 是应用了以下四种方法的亚马逊网站:
- 认证用户
- 获取产品
- 添加产品
- 移除产品
public class RequestHeaderChangeDemo
{
private static final String BASE_URL = "https://amazon.com";
public static IRestResponse<Token> authenticateUser(AuthorizationRequest authRequest) {
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
Response response = request.body(authRequest).post(Route.generateToken());
return new RestResponse(Token.class, response);
}
public static IRestResponse<Products> getProducts()
{
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
Response response = request.get(Route.products());
return new RestResponse(Products.class, response);
}
public static IRestResponse<UserAccount> addProduct(AddProductsRequest addProductsRequest, String token)
{
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
Response response = request.body(addProductsRequest).post(Route.products());
return new RestResponse(UserAccount.class, response);
}
public static Response removeProduct(RemoveProductRequest removeProductRequest, String token)
{
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json");
return request.body(removeProductRequest).delete(Route.product());,
}
}
在上面的Java类文件中,我们在每个连续的方法中重复发送了BASE_URL
和headers
。示例如下所示:
RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given();
request.header("Content-Type", "application/json");
Response response = request.body(authRequest).post(Route.generateToken());
request.header
方法请求 JSON 格式的标头。有大量的代码重复,这降低了代码的可维护性。
如果我们在构造函数中初始化 RequestSpecification
对象并使这些方法非静态(即创建实例方法),则可以避免这种情况。
由于 Java 中的实例方法属于类的 Object 而不是类本身,因此即使在创建类的 Object 之后也可以调用该方法。与此同时,我们还将覆盖实例方法。
将方法转换为实例方法有以下优点:
- 身份验证仅在一个
RequestSpecification
对象中进行一次。不再需要为其他请求创建相同的请求。 - 灵活修改项目中的请求头。
因此,让我们看看当我们使用实例方法时 Java 类 RequestHeaderChangeDemo
和测试步骤文件 TestSteps
的外观。
带有实例方法的 RequestHeaderChangeDemo
类的 Java 类:
public class RequestHeaderChangeDemo
{
private final RequestSpecification request;
public RequestHeaderChangeDemo(String baseUrl)
{
RestAssured.baseURI = baseUrl;
request = RestAssured.given();
request.header("Content-Type", "application/json");
}
public void authenticateUser(AuthorizationRequest authRequest)
{
Response response = request.body(authRequest).post(Route.generateToken());
if (response.statusCode() != HttpStatus.SC_OK)
throw new RuntimeException("Authentication Failed. Content of failed Response: " + response.toString() + " , Status Code : " + response.statusCode());
Token tokenResponse = response.body().jsonPath().getObject("$", Token.class);
request.header("Authorization", "Bearer " + tokenResponse.token);
}
public IRestResponse<Products> getProducts()
{
Response response = request.get(Route.products());
return new RestResponse(Products.class, response);
}
public IRestResponse<UserAccount> addProduct(AddProductsRequest addProductsRequest)
{
Response response = request.body(addProductsRequest).post(Route.products());
return new RestResponse(UserAccount.class, response);
}
public Response removeProducts(RemoveProductRequest removeProductRequest)
{
return request.body(removeProductRequest).delete(Route.product());
}
}
我们创建了一个构造函数来初始化包含 BaseURL
和请求标头的 RequestSpecification
对象。代码演练
- 早些时候,我们必须在每个请求标头中传递令牌。现在,一旦我们在方法
authenticateUser()
中收到令牌响应,我们就将它放入请求的同一个实例中。这使测试步骤的执行能够向前推进,而无需像之前那样为每个请求添加令牌。这使得标头可用于对服务器的后续调用。 - 现在将在
TestSteps
文件中初始化这个 RequestHeaderChangeDemo
Java 类。
我们根据 RequestHeaderChangeDemo
Java 类中的更改而更改 TestSteps
文件。
public class TestSteps
{
private final String USER_ID = " (Enter the user id from your test case )";
private Response response;
private IRestResponse<UserAccount> userAccountResponse;
private Product product;
private final String BaseUrl = "https://amazon.com";
private RequestHeaderChangeDemo endPoints;
@Given("^User is authorized$")
public void authorizedUser()
{
endPoints = new RequestHeaderChangeDemo (BaseUrl);
AuthorizationRequest authRequest = new AuthorizationRequest("(Username)", "(Password)");
endPoints.authenticateUser(authRequest);
}
@Given("^Available Product List$")
public void availableProductLists()
{
IRestResponse<Products> productsResponse = endPoints.getProducts();
Product = productsResponse.getBody().products.get(0);
}
@When("^Adding the Product in Wishlist$")
public void addProductInWishList()
{
ADDPROD code = new ADDPROD(product.code);
AddProductsRequest addProductsRequest = new AddProductsRequest(USER_ID, code);
userAccountResponse = endPoints.addProduct(addProductsRequest);
}
@Then("^The productis added$")
public void productIsAdded()
{
Assert.assertTrue(userAccountResponse.isSuccessful());
Assert.assertEquals(201, userAccountResponse.getStatusCode());
Assert.assertEquals(USER_ID, userAccountResponse.getBody().userID);
Asert.assertEquals(product.code, userAccountResponse.getBody().products.get(0).code);
}
@When("^Product to be removed from the list$")
public void removeProductFromList()
{
RemoveProductRequest removeProductRequest = new RemoveProductRequest(USER_ID, product.code);
response = endPoints.removeProduct(removeProductRequest);
}
@Then("^Product is removed$")
public void productIsRemoved()
{
Assert.assertEquals(204, response.getStatusCode());
userAccountResponse = endPoints.getUserAccount(USER_ID);
Assert.assertEquals(200, userAccountResponse.getStatusCode());
Assert.assertEquals(0, userAccountResponse.getBody().products.size());
}
}
这是我们在修改后的实现中所做的:代码演练
- 初始化
RequestHeaderChangeDemo
类对象作为端点。 -
BaseURL
是在第一个方法(即authorizedUser
)中传递的。 - 在方法
authorizedUser
中,我们调用了RequestHeaderChangeDemo
类的构造函数authenticateUser
。 - 因此,后续步骤定义使用相同的端点对象。
使用浏览器 Mob-Proxy 等反向代理修改 HTTP 请求标头
顾名思义,在 Java-Selenium 自动化测试套件中处理请求标头更改时,我们可以选择使用代理。由于 Selenium 禁止在浏览器和服务器中注入信息,因此可以使用代理进行救援。
如果测试是在公司防火墙后面执行的,则这种方法不是首选。
作为 Web 基础架构组件,代理通过将自身定位在客户端和服务器之间来使 Web 流量通过它。在企业界,代理的工作方式类似,使流量通过它,允许安全的流量通过并阻止潜在威胁。代理具有部分或完全修改请求和响应的能力。
核心思想是发送授权标头,绕过包含凭证对话的阶段,也称为基本认证对话。然而,结果证明这是一个累人的过程,尤其是在测试用例需要频繁重新配置的情况下。
这就是浏览器 mob-proxy 库的用武之地。当你将代理配置作为Selenium 自动化测试套件的一部分时,代理配置将在你每次执行测试套件时有效。
让我们看看如何将浏览器 mob-proxy 与使用基本身份验证保护的示例网站一起使用。为了解决这个问题,我们可能会缩小两种可能的方法:
- 向所有请求添加授权标头,没有条件或例外。
- 仅向满足特定条件的请求添加标头。
尽管我们不会解决标头管理问题,但我们仍将演示如何在浏览器 mob-proxy 授权工具集的帮助下解决授权问题。
在 Selenium Java 教程的这一部分中,我们将只关注第一种方法(即向所有请求添加授权标头)。
首先我们在pom.xml
中添加browsermob-proxy
的依赖
如果要将此方法传递给所有标头请求,即特定代理,在这种情况下,应调用 forAllProxy
方法,如下所示:
public void forAllProxy()
{
proxy = new BrowserMobProxyServer();
try {
String authHeader = "Basic " + Base64.getEncoder().encodeToString("webelement:click".getBytes("utf-8"));
proxy.addHeader("checkauth", authfirstHeader);
}
catch (UnsupportedEncodingException e)
{
System.err.println("the Authorization can not be passed");
e.printStackTrace();
}
proxy.start(0);
}
public class caseFirstTest
{
WebDriver driver;
BrowserMobProxy proxy;
@BeforeAll
public static void globalSetup()
{
System.setProperty("webdriver.gecko.driver", "(path of the driver)");
}
@BeforeEach
public void setUp()
{
setUpProxy();
FirefoxOptions Options = new FirefoxOptions();
Options.setProxy(ClientUtil.createSeleniumProxy(proxy));
driver = new FirefoxDriver(Options);
}
@Test
public void testBasicAuth()
{
driver.get("https://webelement.click/stand/basic?lang=en");
Wait<webdriver> waiter = new FluentWait(driver).withTimeout(Duration.ofSeconds(50)).ignoring(NoSuchElementException.class);
String greetings = waiter.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("(Mention the xpath)"))).getText();
Assertions.assertEquals("(message");
}
@AfterEach
public void tearDown()
{
if(driver != null)
{
driver.quit();
}
if(proxy != null)
{
proxy.stop();
}
}
private void setUpProxy(
{
}
}
</webdriver>
在上面的代码中,以 String authHeader
开头的行表示我们正在创建标头,这将被添加到请求中。之后,这些请求会通过我们在 proxy.addHeader(“checkauth”, authfirstHeader)
中创建的代理传递。
try {
String authHeader = "Basic " + Base64.getEncoder().encodeToString("webelement:click".getBytes("utf-8"));
proxy.addHeader("checkauth", authfirstHeader);
}
catch (UnsupportedEncodingException e)
{
………………………………………………………………………………
………………………………………………………………………………
……………………………………………………………………………...
}
proxy.start(0);
}
最后,我们启动代理设置0
来标记start
参数,代理在端口上启动。
使用 Firefox 扩展修改 HTTP 请求头
在 Selenium Java 教程的这一部分中,我们将了解如何使用适当的 Firefox 浏览器扩展来修改标头请求。此选项的主要缺点是它仅适用于 Firefox(而不适用于 Chrome、Edge 等其他浏览器)。
执行以下步骤以使用 Firefox 扩展修改 HTTP 请求标头:
- 下载 Firefox 浏览器扩展
- 加载扩展。
- 设置扩展首选项。
- 设置所需的功能。
- 准备测试自动化脚本。
让我们一步一步来:
1. 下载火狐浏览器扩展
用 .*xpi
搜索 firefox 扩展名并在项目中设置
2.加载火狐扩展
参考以下代码添加 Firefox 配置文件:
FirefoxProfile profile = new FirefoxProfile();
File modifyHeaders = new File(System.getProperty("user.dir") + "/resources/modify_headers.xpi");
profile.setEnableNativeEvents(false);
try {
profile.addExtension(modifyHeaders);
}
catch (IOException e)
{
e.printStackTrace();
}
一旦我们将 Firefox 扩展加载到项目中,我们设置首选项(即在触发扩展之前需要设置的各种输入)。这是使用 profile.setPreference
方法完成的。
3.设置扩展首选项
此方法通过键集参数机制设置任何给定配置文件的首选项。这里的第一个参数是设置值的键,第二个参数设置相应的整数值。
这是参考实现:
profile.setPreference("modifyheaders.headers.count", 1);
profile.setPreference("modifyheaders.headers.action0", "Add");
profile.setPreference("modifyheaders.headers.name0", "Value");
profile.setPreference("modifyheaders.headers.value0", "numeric value");
profile.setPreference("modifyheaders.headers.enabled0", true);
profile.setPreference("modifyheaders.config.active", true);
profile.setPreference("modifyheaders.config.alwaysOn", true);
在上面的代码中,我们列出了我们想要设置 header
实例的次数。
profile.setPreference("modifyheaders.headers.count", 1)
接下来,我们指定操作,标头名称和标头值包含从 API
调用动态接收的值。
profile.setPreference("modifyheaders.headers.action0", "Add");
对于 .setPreference
实现的其余部分,我们启用 all
以便它允许在 WebDriver
实例化 Firefox 浏览器时加载扩展,并使用 HTTP 标头将扩展设置为活动模式。
4. 设置所需的功能
Selenium 中的 Desired Capabilities
用于设置需要执行自动化测试的浏览器、浏览器版本和平台类型。
在这里,我们如何设置所需的功能:
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setBrowserName("firefox");
capabilities.setPlatform(org.openqa.selenium.Platform.ANY);
capabilities.setCapability(FirefoxDriver.PROFILE, profile);
WebDriver driver = new FirefoxDriver(capabilities);
driver.get("url");
如果你想使用未安装在本地(或测试)计算机上的 Firefox 版本修改 HTTP 请求标头,该怎么办。这就是 LambdaTest
,最大的基于云的自动化测试平台,提供更快的跨浏览器测试基础设施来拯救。
使用 LambdaTest
,你可以灵活地修改不同浏览器和平台组合的 HTTP 请求标头。如果你愿意使用 Firefox 扩展修改 HTTP 请求标头, 你可以使用 LambdaTest
在不同版本的 Firefox 浏览器上实现相同的功能。
5. 起草整个测试自动化脚本
完成上述所有步骤后,我们将继续设计整个测试自动化脚本:
public void startwebsite()
{
FirefoxProfile profile = new FirefoxProfile();
File modifyHeaders = new File(System.getProperty("user.dir") + "/resources/modify_headers.xpi");
profile.setEnableNativeEvents(false);
try
{
profile.addExtension(modifyHeaders);
}
catch (IOException e)
{
e.printStackTrace();
}
profile.setPreference("modifyheaders.headers.count", 1);
profile.setPreference("modifyheaders.headers.action0", "Add");
profile.setPreference("modifyheaders.headers.name0", "Value");
profile.setPreference("modifyheaders.headers.value0", "Numeric Value");
profile.setPreference("modifyheaders.headers.enabled0", true);
profile.setPreference("modifyheaders.config.active", true);
profile.setPreference("modifyheaders.config.alwaysOn", true);
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setBrowserName("firefox");
capabilities.setPlatform(org.openqa.selenium.Platform.ANY);
capabilities.setCapability(FirefoxDriver.PROFILE, profile);
WebDriver driver = new FirefoxDriver(capabilities);
driver.get("url");
}
结论
在这个 Selenium Java 教程中,我们探索了三种不同的方法来处理对 HTTP 请求标头的修改。Selenium 本身就是一个很棒的工具,并且在 Web 自动化测试中一直运行良好。