UIActions pattern for centralized Playwright interactions. Use when implementing clean page object interactions, creating reusable action classes for buttons, inputs, dropdowns, checkboxes, or building a centralized interaction gateway.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
UIActions pattern for centralized Playwright interactions. Use when implementing clean page object interactions, creating reusable action classes for buttons, inputs, dropdowns, checkboxes, or building a centralized interaction gateway.
Action Utilities Skill
A comprehensive guide to implementing centralized action utilities (UIActions pattern) in Playwright for cleaner, more maintainable test automation.
What is the UIActions Pattern?
The UIActions pattern creates a single interaction gateway between your Page Objects and Playwright. Instead of scattering low-level Playwright calls (locator.click(), locator.fill()) throughout your codebase, all interactions flow through one unified, expressive interface.
Why Use UIActions?
Problem
Solution with UIActions
Duplicated wait logic across tests
Centralized auto-wait handling
Inconsistent error handling
Unified error messages with context
Scattered retry logic
Single place for retry configuration
Hard to add logging/screenshots
One place to add cross-cutting concerns
Page Objects become bloated
Page Objects focus on "what", UIActions handles "how"
Core Architecture
┌─────────────────────────────────────────────────────────┐
│ Test Files │
│ (describe what user does) │
└─────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Page Objects │
│ (map UI elements, define page actions) │
└─────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ UIActions │
│ (centralized interaction gateway - THE ONLY WAY │
│ Page Objects talk to Playwright) │
└─────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Specialized Action Classes │
│ EditBoxActions │ ButtonActions │ DropDownActions │ etc │
└─────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Playwright API │
└─────────────────────────────────────────────────────────┘
Implementation
1. Base Action Class
// actions/BaseAction.tsimport { Page, Locator } from'@playwright/test';
exportabstractclassBaseAction {
protectedpage: Page;
protecteddefaultTimeout: number;
constructor(page: Page, timeout: number = 30000) {
this.page = page;
this.defaultTimeout = timeout;
}
/**
* Wait for element to be visible before interacting
*/protectedasyncwaitForVisible(locator: Locator, timeout?: number): Promise<void> {
await locator.waitFor({
state: 'visible',
timeout: timeout ?? this.defaultTimeout,
});
}
/**
* Wait for element to be enabled
*/protectedasyncwaitForEnabled(locator: Locator, timeout?: number): Promise<void> {
await locator.waitFor({
state: 'attached',
timeout: timeout ?? this.defaultTimeout,
});
// Additional check for enabled stateconst isDisabled = await locator.isDisabled();
if (isDisabled) {
thrownewError(`Element is disabled: ${locator}`);
}
}
/**
* Scroll element into view
*/protectedasyncscrollIntoView(locator: Locator): Promise<void> {
await locator.scrollIntoViewIfNeeded();
}
/**
* Highlight element for debugging (optional)
*/protectedasynchighlight(locator: Locator): Promise<void> {
if (process.env.DEBUG_MODE === 'true') {
await locator.evaluate((el) => {
el.style.border = '3px solid red';
setTimeout(() => (el.style.border = ''), 2000);
});
}
}
/**
* Log action for debugging
*/protectedlog(action: string, details?: string): void {
if (process.env.DEBUG_MODE === 'true') {
console.log(`[UIAction] ${action}${details ? `: ${details}` : ''}`);
}
}
}
2. Specialized Action Classes
EditBoxActions (Text Inputs)
// actions/EditBoxActions.tsimport { Page, Locator } from'@playwright/test';
import { BaseAction } from'./BaseAction';
exportclassEditBoxActionsextendsBaseAction {
constructor(page: Page) {
super(page);
}
/**
* Fill text input with value
*/asyncfill(locator: Locator, value: string): Promise<void> {
this.log('Fill', `value: "${value}"`);
awaitthis.waitForVisible(locator);
awaitthis.scrollIntoView(locator);
await locator.fill(value);
}
/**
* Type text character by character (useful for autocomplete fields)
*/asynctype(locator: Locator, value: string, delay: number = 50): Promise<void> {
this.log('Type', `value: "${value}", delay: ${delay}ms`);
awaitthis.waitForVisible(locator);
awaitthis.scrollIntoView(locator);
await locator.pressSequentially(value, { delay });
}
/**
* Clear and fill
*/asyncclearAndFill(locator: Locator, value: string): Promise<void> {
this.log('ClearAndFill', `value: "${value}"`);
awaitthis.waitForVisible(locator);
awaitthis.scrollIntoView(locator);
await locator.clear();
await locator.fill(value);
}
/**
* Clear input field
*/asyncclear(locator: Locator): Promise<void> {
this.log('Clear');
awaitthis.waitForVisible(locator);
await locator.clear();
}
/**
* Get current value
*/asyncgetValue(locator: Locator): Promise<string> {
awaitthis.waitForVisible(locator);
return locator.inputValue();
}
/**
* Check if input is empty
*/asyncisEmpty(locator: Locator): Promise<boolean> {
const value = awaitthis.getValue(locator);
return value.trim() === '';
}
/**
* Fill with masked value (for passwords, sensitive data)
*/asyncfillSensitive(locator: Locator, value: string): Promise<void> {
this.log('Fill', 'value: [MASKED]');
awaitthis.waitForVisible(locator);
awaitthis.scrollIntoView(locator);
await locator.fill(value);
}
}