【nodejs版playwright】03-fixture能力封装实战

fixture 介绍
官网: https://playwright.dev/docs/test-fixtures
作用: 为测试用例提供已封装的能力,如数据库操作,页面操作等
特点: 不同的测试用例之间使用相同fixture,是相互隔离的。如A用例修改了request(内建的fixture,用于做接口测试)中的超时时间,在B用例中沿用fixture定义的超时时间

1. 页面POM的fixture封装

POM定义

// 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();
        }
    }
}

fixture定义

// 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', '未发布');
    });
});

2. 数据库操作的封装

fixture定义

// 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',
    ......
  },
  ......
]

3. 将多个fixture合并使用

为什么要合并?
以上的例子中可以看到一个现象:测试用例必须先引用某个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。但,实际的测试场景中经常有这样的情况:

  1. 先在页面注册用户(假定是A fixture完成)
  2. 到数据库中检查入库是否正确(假定是B fixture完成)
    针对上述情况,必须在一个测试用例中完成,所以必须要将两个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函数完成对应的逻辑
})

【不建议】使用extend来拓展fixture能力

使用该方法可能出现一个问题
如果未来有很多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函数完成对应的逻辑
})

你可能感兴趣的:(playwright,playwright,nodejs,fixture)