@schalkneethling/toolkit 0.2.0 → 0.3.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 +29 -7
- package/dist/index.mjs +90 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/skills/css-coder/SKILL.md +95 -0
- package/skills/css-coder/references/patterns.md +224 -0
- package/skills/css-tokens/README.md +152 -0
- package/skills/css-tokens/SKILL.md +125 -0
- package/skills/css-tokens/references/tokens.css +162 -0
- package/skills/frontend-security/SKILL.md +134 -0
- package/skills/frontend-security/references/csp-configuration.md +191 -0
- package/skills/frontend-security/references/csrf-protection.md +327 -0
- package/skills/frontend-security/references/dom-security.md +229 -0
- package/skills/frontend-security/references/file-upload-security.md +310 -0
- package/skills/frontend-security/references/framework-patterns.md +307 -0
- package/skills/frontend-security/references/input-validation.md +232 -0
- package/skills/frontend-security/references/jwt-security.md +300 -0
- package/skills/frontend-security/references/nodejs-npm-security.md +261 -0
- package/skills/frontend-security/references/xss-prevention.md +163 -0
- package/skills/frontend-testing/SKILL.md +357 -0
- package/skills/frontend-testing/references/accessibility-testing.md +368 -0
- package/skills/frontend-testing/references/aria-snapshots.md +517 -0
- package/skills/frontend-testing/references/locator-strategies.md +295 -0
- package/skills/frontend-testing/references/visual-regression.md +466 -0
- package/skills/refined-plan-mode/SKILL.md +84 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Locator Strategies
|
|
2
|
+
|
|
3
|
+
Locators determine how tests find elements. Good locators are resilient to implementation changes while verifying accessibility.
|
|
4
|
+
|
|
5
|
+
## Priority Hierarchy
|
|
6
|
+
|
|
7
|
+
Both Playwright and Testing Library recommend this order. Each level provides accessibility verification as a byproduct.
|
|
8
|
+
|
|
9
|
+
### 1. Accessible Roles (First Choice)
|
|
10
|
+
|
|
11
|
+
Query the accessibility tree. If this fails, the UI likely has accessibility issues.
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
// Playwright
|
|
15
|
+
page.getByRole("button", { name: /submit/i })
|
|
16
|
+
page.getByRole("textbox", { name: /email/i })
|
|
17
|
+
page.getByRole("heading", { level: 1 })
|
|
18
|
+
page.getByRole("navigation")
|
|
19
|
+
page.getByRole("listitem")
|
|
20
|
+
|
|
21
|
+
// Testing Library
|
|
22
|
+
screen.getByRole("button", { name: /submit/i })
|
|
23
|
+
screen.getByRole("checkbox", { checked: true })
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Why first**: Users and assistive technologies perceive the page through roles. Testing with roles validates both functionality and accessibility.
|
|
27
|
+
|
|
28
|
+
**Name option**: Filters by accessible name (visible text, aria-label, or aria-labelledby).
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
// Multiple buttons? Filter by name
|
|
32
|
+
page.getByRole("button", { name: /save/i }) // Save button
|
|
33
|
+
page.getByRole("button", { name: /cancel/i }) // Cancel button
|
|
34
|
+
page.getByRole("button", { name: /delete/i }) // Delete button
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Common roles**:
|
|
38
|
+
- `button`, `link`, `textbox`, `checkbox`, `radio`
|
|
39
|
+
- `combobox` (select), `listbox`, `option`
|
|
40
|
+
- `heading`, `navigation`, `main`, `article`
|
|
41
|
+
- `dialog`, `alert`, `alertdialog`
|
|
42
|
+
- `list`, `listitem`, `table`, `row`, `cell`
|
|
43
|
+
- `tab`, `tablist`, `tabpanel`
|
|
44
|
+
|
|
45
|
+
### 2. Label Text (Forms)
|
|
46
|
+
|
|
47
|
+
How users find form fields. Validates label-input association.
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
// Playwright
|
|
51
|
+
page.getByLabel("Email address")
|
|
52
|
+
page.getByLabel(/password/i)
|
|
53
|
+
|
|
54
|
+
// Testing Library
|
|
55
|
+
screen.getByLabelText("Email address")
|
|
56
|
+
screen.getByLabelText(/confirm password/i)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Why second**: Form users navigate by labels. Tests using labels fail if labels are missing or improperly associated—catching real accessibility bugs.
|
|
60
|
+
|
|
61
|
+
### 3. Placeholder Text (Fallback for Forms)
|
|
62
|
+
|
|
63
|
+
Use only when labels aren't available. Placeholder is not a label substitute.
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
page.getByPlaceholder("Search products...")
|
|
67
|
+
screen.getByPlaceholderText("Enter your query")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Why third**: Better than test IDs, but UI should have proper labels.
|
|
71
|
+
|
|
72
|
+
### 4. Visible Text (Non-Interactive Content)
|
|
73
|
+
|
|
74
|
+
For elements identified by their content.
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
// Playwright
|
|
78
|
+
page.getByText("Welcome back, Sarah")
|
|
79
|
+
page.getByText(/order confirmed/i)
|
|
80
|
+
|
|
81
|
+
// Testing Library
|
|
82
|
+
screen.getByText("No results found")
|
|
83
|
+
screen.getByText(/loading/i)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Why fourth**: Useful for assertions on content, but doesn't verify semantic structure.
|
|
87
|
+
|
|
88
|
+
### 5. Alt Text (Images)
|
|
89
|
+
|
|
90
|
+
For images with meaningful alt text.
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
page.getByAltText("Company logo")
|
|
94
|
+
screen.getByAltText(/product photo/i)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 6. Title Attribute
|
|
98
|
+
|
|
99
|
+
Rarely used. Most elements shouldn't rely on title for identification.
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
page.getByTitle("Close dialog")
|
|
103
|
+
screen.getByTitle(/help/i)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 7. Test IDs (Last Resort)
|
|
107
|
+
|
|
108
|
+
Escape hatch when semantic queries fail. Provides no accessibility verification.
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
// Playwright
|
|
112
|
+
page.getByTestId("pricing-calculator")
|
|
113
|
+
|
|
114
|
+
// Testing Library
|
|
115
|
+
screen.getByTestId("data-grid")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**When appropriate**:
|
|
119
|
+
- Complex dynamic components (data grids, charts)
|
|
120
|
+
- Elements with no stable accessible name
|
|
121
|
+
- Third-party components you can't modify
|
|
122
|
+
|
|
123
|
+
**When to avoid**:
|
|
124
|
+
- Any element that has text, label, or semantic role
|
|
125
|
+
- As a default choice to "make tests easier"
|
|
126
|
+
|
|
127
|
+
## Playwright-Specific Patterns
|
|
128
|
+
|
|
129
|
+
### Chaining and Filtering
|
|
130
|
+
|
|
131
|
+
Narrow down when multiple matches exist:
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
// Find delete button in specific row
|
|
135
|
+
const userRow = page.getByRole("row").filter({ hasText: "john@example.com" });
|
|
136
|
+
await userRow.getByRole("button", { name: /delete/i }).click();
|
|
137
|
+
|
|
138
|
+
// Combine locators
|
|
139
|
+
const submitButton = page
|
|
140
|
+
.getByRole("button", { name: /submit/i })
|
|
141
|
+
.and(page.locator("[type=submit]"));
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Scoped Queries with `within`
|
|
145
|
+
|
|
146
|
+
Test elements within a container:
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
// Playwright
|
|
150
|
+
const form = page.getByRole("form", { name: /checkout/i });
|
|
151
|
+
await form.getByLabel("Card number").fill("4111111111111111");
|
|
152
|
+
|
|
153
|
+
// Testing Library
|
|
154
|
+
import { within } from "@testing-library/react";
|
|
155
|
+
const form = screen.getByRole("form");
|
|
156
|
+
within(form).getByLabelText("Email");
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Handling Multiple Elements
|
|
160
|
+
|
|
161
|
+
When strict matching fails:
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
// Get all, then filter
|
|
165
|
+
const buttons = await page.getByRole("button").all();
|
|
166
|
+
const buttonTexts = await Promise.all(
|
|
167
|
+
buttons.map(button => button.textContent())
|
|
168
|
+
);
|
|
169
|
+
const deleteButtons = buttons.filter((button, index) =>
|
|
170
|
+
buttonTexts[index]?.includes("Delete")
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Or be more specific with the query
|
|
174
|
+
page.getByRole("button", { name: "Delete", exact: true });
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Testing Library Variants
|
|
178
|
+
|
|
179
|
+
### get vs query vs find
|
|
180
|
+
|
|
181
|
+
| Variant | No match | Multiple matches | Async |
|
|
182
|
+
|---------|----------|------------------|-------|
|
|
183
|
+
| `getBy` | Throws | Throws | No |
|
|
184
|
+
| `queryBy` | Returns null | Throws | No |
|
|
185
|
+
| `findBy` | Throws | Throws | Yes (waits) |
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
// Element should exist now
|
|
189
|
+
screen.getByRole("button", { name: /submit/i });
|
|
190
|
+
|
|
191
|
+
// Element might not exist (testing absence)
|
|
192
|
+
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
|
193
|
+
|
|
194
|
+
// Element appears after async action
|
|
195
|
+
await screen.findByRole("alert");
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Regex vs String Matching
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
// Exact match (case-sensitive)
|
|
202
|
+
screen.getByText("Submit Order");
|
|
203
|
+
|
|
204
|
+
// Flexible match (recommended for resilience)
|
|
205
|
+
screen.getByText(/submit order/i);
|
|
206
|
+
|
|
207
|
+
// Partial match
|
|
208
|
+
screen.getByText(/submit/i);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Anti-Patterns
|
|
212
|
+
|
|
213
|
+
### CSS Selectors as Primary Strategy
|
|
214
|
+
|
|
215
|
+
```javascript
|
|
216
|
+
// BAD: Tied to implementation
|
|
217
|
+
page.locator(".btn-primary");
|
|
218
|
+
page.locator("#submit-form-btn");
|
|
219
|
+
page.locator("div.container > form > button");
|
|
220
|
+
|
|
221
|
+
// GOOD: Tied to user experience
|
|
222
|
+
page.getByRole("button", { name: /submit/i });
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### XPath
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
// BAD: Brittle, hard to read
|
|
229
|
+
page.locator("//div[@class='form']//button[text()='Submit']");
|
|
230
|
+
|
|
231
|
+
// GOOD: Clear and semantic
|
|
232
|
+
page.getByRole("button", { name: /submit/i });
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Position-Based Selection
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
// BAD: Breaks when order changes
|
|
239
|
+
page.getByRole("button").nth(2);
|
|
240
|
+
|
|
241
|
+
// GOOD: Explicit about which element
|
|
242
|
+
page.getByRole("button", { name: /delete account/i });
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Test IDs for Everything
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
// BAD: No accessibility verification
|
|
249
|
+
page.getByTestId("submit-button");
|
|
250
|
+
page.getByTestId("email-input");
|
|
251
|
+
page.getByTestId("error-message");
|
|
252
|
+
|
|
253
|
+
// GOOD: Validates accessibility
|
|
254
|
+
page.getByRole("button", { name: /submit/i });
|
|
255
|
+
page.getByLabel("Email");
|
|
256
|
+
page.getByRole("alert");
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Debugging Locators
|
|
260
|
+
|
|
261
|
+
### Playwright
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
// Log all accessible elements
|
|
265
|
+
await page.getByRole("button").evaluateAll(buttons =>
|
|
266
|
+
buttons.map(b => b.textContent)
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// Use Playwright Inspector
|
|
270
|
+
// npx playwright test --debug
|
|
271
|
+
|
|
272
|
+
// Use codegen for suggestions
|
|
273
|
+
// npx playwright codegen example.com
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Testing Library
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
// Log the DOM
|
|
280
|
+
screen.debug();
|
|
281
|
+
|
|
282
|
+
// Log specific element
|
|
283
|
+
screen.debug(screen.getByRole("form"));
|
|
284
|
+
|
|
285
|
+
// Use Testing Playground
|
|
286
|
+
// testing-playground.com
|
|
287
|
+
// Or browser extension
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## References
|
|
291
|
+
|
|
292
|
+
- [Playwright Locators](https://playwright.dev/docs/locators)
|
|
293
|
+
- [Testing Library Queries](https://testing-library.com/docs/queries/about/)
|
|
294
|
+
- [ARIA Roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles)
|
|
295
|
+
- [Testing Library Priority Guide](https://testing-library.com/docs/queries/about/#priority)
|