@schalkneethling/toolkit 0.1.5 → 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.
Files changed (30) hide show
  1. package/README.md +29 -7
  2. package/dist/index.mjs +90 -6
  3. package/dist/index.mjs.map +1 -1
  4. package/hooks/auto-approve-safe-commands/hook.mjs +134 -0
  5. package/hooks/auto-approve-safe-commands/hook.mts +188 -0
  6. package/hooks/auto-approve-safe-commands/settings-fragment.json +17 -0
  7. package/hooks/block-dangerous-commands/hook.mjs +3 -3
  8. package/hooks/block-dangerous-commands/hook.mts +23 -10
  9. package/package.json +8 -10
  10. package/skills/css-coder/SKILL.md +95 -0
  11. package/skills/css-coder/references/patterns.md +224 -0
  12. package/skills/css-tokens/README.md +152 -0
  13. package/skills/css-tokens/SKILL.md +125 -0
  14. package/skills/css-tokens/references/tokens.css +162 -0
  15. package/skills/frontend-security/SKILL.md +134 -0
  16. package/skills/frontend-security/references/csp-configuration.md +191 -0
  17. package/skills/frontend-security/references/csrf-protection.md +327 -0
  18. package/skills/frontend-security/references/dom-security.md +229 -0
  19. package/skills/frontend-security/references/file-upload-security.md +310 -0
  20. package/skills/frontend-security/references/framework-patterns.md +307 -0
  21. package/skills/frontend-security/references/input-validation.md +232 -0
  22. package/skills/frontend-security/references/jwt-security.md +300 -0
  23. package/skills/frontend-security/references/nodejs-npm-security.md +261 -0
  24. package/skills/frontend-security/references/xss-prevention.md +163 -0
  25. package/skills/frontend-testing/SKILL.md +357 -0
  26. package/skills/frontend-testing/references/accessibility-testing.md +368 -0
  27. package/skills/frontend-testing/references/aria-snapshots.md +517 -0
  28. package/skills/frontend-testing/references/locator-strategies.md +295 -0
  29. package/skills/frontend-testing/references/visual-regression.md +466 -0
  30. package/skills/refined-plan-mode/SKILL.md +84 -0
