1. 原理解析
要验证 Kafka
推送的消息,Playwright
本身并没有直接的支持,但是你可以结合Playwright
与Kafka
客户端(比如kafkajs
)来实现。具体思路是,使用Playwright
来模拟用户的操作并触发后台Kafka
消息推送,然后使用Kafka
客户端(比如kafkajs
)来验证消息是否正确推送到了消息队列中。 以下是一个示例,展示了如何在Playwright
测试中集成Kafka
消息验证。如果问题是想知道怎么在Playwright
中处理消息队列的推送消息,也可以参考本文内容。
2. 实战解析
2.1 安装必要的依赖
首先,你需要安装 Playwright
和 Kafka
客户端库 kafkajs
。
npm install playwright kafkajs
2.2 配置 Kafka
客户端
在你的测试中,使用 Kafka
客户端来连接 Kafka
消息队列并消费消息。
import { Kafka } from 'kafkajs';
const kafka = new Kafka({
clientId: 'playwright-test',
brokers: ['localhost:9092'], // Kafka 服务器地址
});
const producer = kafka.producer();
const consumer = kafka.consumer({ groupId: 'playwright-test-group' });
async function produceMessage(topic: string, message: string) {
await producer.send({
topic,
messages: [
{ value: message },
],
});
}
async function consumeMessage(topic: string) {
await consumer.connect();
await consumer.subscribe({ topic, fromBeginning: true });
// 消费消息并返回
return new Promise<string>((resolve, reject) => {
consumer.run({
eachMessage: async ({ message }) => {
resolve(message.value.toString());
},
});
});
}
export { produceMessage, consumeMessage };
2.3 使用 Playwright
触发事件并验证消息
现在,你可以在 Playwright
测试中使用 produceMessage
来触发 Kafka
推送的消息,然后使用 consumeMessage
来验证消息是否被成功推送。
import { test, expect } from '@playwright/test';
import { produceMessage, consumeMessage } from './kafkaClient'; // 引入 Kafka 客户端
test('验证 Kafka 消息推送', async () => {
const topic = 'test-topic'; // Kafka 主题
const message = 'Hello from Playwright!';
// 先启动消费者
const consumePromise = consumeMessage(topic);
// 使用 Playwright 执行用户操作,触发 Kafka 消息推送
await page.goto('https://your-app-url.com'); // 访问应用页面
await page.click('button#triggerKafkaEvent'); // 触发某个事件,后台推送 Kafka 消息
// 推送 Kafka 消息
await produceMessage(topic, message);
// 验证消息是否被正确推送
const receivedMessage = await consumePromise;
expect(receivedMessage).toBe(message); // 验证收到的消息与期望的消息相同
});
2.4 测试步骤解释
1.
Kafka
消费者:consumeMessage
会连接到Kafka
,并订阅特定的主题。 在消费者运行时,它会监听消息队列,并在接收到新消息时返回。2.
Playwright
操作: 使用Playwright
执行浏览器操作,模拟用户点击或其他动作,从而触发后台推送Kafka
消息的操作。 例如,在上面的例子中,点击一个按钮会触发一个事件,该事件会将消息发送到Kafka
消息队列中。3.
Kafka
生产者: 使用produceMessage
将消息推送到Kafka
队列。4. 验证消息:
consumeMessage
会等待Kafka
队列中推送的消息,并与预期的消息进行比较,验证消息是否正确推送。
2.5 注意事项
Kafka
服务器配置:确保你的 Kafka 服务器正确配置并可用。你可以在本地运行 Kafka
,也可以使用云服务提供商的 Kafka
服务(例如 Confluent Cloud
)。 异步验证:由于 Kafka
的消息消费是异步的,测试中的验证需要确保消息消费完成后再进行断言。通过 Promise
来确保消息消费完成后进行验证。
小结
通过结合 Playwright
和 Kafka
客户端(如 kafkajs
),你可以在 Playwright
测试中验证 Kafka
推送的消息。这个方法适用于需要验证后台服务推送消息的场景,尤其是在消息驱动的应用程序中。
利用 Playwright 构建一个简洁、高效且强大的移动自动化测试框架。
3. 真机与模拟器测试
使用模拟器是应用开发过程中的一个重要环节,它能够让 QA
工程师在类似用户体验的模拟环境中测试应用,提供比仅在桌面浏览器上测试更准确的结果。虽然在桌面浏览器上通过不同的视口可以大致了解屏幕响应情况,但它无法完全模拟真实设备或模拟器的体验。模拟器能够模仿设备的硬件和移动架构,从而影响应用的渲染和显示效果。
4. Playwright
的独特优势
Playwright
是一个开源自动化测试框架,独立于 Selenium
运行。它采用了独特的架构,基于客户端-服务器模型,客户端库与本地运行的浏览器进程进行通信。通过 DevTools
协议与浏览器进行直接交互,从而加快执行速度,并支持多个浏览器的执行。这个架构使得 Playwright
与其他测试框架有所区别,并支持一些高级功能。
4.1 为什么选择 Playwright
?
Playwright
在移动自动化测试中具有独特的优势,它无需额外的驱动程序或代理来与真实设备或模拟器进行通信。这意味着测试可以直接在目标设备上运行,无需任何中介软件。这样可以大大提高测试的速度和可靠性,因为没有多余的延迟或测试工具与设备之间的通信问题。
此外,Playwright
的这一特性使得测试过程更加简化,节省了时间和资源。因此,Playwright
成为移动自动化测试的一个宝贵工具,能够提供高效、准确的测试结果。
4.2 Playwright
与 Selenium
架构对比
5. Playwright
移动端实战
5.1 环境要求
• 已安装
Android SDK
并配置好路径变量。• 已设置好
Android
模拟器或真实设备。•
Playwright
模板已准备好并可以使用。
5.2 开始使用 Playwright
Android 模拟器的使用可以参考官方文档中,结合代码示例可构建一个可扩展、可重用且可靠的定制自动化框架。如果希望充分发挥 Playwright
的能力,更建议根据实际项目需求来开发一个定制化的框架,下面会详细介绍如何实现。
5.3官方文档中的代码片段
在安装了 Playwright
的 TypeScript
模板后,我修改了配置文件,去除了几个与桌面浏览器相关的属性,因为它们对 Android
设备没有影响。因此,无法通过 Playwright
配置生成屏幕截图和跟踪文件。
5.4 Playwright
配置
为了让框架更易于扩展和重用,我们需要使其尽可能简洁和易于理解,避免在每个测试中重复复制代码。为了实现这个目标,我们需要理解测试执行的工作流程。这个流程包括启动服务器,获取所需设备的相关信息、获取 wsEndpoint
,连接设备并启动 Android
浏览器,创建 Web
上下文,最后创建一个新的页面实例。代码示例如下:
// Playwright 配置和初始化
const { devices, chromium } = require('playwright');
const android = devices['Pixel 5']; // 使用 Pixel 5 模拟器
const browser = await chromium.launch();
const context = await browser.newContext({
...android, // 模拟器配置
});
const page = await context.newPage();
6. 编写 Page Object
为了提高可重用性,我们创建了一个 Page Object
模式来定义元素,并描述测试步骤。我们为页面实例创建了一个构造函数,并将其作为夹具传递到测试用例中。
// Page Object 示例:LandingPage
class LandingPage {
constructor(page) {
this.page = page;
this.loginButton = page.locator('#login');
}
async clickLogin() {
await this.loginButton.click();
}
}
export default LandingPage;
测试用例
通过使用夹具和 test
注解,我们可以将 page
和 landingPage
作为参数传递给测试用例。每个测试都会创建一个隔离的 Page
实例,确保测试之间的隔离。
// 测试用例示例
import { test, expect } from '@playwright/test';
import LandingPage from './LandingPage';
test('登录测试', async ({ page }) => {
const landingPage = new LandingPage(page);
await landingPage.clickLogin();
// 检查是否成功跳转到登录页面
expect(page.url()).toBe('https://example.com/login');
});
启动模拟器
在开始测试前,记得启动模拟器,并连接 ADB
服务器。通过运行以下命令可以连接到 ADB
服务器:
$ adb devices
通过 Playwright
与 ADB
服务器连接后,我们便可以访问目标模拟器或真实设备。
为了减少手动干预,可以在运行测试前自动启动 ADB
服务器,省去手动运行 adb devices
的步骤。
{
"scripts": {
"test": "adb devices && playwright test --config=playwright.config.ts"
}
}
测试执行
测试执行的初始展示已经如预期那样进行,尽管第一个测试出于调试目的失败了,其他测试都通过了。然而,我发现了需要解决的一些问题。
常见问题demo
弹出窗口问题
当首次运行浏览器时,Android
模拟器上总会弹出一个窗口。尽管它不会影响测试,但这种不美观的弹窗需要通过 ADB shell
命令处理。通过传递键事件“4”
(表示返回按钮),我们可以在测试开始前自动关闭该弹窗。
浏览器标签堆积问题 如图所示,由于页面实例没有自动终止,标签会不断累积,导致 Android
性能下降,增加 RAM
使用。为了解决这个问题,我们建议在每次测试后清理浏览器缓存,并关闭无用的标签页。
我已更新了夹具以解决这些问题。新增的操作包括:
• 在每次测试前,强制停止
Chrome
浏览器并清理缓存。• 自动发送
input keyevent 4
来关闭弹窗。• 每次测试结束后关闭页面实例。
// 更新后的 Fixture 示例
test.beforeEach(async ({ page }) => {
await page.context().clearCookies();
await page.context().clearCache();
});
test.afterEach(async ({ page }) => {
await page.close();
});
截图与跟踪
对于失败的测试,我们需要在 afterEach
钩子中添加截图和跟踪记录。通过 testInfo
参数,在测试失败时捕捉截图并保存到输出目录。
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status === 'failed') {
await page.screenshot({ path: `./test-results/${testInfo.title}.png` });
}
});
测试报告
通过解决截图和跟踪问题后,我们的测试报告显示一切正常。如下所示:
npx playwright show-report
梳理
通过 Playwright
,我们成功地构建了一个高效且强大的移动自动化测试框架。这一框架不仅简化了测试过程,还提高了测试的准确性和可维护性。我们解决了常见的弹窗、标签堆积等问题,并优化了测试执行流程。现在,测试运行更加高效,能够帮助开发团队更快地发现和修复问题。
7. 为什么要使用数据驱动测试?
数据驱动测试是一种将测试输入和预期结果与测试脚本本身分离的测试方法。这种方式允许我们使用外部数据驱动测试行为,使得相同的测试可以多次运行,涵盖不同的数据集,从而显著提高测试覆盖率,而无需重复编写代码。本文介绍并演示如何利用 Playwright
实现基于数据驱动的自动化测试过程。
8. 测试实战
8.1 测试目标
假设你正在测试一个财务应用程序,该应用程序根据不同的国家、货币和交易类型执行多种场景。为每种组合创建单独的测试将是一个既耗时又难以维护的任务。虽然这种方式在一开始可能有效,但随着应用程序的增长以及场景的增加,你会发现这种方法的局限性。这正是数据驱动测试能够发挥优势的地方。
8.2 用例概述
我们要测试的应用是一个薪资洞察工具,用于提供不同国家各种职位的全球市场薪资数据。这款工具对于招聘时提供公平、具有竞争力的国际报价至关重要。
该应用的界面包含两个主要的下拉菜单:
1. 职位选择:用户可以选择各种职位,如
QA
工程师、会计、客户经理等。2. 国家选择:用户可以从列表中选择一个国家,每个国家用国旗和名称表示。
8.3 测试挑战
1. 数据多样性:有大量的职位和国家组合,每个组合可能会产生不同的薪资信息。
2. 数据有效性:某些职位可能在某些国家不存在,或者某些职位的薪资数据可能不完整。
3. 数据准确性:确保每个有效的职位-国家组合返回准确的薪资信息至关重要。
4. 维护性:随着新职位或国家的增加,或者薪资数据的更新,测试需要适应这些变化,而不需要做大量的代码修改。
使用数据驱动的测试方法非常适合这种情况,因为:
• 它允许我们在不重复编写测试代码的情况下测试多个职位和国家组合。
• 我们可以轻松地更新或扩展测试数据,添加新的职位或国家。
• 它帮助我们高效地管理和验证不同的数据场景。
• 它使得我们的测试更易于维护,减少了硬编码值可能引发的错误。
8.4 设置Playwright测试
前提条件:确保你的机器上已安装npm
。 首先,创建一个新的Playwright
项目:
npm init playwright@latest
按照提示完成设置过程,选择使用TypeScript
或JavaScript
,并设置GitHub Actions
进行CI
。
安装完成后,你将得到如下的基本项目结构:
playwright.config.ts
package.json
tests/
example.spec.ts
8.5 初始测试(非数据驱动)
接下来,我们为薪资洞察工具创建一个简单的初始测试。这个测试将检查某个职位和国家组合的功能。
在tests
文件夹中创建一个新文件,命名为salary-insights.spec.ts
:
import { test, expect } from '@playwright/test';
test('Salary Insights Test - Simple', async ({ page }) => {
await page.goto("/dev/salary-insights");
await page.waitForLoadState("load");
const role = "QA Engineer";
const country = "Canada";
await page.locator('role-dropdown-selector').click();
await page.fill('role-input-selector', role);
await page.click(`text=${role}`);
await page.click('country-dropdown-selector');
await page.fill('country-input-selector', country);
await page.click(`text=${country}`);
await page.click('search-button-selector');
await expect(page.locator('filter-container').getByText(role)).toBeVisible();
await expect(page.locator('filter-container').getByText(country)).toBeVisible();
const compensationDetails = await page.locator('compensation-details-selector').innerText();
expect(compensationDetails).toContain(role);
expect(compensationDetails).toContain(country);
});
该测试执行以下操作:
1. 导航到薪资洞察页面
2. 等待页面加载完成
3. 选择职位(
QA
工程师)和国家(加拿大)4. 点击搜索按钮
5. 验证选定的职位和国家是否出现在结果中
6. 检查薪资详情和信息是否包含正确的职位和国家
运行测试:
npx playwright test
8.6 转向数据驱动测试
8.6.1 数据驱动测试的思路
数据驱动测试的核心思想是将测试逻辑与测试数据分离。这样做的好处包括:
• 可以在不同的场景中重用相同的测试逻辑。
• 可以轻松添加或修改测试用例,而无需更改测试代码。
• 提高了测试的可维护性和可读性。
8.6.2 使用JSON
变量的简单实现
我们首先使用JSON
数组来存储测试数据,并通过它迭代生成多个测试用例:
const testData = [
{ role: "QA Engineer", country: "Canada" },
{ role: "QA Engineer", country: "Brazil" },
{ role: "DevOps Engineer", country: "United States" },
{ role: "DevOps Engineer", country: "United Kingdom" },
];
test.describe("Salary Insights Tests Naive", () => {
testData.forEach(({ role, country }) => {
test(`Should display correct compensation info for ${role} in ${country}`, async ({ page }) => {
await page.goto("/dev/salary-insights");
await page.waitForLoadState("load");
await page.locator('role-dropdown-selector').click();
await page.fill('role-input-selector', role);
await page.click(`text=${role}`);
// 继续你的测试逻辑...
});
});
});
这种方式的优点是:
• 可以轻松地向
testData
数组中添加更多的测试数据。• 测试逻辑保持一致,减少了代码重复。
• 每种职位和国家组合生成一个独立的测试用例,增加了可读性。
然而,这种方法也有局限性:
• 测试数据仍然在测试文件中,可能对于较大的数据集变得难以管理。
• 测试数据没有与测试代码完全分离,独立管理数据的难度较大。
• 对于非常大的数据集或需要非技术团队成员管理测试数据的情况,可能不太适用。
8.6.3 更清晰的方式:使用JSON
文件
为了使数据与逻辑完全分离,我们可以将测试数据存储在外部JSON文件中,并将其加载到测试脚本中:
1. 创建一个名为
salary_insights.json
的JSON
文件,内容如下:
[
{ "role": "QA Engineer", "country": "Canada" },
{ "role": "QA Engineer", "country": "Brazil" },
{ "role": "DevOps Engineer", "country": "United States" },
{ "role": "DevOps Engineer", "country": "United Kingdom" }
]
2. 修改测试脚本,从
JSON
文件中读取数据:
import { test, expect } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
interface TestData {
role: string;
country: string;
}
const testDataPath = path.resolve(__dirname, "data", "salary_insights.json");
let salaryTestData: TestData[];
try {
const rawData = fs.readFileSync(testDataPath, "utf8");
salaryTestData = JSON.parse(rawData) as TestData[];
} catch (error) {
console.error("Error reading or parsing salary_insights.json:", error);
process.exit(1); // Exit the process with an error code
}
test.describe("Salary Insights Tests From a JSON file", () => {
salaryTestData.forEach(({ role, country }) => {
test(`Should display correct compensation info for ${role} in ${country}`, async ({ page }) => {
await page.goto("/dev/salary-insights");
await page.waitForLoadState("load");
// 继续你的测试逻辑...
});
});
});
这种方法的优势在于它将测试数据与测试逻辑分离,使得测试更加可维护和可扩展。
8.7 使用API
实现动态数据源
动态数据驱动测试允许你快速适应变化的业务规则、新功能或数据更新,而无需修改测试代码。这种方法在CI/CD
流水线中尤其有用,因为测试失败可能会阻塞部署并让开发人员感到沮丧。
使用Postman
模拟的API
可以帮助我们灵活地处理动态测试数据。在Postman
中创建一个模拟服务器,并将其暴露为接口,通过接口获取不同职位、国家的数据来驱动测试。
import { test, expect } from '@playwright/test';
test.describe('Dynamic Salary Insights Test from API', () => {
test('Should display correct compensation info from API', async ({ page }) => {
const response = await fetch("https://api.example.com/roles-and-countries");
const data = await response.json();
for (let { role, country } of data) {
test(`Should display correct compensation info for ${role} in ${country}`, async () => {
// 用从API获取的数据继续编写测试...
});
}
});
});
总结
通过将数据与测试逻辑分离,我们使得测试更加灵活、可维护,并能处理不断变化的数据。数据驱动测试不仅提高了测试覆盖率,还让我们能够应对更大规模的项目。