- name
- generate-storefront-e2e-test
- description
- Generate E2E tests for Salesforce Commerce Cloud storefront using CodeceptJS with AI-powered features. Specializes in commerce-specific test patterns, page objects, and interactive development workflows.
# Generate Storefront E2E Test
Systematically create end-to-end tests for Salesforce Commerce Cloud storefront using CodeceptJS Framework with AI-powered features. This skill focuses on commerce-specific test patterns and leverages DOM-driven AI capabilities.
## Prerequisites
Before starting, ensure you understand the storefront context:
```bash
view CLAUDE.md # in current package directory
```
## Workflow
### 1. Define Test Requirements
**Understand the commerce scenario**:
- Identify storefront functionality to test (search, cart, checkout, etc.)
- List specific test cases for commerce flows
- **IMPORTANT**: Always ask clarifying questions about the test scope
- **IMPORTANT**: Focus on actual storefront, NOT demo/workbench environments
**Example questions**:
- "Should this test cover both desktop and mobile views?"
- "Do you want to test guest checkout or authenticated user flow?"
- "Should we validate SFCC cookies and session management?"
- "Which product categories or search terms should we use?"
### 1.5. Audit Existing Unit Tests and Storybook Stories
**Review existing tests before writing E2E scenarios** to understand what's already covered and ensure E2E tests focus on integration value.
**Key principle**: E2E tests should validate flows that require real authentication, routing, cross-component interactions, or live API data — not duplicate component-isolated unit tests or Storybook `play` functions.
### 2. Verify Storefront Context
**Check storefront implementation** to understand:
**Environment Configuration**:
- Verify `.env` exists (gitignored, maintained by developers/CI)
- If `.env` doesn't exist, copy from `.env.sample`
- Confirm BASE_URL points to actual storefront (not demo)
- Check SITE_ID configuration for cookie validation
**Existing Page Objects**:
```bash
view src/pages/**/*.page.ts
```
- Look for reusable page objects (storefrontPage, cartPage, etc.)
- Identify if new page objects are needed
**Existing Tests**:
```bash
view src/specs/**/*.spec.ts
```
- Review existing test patterns and tags
- Ensure no duplication of test scenarios
### 3. Get User Approval
**Present test plan** to user:
- List specific test scenarios you'll create
- Explain commerce flows to be covered
- Confirm AI features to be demonstrated (pause(), I.askForPageObject())
- Specify tags and organization strategy
**Wait for approval** before proceeding to implementation.
### 4. Implement Tests with AI Features
**Create tests using Scenario-Mocha style**:
```typescript
Feature('Storefront Commerce Tests').tag('@commerce');
const { I, storefrontPage, cartPage } = inject();
import { expect } from 'chai';
Scenario('Search and add product to cart', async () => {
storefrontPage.navigate();
storefrontPage.searchForProduct('shoes');
storefrontPage.clickFirstProduct();
// Example of interactive AI development
// pause(); // Uncomment to try: "Add product to cart and verify"
cartPage.addToCart();
cartPage.validateItemAdded();
}).tag('@search').tag('@cart');
export {};
```
**Key Requirements**:
- Use `Feature()` and `Scenario()` structure
- Add meaningful tags for filtering
- Include `export {};` at end
- Import page objects via `inject()`
- Import Chai `expect` for value assertions: `import { expect } from 'chai';`
- Use Chai `expect()` assertions for validating retrieved values (counts, text, URLs, etc.)
- Use CodeceptJS page object methods for UI interactions
- Focus on commerce-specific flows
- **CRITICAL**: Never call `I.*` methods directly inside a Scenario — all `I.*` usage (`I.click`, `I.amOnPage`, `I.seeElement`, etc.) must live in page objects or flows. Scenarios only call page object / flow methods and Chai assertions.
- **CRITICAL**: Each scenario must be independent — create necessary test data within the scenario, never depend on other scenarios running first or in specific order
- **CRITICAL**: Wrap all test-authored paths in `buildSitePath()` before passing to `I.amOnPage()` — import from `../utils/url-utils` (or `../../utils/url-utils` from specs). Do NOT apply `buildSitePath()` to URLs extracted from the page DOM (they already contain the url prefix).
### 5. Generate Page Objects with AI
**Use AI-powered page object generation**:
```typescript
import { buildSitePath } from '../../utils/url-utils';
Scenario('Generate product page object', async () => {
I.amOnPage(buildSitePath('/product/sample-product'));
pause();
// In console: I.askForPageObject("productDetail")
// AI reads runtime DOM and generates complete page object
}).tag('@page-object-generation');
```
> **Multi-site note:** All `I.amOnPage()` calls with test-authored paths must use `buildSitePath()` to prepend the optional `/{siteAlias}/{locale}` prefix. See `CLAUDE.md` "Multi-site URL Prefixing" for details.
**Locator Strategy**:
Playwright's recommended priority is user-facing attributes first, `data-testid` last:
1. `getByRole` / `[role]` + accessible name — closest to how users and screen readers perceive the page
2. `getByLabel` / associated `<label>` — preferred for form fields
3. `getByText` / `.withText()` — visible text content
4. `getByPlaceholder`, `getByAltText`, `getByTitle` — other user-facing attributes
5. `data-testid` — last resort; resilient but not user-facing
**Rule of thumb:** if `locate('[role="button"]').withText('Add to Cart')` uniquely identifies the element, prefer it. If there are multiple matches, reach for `data-testid`.
**No good locator? Add one.** If no semantic attribute or `data-testid` exists on the element, add `data-testid` directly to the source component in the template. Don't settle for a brittle CSS class or positional selector — a two-minute source edit produces a stable, self-documenting locator.
```tsx
// ✅ Add data-testid to the source component
<button data-testid="add-to-cart-button" onClick={handleAddToCart}>
Add to Cart
</button>
// Then reference it in the page object
addToCartButton: locate('[data-testid="add-to-cart-button"]').as('Add to Cart Button'),
```
**⚠️ Avoid UI-Only Classes:**
**NEVER** use styling classes (Tailwind utilities like `text-muted-foreground`, `text-destructive`, `bg-*`, `flex`, etc.) as locators — they're non-semantic and match multiple elements.
```typescript
// ❌ Bad: UI-only classes
locate('p.text-muted-foreground').as('Subtitle')
locate('.text-destructive').as('Error')
// ✅ Better: Add semantic context or use data attributes
locate('[data-slot="card"] p.text-muted-foreground').first().as('Subtitle')
locate('[role="dialog"] .text-destructive').as('Form Error')
locate('[role="alert"]').as('Error Message')
```
**Page Object Pattern**:
```typescript
const { I } = inject();
import { buildSitePath } from '../utils/url-utils';
class ProductDetailPage {
locators = {
productTitle: locate('[data-testid*="product-title"]').as('Product Title'),
addToCartButton: locate('[data-testid*="add-to-cart"]').as('Add to Cart'),
priceDisplay: locate('[data-testid*="price"]').as('Price'),
sizeSelector: locate('[data-testid*="size"]').as('Size Selector'),
};
navigate(productSlug: string): void {
I.amOnPage(buildSitePath(`/product/${productSlug}`));
}
async selectSize(size: string): Promise<void> {
I.click(this.locators.sizeSelector);
I.click(`option:has-text("${size}")`);
}
addToCart(): void {
I.click(this.locators.addToCartButton);
I.waitForText('Added to cart', 10);
}
}
module.exports = new ProductDetailPage();
```
**After creating page object**:
1. **Register in `src/pages/index.ts`** → Add entry to `pageObjects` object (or `src/flows/index.ts` for flows)
2. **Update `helpers/self-healing/recipes.ts`** → Add healing recipes for each locator (see step 7.5)
See **step 6** below for full registration details and examples.
### 6. Register Page Objects and Flows
**IMPORTANT**: All page objects and flows must be registered in their respective index files to be available via `inject()`.
#### Registry System Architecture
The project uses a centralized registry system to keep `codecept.conf.cjs` clean and maintainable:
**Page Objects Registry** (`src/pages/index.ts`):
```typescript
export const pageObjects = {
storefrontPage: './src/pages/storefront.page.ts',
cartPage: './src/pages/cart.page.ts',
megaMenuPage: './src/pages/mega-menu.page.ts',
productListPage: './src/pages/product-list.page.ts',
productDetailPage: './src/pages/product-detail.page.ts',
};
```
**Flows Registry** (`src/flows/index.ts`):
```typescript
export const flows = {
addToCartFlow: './src/flows/add-to-cart.flow.ts',
checkoutFlow: './src/flows/checkout.flow.ts',
};
```
**How it works**:
- `codecept.conf.cjs` imports both registries: `const { pageObjects } = require('./src/pages/index.ts');`
- Config spreads them into `include`: `include: { ...pageObjects, ...flows }`
- All registered objects are available via `inject()` in tests
- **No need to modify** `codecept.conf.cjs` when adding new page objects/flows
**When to register**:
- ✅ **Always** register page objects in `src/pages/index.ts`
- ✅ **Always** register flows in `src/flows/index.ts`
- ❌ **Never** add them directly to `codecept.conf.cjs`
**Benefits**:
- Clean separation of concerns
- Easy to find and manage all page objects/flows
- No mixed imports in config file
- Scales well as project grows
### 7. Register TypeScript Definitions
**AUTOMATIC**: TypeScript definitions are now auto-generated before each test run.
```bash
# Definitions auto-generated when running tests
pnpm e2e --grep "@your-test"
# Manual generation (optional)
pnpm def
```
The `steps.d.ts` file is automatically updated with new page object registrations.
### 7.5. Update Self-Healing Recipes
**REQUIRED**: When creating page objects, adding new locators, or modifying existing locator selectors, always update `helpers/self-healing/recipes.ts` to keep healing recipes in sync.
**Purpose**: Healing recipes provide fallback selectors for AI self-healing when primary locators break. They help the AI understand context and suggest alternative selectors.
**Process**:
1. **Extract locators** from the new/updated page object
2. **Create a `HealingRecipe`** for each important locator:
```typescript
export const newLocatorRecipe: HealingRecipe = {
name: 'locatorName', // Match the page object locator name
description: 'Human-readable description of the element',
selectors: [
'primary-selector', // From page object locators
'semantic-selector', // Semantic HTML fallback
'[aria-label*="text" i]', // Accessibility fallback
'location-based-selector', // Context-aware selector
],
context: 'Where the element appears and its purpose',
fallbackStrategy: 'What to look for if primary selector fails',
};
```
3. **Add to `healingRecipes` array**:
```typescript
export const healingRecipes: HealingRecipe[] = [
// ... existing recipes
newLocatorRecipe,
];
```
**Recipe Pattern**:
- **Primary selector**: Exact selector from page object (highest priority)
- **Semantic selectors**: HTML element types, ARIA roles, semantic attributes
- **Context selectors**: Location-based (e.g., `header input`, `footer a`)
- **Accessibility selectors**: `aria-label`, `role`, `placeholder` attributes
- **Fallback strategy**: Human-readable guidance for AI when all selectors fail
**Example** (from `storefront.page.ts`):
```typescript
// Page object locator:
searchInput: locate('input[data-testid*="search"]').as('Search Input'),
// Corresponding recipe:
export const searchInputRecipe: HealingRecipe = {
name: 'searchInput',
description: 'Search input field in storefront header',
selectors: [
'input[data-testid*="search"]', // Primary from page object
'input[type="search"]', // Semantic HTML
'input[placeholder*="search" i]', // Placeholder text
'input[aria-label*="search" i]', // Accessibility label
'header input[type="text"]', // Location + type
'[role="searchbox"]', // ARIA role
],
context: 'Located in storefront header, used for product search',
fallbackStrategy: 'Look for input field in header navigation area',
};
```
**When to update**:
- ✅ Creating a new page object → Add recipes for all locators
- ✅ Adding new locators to existing page object → Add recipes for new locators
- ✅ **Modifying existing locator selectors → Update corresponding recipe immediately**
- ❌ Don't create recipes for temporary/test-specific locators
عرض على GitHub