@@ -0,0 +1,368 @@
1
+ # Accessibility Testing
2
+
3
+ Automated accessibility testing catches common issues early. Combine with manual testing and assistive technology validation for comprehensive coverage.
4
+
5
+ ## Limitations of Automated Testing
6
+
7
+ Automated tools detect approximately 30-40% of accessibility issues. They catch:
8
+ - Missing labels and alt text
9
+ - Color contrast violations
10
+ - Invalid ARIA attributes
11
+ - Duplicate IDs
12
+ - Missing landmark regions
13
+
14
+ They cannot catch:
15
+ - Logical reading order
16
+ - Meaningful link text in context
17
+ - Appropriate focus management
18
+ - Keyboard trap issues in complex flows
19
+ - Whether content is actually understandable
20
+
21
+ **Always combine automated testing with manual review and user testing.**
22
+
23
+ ## Playwright + axe-core Integration
24
+
25
+ ### Setup
26
+
27
+ ```bash
28
+ npm install @axe-core/playwright --save-dev
29
+ ```
30
+
31
+ ### Basic Page Scan
32
+
33
+ ```javascript
34
+ import { test, expect } from "@playwright/test";
35
+ import AxeBuilder from "@axe-core/playwright";
36
+
37
+ test.describe("Homepage", () => {
38
+ test("has no automatically detectable accessibility violations", async ({ page }) => {
39
+ await page.goto("/");
40
+
41
+ const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
42
+
43
+ expect(accessibilityScanResults.violations).toEqual([]);
44
+ });
45
+ });
46
+ ```
47
+
48
+ ### Wait for Page State
49
+
50
+ Always ensure the page is in the expected state before scanning:
51
+
52
+ ```javascript
53
+ test("navigation menu is accessible when open", async ({ page }) => {
54
+ await page.goto("/");
55
+
56
+ // Open the menu
57
+ await page.getByRole("button", { name: /menu/i }).click();
58
+
59
+ // Wait for menu to be visible before scanning
60
+ await page.getByRole("navigation", { name: /main/i }).waitFor();
61
+
62
+ const results = await new AxeBuilder({ page }).analyze();
63
+
64
+ expect(results.violations).toEqual([]);
65
+ });
66
+ ```
67
+
68
+ ### Scan Specific Regions
69
+
70
+ Focus on components you're testing:
71
+
72
+ ```javascript
73
+ test("checkout form is accessible", async ({ page }) => {
74
+ await page.goto("/checkout");
75
+
76
+ const results = await new AxeBuilder({ page })
77
+ .include("#checkout-form") // Only scan this region
78
+ .analyze();
79
+
80
+ expect(results.violations).toEqual([]);
81
+ });
82
+ ```
83
+
84
+ ### Exclude Known Issues
85
+
86
+ Temporarily exclude elements while fixing issues:
87
+
88
+ ```javascript
89
+ const results = await new AxeBuilder({ page })
90
+ .exclude("#third-party-widget") // Can't control this
91
+ .exclude("[data-ad-unit]") // Ads managed externally
92
+ .analyze();
93
+ ```
94
+
95
+ **Important**: Document why exclusions exist. Remove them when fixed.
96
+
97
+ ## WCAG Targeting
98
+
99
+ ### Target Specific WCAG Levels
100
+
101
+ ```javascript
102
+ // WCAG 2.1 Level AA (most common compliance target)
103
+ const results = await new AxeBuilder({ page })
104
+ .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"])
105
+ .analyze();
106
+
107
+ // WCAG 2.2 (latest standard)
108
+ const results = await new AxeBuilder({ page })
109
+ .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"])
110
+ .analyze();
111
+
112
+ // Best practices (not WCAG requirements but recommended)
113
+ const results = await new AxeBuilder({ page })
114
+ .withTags(["best-practice"])
115
+ .analyze();
116
+ ```
117
+
118
+ ### Common Tag Sets
119
+
120
+ | Target | Tags |
121
+ |--------|------|
122
+ | WCAG 2.1 AA | `["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"]` |
123
+ | WCAG 2.2 AA | Add `"wcag22aa"` |
124
+ | Section 508 | `["section508"]` |
125
+ | Best practices | `["best-practice"]` |
126
+
127
+ ## Creating Reusable Fixtures
128
+
129
+ ### Custom Test Fixture
130
+
131
+ ```javascript
132
+ // fixtures/axe.js
133
+ import { test as base } from "@playwright/test";
134
+ import AxeBuilder from "@axe-core/playwright";
135
+
136
+ export const test = base.extend({
137
+ makeAxeBuilder: async ({ page }, use) => {
138
+ const makeAxeBuilder = () => new AxeBuilder({ page })
139
+ .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"])
140
+ .exclude("#cookie-banner") // Known third-party issue
141
+ .exclude("[data-testid='ad-unit']");
142
+
143
+ await use(makeAxeBuilder);
144
+ },
145
+ });
146
+
147
+ export { expect } from "@playwright/test";
148
+ ```
149
+
150
+ ### Using the Fixture
151
+
152
+ ```javascript
153
+ import { test, expect } from "./fixtures/axe";
154
+
155
+ test("product page is accessible", async ({ page, makeAxeBuilder }) => {
156
+ await page.goto("/products/123");
157
+
158
+ const results = await makeAxeBuilder().analyze();
159
+
160
+ expect(results.violations).toEqual([]);
161
+ });
162
+ ```
163
+
164
+ ## Handling Violations
165
+
166
+ ### Understanding Results
167
+
168
+ ```javascript
169
+ const results = await new AxeBuilder({ page }).analyze();
170
+
171
+ // Structure of a violation
172
+ results.violations.forEach(violation => {
173
+ console.log(`Rule: ${violation.id}`);
174
+ console.log(`Impact: ${violation.impact}`); // minor, moderate, serious, critical
175
+ console.log(`Description: ${violation.description}`);
176
+ console.log(`Help: ${violation.helpUrl}`);
177
+
178
+ violation.nodes.forEach(node => {
179
+ console.log(` Element: ${node.html}`);
180
+ console.log(` Fix: ${node.failureSummary}`);
181
+ });
182
+ });
183
+ ```
184
+
185
+ ### Impact Levels
186
+
187
+ | Level | Description | Priority |
188
+ |-------|-------------|----------|
189
+ | Critical | Blocks access for some users | Fix immediately |
190
+ | Serious | Major barriers | Fix soon |
191
+ | Moderate | Inconsistencies | Plan to fix |
192
+ | Minor | Annoyances | Improve when possible |
193
+
194
+ ### Progressive Enforcement
195
+
196
+ Start permissive, tighten over time:
197
+
198
+ ```javascript
199
+ // Phase 1: Only critical issues fail
200
+ const criticalViolations = results.violations.filter(
201
+ v => v.impact === "critical"
202
+ );
203
+ expect(criticalViolations).toEqual([]);
204
+
205
+ // Phase 2: Critical and serious
206
+ const seriousViolations = results.violations.filter(
207
+ v => ["critical", "serious"].includes(v.impact)
208
+ );
209
+ expect(seriousViolations).toEqual([]);
210
+
211
+ // Phase 3: All violations (goal state)
212
+ expect(results.violations).toEqual([]);
213
+ ```
214
+
215
+ ### Disable Specific Rules
216
+
217
+ When you have a known issue being addressed:
218
+
219
+ ```javascript
220
+ const results = await new AxeBuilder({ page })
221
+ .disableRules(["color-contrast"]) // Tracked in JIRA-123
222
+ .analyze();
223
+ ```
224
+
225
+ **Document why rules are disabled. Remove when fixed.**
226
+
227
+ ## Integration Patterns
228
+
229
+ ### Per-Component Tests
230
+
231
+ Test components in isolation:
232
+
233
+ ```javascript
234
+ test.describe("Button component", () => {
235
+ test("default button is accessible", async ({ page }) => {
236
+ await page.goto("/storybook/button--default");
237
+ const results = await new AxeBuilder({ page })
238
+ .include("#storybook-root")
239
+ .analyze();
240
+ expect(results.violations).toEqual([]);
241
+ });
242
+
243
+ test("disabled button is accessible", async ({ page }) => {
244
+ await page.goto("/storybook/button--disabled");
245
+ const results = await new AxeBuilder({ page })
246
+ .include("#storybook-root")
247
+ .analyze();
248
+ expect(results.violations).toEqual([]);
249
+ });
250
+ });
251
+ ```
252
+
253
+ ### Critical User Flows
254
+
255
+ Scan at each step of important journeys:
256
+
257
+ ```javascript
258
+ test("checkout flow is accessible at each step", async ({ page }) => {
259
+ // Cart page
260
+ await page.goto("/cart");
261
+ let results = await new AxeBuilder({ page }).analyze();
262
+ expect(results.violations).toEqual([]);
263
+
264
+ // Shipping form
265
+ await page.getByRole("link", { name: /checkout/i }).click();
266
+ await page.getByRole("heading", { name: /shipping/i }).waitFor();
267
+ results = await new AxeBuilder({ page }).analyze();
268
+ expect(results.violations).toEqual([]);
269
+
270
+ // Payment form
271
+ await page.getByRole("button", { name: /continue/i }).click();
272
+ await page.getByRole("heading", { name: /payment/i }).waitFor();
273
+ results = await new AxeBuilder({ page }).analyze();
274
+ expect(results.violations).toEqual([]);
275
+ });
276
+ ```
277
+
278
+ ### After Dynamic Content
279
+
280
+ Always re-scan after content changes:
281
+
282
+ ```javascript
283
+ test("modal is accessible when opened", async ({ page }) => {
284
+ await page.goto("/");
285
+
286
+ // Initial page scan
287
+ let results = await new AxeBuilder({ page }).analyze();
288
+ expect(results.violations).toEqual([]);
289
+
290
+ // Open modal
291
+ await page.getByRole("button", { name: /settings/i }).click();
292
+ await page.getByRole("dialog").waitFor();
293
+
294
+ // Scan modal
295
+ results = await new AxeBuilder({ page })
296
+ .include("[role='dialog']")
297
+ .analyze();
298
+ expect(results.violations).toEqual([]);
299
+ });
300
+ ```
301
+
302
+ ## Common Issues and Fixes
303
+
304
+ ### Missing Form Labels
305
+
306
+ ```html
307
+ <!-- BAD -->
308
+ <input type="email" placeholder="Email">
309
+
310
+ <!-- GOOD -->
311
+ <label for="email">Email address</label>
312
+ <input id="email" type="email">
313
+
314
+ <!-- ALSO GOOD (visually hidden label) -->
315
+ <label for="search" class="visually-hidden">Search products</label>
316
+ <input id="search" type="search" placeholder="Search...">
317
+ ```
318
+
319
+ ### Color Contrast
320
+
321
+ ```css
322
+ /* BAD: 2.61:1 ratio */
323
+ .muted-text {
324
+ color: #a0a0a0;
325
+ background: #ffffff;
326
+ }
327
+
328
+ /* GOOD: 4.5:1+ ratio for normal text */
329
+ .muted-text {
330
+ color: #6b6b6b;
331
+ background: #ffffff;
332
+ }
333
+ ```
334
+
335
+ ### Missing Button Names
336
+
337
+ ```html
338
+ <!-- BAD -->
339
+ <button><svg>...</svg></button>
340
+
341
+ <!-- GOOD -->
342
+ <button aria-label="Close dialog"><svg>...</svg></button>
343
+
344
+ <!-- ALSO GOOD -->
345
+ <button>
346
+ <svg aria-hidden="true">...</svg>
347
+ <span class="visually-hidden">Close dialog</span>
348
+ </button>
349
+ ```
350
+
351
+ ### Duplicate IDs
352
+
353
+ ```html
354
+ <!-- BAD -->
355
+ <input id="email" />
356
+ <input id="email" /> <!-- Duplicate! -->
357
+
358
+ <!-- GOOD -->
359
+ <input id="billing-email" />
360
+ <input id="shipping-email" />
361
+ ```
362
+
363
+ ## References
364
+
365
+ - [axe-core Rules](https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md)
366
+ - [Playwright Accessibility Testing](https://playwright.dev/docs/accessibility-testing)
367
+ - [WCAG Quick Reference](https://www.w3.org/WAI/WCAG21/quickref/)
368
+ - [Deque University](https://dequeuniversity.com/) — Free accessibility training