You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于TypeScript开发Cucumber .feature到PageObject的HTML标识符解析及控件函数

Got it, let's work through these two technical tasks to build out your Cucumber + Page Object Framework setup with TypeScript. I'll break each task down with actionable code examples and plain-language explanations.

Task 1: TypeScript Function to Parse Cucumber .feature Files & Generate PageObject Files

The goal here is to automate extracting target elements from your Cucumber step definitions and spit out a structured PageObject class. Here's how to approach it:

Step 1: Read & Extract Elements from the .feature File

First, we'll read the feature file content and use regex to pull out elements like Account Name from your example step. These elements will map to the HTML identifiers we need for the PageObject.

import * as fs from 'fs';
import * as path from 'path';

// Extract unique elements from .feature file steps
function extractElementsFromFeature(featurePath: string): string[] {
  const featureContent = fs.readFileSync(featurePath, 'utf8');
  // Regex to match elements mentioned in filter/input steps
  const elementRegex = /(filters|selects|enters)\s+(.*?)\s+(so that|into|from)/gi;
  const elements: string[] = [];
  let match;

  while ((match = elementRegex.exec(featureContent)) !== null) {
    const elementName = match[2].trim();
    if (!elements.includes(elementName)) {
      elements.push(elementName);
    }
  }

  return elements;
}

Step 2: Generate the PageObject File

Next, we'll convert those element names to kebab-case (split by "-" as requested) and generate a TypeScript PageObject class with pre-built locators and methods.

// Generate a PageObject file from extracted elements
function generatePageObject(elements: string[], outputPath: string): void {
  // Convert element names to kebab-case (e.g., "Account Name" → "account-name")
  const kebabCaseElements = elements.map(el => 
    el.toLowerCase().replace(/\s+/g, '-')
  );

  // Build PageObject class content
  let pageObjectContent = `import { Page, Locator } from '@playwright/test'; // Swap with your test runner (WebdriverIO, etc.)\n\n`;
  pageObjectContent += `export class FilterPage {\n`;
  pageObjectContent += `  readonly page: Page;\n`;
  
  // Add locator properties for each element's dropdown and input
  kebabCaseElements.forEach(el => {
    const camelCaseName = el.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
    pageObjectContent += `  readonly ${camelCaseName}Dropdown: Locator;\n`;
    pageObjectContent += `  readonly ${camelCaseName}Input: Locator;\n`;
  });

  // Constructor to initialize locators
  pageObjectContent += `\n  constructor(page: Page) {\n`;
  pageObjectContent += `    this.page = page;\n`;
  kebabCaseElements.forEach(el => {
    const camelCaseName = el.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
    pageObjectContent += `    this.${camelCaseName}Dropdown = page.locator('.${el}-dropdown'); // CSS class selector\n`;
    pageObjectContent += `    this.${camelCaseName}Input = page.locator('.${el}-input'); // CSS class selector\n`;
  });
  pageObjectContent += `  }\n\n`;

  // Add reusable filter methods
  kebabCaseElements.forEach(el => {
    const methodName = `filter${el.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('')}`;
    pageObjectContent += `  async ${methodName}(filterText: string): Promise<void> {\n`;
    pageObjectContent += `    await this.${el.replace(/-([a-z])/g, (g) => g[1].toUpperCase())}Dropdown.selectOption('contains');\n`;
    pageObjectContent += `    await this.${el.replace(/-([a-z])/g, (g) => g[1].toUpperCase())}Input.fill(filterText);\n`;
    pageObjectContent += `    // Optional: Add code to trigger filter application (e.g., click "Apply")\n`;
    pageObjectContent += `  }\n\n`;
  });

  pageObjectContent += `}`;

  // Write the file to your project
  fs.writeFileSync(outputPath, pageObjectContent);
  console.log(`PageObject successfully generated at ${outputPath}`);
}

// Example usage
const featurePath = path.join(__dirname, '../features/account-filters.feature');
const elements = extractElementsFromFeature(featurePath);
generatePageObject(elements, path.join(__dirname, '../pageObjects/FilterPage.ts'));
Task 2: PageObject Functions for Dropdowns & Text Inputs

For this task, we'll build focused methods that work with kebab-case CSS/class identifiers, and handle your example scenario: filtering Account Name to contain dyno.

Step 1: Build the Targeted PageObject Class

This class uses kebab-case identifiers to map directly to HTML elements, making it easy to maintain:

import { Page, Locator } from '@playwright/test';

export class AccountPage {
  readonly page: Page;
  // Locators using kebab-case class names
  readonly accountNameDropdown: Locator;
  readonly accountNameInput: Locator;

  constructor(page: Page) {
    this.page = page;
    this.accountNameDropdown = page.locator('.account-name-dropdown');
    this.accountNameInput = page.locator('.account-name-input');
  }

  /**
   * Generic method to filter a column by text
   * @param columnIdentifier - Kebab-case name of the column (e.g., "account-name")
   * @param filterText - Text to filter the column for
   */
  async filterColumnByText(columnIdentifier: string, filterText: string): Promise<void> {
    const dropdown = this.page.locator(`.${columnIdentifier}-dropdown`);
    const input = this.page.locator(`.${columnIdentifier}-input`);

    await dropdown.selectOption('contains'); // Adjust to match your dropdown's value
    await input.fill(filterText);
  }

  // Specific method for Account Name filter (optional but helpful for readability)
  async filterAccountNameToContain(filterText: string): Promise<void> {
    await this.accountNameDropdown.selectOption('contains');
    await this.accountNameInput.fill(filterText);
  }
}

Step 2: Map Cucumber Step to PageObject Method

Connect your example Cucumber step to the PageObject method in your step definition:

import { When } from '@cucumber/cucumber';
import { AccountPage } from '../pageObjects/AccountPage';
import { page } from '../hooks/hooks'; // Adjust based on your test setup

let accountPage: AccountPage;

When('he filters account Name so that it contains dyno', async () => {
  accountPage = new AccountPage(page);
  // Option 1: Use the generic method
  await accountPage.filterColumnByText('account-name', 'dyno');
  // Option 2: Use the specific method
  // await accountPage.filterAccountNameToContain('dyno');
});

Coordinating with Developers

Make sure to align with your dev team to enforce the kebab-case naming convention for HTML elements:

  • Dropdown for Account Name → add class account-name-dropdown
  • Text input for Account Name filter → add class account-name-input

This ensures your PageObject locators will work reliably without constant tweaks.

内容的提问来源于stack exchange,提问作者kapperkp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:29:10