pw-element-interactions 0.0.8 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -79
- package/dist/fixture/BaseFixture.js +10 -0
- package/dist/interactions/Interaction.js +5 -4
- package/dist/interactions/Navigation.d.ts +8 -4
- package/dist/interactions/Navigation.js +17 -5
- package/dist/interactions/Verification.js +6 -6
- package/dist/logger/Logger.d.ts +25 -0
- package/dist/logger/Logger.js +66 -0
- package/dist/steps/CommonSteps.d.ts +0 -145
- package/dist/steps/CommonSteps.js +38 -172
- package/dist/utils/ElementUtilities.js +3 -3
- package/package.json +12 -4
- package/scripts/postinstall.js +25 -0
- package/skills/pw-element-interactions.md +235 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pw-element-interactions
|
|
3
|
+
description: >
|
|
4
|
+
Use this skill whenever writing, editing, or generating Playwright tests! Triggers on any mention of
|
|
5
|
+
Playwright tests, pw-element-interactions or pw-element-repository packages, the Steps API, ElementRepository, ElementInteractions, baseFixture,
|
|
6
|
+
ContextStore, page-repository.json, or any request to write, fix, or add to a Playwright test in this project.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# pw-element-interactions — Agent Skill
|
|
10
|
+
|
|
11
|
+
A two-package Playwright framework that fully decouples **element acquisition** (`pw-element-repository`) from **element interaction** (`pw-element-interactions`). Tests reference elements by plain strings (`'HomePage'`, `'submitButton'`); raw selectors never appear in test code.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 🚨 ABSOLUTE RULES — READ BEFORE DOING ANYTHING ELSE
|
|
16
|
+
|
|
17
|
+
These rules are non-negotiable and override any perceived helpfulness or initiative:
|
|
18
|
+
|
|
19
|
+
### 1. NEVER write tests unless explicitly asked
|
|
20
|
+
- NEVER create, write, or scaffold a test file unless the user has directly asked for it in this conversation.
|
|
21
|
+
- NEVER infer that tests are needed from context, file structure, or prior messages.
|
|
22
|
+
- If unsure whether the user wants a test written, **ask first. Do not write first.**
|
|
23
|
+
- When asked to write tests, ALWAYS start by producing a brief plan — test file(s), scenarios, and locators needed — and wait for the user to approve it before writing anything.
|
|
24
|
+
- If the plan covers more than one test file, suggest splitting into separate sessions (one per file) before proceeding.
|
|
25
|
+
|
|
26
|
+
### 2. NEVER edit `page-repository.json` without explicit permission
|
|
27
|
+
- NEVER add, modify, or delete entries in `page-repository.json` (or any locator JSON file) without the user explicitly approving the change.
|
|
28
|
+
- If new locators are needed, **show the user exactly what you intend to add** and wait for a clear "yes" before touching the file.
|
|
29
|
+
|
|
30
|
+
### 3. NEVER invent selectors — use Playwright MCP to inspect the live site
|
|
31
|
+
- NEVER guess or invent CSS selectors, XPath, IDs, or text values.
|
|
32
|
+
- ALWAYS use the Playwright MCP to navigate to the page and inspect the real DOM before adding any locator.
|
|
33
|
+
- If the Playwright MCP is not connected, stop and tell the user: *"I need the Playwright MCP to inspect the site. Please add it to your Claude Code MCP settings and restart."* Do not proceed until it is available.
|
|
34
|
+
|
|
35
|
+
### 4. NEVER invent type definitions or API shapes
|
|
36
|
+
- NEVER create `.d.ts` stubs or type shims for `pw-element-interactions` or `pw-element-repository`.
|
|
37
|
+
- If a type is missing, report the problem to the user and ask how to proceed. Do not work around it silently.
|
|
38
|
+
|
|
39
|
+
### 5. ALWAYS inspect a screenshot when a test fails
|
|
40
|
+
- The base fixture automatically captures a `failure-screenshot` on every failed test — run `npx playwright show-report` and open the report in a browser using Playwright MCP or a browser MCP to view it.
|
|
41
|
+
- Ensure `reporter: 'html'` is set in `playwright.config.ts` — this is required for `failure-screenshot` attachments to appear in the report.
|
|
42
|
+
- If the report is not accessible, use the Playwright MCP to take a screenshot of the current page state manually.
|
|
43
|
+
- NEVER attempt to fix a failing test based solely on the error message or stack trace — always verify visually first.
|
|
44
|
+
- Describe what you see in the screenshot to the user, then propose a fix based on the visual evidence.
|
|
45
|
+
- If the screenshot suggests a selector problem, re-inspect the live DOM via Playwright MCP before touching `page-repository.json`.
|
|
46
|
+
- After a fully passing run, do NOT open the report unless the user asks.
|
|
47
|
+
|
|
48
|
+
### 6. Before creating or modifying `playwright.config.ts`, read the existing file first — do not overwrite it.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 1. Adding Locators
|
|
53
|
+
|
|
54
|
+
All selectors live in `tests/data/page-repository.json`.
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"pages": [
|
|
59
|
+
{
|
|
60
|
+
"name": "HomePage",
|
|
61
|
+
"elements": [
|
|
62
|
+
{
|
|
63
|
+
"elementName": "submitButton",
|
|
64
|
+
"selector": {
|
|
65
|
+
"css": "button[data-test='submit']",
|
|
66
|
+
"xpath": "//button[@data-test='submit']",
|
|
67
|
+
"id": "submit-btn",
|
|
68
|
+
"text": "Submit"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Naming conventions:**
|
|
78
|
+
- `name` — PascalCase page identifier, e.g. `CheckoutPage`, `ProductDetailsPage`
|
|
79
|
+
- `elementName` — camelCase element identifier, e.g. `submitButton`, `galleryImages`
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 2. Setup — Fixtures
|
|
84
|
+
|
|
85
|
+
Before writing `tests/fixtures/base.ts`, **read it first if it already exists** — do not overwrite it without checking. The `baseFixture` automatically includes screenshot-on-failure capture, so no extension is needed:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
// tests/fixtures/base.ts
|
|
89
|
+
import { test as base } from '@playwright/test';
|
|
90
|
+
import { baseFixture } from 'pw-element-interactions';
|
|
91
|
+
|
|
92
|
+
export const test = baseFixture(base, 'tests/data/page-repository.json');
|
|
93
|
+
export { expect } from '@playwright/test';
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// tests/example.spec.ts
|
|
98
|
+
import { test } from '../fixtures/base';
|
|
99
|
+
|
|
100
|
+
test('example', async ({ steps }) => {
|
|
101
|
+
await steps.navigateTo('https://example.com/');
|
|
102
|
+
await steps.click('HomePage', 'submitButton');
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 3. Steps API
|
|
109
|
+
|
|
110
|
+
Every method takes `pageName` and `elementName` as its first two arguments, matching keys in your JSON file.
|
|
111
|
+
|
|
112
|
+
### 🧭 Navigation
|
|
113
|
+
|
|
114
|
+
Relative URLs are resolved against `baseURL` from `playwright.config.ts`. If a relative URL is passed and `baseURL` is not configured, an error will be thrown.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
await steps.navigateTo('https://example.com/path');
|
|
118
|
+
await steps.refresh();
|
|
119
|
+
await steps.backOrForward('BACKWARDS'); // or 'FORWARDS'
|
|
120
|
+
await steps.setViewport(1280, 720);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 🖱️ Interaction
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
await steps.click('PageName', 'elementName');
|
|
127
|
+
await steps.clickWithoutScrolling('PageName', 'elementName');
|
|
128
|
+
await steps.clickIfPresent('PageName', 'elementName');
|
|
129
|
+
await steps.clickRandom('PageName', 'elementName');
|
|
130
|
+
await steps.hover('PageName', 'elementName');
|
|
131
|
+
await steps.scrollIntoView('PageName', 'elementName');
|
|
132
|
+
await steps.fill('PageName', 'elementName', 'my input');
|
|
133
|
+
await steps.typeSequentially('PageName', 'elementName', 'my input');
|
|
134
|
+
await steps.typeSequentially('PageName', 'elementName', 'my input', 50); // custom delay ms
|
|
135
|
+
await steps.uploadFile('PageName', 'elementName', 'tests/fixtures/file.pdf');
|
|
136
|
+
await steps.dragAndDrop('PageName', 'elementName', { target: otherLocator });
|
|
137
|
+
await steps.dragAndDrop('PageName', 'elementName', { xOffset: 100, yOffset: 0 });
|
|
138
|
+
await steps.dragAndDropListedElement('PageName', 'elementName', 'Item Label', { target: otherLocator });
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
For dropdown selection, import `DropdownSelectType` at the top of your test file:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { DropdownSelectType } from 'pw-element-interactions';
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Then use it in your test:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
const value1 = await steps.selectDropdown('PageName', 'elementName'); // random (default)
|
|
151
|
+
const value2 = await steps.selectDropdown('PageName', 'elementName', { type: DropdownSelectType.RANDOM }); // explicit random
|
|
152
|
+
const value3 = await steps.selectDropdown('PageName', 'elementName', { type: DropdownSelectType.VALUE, value: 'xl' }); // by value
|
|
153
|
+
const value4 = await steps.selectDropdown('PageName', 'elementName', { type: DropdownSelectType.INDEX, index: 2 }); // by index
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 📊 Data Extraction
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
const text = await steps.getText('PageName', 'elementName');
|
|
160
|
+
const href = await steps.getAttribute('PageName', 'elementName', 'href');
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### ✅ Verification
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
await steps.verifyPresence('PageName', 'elementName');
|
|
167
|
+
await steps.verifyAbsence('PageName', 'elementName');
|
|
168
|
+
await steps.verifyText('PageName', 'elementName', 'Expected text');
|
|
169
|
+
await steps.verifyText('PageName', 'elementName', undefined, { notEmpty: true });
|
|
170
|
+
await steps.verifyCount('PageName', 'elementName', { exact: 3 });
|
|
171
|
+
await steps.verifyCount('PageName', 'elementName', { greaterThan: 0 });
|
|
172
|
+
await steps.verifyCount('PageName', 'elementName', { lessThan: 10 });
|
|
173
|
+
await steps.verifyImages('PageName', 'elementName');
|
|
174
|
+
await steps.verifyImages('PageName', 'elementName', false); // skip scroll-into-view
|
|
175
|
+
await steps.verifyUrlContains('/dashboard');
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### ⏳ Waiting
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
await steps.waitForState('PageName', 'elementName'); // default: 'visible'
|
|
182
|
+
await steps.waitForState('PageName', 'elementName', 'hidden');
|
|
183
|
+
await steps.waitForState('PageName', 'elementName', 'attached');
|
|
184
|
+
await steps.waitForState('PageName', 'elementName', 'detached');
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 4. Accessing the Repository Directly
|
|
190
|
+
|
|
191
|
+
Use `repo` when you need to filter by visible text, iterate all matches, or pick a random item:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
test('navigate to Forms', async ({ page, repo, steps }) => {
|
|
195
|
+
await steps.navigateTo('https://example.com/');
|
|
196
|
+
const formsLink = await repo.getByText(page, 'HomePage', 'categories', 'Forms');
|
|
197
|
+
await formsLink?.click();
|
|
198
|
+
await steps.verifyAbsence('HomePage', 'categories');
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Repository API
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
await repo.get(page, 'PageName', 'elementName');
|
|
206
|
+
await repo.getAll(page, 'PageName', 'elementName');
|
|
207
|
+
await repo.getRandom(page, 'PageName', 'elementName');
|
|
208
|
+
await repo.getByText(page, 'PageName', 'elementName', 'Desired Text');
|
|
209
|
+
repo.getSelector('PageName', 'elementName'); // sync, returns raw selector string
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### ⚠️ NEVER use index-based element access — always target by context
|
|
215
|
+
|
|
216
|
+
NEVER access elements by hardcoded index (e.g. `elements[1]`, `elements[3]`). Order can change and will silently break tests. Always identify elements by visible text, labels, attributes, or sibling content:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
// ❌ Fragile — breaks if order changes
|
|
220
|
+
const nameCell = tableRows[1]?.locator('td:first-child');
|
|
221
|
+
|
|
222
|
+
// ✅ Robust — finds the element by its meaningful label
|
|
223
|
+
const nameRow = await repo.getByText(page, 'FormsPage', 'submissionEntries', 'Name');
|
|
224
|
+
const nameValue = await nameRow?.locator('td:nth-child(2)').textContent();
|
|
225
|
+
expect(nameValue?.trim()).toBe(testData.name);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Before writing any verification logic against a list or table, inspect the live page via Playwright MCP to understand the structure and identify the most stable way to target each element. If no meaningful context exists to distinguish elements, stop and ask the user how to proceed.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 5. Workflow
|
|
233
|
+
|
|
234
|
+
- After any fix, feature, or test is confirmed working, run a `git commit` with a clear message before moving on.
|
|
235
|
+
- Do not batch multiple successes into a single commit.
|