| name | action-utilities |
| description | 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
import { Page, Locator } from '@playwright/test';
export abstract class BaseAction {
protected page: Page;
protected defaultTimeout: number;
constructor(page: Page, timeout: number = 30000) {
this.page = page;
this.defaultTimeout = timeout;
}
protected async waitForVisible(locator: Locator, timeout?: number): Promise<void> {
await locator.waitFor({
state: 'visible',
timeout: timeout ?? this.defaultTimeout,
});
}
protected async waitForEnabled(locator: Locator, timeout?: ): <> {
locator.({
: ,
: timeout ?? .,
});
isDisabled = locator.();
(isDisabled) {
();
}
}
(: ): <> {
locator.();
}
(: ): <> {
(process.. === ) {
locator.( {
el.. = ;
( (el.. = ), );
});
}
}
(: , ?: ): {
(process.. === ) {
.();
}
}
}
2. Specialized Action Classes
EditBoxActions (Text Inputs)
import { Page, Locator } from '@playwright/test';
import { BaseAction } from './BaseAction';
export class EditBoxActions extends BaseAction {
constructor(page: Page) {
super(page);
}
async fill(locator: Locator, value: string): Promise<void> {
this.log('Fill', `value: "${value}"`);
await this.waitForVisible(locator);
await this.scrollIntoView(locator);
await locator.fill(value);
}
async type(locator: Locator, value: string, delay: = ): <> {
.(, );
.(locator);
.(locator);
locator.(value, { delay });
}
(: , : ): <> {
.(, );
.(locator);
.(locator);
locator.();
locator.(value);
}
(: ): <> {
.();
.(locator);
locator.();
}
(: ): <> {
.(locator);
locator.();
}
(: ): <> {
value = .(locator);
value.() === ;
}
(: , : ): <> {
.(, );
.(locator);
.(locator);
locator.(value);
}
}
ButtonActions (Clickable Elements)
import { Page, Locator } from '@playwright/test';
import { BaseAction } from './BaseAction';
export class ButtonActions extends BaseAction {
constructor(page: Page) {
super(page);
}
async click(locator: Locator): Promise<void> {
this.log('Click');
await this.waitForVisible(locator);
await this.waitForEnabled(locator);
await this.scrollIntoView(locator);
await locator.click();
}
async doubleClick(locator: Locator): Promise<void> {
this.log();
.(locator);
.(locator);
locator.();
}
(: ): <> {
.();
.(locator);
.(locator);
locator.({ : });
}
(: ): <> {
.();
.(locator);
.(locator);
.([
..(),
locator.(),
]);
}
(: ): <> {
.();
.(locator);
locator.();
..();
}
(: ): <> {
.();
locator.({ : });
}
(: ): <> {
.();
.(locator);
locator.();
}
(: ): <> {
{
locator.({ : , : });
isEnabled = locator.();
isEnabled;
} {
;
}
}
}
CheckboxActions
import { Page, Locator } from '@playwright/test';
import { BaseAction } from './BaseAction';
export class CheckboxActions extends BaseAction {
constructor(page: Page) {
super(page);
}
async check(locator: Locator): Promise<void> {
this.log('Check');
await this.waitForVisible(locator);
await this.scrollIntoView(locator);
await locator.check();
}
async uncheck(locator: Locator): Promise<void> {
this.log('Uncheck');
await this.(locator);
.(locator);
locator.();
}
(: , : ): <> {
.(, );
.(locator);
.(locator);
locator.(checked);
}
(: ): <> {
.();
isChecked = .(locator);
.(locator, !isChecked);
}
(: ): <> {
.(locator);
locator.();
}
}
DropdownActions
import { Page, Locator } from '@playwright/test';
import { BaseAction } from './BaseAction';
export class DropdownActions extends BaseAction {
constructor(page: Page) {
super(page);
}
async selectByText(locator: Locator, text: string): Promise<void> {
this.log('SelectByText', `text: "${text}"`);
await this.waitForVisible(locator);
await this.scrollIntoView(locator);
await locator.selectOption({ label: text });
}
async selectByValue(locator: Locator, value: string): <> {
.(, );
.(locator);
.(locator);
locator.({ value });
}
(: , : ): <> {
.(, );
.(locator);
.(locator);
locator.({ index });
}
(: , : []): <> {
.(, );
.(locator);
locator.(values);
}
(: ): <> {
.(locator);
locator.( {
select.[select.]?. ?? ;
});
}
(: ): <> {
.(locator);
locator.();
}
(: ): <[]> {
.(locator);
locator.( {
.(select.).( option.);
});
}
}
UIElementActions (Generic Elements)
import { Page, Locator } from '@playwright/test';
import { BaseAction } from './BaseAction';
export class UIElementActions extends BaseAction {
constructor(page: Page) {
super(page);
}
async getText(locator: Locator): Promise<string> {
await this.waitForVisible(locator);
return (await locator.textContent()) ?? '';
}
async getInnerText(locator: Locator): Promise<string> {
await this.waitForVisible(locator);
return locator.innerText();
}
async getAttribute(: , : ): < | > {
.(locator);
locator.(attribute);
}
(: , ?: ): <> {
{
locator.({ : , : timeout ?? });
;
} {
;
}
}
(: ): <> {
locator.();
}
(: ): <> {
locator.();
}
(: , ?: ): <> {
locator.({
: ,
: timeout ?? .,
});
}
(: , ?: ): <> {
locator.({
: ,
: timeout ?? .,
});
}
(: ): <> {
locator.();
}
(: ): <[]> {
locator.();
}
(: , : ): <> {
.(locator);
locator.( {
.(el).(prop);
}, property);
}
(: , : ): <> {
classAttr = .(locator, );
classAttr?.(className) ?? ;
}
}
PageActions (Page-Level Operations)
import { Page } from '@playwright/test';
import { BaseAction } from './BaseAction';
export class PageActions extends BaseAction {
constructor(page: Page) {
super(page);
}
async navigate(url: string): Promise<void> {
this.log('Navigate', url);
await this.page.goto(url);
}
async navigateAndWait(url: string): Promise<void> {
this.log('NavigateAndWait', url);
await this.page.goto(url, { waitUntil: 'networkidle' });
}
async (): <> {
.();
..();
}
(): <> {
.();
..();
}
(): <> {
.();
..();
}
(): {
..();
}
(): <> {
..();
}
(): <> {
..();
}
(): <> {
..();
}
(): <> {
..();
}
(: ): <> {
.(, name);
..({
: ,
: ,
});
}
(): <> {
..(, dialog.());
}
(): <> {
..(, dialog.());
}
(: ): <> {
.(, key);
...(key);
}
(): <> {
..( .(, ));
}
(): <> {
..( .(, ..));
}
}
3. Main UIActions Class (The Gateway)
import { Page } from '@playwright/test';
import { EditBoxActions } from './EditBoxActions';
import { ButtonActions } from './ButtonActions';
import { CheckboxActions } from './CheckboxActions';
import { DropdownActions } from './DropdownActions';
import { UIElementActions } from './UIElementActions';
import { PageActions } from './PageActions';
export class UIActions {
private page: Page;
private _editBox: EditBoxActions;
private _button: ButtonActions;
private _checkbox: ;
: ;
: ;
: ;
() {
. = page;
. = (page);
. = (page);
. = (page);
. = (page);
. = (page);
. = (page);
}
(): {
.;
}
(): {
.;
}
(): {
.;
}
(): {
.;
}
(): {
.;
}
(): {
.;
}
(): {
.;
}
}
Using UIActions with Page Objects
Before (Without UIActions)
class LoginPage {
constructor(private page: Page) {}
readonly emailInput = this.page.getByLabel('Email');
readonly passwordInput = this.page.getByLabel('Password');
readonly loginButton = this.page.getByRole('button', { name: 'Sign in' });
async login(email: string, password: string) {
await this.emailInput.waitFor({ state: 'visible' });
await this.emailInput.fill(email);
await this.passwordInput.waitFor({ state: 'visible' });
await this.passwordInput.(password);
..({ : });
..();
..();
}
}
After (With UIActions)
class LoginPage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
constructor(private page: Page, private ui: UIActions) {
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.loginButton = page.getByRole('button', { name: 'Sign in' });
}
async login(email: string, password: string) {
await this.ui.editBox().fill(this.emailInput, email);
await this.ui.().(., password);
..().(.);
}
(): <> {
..().(.);
}
}
Setting Up UIActions with Fixtures
import { test as base } from '@playwright/test';
import { UIActions } from '../actions/UIActions';
type UIFixtures = {
ui: UIActions;
};
export const test = base.extend<UIFixtures>({
ui: async ({ page }, use) => {
const ui = new UIActions(page);
await use(ui);
},
});
export { expect } from '@playwright/test';
import { test, expect } from '../fixtures/ui.fixture';
import { LoginPage } from '../pages/LoginPage';
test('user can login', async ({ page, ui }) => {
const loginPage = new LoginPage(page, ui);
await ui.pageAction().navigate('/login');
await loginPage.login('user@example.com', 'password');
await expect(page).toHaveURL('/dashboard');
});
Folder Structure
your-project/
├── actions/
│ ├── BaseAction.ts # Abstract base class
│ ├── EditBoxActions.ts # Text input actions
│ ├── ButtonActions.ts # Click actions
│ ├── CheckboxActions.ts # Checkbox/radio actions
│ ├── DropdownActions.ts # Select actions
│ ├── UIElementActions.ts # Generic element actions
│ ├── PageActions.ts # Page-level actions
│ ├── UIActions.ts # Main gateway class
│ └── index.ts # Barrel exports
├── pages/
│ ├── BasePage.ts
│ ├── LoginPage.ts
│ └── ...
├── fixtures/
│ └── ui.fixture.ts
└── tests/
└── ...
Best Practices
Do's
Don'ts
Rules for Page Objects (When Using UIActions)
- Never write raw Playwright code - No
page.locator(), locator.click(), expect() in Page Objects
- Never write inline selectors - Define all locators as class properties
- Never hardcode values - Use parameters or test data
- All interactions through UIActions - No exceptions
Related Resources