@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
|
+
# Node.js Advanced Patterns
|
|
2
|
+
|
|
3
|
+
Advanced patterns for dependency injection, database integration, authentication, caching, and API response formatting.
|
|
4
|
+
|
|
5
|
+
## Dependency Injection
|
|
6
|
+
|
|
7
|
+
### DI Container
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// di-container.ts
|
|
11
|
+
import { Pool } from "pg";
|
|
12
|
+
import { UserRepository } from "./repositories/user.repository";
|
|
13
|
+
import { UserService } from "./services/user.service";
|
|
14
|
+
import { UserController } from "./controllers/user.controller";
|
|
15
|
+
import { AuthService } from "./services/auth.service";
|
|
16
|
+
|
|
17
|
+
class Container {
|
|
18
|
+
private instances = new Map<string, any>();
|
|
19
|
+
|
|
20
|
+
register<T>(key: string, factory: () => T): void {
|
|
21
|
+
this.instances.set(key, factory);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
resolve<T>(key: string): T {
|
|
25
|
+
const factory = this.instances.get(key);
|
|
26
|
+
if (!factory) {
|
|
27
|
+
throw new Error(`No factory registered for ${key}`);
|
|
28
|
+
}
|
|
29
|
+
return factory();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
singleton<T>(key: string, factory: () => T): void {
|
|
33
|
+
let instance: T;
|
|
34
|
+
this.instances.set(key, () => {
|
|
35
|
+
if (!instance) {
|
|
36
|
+
instance = factory();
|
|
37
|
+
}
|
|
38
|
+
return instance;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const container = new Container();
|
|
44
|
+
|
|
45
|
+
// Register dependencies
|
|
46
|
+
container.singleton(
|
|
47
|
+
"db",
|
|
48
|
+
() =>
|
|
49
|
+
new Pool({
|
|
50
|
+
host: process.env.DB_HOST,
|
|
51
|
+
port: parseInt(process.env.DB_PORT || "5432"),
|
|
52
|
+
database: process.env.DB_NAME,
|
|
53
|
+
user: process.env.DB_USER,
|
|
54
|
+
password: process.env.DB_PASSWORD,
|
|
55
|
+
max: 20,
|
|
56
|
+
idleTimeoutMillis: 30000,
|
|
57
|
+
connectionTimeoutMillis: 2000,
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
container.singleton(
|
|
62
|
+
"userRepository",
|
|
63
|
+
() => new UserRepository(container.resolve("db")),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
container.singleton(
|
|
67
|
+
"userService",
|
|
68
|
+
() => new UserService(container.resolve("userRepository")),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
container.register(
|
|
72
|
+
"userController",
|
|
73
|
+
() => new UserController(container.resolve("userService")),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
container.singleton(
|
|
77
|
+
"authService",
|
|
78
|
+
() => new AuthService(container.resolve("userRepository")),
|
|
79
|
+
);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Database Patterns
|
|
83
|
+
|
|
84
|
+
### PostgreSQL with Connection Pool
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// config/database.ts
|
|
88
|
+
import { Pool, PoolConfig } from "pg";
|
|
89
|
+
|
|
90
|
+
const poolConfig: PoolConfig = {
|
|
91
|
+
host: process.env.DB_HOST,
|
|
92
|
+
port: parseInt(process.env.DB_PORT || "5432"),
|
|
93
|
+
database: process.env.DB_NAME,
|
|
94
|
+
user: process.env.DB_USER,
|
|
95
|
+
password: process.env.DB_PASSWORD,
|
|
96
|
+
max: 20,
|
|
97
|
+
idleTimeoutMillis: 30000,
|
|
98
|
+
connectionTimeoutMillis: 2000,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const pool = new Pool(poolConfig);
|
|
102
|
+
|
|
103
|
+
// Test connection
|
|
104
|
+
pool.on("connect", () => {
|
|
105
|
+
console.log("Database connected");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
pool.on("error", (err) => {
|
|
109
|
+
console.error("Unexpected database error", err);
|
|
110
|
+
process.exit(-1);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Graceful shutdown
|
|
114
|
+
export const closeDatabase = async () => {
|
|
115
|
+
await pool.end();
|
|
116
|
+
console.log("Database connection closed");
|
|
117
|
+
};
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### MongoDB with Mongoose
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// config/mongoose.ts
|
|
124
|
+
import mongoose from "mongoose";
|
|
125
|
+
|
|
126
|
+
const connectDB = async () => {
|
|
127
|
+
try {
|
|
128
|
+
await mongoose.connect(process.env.MONGODB_URI!, {
|
|
129
|
+
maxPoolSize: 10,
|
|
130
|
+
serverSelectionTimeoutMS: 5000,
|
|
131
|
+
socketTimeoutMS: 45000,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
console.log("MongoDB connected");
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error("MongoDB connection error:", error);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
mongoose.connection.on("disconnected", () => {
|
|
142
|
+
console.log("MongoDB disconnected");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
mongoose.connection.on("error", (err) => {
|
|
146
|
+
console.error("MongoDB error:", err);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
export { connectDB };
|
|
150
|
+
|
|
151
|
+
// Model example
|
|
152
|
+
import { Schema, model, Document } from "mongoose";
|
|
153
|
+
|
|
154
|
+
interface IUser extends Document {
|
|
155
|
+
name: string;
|
|
156
|
+
email: string;
|
|
157
|
+
password: string;
|
|
158
|
+
createdAt: Date;
|
|
159
|
+
updatedAt: Date;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const userSchema = new Schema<IUser>(
|
|
163
|
+
{
|
|
164
|
+
name: { type: String, required: true },
|
|
165
|
+
email: { type: String, required: true, unique: true },
|
|
166
|
+
password: { type: String, required: true },
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
timestamps: true,
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Indexes
|
|
174
|
+
userSchema.index({ email: 1 });
|
|
175
|
+
|
|
176
|
+
export const User = model<IUser>("User", userSchema);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Transaction Pattern
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// services/order.service.ts
|
|
183
|
+
import { Pool } from "pg";
|
|
184
|
+
|
|
185
|
+
export class OrderService {
|
|
186
|
+
constructor(private db: Pool) {}
|
|
187
|
+
|
|
188
|
+
async createOrder(userId: string, items: any[]) {
|
|
189
|
+
const client = await this.db.connect();
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
await client.query("BEGIN");
|
|
193
|
+
|
|
194
|
+
// Create order
|
|
195
|
+
const orderResult = await client.query(
|
|
196
|
+
"INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING id",
|
|
197
|
+
[userId, calculateTotal(items)],
|
|
198
|
+
);
|
|
199
|
+
const orderId = orderResult.rows[0].id;
|
|
200
|
+
|
|
201
|
+
// Create order items
|
|
202
|
+
for (const item of items) {
|
|
203
|
+
await client.query(
|
|
204
|
+
"INSERT INTO order_items (order_id, product_id, quantity, price) VALUES ($1, $2, $3, $4)",
|
|
205
|
+
[orderId, item.productId, item.quantity, item.price],
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Update inventory
|
|
209
|
+
await client.query(
|
|
210
|
+
"UPDATE products SET stock = stock - $1 WHERE id = $2",
|
|
211
|
+
[item.quantity, item.productId],
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
await client.query("COMMIT");
|
|
216
|
+
return orderId;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
await client.query("ROLLBACK");
|
|
219
|
+
throw error;
|
|
220
|
+
} finally {
|
|
221
|
+
client.release();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Authentication & Authorization
|
|
228
|
+
|
|
229
|
+
### JWT Authentication
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// services/auth.service.ts
|
|
233
|
+
import jwt from "jsonwebtoken";
|
|
234
|
+
import bcrypt from "bcrypt";
|
|
235
|
+
import { UserRepository } from "../repositories/user.repository";
|
|
236
|
+
import { UnauthorizedError } from "../utils/errors";
|
|
237
|
+
|
|
238
|
+
export class AuthService {
|
|
239
|
+
constructor(private userRepository: UserRepository) {}
|
|
240
|
+
|
|
241
|
+
async login(email: string, password: string) {
|
|
242
|
+
const user = await this.userRepository.findByEmail(email);
|
|
243
|
+
|
|
244
|
+
if (!user) {
|
|
245
|
+
throw new UnauthorizedError("Invalid credentials");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const isValid = await bcrypt.compare(password, user.password);
|
|
249
|
+
|
|
250
|
+
if (!isValid) {
|
|
251
|
+
throw new UnauthorizedError("Invalid credentials");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const token = this.generateToken({
|
|
255
|
+
userId: user.id,
|
|
256
|
+
email: user.email,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const refreshToken = this.generateRefreshToken({
|
|
260
|
+
userId: user.id,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
token,
|
|
265
|
+
refreshToken,
|
|
266
|
+
user: {
|
|
267
|
+
id: user.id,
|
|
268
|
+
name: user.name,
|
|
269
|
+
email: user.email,
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async refreshToken(refreshToken: string) {
|
|
275
|
+
try {
|
|
276
|
+
const payload = jwt.verify(
|
|
277
|
+
refreshToken,
|
|
278
|
+
process.env.REFRESH_TOKEN_SECRET!,
|
|
279
|
+
) as { userId: string };
|
|
280
|
+
|
|
281
|
+
const user = await this.userRepository.findById(payload.userId);
|
|
282
|
+
|
|
283
|
+
if (!user) {
|
|
284
|
+
throw new UnauthorizedError("User not found");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const token = this.generateToken({
|
|
288
|
+
userId: user.id,
|
|
289
|
+
email: user.email,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return { token };
|
|
293
|
+
} catch (error) {
|
|
294
|
+
throw new UnauthorizedError("Invalid refresh token");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private generateToken(payload: any): string {
|
|
299
|
+
return jwt.sign(payload, process.env.JWT_SECRET!, {
|
|
300
|
+
expiresIn: "15m",
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private generateRefreshToken(payload: any): string {
|
|
305
|
+
return jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET!, {
|
|
306
|
+
expiresIn: "7d",
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Caching Strategies
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// utils/cache.ts
|
|
316
|
+
import Redis from "ioredis";
|
|
317
|
+
|
|
318
|
+
const redis = new Redis({
|
|
319
|
+
host: process.env.REDIS_HOST,
|
|
320
|
+
port: parseInt(process.env.REDIS_PORT || "6379"),
|
|
321
|
+
retryStrategy: (times) => {
|
|
322
|
+
const delay = Math.min(times * 50, 2000);
|
|
323
|
+
return delay;
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
export class CacheService {
|
|
328
|
+
async get<T>(key: string): Promise<T | null> {
|
|
329
|
+
const data = await redis.get(key);
|
|
330
|
+
return data ? JSON.parse(data) : null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async set(key: string, value: any, ttl?: number): Promise<void> {
|
|
334
|
+
const serialized = JSON.stringify(value);
|
|
335
|
+
if (ttl) {
|
|
336
|
+
await redis.setex(key, ttl, serialized);
|
|
337
|
+
} else {
|
|
338
|
+
await redis.set(key, serialized);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async delete(key: string): Promise<void> {
|
|
343
|
+
await redis.del(key);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async invalidatePattern(pattern: string): Promise<void> {
|
|
347
|
+
const keys = await redis.keys(pattern);
|
|
348
|
+
if (keys.length > 0) {
|
|
349
|
+
await redis.del(...keys);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Cache decorator
|
|
355
|
+
export function Cacheable(ttl: number = 300) {
|
|
356
|
+
return function (
|
|
357
|
+
target: any,
|
|
358
|
+
propertyKey: string,
|
|
359
|
+
descriptor: PropertyDescriptor,
|
|
360
|
+
) {
|
|
361
|
+
const originalMethod = descriptor.value;
|
|
362
|
+
|
|
363
|
+
descriptor.value = async function (...args: any[]) {
|
|
364
|
+
const cache = new CacheService();
|
|
365
|
+
const cacheKey = `${propertyKey}:${JSON.stringify(args)}`;
|
|
366
|
+
|
|
367
|
+
const cached = await cache.get(cacheKey);
|
|
368
|
+
if (cached) {
|
|
369
|
+
return cached;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const result = await originalMethod.apply(this, args);
|
|
373
|
+
await cache.set(cacheKey, result, ttl);
|
|
374
|
+
|
|
375
|
+
return result;
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
return descriptor;
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## API Response Format
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
// utils/response.ts
|
|
387
|
+
import { Response } from "express";
|
|
388
|
+
|
|
389
|
+
export class ApiResponse {
|
|
390
|
+
static success<T>(
|
|
391
|
+
res: Response,
|
|
392
|
+
data: T,
|
|
393
|
+
message?: string,
|
|
394
|
+
statusCode = 200,
|
|
395
|
+
) {
|
|
396
|
+
return res.status(statusCode).json({
|
|
397
|
+
status: "success",
|
|
398
|
+
message,
|
|
399
|
+
data,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
static error(res: Response, message: string, statusCode = 500, errors?: any) {
|
|
404
|
+
return res.status(statusCode).json({
|
|
405
|
+
status: "error",
|
|
406
|
+
message,
|
|
407
|
+
...(errors && { errors }),
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
static paginated<T>(
|
|
412
|
+
res: Response,
|
|
413
|
+
data: T[],
|
|
414
|
+
page: number,
|
|
415
|
+
limit: number,
|
|
416
|
+
total: number,
|
|
417
|
+
) {
|
|
418
|
+
return res.json({
|
|
419
|
+
status: "success",
|
|
420
|
+
data,
|
|
421
|
+
pagination: {
|
|
422
|
+
page,
|
|
423
|
+
limit,
|
|
424
|
+
total,
|
|
425
|
+
pages: Math.ceil(total / limit),
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright © 2026 Currents Software Inc.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
```
|
|
2
|
+
|
|
3
|
+
░█▀█░█░░░█▀█░█░█░█░█░█▀▄░▀█▀░█▀▀░█░█░▀█▀░░░█▀▄░█▀▀░█▀▀░▀█▀░░░█▀█░█▀▄░█▀█░█▀▀░▀█▀░▀█▀░█▀▀░█▀▀░█▀▀░
|
|
4
|
+
░█▀▀░█░░░█▀█░░█░░█▄█░█▀▄░░█░░█░█░█▀█░░█░░░░█▀▄░█▀▀░▀▀█░░█░░░░█▀▀░█▀▄░█▀█░█░░░░█░░░█░░█░░░█▀▀░▀▀█░
|
|
5
|
+
░▀░░░▀▀▀░▀░▀░░▀░░▀░▀░▀░▀░▀▀▀░▀▀▀░▀░▀░░▀░░░░▀▀░░▀▀▀░▀▀▀░░▀░░░░▀░░░▀░▀░▀░▀░▀▀▀░░▀░░▀▀▀░▀▀▀░▀▀▀░▀▀▀░
|
|
6
|
+
```
|
|
7
|
+
<img src="https://currents.dev/favicon-96x96.png" width="24" height="24" align="left" />by [currents.dev](https://currents.dev?utm_source=ai-skill) - The all-in-one Dashboard for Playwright Testing.
|
|
8
|
+
|
|
9
|
+
# Playwright Best Practices Skill
|
|
10
|
+
|
|
11
|
+
A skill that gives the AI specialized guidance for writing, debugging, and maintaining **Playwright** tests in **TypeScript**. Use it in any repo where you work with Playwright so the assistant follows best practices for E2E, component, API, visual regression, accessibility, security, i18n, Electron, and browser extension testing.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx skills add https://github.com/currents-dev/playwright-best-practices-skill
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The skill is activity-based: the AI is directed to the right reference depending on what you're doing, so you get focused advice without loading everything at once.
|
|
20
|
+
|
|
21
|
+
## When the Skill Is Used
|
|
22
|
+
|
|
23
|
+
The skill triggers when the AI infers you need help with things like:
|
|
24
|
+
|
|
25
|
+
- Writing new E2E, component, API, visual regression, or accessibility tests
|
|
26
|
+
- Testing mobile/responsive layouts, touch gestures, or device emulation
|
|
27
|
+
- Implementing file uploads/downloads, date/time mocking, or WebSocket testing
|
|
28
|
+
- Handling OAuth popups, geolocation, permissions, or multi-tab flows
|
|
29
|
+
- Testing iframes, canvas/WebGL, service workers, or PWA features
|
|
30
|
+
- Testing Electron desktop apps or browser extensions
|
|
31
|
+
- Internationalization (i18n), locales, RTL layouts, or date/number formats
|
|
32
|
+
- Testing error states, offline mode, or network failure scenarios
|
|
33
|
+
- Security testing (XSS, CSRF, authentication, authorization)
|
|
34
|
+
- Performance testing with Web Vitals or Lighthouse
|
|
35
|
+
- Reviewing or refactoring Playwright test code
|
|
36
|
+
- Fixing flaky tests or debugging failures
|
|
37
|
+
- Setting up CI/CD, test coverage, or global setup/teardown
|
|
38
|
+
- Configuring projects, dependencies, parallel runs, or sharding
|
|
39
|
+
|
|
40
|
+
You don't have to mention "skill" or "Playwright best practices"; describe your task (e.g. "fix this flaky login test" or "add accessibility tests") and the AI will use the skill when it's relevant.
|
|
41
|
+
|
|
42
|
+
## What's Inside
|
|
43
|
+
|
|
44
|
+
**57 reference documents** organized into 8 categories:
|
|
45
|
+
|
|
46
|
+
### Core (`core/`)
|
|
47
|
+
|
|
48
|
+
| Topic | Reference | Use for |
|
|
49
|
+
| -------------------- | --------------------------- | ------------------------------------------------ |
|
|
50
|
+
| Test structure | `test-suite-structure.md` | Structure, config, E2E/component/API/visual tests |
|
|
51
|
+
| Locators | `locators.md` | Selectors, robustness, avoiding brittle locators |
|
|
52
|
+
| Assertions & waiting | `assertions-waiting.md` | Expect APIs, auto-waiting, polling |
|
|
53
|
+
| Page Object Model | `page-object-model.md` | POM structure and patterns |
|
|
54
|
+
| Fixtures & hooks | `fixtures-hooks.md` | Setup, teardown, auth, custom fixtures |
|
|
55
|
+
| Test data | `test-data.md` | Factories, Faker, data-driven testing |
|
|
56
|
+
| Annotations | `annotations.md` | skip, fixme, slow, test steps |
|
|
57
|
+
| Configuration | `configuration.md` | playwright.config.ts options |
|
|
58
|
+
| Global setup | `global-setup.md` | globalSetup/Teardown, DB migrations |
|
|
59
|
+
| Projects | `projects-dependencies.md` | Project config, dependencies, filtering |
|
|
60
|
+
|
|
61
|
+
### Debugging (`debugging/`)
|
|
62
|
+
|
|
63
|
+
| Topic | Reference | Use for |
|
|
64
|
+
| -------------- | -------------------- | ------------------------------------------ |
|
|
65
|
+
| Debugging | `debugging.md` | Trace viewer, inspector, common issues |
|
|
66
|
+
| Flaky tests | `flaky-tests.md` | Detection, diagnosis, fixing, quarantine |
|
|
67
|
+
| Error testing | `error-testing.md` | Error boundaries, offline, network failures |
|
|
68
|
+
| Console errors | `console-errors.md` | Capturing and failing on JS errors |
|
|
69
|
+
|
|
70
|
+
### Testing Patterns (`testing-patterns/`)
|
|
71
|
+
|
|
72
|
+
| Topic | Reference | Use for |
|
|
73
|
+
| ------------------- | ------------------------- | ---------------------------------------------- |
|
|
74
|
+
| Accessibility | `accessibility.md` | Axe-core, keyboard nav, ARIA, focus management |
|
|
75
|
+
| API testing | `api-testing.md` | REST API testing, request context |
|
|
76
|
+
| Component testing | `component-testing.md` | CT setup, mounting, props, mocking |
|
|
77
|
+
| Visual regression | `visual-regression.md` | Screenshot comparison, thresholds |
|
|
78
|
+
| File operations | `file-operations.md` | Upload, download basics |
|
|
79
|
+
| File upload/download| `file-upload-download.md` | Progress, cancellation, retry patterns |
|
|
80
|
+
| Forms validation | `forms-validation.md` | Form testing, validation states |
|
|
81
|
+
| Drag and drop | `drag-drop.md` | Drag-and-drop interactions |
|
|
82
|
+
| GraphQL testing | `graphql-testing.md` | GraphQL queries, mutations, mocking |
|
|
83
|
+
| Canvas/WebGL | `canvas-webgl.md` | Canvas testing, charts, WebGL, games |
|
|
84
|
+
| i18n | `i18n.md` | Locales, RTL, date/number formats |
|
|
85
|
+
| Electron | `electron.md` | Desktop apps, IPC, main/renderer process |
|
|
86
|
+
| Browser extensions | `browser-extensions.md` | Popup, background, content scripts, APIs |
|
|
87
|
+
| Security testing | `security-testing.md` | XSS, CSRF, auth security, authorization |
|
|
88
|
+
| Performance testing | `performance-testing.md` | Web Vitals, budgets, Lighthouse |
|
|
89
|
+
|
|
90
|
+
### Advanced (`advanced/`)
|
|
91
|
+
|
|
92
|
+
| Topic | Reference | Use for |
|
|
93
|
+
| ---------------- | ----------------------- | ------------------------------------------- |
|
|
94
|
+
| Authentication | `authentication.md` | Login flows, session storage, cookies |
|
|
95
|
+
| Auth flows | `authentication-flows.md` | MFA, password reset, complex auth |
|
|
96
|
+
| Mobile testing | `mobile-testing.md` | Device emulation, touch gestures, viewports |
|
|
97
|
+
| Clock mocking | `clock-mocking.md` | Date/time mocking, timezones, timers |
|
|
98
|
+
| Multi-context | `multi-context.md` | Popups, new tabs, OAuth flows |
|
|
99
|
+
| Multi-user | `multi-user.md` | Collaboration, RBAC, concurrent actions |
|
|
100
|
+
| Network advanced | `network-advanced.md` | GraphQL, HAR, request modification |
|
|
101
|
+
| Third-party | `third-party.md` | OAuth, payments, email/SMS mocking |
|
|
102
|
+
|
|
103
|
+
### Browser APIs (`browser-apis/`)
|
|
104
|
+
|
|
105
|
+
| Topic | Reference | Use for |
|
|
106
|
+
| --------------- | -------------------- | ------------------------------------------- |
|
|
107
|
+
| Browser APIs | `browser-apis.md` | Geolocation, permissions, clipboard, camera |
|
|
108
|
+
| WebSockets | `websockets.md` | Real-time testing, SSE, reconnection |
|
|
109
|
+
| iFrames | `iframes.md` | Cross-origin, nested, dynamic iframes |
|
|
110
|
+
| Service workers | `service-workers.md` | PWA, caching, offline, push notifications |
|
|
111
|
+
|
|
112
|
+
### Architecture (`architecture/`)
|
|
113
|
+
|
|
114
|
+
| Topic | Reference | Use for |
|
|
115
|
+
| ----------------- | ---------------------- | ------------------------------------ |
|
|
116
|
+
| POM vs fixtures | `pom-vs-fixtures.md` | Choosing between patterns |
|
|
117
|
+
| Test architecture | `test-architecture.md` | Test type selection, structure |
|
|
118
|
+
| When to mock | `when-to-mock.md` | Mock vs real services decisions |
|
|
119
|
+
|
|
120
|
+
### Frameworks (`frameworks/`)
|
|
121
|
+
|
|
122
|
+
| Topic | Reference | Use for |
|
|
123
|
+
| ------- | ------------- | ------------------------------ |
|
|
124
|
+
| React | `react.md` | React-specific testing patterns |
|
|
125
|
+
| Angular | `angular.md` | Angular-specific testing |
|
|
126
|
+
| Vue | `vue.md` | Vue/Nuxt testing patterns |
|
|
127
|
+
| Next.js | `nextjs.md` | Next.js SSR/SSG testing |
|
|
128
|
+
|
|
129
|
+
### Infrastructure & CI/CD (`infrastructure-ci-cd/`)
|
|
130
|
+
|
|
131
|
+
| Topic | Reference | Use for |
|
|
132
|
+
| ---------------- | ---------------------- | ------------------------------------ |
|
|
133
|
+
| CI/CD | `ci-cd.md` | Pipelines, general CI setup |
|
|
134
|
+
| GitHub Actions | `github-actions.md` | GitHub-specific workflows |
|
|
135
|
+
| GitLab CI | `gitlab.md` | GitLab-specific pipelines |
|
|
136
|
+
| Other providers | `other-providers.md` | CircleCI, Azure DevOps, Jenkins |
|
|
137
|
+
| Docker | `docker.md` | Container setup, Playwright images |
|
|
138
|
+
| Parallel/sharding| `parallel-sharding.md` | Sharding, parallel execution |
|
|
139
|
+
| Performance | `performance.md` | Parallel runs, optimization |
|
|
140
|
+
| Reporting | `reporting.md` | Test reporters, artifacts |
|
|
141
|
+
| Test coverage | `test-coverage.md` | V8 coverage, reports, thresholds, CI |
|
|
142
|
+
|
|
143
|
+
The skill's `SKILL.md` maps your current activity to these references so the right content is used in context.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|