create-ui-layer
Create Page Objects (MainLayout/CheckoutLayout subclasses) and UI Components (Component subclasses) with Playwright locators and data-test-id conventions
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Create Page Objects (MainLayout/CheckoutLayout subclasses) and UI Components (Component subclasses) with Playwright locators and data-test-id conventions
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Review test files for pattern compliance, code quality, correctness, coverage gaps, and best practices — produces actionable feedback
Scaffold E2E UI test files following project patterns — Playwright assertions, Page Objects, Components, markers, fixtures, Allure decorators, BrowserStorage
Scaffold GraphQL API test files following project patterns — markers, fixtures, Allure decorators, Pydantic assertions, try-finally cleanup
Scaffold REST API test files and factory fixture conftest files — admin auth, RestClient, factory fixtures with auto-teardown, Allure steps, CRUD patterns
Create GraphQL Operations classes (BaseOperations subclass with auto-fragment injection) and Pydantic GqlModel types for response/input types
Migrate a Katalon REST API test module from vc-quality-gate-katalon into the refactored Pytest project — end-to-end flow from inventory to CI-verified PR
| name | create-ui-layer |
| description | Create Page Objects (MainLayout/CheckoutLayout subclasses) and UI Components (Component subclasses) with Playwright locators and data-test-id conventions |
| argument-hint | <page-or-component> |
When creating Page Objects or UI Components, follow these patterns exactly.
page_objects/layouts/<layout>.pypage_objects/pages/<page>.pypage_objects/components/<component>.pypage_objects/pages/__init__.py, page_objects/components/__init__.pyLayouts provide shared UI sections (header, footer). Pages extend layouts.
from playwright.sync_api import Locator, Page
from core.global_settings import GlobalSettings
from page_objects.components.top_header import TopHeader
class MainLayout:
def __init__(self, page: Page, global_settings: GlobalSettings) -> None:
self._global_settings = global_settings
self._page = page
@property
def root(self) -> Locator:
return self._page.locator(".main-layout")
@property
def top_header(self) -> TopHeader:
return TopHeader(root=self._page.locator("[data-test-id='top-header']"))
@property
def cart_quantity_label(self) -> Locator:
return self._page.locator(
"[data-test-id='desktop-main-menu-cart-link'] .vc-badge__content"
)
def click_outside(self) -> None:
self._page.locator("body").click()
Pages extend a layout and add page-specific elements and actions.
from playwright.sync_api import Locator
from page_objects.components.line_item import LineItem
from page_objects.components.shipping_details_section import ShippingDetailsSection
from page_objects.layouts.main import MainLayout
class CartPage(MainLayout):
@property
def url(self) -> str:
return f"{self._global_settings.frontend_base_url}/cart"
@property
def shipping_details_section(self) -> ShippingDetailsSection:
return ShippingDetailsSection(
root=self._page.locator("[data-test-id='shipping-details-section']")
)
@property
def line_items(self) -> Locator:
return self._page.locator("[data-product-sku]")
@property
def clear_cart_button(self) -> Locator:
return self._page.locator("[data-test-id='clear-cart-button']")
@property
def checkout_button(self) -> Locator:
return self._page.locator("[data-test-id='checkout-button']")
def find_line_item(self, sku: str) -> LineItem:
return LineItem(root=self._page.locator(f"[data-product-sku='{sku}']"))
def navigate(self) -> None:
self._page.goto(url=self.url, wait_until="load")
Key patterns:
MainLayout (or CheckoutLayout for checkout pages)url property from self._global_settings.frontend_base_urlnavigate() uses wait_until="load" (not networkidle)Locator or child Componentfind_* methods return components for dynamic elementsfrom playwright.sync_api import Locator
class Component:
def __init__(self, root: Locator) -> None:
self._root = root
@property
def root(self) -> Locator:
return self._root
Do not add a wait_for_results() (or any wait_for_load_state("networkidle")) helper. networkidle is discouraged by Playwright and hangs on apps with WebSocket/polling traffic. Tests should wait via explicit expect(locator).to_be_visible() / to_have_count(N) assertions on the specific element they care about — Playwright's auto-wait covers the legitimate cases.
from playwright.sync_api import Locator
from page_objects.components.component import Component
from page_objects.components.add_to_cart_button import AddToCartButton
from page_objects.components.quantity_stepper import QuantityStepper
from page_objects.components.line_item import LineItem
class ProductCard(Component):
@property
def sku(self) -> str | None:
return self._root.get_attribute("data-product-sku")
@property
def quantity_stepper(self) -> QuantityStepper:
return QuantityStepper(
root=self._root.locator("[data-test-id='quantity-stepper']")
)
@property
def add_to_cart_button(self) -> AddToCartButton:
return AddToCartButton(
root=self._root.locator("[data-test-id='add-to-cart-button']")
)
@property
def variations_button(self) -> Locator:
return self._root.locator(
f"[data-test-id='variations-{self.sku}-button']"
).first
def find_variation_item(self, sku: str) -> LineItem:
return LineItem(root=self._root.locator(f"[data-item-sku='{sku}']"))
from playwright.sync_api import Locator
from page_objects.components.component import Component
class ClearCartModal(Component):
@property
def yes_button(self) -> Locator:
return self._root.locator("[data-test-id='yes-button']")
@property
def no_button(self) -> Locator:
return self._root.locator("[data-test-id='no-button']")
from page_objects.browser_storage import BrowserStorage
storage = BrowserStorage(page)
storage.set_user_id(user_id) # Anonymous cart association
storage.set_auth(provider.token_info) # Inject auth token
user_id = storage.get_user_id() # Read from localStorage
# Primary: data-test-id attributes
self._page.locator("[data-test-id='clear-cart-button']")
# Data attributes for element state
self._root.get_attribute("data-product-sku")
# Dynamic locators with interpolation
self._page.locator(f"[data-product-sku='{sku}']")
# Scoped to component root
self._root.locator("[data-test-id='quantity-stepper']")
# First match for non-unique elements
self._root.locator(f"[data-test-id='variations-{self.sku}-button']").first
# CSS class fallback (only when data-test-id unavailable)
self._page.locator(".main-layout")
# page_objects/pages/__init__.py
from page_objects.pages.cart import CartPage
from page_objects.pages.home import HomePage
from page_objects.pages.sign_in import SignInPage
# page_objects/components/__init__.py
from page_objects.components.clear_cart_modal import ClearCartModal
from page_objects.components.line_item import LineItem
from page_objects.components.product_card import ProductCard
MainLayout or CheckoutLayout) — never standaloneComponent base class — constructor: __init__(self, root: Locator)CartPage(global_settings=global_settings, page=page)LineItem(root=locator)@property for all element locators and child componentsself._root in components, self._page in pagesdata-test-id attributes — prefer over CSS selectors or XPathnavigate() uses wait_until="load" (not networkidle)time.sleep() and no networkidle waits — wait via explicit expect() assertions on specific locators__init__.py exports-> Locator, -> str | None, -> ChildComponent