mcpField
C
create-pom

create-pom

github.com/agentmantis测试与质量12121 次安装29 周前更新
代码仓库

关于 create-pom

用于生成 Playwright 端到端测试 TypeScript Page Object Model 类的 Skill,指导开发者检查现有 POM、继承基础页面并验证结果。

主要功能

  1. 01检查是否已有页面对象
  2. 02继承共享基础页面
  3. 03遵循选择器优先级指南
  4. 04用 JSDoc 文档化公有方法
  5. 05验证 TypeScript 编译

适用场景

  1. 01为新的应用页面创建 POM
  2. 02为现有 POM 添加行为
  3. 03使页面对象符合测试约定

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/agentmantis/test-skills --skill create-pom

安装后在 Claude Code 中输入/create-pom确认已加载。

附带文件references/pom-template.md

详细介绍

创建页面对象模型

按照所有 E2E 测试约定,为指定页面生成一个 POM 类。


工作流

  1. 确定页面名称:根据用户的请求确定(例如,“Settings” → settings.page.ts)
  2. 检查是否已存在 POM:检查 e2e/poms/ 中是否已有该页面的 POM。如果有,则在现有 POM 中添加方法——不要为同一页面创建第二个 POM
  3. 确定基类页面类:在 e2e/poms/base.page.ts 中查找并继承该类
  4. 阅读应用中的目标页面,以了解其元素和交互
  5. 生成 POM 文件,并遵循下方的模板
  6. 验证文件编译时没有 TypeScript 错误

规则

  • 一个页面 = 一个 POM——每个不同的页面/视图都只能有一个 POM 文件
  • 所有 POM 都必须继承基类页面类——检查 e2e/poms/base.page.ts 中的抽象类
  • 实现 setUp() 和 tearDown()——从基类继承
  • 可复用的辅助方法应放在 BasePage 中——如果某个辅助方法可用于多个 POM(例如,waitForToast、dismissModal),它必须放在 BasePage 中,而不是派生 POM 中。派生 POM 只能包含特定于页面的行为。如果你发现自己要在第二个 POM 中编写相同的辅助方法,请立即将它移到 BasePage
  • 绝不要在 POM 中引用另一个 POM——如果测试需要操作多个页面,应由 spec 文件协调各个 POM
  • 为每个公共方法编写详细的 JSDoc——逐步描述方法的功能,并添加 @param 标记
  • 按以下优先级顺序使用选择器:getByRole() > getByLabel() > getByText() > getByPlaceholder() > locator()

POM 结构

使用注释横幅将每个 POM 清晰地划分为以下部分:

import { Page, Locator, expect } from '@playwright/test';
// TODO: Import your project's base page class
import { BasePage } from './base.page';

export class FeaturePage extends BasePage {
    constructor(page: Page) {
        super(page);
    }

    // ==========================================
    // LIFECYCLE (from BasePage)
    // ==========================================

    /**
     * Navigates to the feature page and cleans up any stale data
     * left by previous failed test runs.
     *
     * Steps:
     * 1. Navigates to /feature via direct URL.
     * 2. Waits for the page heading to be visible.
     * 3. Deletes any items matching the test data pattern.
     */
    async setUp(): Promise<void> {
        // TODO: Implement navigation and cleanup
    }

    /**
     * Cleans up any data created during the test suite.
     *
     * Steps:
     * 1. Navigates to /feature.
     * 2. Deletes all items created by this test run.
     */
    async tearDown(): Promise<void> {
        // TODO: Implement cleanup
    }

    // ==========================================
    // LOCATORS
    // ==========================================

    /** The main heading of the feature page. */
    get heading(): Locator {
        return this.page.getByRole('heading', { name: 'Feature Name' });
    }

    /** The "Create" button that opens the creation modal. */
    get createButton(): Locator {
        return this.page.getByRole('button', { name: /create/i });
    }

    // TODO: Add locators for all interactive elements on this page

    // ==========================================
    // NAVIGATION
    // ==========================================

    /**
     * Navigates directly to the feature page via URL.
     *
     * Steps:
     * 1. Calls page.goto('/feature').
     * 2. Asserts the page heading is visible.
     * 3. Asserts the URL contains '/feature'.
     */
    async navigateToPage(): Promise<void> {
        await this.page.goto('/feature');
        await expect(this.heading).toBeVisible();
        await expect(this.page).toHaveURL(/\/feature/);
    }

    // ==========================================
    // VERIFICATION
    // ==========================================

    /**
     * Verifies an item appears in the list with the expected name.
     *
     * Steps:
     * 1. Locates the item row by name text.
     * 2. Asserts the row is visible.
     *
     * @param name - The expected item name
     */
    async verifyItemExists(name: string): Promise<void> {
        await expect(
            this.page.getByRole('row', { name: new RegExp(name, 'i') })
        ).toBeVisible();
    }

    // ==========================================
    // CREATE
    // ==========================================

    /**
     * Creates a new item via the creation modal.
     *
     * Steps:
     * 1. Clicks the "Create" button to open the modal.
     * 2. Fills in the name field.
     * 3. Clicks "Submit" and waits for the modal to close.
     *
     * @param name - Display name for the new item
     */
    async createItem(name: string): Promise<void> {
        await this.createButton.click();
        const modal = this.page.getByRole('dialog');
        await modal.getByLabel('Name').fill(name);
        await modal.getByRole('button', { name: 'Submit' }).click();
        await expect(modal).toBeHidden();
    }

    // ==========================================
    // EDIT
    // ==========================================

    // TODO: Add edit methods

    // ==========================================
    // DELETE
    // ==========================================

    // TODO: Add delete methods
}

JSDoc 要求

每个公共方法都必须包含 JSDoc 注释,其中包含:

  1. 摘要行——说明方法的功能
  2. 步骤块——以编号列表说明调用时会发生什么
  3. @param 标记——为每个参数添加
  4. @returns 标记——如果方法返回值,则添加

这点至关重要,因为 LLM 会阅读这些注释,了解可执行的操作,并据此生成测试 spec。

/**
 * Updates an existing item's name and value.
 *
 * Steps:
 * 1. Locates the item row by its current name.
 * 2. Clicks the "Edit" button within that row.
 * 3. Clears and fills the name field with the new name.
 * 4. Clears and fills the value field with the new value.
 * 5. Clicks "Save" and waits for the edit modal to close.
 * 6. Waits for the success toast notification to appear.
 *
 * @param currentName - The item's current display name (used to locate the row)
 * @param newName     - The new display name
 * @param newValue    - The new value
 */
async editItem(currentName: string, newName: string, newValue: string): Promise<void> {

命名

  • 文件:e2e/poms/{feature}.page.ts(使用小写字母和 kebab-case 格式的 feature 名称)
  • 类:{Feature}Page(PascalCase)
  • feature 名称必须与对应的 spec 和 test-data 文件一致

完整的样板代码请参阅 references/pom-template.md。

评论

支持 Markdown