Imported from mrkanitkar/playwright-praman (
AGENTS.md). Install upstream withnpx skills add mrkanitkar/playwright-praman. Copyright stays with the author.
AGENTS.md — Universal Agent Instructions for Praman v1.0
For Plugin Contributors
If you are an AI agent working on the playwright-praman source code, follow these rules.
Architecture
- 5-layer: Core Infrastructure → Bridge Adapters → Typed Proxy → Fixtures → AI
- Layer dependency: lower layers NEVER import from higher layers
- All modules ≤ 300 LOC (document exceptions)
Rules
- TypeScript strict mode — no
any, noas unknown as T - Every public function: TSDoc +
@exampletag (TSDoc only, NOT JSDoc) - Every module ≤ 300 LOC
- Every error:
extends PramanError, includescode,attempted,retryable,suggestions[] - No
console.log— use pino:import { logger } from '#core/logging'; - No
page.waitForTimeout()— banned - Unit tests: hermetic, use Vitest, mock bridge interactions
- Config is
Readonly<PramanConfig>— never mutate - Imports: use
#core/*,#bridge/*,#proxy/*path aliases - Files: kebab-case. Types: PascalCase. Functions: camelCase. Constants: UPPER_CASE.
- All relative imports must include
.jsextension - Node builtins must use
node:prefix (node:path,node:fs) - ESM only in source —
import, neverrequire - No
Iprefix on interfaces —BridgeAdapternotIBridgeAdapter
Cross-Platform Requirements
- Always use
node:pathmethods — never hardcoded/or\ - Always use
node:fs/promisesfor async file operations - Use
import.meta.url+fileURLToPathfor__dirnameequivalent - No bash-only npm scripts — use Node.js built-ins (
fs.rmSync, notrm -rf) - Dual ESM+CJS build: validate with
npm run check:exports
Import Order
- Node built-ins (
node:path,node:fs) - External packages (
zod,pino) - Internal (
#core/,#bridge/,#proxy/) - Parent (
../) - Sibling (
./)
Error Pattern
throw new ControlError({
code: 'ERR_CONTROL_NOT_FOUND',
message: `Control not found: ${selector}`,
attempted: `Find control with selector: ${JSON.stringify(selector)}`,
retryable: true,
details: { selector, timeout: config.controlDiscoveryTimeout },
suggestions: [
'Verify the control ID exists in the UI5 view',
'Check if the page has fully loaded (waitForUI5Stable)',
'Try using controlType + properties instead of ID',
],
});
Testing
- Unit tests: Vitest, hermetic,
*.test.ts - Integration tests: Playwright,
*.spec.ts, usetest.step() - Coverage: Tiered (100% errors/API, 95% core, 90% global), per-file enforced via @vitest/coverage-v8
- Mock bridge with typed test doubles from
tests/helpers/
Commands
npm run lint— ESLint (0 errors, 0 warnings)npm run typecheck— tsc --noEmitnpm run test:unit— Vitest (hermetic)npm run build— tsup (ESM + CJS)npm run check:exports— attw export validationnpm run ci— lint + typecheck + test:unit + build
Commit Messages
Conventional Commits: feat(scope): description
Scopes: core, config, errors, logging, bridge, adapter, proxy, fixtures, auth, ai, intents, vocabulary, fe, reporters, cli, docs, ci, deps, release
Skill Files
For detailed domain knowledge, see skills/playwright-praman-sap-testing/:
| Task | Skill File |
|---|---|
| Architecture decisions | skills-architect.md |
| TypeScript implementation | skills-implementer.md |
| TDD, RED-GREEN-REFACTOR cycle | skills-tdd.md |
| Unit/integration tests | skills-tester.md |
| Playwright fixtures, selectors | skills-playwright-expert.md |
| SAP UI5 controls, FLP, OData | skills-sap-ui5-expert.md |
| SAP UI5 Web Components, Shadow DOM | skills-sap-ui5-webcomponents-expert.md |
| SAP Fiori E2E scenarios, auth | skills-sap-fiori-consultant.md |
| OData V2/V4, Gateway, mocks | skills-sap-odata-expert.md |
| PR review, quality gates | skills-reviewer.md |
| CI/CD, security, build | skills-security-build.md |
For Test Writers (Users of playwright-praman)
If you are an AI agent helping a user write SAP UI5 tests with playwright-praman, this section is for you.
What is Praman?
An Agent-First SAP UI5 Test Automation Plugin that extends Playwright with UI5-aware fixtures.
Install: npm i -D playwright-praman
Import: import { test, expect } from 'playwright-praman'
SAP pages are always hybrid — UI5 controls, Web Components, and plain DOM coexist on every page. A single test uses Praman fixtures for UI5 and Playwright native for everything else. The rule is per-element, not per-page.
The 7 Mandatory Rules
- UI5 controls (
sap.m.*,sap.ui.comp.*,sap.ui.mdc.*) →ui5.control()+ proxy methods ONLY - NEVER use Playwright native selectors for UI5 elements (
page.click('#__...'),page.locator('.sapM...')) - Non-UI5 elements (login forms, Web Components, custom HTML) → Playwright native (
page.locator(),page.getByRole()) import { test, expect } from 'playwright-praman'— the ONLY valid import- Auth via seed — raw Playwright auth in seed file, NEVER
sapAuth.login()in test body - Post-generation: scan against 16+ forbidden patterns before writing test
- TSDoc compliance header in every generated test
Test Template
/**
* {App Name} E2E Test
*
* COMPLIANCE: 100% Praman fixture-only
* Forbidden Pattern Scan: PASSED
*/
import { test, expect } from 'playwright-praman';
test.describe('{App Name} Tests', () => {
test('Complete scenario - single session', async ({
page,
ui5,
ui5Navigation,
ui5Footer,
intent,
fe,
}) => {
await test.step('Step 1: Navigate', async () => {
await ui5Navigation.navigateToTile('App Name');
await ui5.waitForUI5();
});
await test.step('Step 2: Fill form (gold pattern)', async () => {
const input = await ui5.control({ id: 'materialInput' });
await input.setValue('MAT-001');
await input.fireChange({ value: 'MAT-001' });
await ui5.waitForUI5();
});
await test.step('Step 3: Save and verify', async () => {
await ui5Footer.clickSave();
await ui5.dialog.confirm();
await intent.core.assertField('Status', 'Created');
});
});
});
Fixture Quick Reference
| Fixture | Key Methods |
|---|---|
ui5 |
control(), controls(), click(), fill(), waitForUI5(), waitFor() |
ui5.table |
getRows(id), clickRow(id, row), getCellValue(id, row, col), findRowByValues(id, values) |
ui5.dialog |
waitFor(), confirm(), dismiss(), getOpen() |
ui5.date |
setDatePicker(id, date), getDatePicker(id), setDateRange(id, start, end) |
ui5.odata |
getModelData(path), createEntity(url, set, data), queryEntities(url, set) |
ui5Navigation |
navigateToTile(title), navigateToIntent(intent), navigateBack(), navigateToHome() |
ui5Footer |
clickSave(), clickEdit(), clickCancel(), clickCreate(), clickDelete() |
fe.listReport |
setFilter(field, value), search(), clearFilters(), navigateToItem(row) |
fe.objectPage |
clickEdit(), clickSave(), navigateToSection(id), getSections() |
intent.core |
fillField(label, value), clickButton(text), selectOption(label, opt), assertField(label, expected) |
sapAuth |
login(page, config), loginFromEnv(page) — seed file only, NEVER in tests |
btpWorkZone |
switchTenant(id), getTenantId(), navigateWorkZone(space, page) — BTP Work Zone multi-tenant |
pramanAI |
discoverPage(), buildContext(), capabilities, recipes |
Forbidden Patterns
| Forbidden | Replacement |
|---|---|
page.click('#__...') |
ui5.control().press() |
page.fill('#__...') |
ui5.control().setValue() |
page.locator('[data-sap-ui]') |
ui5.control() |
page.locator('.sapM...') |
ui5.control({ controlType }) |
from '@playwright/test' |
from 'playwright-praman' |
page.waitForTimeout(...) |
ui5.waitForUI5() |
new UI5Handler(...) |
Use fixture ui5 directly |
Custom Matchers
Praman extends Playwright's expect() with UI5-aware matchers. All support auto-retry via timeout option.
| Matcher | Signature | Example |
|---|---|---|
toHaveUI5Text |
expect(proxy).toHaveUI5Text(expected, options?) |
await expect(btn).toHaveUI5Text('Save') |
toBeUI5Visible |
expect(proxy).toBeUI5Visible(options?) |
await expect(field).toBeUI5Visible() |
toBeUI5Enabled |
expect(proxy).toBeUI5Enabled(options?) |
await expect(btn).toBeUI5Enabled() |
toHaveUI5Property |
expect(proxy).toHaveUI5Property(prop, expected, options?) |
await expect(ctrl).toHaveUI5Property('value', '100') |
toHaveUI5ValueState |
expect(proxy).toHaveUI5ValueState(state, options?) |
await expect(field).toHaveUI5ValueState('Error') |
toHaveUI5Binding |
expect(proxy).toHaveUI5Binding(path, options?) |
await expect(field).toHaveUI5Binding('/Material') |
toBeUI5ControlType |
expect(proxy).toBeUI5ControlType(type, options?) |
await expect(ctrl).toBeUI5ControlType('sap.m.Input') |
toHaveUI5CellText |
expect(proxy).toHaveUI5CellText(row, col, expected, options?) |
await expect(table).toHaveUI5CellText(0, 1, 'Active') |
toHaveUI5RowCount |
expect(proxy).toHaveUI5RowCount(expected, options?) |
await expect(table).toHaveUI5RowCount(5) |
toHaveUI5SelectedRows |
expect(proxy).toHaveUI5SelectedRows(expected, options?) |
await expect(table).toHaveUI5SelectedRows([0, 2]) |
All matchers accept a ControlProxy (from ui5.control()) as the argument.
Error Self-Correction
Praman throws typed PramanError subclasses. On ControlError:
- Read
error.suggestions[]for specific fix advice - Read
error.availableControlsfor what is on screen - Read
error.suggestedSelectorfor Praman's best guess - Adjust your selector — do NOT fall back to
page.locator()
Deep Reference
For complete fixture maps, V2/V4 patterns, control type lookups, and selector shapes, read the full skill file:
node_modules/playwright-praman/skills/playwright-praman-sap-testing/SKILL.md