@arcadialdev/arcality 2.2.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/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
- package/.agents/skills/frontend-design/LICENSE.txt +177 -0
- package/.agents/skills/frontend-design/SKILL.md +42 -0
- package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
- package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
- package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
- package/.agents/skills/playwright-best-practices/README.md +147 -0
- package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
- package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
- package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
- package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
- package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
- package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
- package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
- package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
- package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
- package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
- package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
- package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
- package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
- package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
- package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
- package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
- package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
- package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
- package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
- package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
- package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
- package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
- package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
- package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
- package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
- package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
- package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
- package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
- package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
- package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
- package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
- package/.env.example +21 -0
- package/README.md +30 -0
- package/bin/arcality.mjs +86 -0
- package/package.json +66 -0
- package/playwright.config.ts +12 -0
- package/scripts/cleanup-qmsdev.mjs +63 -0
- package/scripts/discover-view.mjs +52 -0
- package/scripts/extract-view.mjs +64 -0
- package/scripts/gen-and-run.mjs +838 -0
- package/scripts/init.mjs +290 -0
- package/scripts/migrate-to-central-out.mjs +157 -0
- package/scripts/postinstall.mjs +63 -0
- package/scripts/rebrand-report.mjs +241 -0
- package/scripts/setup.mjs +166 -0
- package/src/KnowledgeService.ts +239 -0
- package/src/arcalityClient.mjs +266 -0
- package/src/configLoader.mjs +179 -0
- package/src/configManager.mjs +172 -0
- package/src/consoleBanner.ts +32 -0
- package/src/envSetup.ts +205 -0
- package/src/index.ts +25 -0
- package/src/projectInspector.ts +42 -0
- package/src/services/collectiveMemoryService.ts +178 -0
- package/src/testRunner.ts +201 -0
- package/tests/_helpers/ArcalityReporter.ts +490 -0
- package/tests/_helpers/agentic-runner.spec.ts +741 -0
- package/tests/_helpers/ai-agent-helper.ts +1573 -0
- package/tests/_helpers/discover-view.spec.ts +238 -0
- package/tests/_helpers/extract-view.spec.ts +118 -0
- package/tests/_helpers/qa-tools.ts +333 -0
- package/tests/_helpers/smart-action.spec.ts +1458 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# Security Testing Basics
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [XSS Prevention](#xss-prevention)
|
|
6
|
+
2. [CSRF Protection](#csrf-protection)
|
|
7
|
+
3. [Authentication Security](#authentication-security)
|
|
8
|
+
4. [Authorization Testing](#authorization-testing)
|
|
9
|
+
5. [Input Validation](#input-validation)
|
|
10
|
+
6. [Security Headers](#security-headers)
|
|
11
|
+
|
|
12
|
+
## XSS Prevention
|
|
13
|
+
|
|
14
|
+
### Test Reflected XSS
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
test("input is properly escaped", async ({ page }) => {
|
|
18
|
+
const xssPayloads = [
|
|
19
|
+
'<script>alert("xss")</script>',
|
|
20
|
+
'<img src="x" onerror="alert(1)">',
|
|
21
|
+
'"><script>alert(1)</script>',
|
|
22
|
+
"javascript:alert(1)",
|
|
23
|
+
'<svg onload="alert(1)">',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
for (const payload of xssPayloads) {
|
|
27
|
+
await page.goto(`/search?q=${encodeURIComponent(payload)}`);
|
|
28
|
+
|
|
29
|
+
// Verify script didn't execute
|
|
30
|
+
const alertTriggered = await page.evaluate(() => {
|
|
31
|
+
return (window as any).__xssTriggered === true;
|
|
32
|
+
});
|
|
33
|
+
expect(alertTriggered).toBe(false);
|
|
34
|
+
|
|
35
|
+
// Verify payload is escaped in HTML
|
|
36
|
+
const content = await page.content();
|
|
37
|
+
expect(content).not.toContain("<script>alert");
|
|
38
|
+
expect(content).not.toContain("onerror=");
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Test Stored XSS
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
test("user content is sanitized", async ({ page }) => {
|
|
47
|
+
await page.goto("/create-post");
|
|
48
|
+
|
|
49
|
+
// Try to inject script via form
|
|
50
|
+
await page.getByLabel("Content").fill('<script>alert("xss")</script>Hello');
|
|
51
|
+
await page.getByRole("button", { name: "Submit" }).click();
|
|
52
|
+
|
|
53
|
+
// View the post
|
|
54
|
+
await page.goto("/posts/latest");
|
|
55
|
+
|
|
56
|
+
// Script should not be in page
|
|
57
|
+
const scripts = await page.locator("script").count();
|
|
58
|
+
const pageContent = await page.content();
|
|
59
|
+
|
|
60
|
+
// The script tag should be escaped or removed
|
|
61
|
+
expect(pageContent).not.toContain("<script>alert");
|
|
62
|
+
|
|
63
|
+
// Text should still be visible (just sanitized)
|
|
64
|
+
await expect(page.getByText("Hello")).toBeVisible();
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Monitor for XSS Execution
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
test("no XSS execution", async ({ page }) => {
|
|
72
|
+
// Set up XSS detection
|
|
73
|
+
await page.addInitScript(() => {
|
|
74
|
+
(window as any).__xssDetected = false;
|
|
75
|
+
|
|
76
|
+
// Override alert/confirm/prompt
|
|
77
|
+
window.alert = () => {
|
|
78
|
+
(window as any).__xssDetected = true;
|
|
79
|
+
};
|
|
80
|
+
window.confirm = () => {
|
|
81
|
+
(window as any).__xssDetected = true;
|
|
82
|
+
return false;
|
|
83
|
+
};
|
|
84
|
+
window.prompt = () => {
|
|
85
|
+
(window as any).__xssDetected = true;
|
|
86
|
+
return null;
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Perform test actions
|
|
91
|
+
await page.goto("/vulnerable-page");
|
|
92
|
+
await page.getByLabel("Search").fill('"><img src=x onerror=alert(1)>');
|
|
93
|
+
await page.getByLabel("Search").press("Enter");
|
|
94
|
+
|
|
95
|
+
// Check if XSS triggered
|
|
96
|
+
const xssDetected = await page.evaluate(() => (window as any).__xssDetected);
|
|
97
|
+
expect(xssDetected).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## CSRF Protection
|
|
102
|
+
|
|
103
|
+
### Verify CSRF Token Present
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
test("forms include CSRF token", async ({ page }) => {
|
|
107
|
+
await page.goto("/settings");
|
|
108
|
+
|
|
109
|
+
// Check form has CSRF token
|
|
110
|
+
const csrfInput = page.locator(
|
|
111
|
+
'input[name="_csrf"], input[name="csrf_token"]',
|
|
112
|
+
);
|
|
113
|
+
await expect(csrfInput).toBeAttached();
|
|
114
|
+
|
|
115
|
+
const csrfValue = await csrfInput.getAttribute("value");
|
|
116
|
+
expect(csrfValue).toBeTruthy();
|
|
117
|
+
expect(csrfValue!.length).toBeGreaterThan(20);
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Test CSRF Token Validation
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
test("rejects requests without CSRF token", async ({ page, request }) => {
|
|
125
|
+
await page.goto("/settings");
|
|
126
|
+
|
|
127
|
+
// Try to submit without CSRF token
|
|
128
|
+
const response = await request.post("/api/settings", {
|
|
129
|
+
data: { theme: "dark" },
|
|
130
|
+
headers: {
|
|
131
|
+
"Content-Type": "application/json",
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Should be rejected
|
|
136
|
+
expect(response.status()).toBe(403);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("rejects requests with invalid CSRF token", async ({ page, request }) => {
|
|
140
|
+
await page.goto("/settings");
|
|
141
|
+
|
|
142
|
+
const response = await request.post("/api/settings", {
|
|
143
|
+
data: { theme: "dark" },
|
|
144
|
+
headers: {
|
|
145
|
+
"X-CSRF-Token": "invalid-token",
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(response.status()).toBe(403);
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Test CSRF with Valid Token
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
test("accepts requests with valid CSRF token", async ({ page }) => {
|
|
157
|
+
await page.goto("/settings");
|
|
158
|
+
|
|
159
|
+
// Get CSRF token from page
|
|
160
|
+
const csrfToken = await page
|
|
161
|
+
.locator('meta[name="csrf-token"]')
|
|
162
|
+
.getAttribute("content");
|
|
163
|
+
|
|
164
|
+
// Submit form normally
|
|
165
|
+
await page.getByLabel("Theme").selectOption("dark");
|
|
166
|
+
await page.getByRole("button", { name: "Save" }).click();
|
|
167
|
+
|
|
168
|
+
// Should succeed
|
|
169
|
+
await expect(page.getByText("Settings saved")).toBeVisible();
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Authentication Security
|
|
174
|
+
|
|
175
|
+
### Test Session Expiry
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
test("session expires after timeout", async ({ page, context }) => {
|
|
179
|
+
await page.goto("/login");
|
|
180
|
+
await page.getByLabel("Email").fill("user@example.com");
|
|
181
|
+
await page.getByLabel("Password").fill("password");
|
|
182
|
+
await page.getByRole("button", { name: "Sign in" }).click();
|
|
183
|
+
|
|
184
|
+
await expect(page).toHaveURL("/dashboard");
|
|
185
|
+
|
|
186
|
+
// Simulate time passing (if using clock mocking)
|
|
187
|
+
await page.clock.fastForward("02:00:00"); // 2 hours
|
|
188
|
+
|
|
189
|
+
// Try to access protected page
|
|
190
|
+
await page.goto("/profile");
|
|
191
|
+
|
|
192
|
+
// Should redirect to login
|
|
193
|
+
await expect(page).toHaveURL(/\/login/);
|
|
194
|
+
await expect(page.getByText("Session expired")).toBeVisible();
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Test Concurrent Sessions
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
test("handles concurrent session limit", async ({ browser }) => {
|
|
202
|
+
// Login from first browser
|
|
203
|
+
const context1 = await browser.newContext();
|
|
204
|
+
const page1 = await context1.newPage();
|
|
205
|
+
|
|
206
|
+
await page1.goto("/login");
|
|
207
|
+
await page1.getByLabel("Email").fill("user@example.com");
|
|
208
|
+
await page1.getByLabel("Password").fill("password");
|
|
209
|
+
await page1.getByRole("button", { name: "Sign in" }).click();
|
|
210
|
+
await expect(page1).toHaveURL("/dashboard");
|
|
211
|
+
|
|
212
|
+
// Login from second browser (same user)
|
|
213
|
+
const context2 = await browser.newContext();
|
|
214
|
+
const page2 = await context2.newPage();
|
|
215
|
+
|
|
216
|
+
await page2.goto("/login");
|
|
217
|
+
await page2.getByLabel("Email").fill("user@example.com");
|
|
218
|
+
await page2.getByLabel("Password").fill("password");
|
|
219
|
+
await page2.getByRole("button", { name: "Sign in" }).click();
|
|
220
|
+
|
|
221
|
+
// First session should be invalidated (or warning shown)
|
|
222
|
+
await page1.reload();
|
|
223
|
+
await expect(
|
|
224
|
+
page1.getByText(/session.*another device|logged out/i),
|
|
225
|
+
).toBeVisible();
|
|
226
|
+
|
|
227
|
+
await context1.close();
|
|
228
|
+
await context2.close();
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Test Password Reset Security
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
test("password reset token is single-use", async ({ page, request }) => {
|
|
236
|
+
// Request password reset
|
|
237
|
+
await page.goto("/forgot-password");
|
|
238
|
+
await page.getByLabel("Email").fill("user@example.com");
|
|
239
|
+
await page.getByRole("button", { name: "Reset" }).click();
|
|
240
|
+
|
|
241
|
+
// Get token (in test env, might be exposed or use email mock)
|
|
242
|
+
const resetToken = "mock-reset-token";
|
|
243
|
+
|
|
244
|
+
// Use token first time
|
|
245
|
+
await page.goto(`/reset-password?token=${resetToken}`);
|
|
246
|
+
await page.getByLabel("New Password").fill("NewPassword123");
|
|
247
|
+
await page.getByRole("button", { name: "Reset" }).click();
|
|
248
|
+
|
|
249
|
+
await expect(page.getByText("Password updated")).toBeVisible();
|
|
250
|
+
|
|
251
|
+
// Try to use same token again
|
|
252
|
+
await page.goto(`/reset-password?token=${resetToken}`);
|
|
253
|
+
|
|
254
|
+
await expect(page.getByText("Invalid or expired token")).toBeVisible();
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Authorization Testing
|
|
259
|
+
|
|
260
|
+
### Test Unauthorized Access
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
test.describe("authorization", () => {
|
|
264
|
+
test("cannot access admin routes as user", async ({ browser }) => {
|
|
265
|
+
const context = await browser.newContext({
|
|
266
|
+
storageState: ".auth/user.json", // Regular user
|
|
267
|
+
});
|
|
268
|
+
const page = await context.newPage();
|
|
269
|
+
|
|
270
|
+
// Try to access admin page
|
|
271
|
+
await page.goto("/admin/users");
|
|
272
|
+
|
|
273
|
+
// Should be denied
|
|
274
|
+
await expect(page).not.toHaveURL("/admin/users");
|
|
275
|
+
expect(
|
|
276
|
+
(await page.getByText("Access denied").isVisible()) ||
|
|
277
|
+
(await page.url()).includes("/login") ||
|
|
278
|
+
(await page.url()).includes("/403"),
|
|
279
|
+
).toBe(true);
|
|
280
|
+
|
|
281
|
+
await context.close();
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("cannot access other user's data", async ({ page }) => {
|
|
285
|
+
// Logged in as user 1, try to access user 2's profile
|
|
286
|
+
await page.goto("/users/other-user-id/settings");
|
|
287
|
+
|
|
288
|
+
await expect(page.getByText("Access denied")).toBeVisible();
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Test IDOR (Insecure Direct Object Reference)
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
test("cannot access other user resources by changing ID", async ({
|
|
297
|
+
page,
|
|
298
|
+
request,
|
|
299
|
+
}) => {
|
|
300
|
+
// Get current user's order
|
|
301
|
+
await page.goto("/orders/my-order-123");
|
|
302
|
+
await expect(page.getByText("Order #my-order-123")).toBeVisible();
|
|
303
|
+
|
|
304
|
+
// Try to access another user's order
|
|
305
|
+
const response = await request.get("/api/orders/other-user-order-456");
|
|
306
|
+
|
|
307
|
+
// Should be forbidden
|
|
308
|
+
expect(response.status()).toBe(403);
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Input Validation
|
|
313
|
+
|
|
314
|
+
### Test SQL Injection Prevention
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
test("SQL injection is prevented", async ({ page }) => {
|
|
318
|
+
const sqlPayloads = [
|
|
319
|
+
"'; DROP TABLE users; --",
|
|
320
|
+
"1' OR '1'='1",
|
|
321
|
+
"1; DELETE FROM orders",
|
|
322
|
+
"' UNION SELECT * FROM users --",
|
|
323
|
+
];
|
|
324
|
+
|
|
325
|
+
for (const payload of sqlPayloads) {
|
|
326
|
+
await page.goto("/search");
|
|
327
|
+
await page.getByLabel("Search").fill(payload);
|
|
328
|
+
await page.getByRole("button", { name: "Search" }).click();
|
|
329
|
+
|
|
330
|
+
// Should not error (injection blocked/escaped)
|
|
331
|
+
await expect(page.getByText("Error")).not.toBeVisible();
|
|
332
|
+
|
|
333
|
+
// Should show no results or escaped text
|
|
334
|
+
const hasError = await page
|
|
335
|
+
.getByText(/database error|sql|syntax/i)
|
|
336
|
+
.isVisible();
|
|
337
|
+
expect(hasError).toBe(false);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Test Input Length Limits
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
test("enforces input length limits", async ({ page }) => {
|
|
346
|
+
await page.goto("/profile");
|
|
347
|
+
|
|
348
|
+
// Try to submit very long input
|
|
349
|
+
const longString = "a".repeat(10000);
|
|
350
|
+
|
|
351
|
+
await page.getByLabel("Bio").fill(longString);
|
|
352
|
+
await page.getByRole("button", { name: "Save" }).click();
|
|
353
|
+
|
|
354
|
+
// Should show validation error or truncate
|
|
355
|
+
const bioValue = await page.getByLabel("Bio").inputValue();
|
|
356
|
+
expect(bioValue.length).toBeLessThanOrEqual(500); // Expected max
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Security Headers
|
|
361
|
+
|
|
362
|
+
### Verify Security Headers
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
test("response includes security headers", async ({ page }) => {
|
|
366
|
+
const response = await page.goto("/");
|
|
367
|
+
|
|
368
|
+
const headers = response!.headers();
|
|
369
|
+
|
|
370
|
+
// Content Security Policy
|
|
371
|
+
expect(headers["content-security-policy"]).toBeTruthy();
|
|
372
|
+
|
|
373
|
+
// Prevent clickjacking
|
|
374
|
+
expect(headers["x-frame-options"]).toMatch(/DENY|SAMEORIGIN/);
|
|
375
|
+
|
|
376
|
+
// Prevent MIME type sniffing
|
|
377
|
+
expect(headers["x-content-type-options"]).toBe("nosniff");
|
|
378
|
+
|
|
379
|
+
// XSS Protection (legacy but good to have)
|
|
380
|
+
expect(headers["x-xss-protection"]).toBeTruthy();
|
|
381
|
+
|
|
382
|
+
// HTTPS enforcement
|
|
383
|
+
if (!page.url().includes("localhost")) {
|
|
384
|
+
expect(headers["strict-transport-security"]).toBeTruthy();
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Test CSP Violations
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
test("CSP blocks inline scripts", async ({ page }) => {
|
|
393
|
+
const cspViolations: string[] = [];
|
|
394
|
+
|
|
395
|
+
// Listen for CSP violations via console
|
|
396
|
+
page.on("console", (msg) => {
|
|
397
|
+
if (msg.text().includes("Content Security Policy")) {
|
|
398
|
+
cspViolations.push(msg.text());
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
await page.goto("/");
|
|
403
|
+
|
|
404
|
+
// Try to inject inline script - CSP should block it
|
|
405
|
+
await page.evaluate(() => {
|
|
406
|
+
const script = document.createElement("script");
|
|
407
|
+
script.textContent = 'console.log("injected")';
|
|
408
|
+
document.body.appendChild(script);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
expect(cspViolations.length).toBeGreaterThan(0);
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
> **For comprehensive console monitoring** (fixtures, allowed patterns, fail on errors), see [console-errors.md](../debugging/console-errors.md).
|
|
416
|
+
|
|
417
|
+
## Anti-Patterns to Avoid
|
|
418
|
+
|
|
419
|
+
| Anti-Pattern | Problem | Solution |
|
|
420
|
+
| -------------------------- | --------------------- | ----------------------------- |
|
|
421
|
+
| Testing only happy path | Misses security holes | Test malicious inputs |
|
|
422
|
+
| Hardcoded test credentials | Security risk | Use environment variables |
|
|
423
|
+
| Skipping auth tests in dev | Bugs reach production | Test auth in all environments |
|
|
424
|
+
| Not testing authorization | Access control bugs | Test all role combinations |
|
|
425
|
+
|
|
426
|
+
## Related References
|
|
427
|
+
|
|
428
|
+
- **Authentication**: See [fixtures-hooks.md](../core/fixtures-hooks.md) for auth fixtures
|
|
429
|
+
- **Multi-User**: See [multi-user.md](../advanced/multi-user.md) for role-based testing
|
|
430
|
+
- **Error Testing**: See [error-testing.md](../debugging/error-testing.md) for validation testing
|