fixture 介绍
官网: https://playwright.dev/docs/test-fixtures
作用: 为测试用例提供已封装的能力,如数据库操作,页面操作等
特点: 不同的测试用例之间使用相同fixture,是相互隔离的。如A用例修改了request(内建的fixture,用于做接口测试)中的超时时间,在B用例中沿用fixture定义的超时时间
// app-list-page.ts
import { type Page, type Locator, expect } from "@playwright/test";
export class AppListPage {
private readonly searchInput: Locator // 应用列表页搜索框
constructor(public readonly page: Page) {
this.searchInput = this.page.getByPlaceholder('请输入应用名称或包名');
}
/**
* 访问应用列表页(必须先小米账号登录)
*/
async goto() {
await this.page.goto('/distribute');
}
/**
* 校验应用列表页是否存在该应用, 并且应用状态是否为指定状态
* @param packageName 应用包名
* @param status 应用状态
*/
async checkAppListStatus(packageName: string, status: string) {
console.log(`开始检查应用列表中是否存在应用: ${packageName}`, `应用状态是否为: ${status}...`);
this.searchInput.fill(packageName);
this.page.getByLabel('图标: search').locator('svg').click();
const visibale = this.page.getByText(`包名:${packageName}`).isVisible({ timeout: 5000 });
if (visibale) {
console.log(`===>[PASS]应用: ${packageName} 存在.`);
await expect(this.page.getByRole('cell', { name: status }).locator('span')).toBeVisible();
console.log(`===>[PASS]状态为: ${status}.`);
} else {
console.error(`===>[FAIL]检查应用列表结束, 应用: ${packageName} 不存在.`);
expect(visibale).toBeTruthy();
}
}
}
// home-fixture.ts
import { test as base } from "@playwright/test";
import { AppListPage } from '../pages/home/app-list-page';
type HomeFixtures = {
appList: AppListPage;
}
export const homeFixtures = base.extend<HomeFixtures>({
appList: async ({ page }, use) => {
const appList = new AppListPage(page);
await use(appList);
},
});
import { homeFixtures } from "../../src/fixtures/home-fixture";
import { deleteAppData } from "../../src/utils/delete-data";
homeFixtures.describe("创建空包名", () => {
homeFixtures.beforeEach(async ({ }, testInfo) => {
console.log('==================================');
console.log(`开始执行测试用例: ${testInfo.title}`);
});
homeFixtures.afterEach(async ({ }, testInfo) => {
console.log(`测试用例: ${testInfo.title} 执行结束.`);
console.log('==================================');
});
homeFixtures("@P0 能正常创建空包名应用", async ({ appList}) => {
await appList.goto();
await appList.checkAppListStatus('package.com.com4', '未发布');
});
});
// db-fixture.ts
import { test as base } from "@playwright/test";
import mysql, { ConnectionOptions } from 'mysql2';
import ENV from "../utils/env";
const dbConfig: ConnectionOptions = {
host: ENV.DB_HOST!,
port: Number(ENV.DB_PORT!),
user: ENV.DB_USER!,
password: ENV.DB_PASSWORD!,
};
export const dbFixtures = base.extend({
dbAction: async ({ }, use) => {
await use(async ({database, sql}) => {
console.log(`开始执行数据库: ${database}, sql: ${sql}`);
const result = await new Promise((resolve, reject) => {
dbConfig.database = database;
const conn = mysql.createConnection(dbConfig);
conn.query(sql, (error, results) => {
if (error) {
console.log(`执行失败, 错误信息: ${error.message}`);
reject(error);
} else {
console.log(`执行成功.`);
resolve(results);
}
});
});
return result;
});
},
});
import { dbFixtures } from "../../src/fixtures/db-fixture";
dbFixtures.only('dbAction', async ({ dbAction }) => {
const database = 'appmarket';
const sql = 'select * from app_info limit 2';
const result = await dbAction({ database, sql });
console.log(result);
});
执行成功
[
{
app_info_id: 1,
package_name: 'com.flightmanager.view',
package_name_hash: '410cd078e2a000fa065072ca14c22589',
......
},
......
]
为什么要合并?
以上的例子中可以看到一个现象:测试用例必须先引用某个fixture,继而才能使用该fixture中的能力
但如果fixture存在于多个文件中定义,在一个测试用例中想使用多个fixture,则变的无能为力
举个例子
// test.spec.ts
// 假定有两个fixture,分别导出A、B。每个fixture下均有一个函数为a()、b()
import { A } from "./a-fixture";
import { B } from "./b-fixture";
// 测试用例中使用
A('test A', async ({ a }) => {
//调用a函数完成对应的逻辑
})
B('test B', async ({ b }) => {
//调用b函数完成对应的逻辑
})
可以看出,以上测试用例中有两个测试用例,每个测试用例中仅能引用一个fixture。但,实际的测试场景中经常有这样的情况:
以下方法仅在 playwright 版本 >= 1.39可用
// index-fixture.ts
import { A } from "./a-fixture";
import { B } from "./b-fixture";
import { mergeTests } from "@playwright/test";
export const test = mergeTests(A, B)
// test.spec.ts
import { test } from "./index-fixture";
// 测试用例中使用
test('test', async ({ a, b }) => {
//调用a函数完成对应的逻辑
//调用b函数完成对应的逻辑
})
使用该方法可能出现一个问题
如果未来有很多fixture,且某个测试用例必须使用最全的fixture能力,则必须在最后的fixture中进行extend
例如必须在B中使用A.extend()进行拓展
// a-fixture.ts
import { test as base } from "@playwright/test";
export const A = base.extend({
a: async ({ page }, use) => {
// ......
}
});
// b-fixture.ts
import { A } from "./a-fuxture";
export const B = A.extend({
b: async ({ page }, use) => {
// ......
}
});
// test-spec.ts
import { B as test } from "./b-fixture";
// 测试用例中使用
test('test', async ({ a, b }) => {
//调用a函数完成对应的逻辑
//调用b函数完成对应的逻辑
})