@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,331 @@
|
|
|
1
|
+
# GraphQL Testing
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Patterns](#patterns)
|
|
6
|
+
2. [Anti-Patterns](#anti-patterns)
|
|
7
|
+
3. [Troubleshooting](#troubleshooting)
|
|
8
|
+
|
|
9
|
+
> **When to use**: Testing GraphQL APIs — queries, mutations, variables, and error handling.
|
|
10
|
+
|
|
11
|
+
## Patterns
|
|
12
|
+
|
|
13
|
+
### Basic Query with Variables
|
|
14
|
+
|
|
15
|
+
All GraphQL requests go through `POST` to a single endpoint. Send `query`, `variables`, and optionally `operationName` in the JSON body.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { test, expect } from "@playwright/test";
|
|
19
|
+
|
|
20
|
+
const GQL_ENDPOINT = "/graphql";
|
|
21
|
+
|
|
22
|
+
test("query with variables", async ({ request }) => {
|
|
23
|
+
const resp = await request.post(GQL_ENDPOINT, {
|
|
24
|
+
data: {
|
|
25
|
+
query: `
|
|
26
|
+
query FetchItem($id: ID!) {
|
|
27
|
+
item(id: $id) {
|
|
28
|
+
id
|
|
29
|
+
title
|
|
30
|
+
price
|
|
31
|
+
reviews { id rating }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
`,
|
|
35
|
+
variables: { id: "101" },
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(resp.ok()).toBeTruthy();
|
|
40
|
+
const { data, errors } = await resp.json();
|
|
41
|
+
|
|
42
|
+
// GraphQL returns 200 even on errors — always check both
|
|
43
|
+
expect(errors).toBeUndefined();
|
|
44
|
+
expect(data.item).toMatchObject({
|
|
45
|
+
id: "101",
|
|
46
|
+
title: expect.any(String),
|
|
47
|
+
price: expect.any(Number),
|
|
48
|
+
});
|
|
49
|
+
expect(data.item.reviews).toEqual(
|
|
50
|
+
expect.arrayContaining([
|
|
51
|
+
expect.objectContaining({
|
|
52
|
+
id: expect.any(String),
|
|
53
|
+
rating: expect.any(Number),
|
|
54
|
+
}),
|
|
55
|
+
])
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Mutations
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { test, expect } from "@playwright/test";
|
|
64
|
+
|
|
65
|
+
const GQL_ENDPOINT = "/graphql";
|
|
66
|
+
|
|
67
|
+
test("mutation creates resource", async ({ request }) => {
|
|
68
|
+
const resp = await request.post(GQL_ENDPOINT, {
|
|
69
|
+
data: {
|
|
70
|
+
query: `
|
|
71
|
+
mutation AddItem($input: ItemInput!) {
|
|
72
|
+
addItem(input: $input) {
|
|
73
|
+
id
|
|
74
|
+
title
|
|
75
|
+
status
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
`,
|
|
79
|
+
variables: {
|
|
80
|
+
input: {
|
|
81
|
+
title: "New Widget",
|
|
82
|
+
price: 15.0,
|
|
83
|
+
status: "DRAFT",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const { data, errors } = await resp.json();
|
|
90
|
+
expect(errors).toBeUndefined();
|
|
91
|
+
expect(data.addItem).toMatchObject({
|
|
92
|
+
id: expect.any(String),
|
|
93
|
+
title: "New Widget",
|
|
94
|
+
status: "DRAFT",
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Validation Errors
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { test, expect } from "@playwright/test";
|
|
103
|
+
|
|
104
|
+
const GQL_ENDPOINT = "/graphql";
|
|
105
|
+
|
|
106
|
+
test("handles validation errors", async ({ request }) => {
|
|
107
|
+
const resp = await request.post(GQL_ENDPOINT, {
|
|
108
|
+
data: {
|
|
109
|
+
query: `
|
|
110
|
+
mutation AddItem($input: ItemInput!) {
|
|
111
|
+
addItem(input: $input) { id }
|
|
112
|
+
}
|
|
113
|
+
`,
|
|
114
|
+
variables: { input: { title: "" } },
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const { data, errors } = await resp.json();
|
|
119
|
+
expect(errors).toBeDefined();
|
|
120
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
121
|
+
expect(errors[0].message).toContain("title");
|
|
122
|
+
expect(errors[0].extensions?.code).toBe("BAD_USER_INPUT");
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Authorization Errors
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { test, expect } from "@playwright/test";
|
|
130
|
+
|
|
131
|
+
const GQL_ENDPOINT = "/graphql";
|
|
132
|
+
|
|
133
|
+
test("handles authorization errors", async ({ request }) => {
|
|
134
|
+
const resp = await request.post(GQL_ENDPOINT, {
|
|
135
|
+
data: {
|
|
136
|
+
query: `
|
|
137
|
+
query AdminDashboard {
|
|
138
|
+
adminMetrics { revenue activeUsers }
|
|
139
|
+
}
|
|
140
|
+
`,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const { data, errors } = await resp.json();
|
|
145
|
+
expect(errors).toBeDefined();
|
|
146
|
+
expect(errors[0].extensions?.code).toBe("UNAUTHORIZED");
|
|
147
|
+
expect(data?.adminMetrics).toBeNull();
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Authenticated GraphQL Fixture
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// fixtures/graphql-fixtures.ts
|
|
155
|
+
import { test as base, expect, APIRequestContext } from "@playwright/test";
|
|
156
|
+
|
|
157
|
+
type GraphQLFixtures = {
|
|
158
|
+
gqlClient: APIRequestContext;
|
|
159
|
+
adminGqlClient: APIRequestContext;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const test = base.extend<GraphQLFixtures>({
|
|
163
|
+
gqlClient: async ({ playwright }, use) => {
|
|
164
|
+
const ctx = await playwright.request.newContext({
|
|
165
|
+
baseURL: "https://api.myapp.io",
|
|
166
|
+
extraHTTPHeaders: {
|
|
167
|
+
Authorization: `Bearer ${process.env.API_TOKEN}`,
|
|
168
|
+
"Content-Type": "application/json",
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
await use(ctx);
|
|
172
|
+
await ctx.dispose();
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
adminGqlClient: async ({ playwright }, use) => {
|
|
176
|
+
const loginCtx = await playwright.request.newContext({
|
|
177
|
+
baseURL: "https://api.myapp.io",
|
|
178
|
+
});
|
|
179
|
+
const loginResp = await loginCtx.post("/graphql", {
|
|
180
|
+
data: {
|
|
181
|
+
query: `
|
|
182
|
+
mutation Login($email: String!, $password: String!) {
|
|
183
|
+
login(email: $email, password: $password) { token }
|
|
184
|
+
}
|
|
185
|
+
`,
|
|
186
|
+
variables: {
|
|
187
|
+
email: process.env.ADMIN_EMAIL,
|
|
188
|
+
password: process.env.ADMIN_PASSWORD,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
const { data } = await loginResp.json();
|
|
193
|
+
|
|
194
|
+
if (!data?.login?.token) {
|
|
195
|
+
throw new Error(`Admin login failed: status ${loginResp.status()}, response: ${JSON.stringify(data)}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await loginCtx.dispose();
|
|
199
|
+
|
|
200
|
+
const ctx = await playwright.request.newContext({
|
|
201
|
+
baseURL: "https://api.myapp.io",
|
|
202
|
+
extraHTTPHeaders: {
|
|
203
|
+
Authorization: `Bearer ${data.login.token}`,
|
|
204
|
+
"Content-Type": "application/json",
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
await use(ctx);
|
|
208
|
+
await ctx.dispose();
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
export { expect };
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### GraphQL Helper Function
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// utils/graphql.ts
|
|
219
|
+
import { APIRequestContext, expect } from "@playwright/test";
|
|
220
|
+
|
|
221
|
+
export async function gqlQuery<T = any>(
|
|
222
|
+
request: APIRequestContext,
|
|
223
|
+
query: string,
|
|
224
|
+
variables?: Record<string, any>
|
|
225
|
+
): Promise<{ data: T; errors?: any[] }> {
|
|
226
|
+
const resp = await request.post("/graphql", {
|
|
227
|
+
data: { query, variables },
|
|
228
|
+
});
|
|
229
|
+
expect(resp.ok()).toBeTruthy();
|
|
230
|
+
return resp.json();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function gqlMutation<T = any>(
|
|
234
|
+
request: APIRequestContext,
|
|
235
|
+
mutation: string,
|
|
236
|
+
variables?: Record<string, any>
|
|
237
|
+
): Promise<{ data: T; errors?: any[] }> {
|
|
238
|
+
return gqlQuery<T>(request, mutation, variables);
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// tests/api/items.spec.ts
|
|
244
|
+
import { test, expect } from "@playwright/test";
|
|
245
|
+
import { gqlQuery, gqlMutation } from "../../utils/graphql";
|
|
246
|
+
|
|
247
|
+
test("fetch and update item", async ({ request }) => {
|
|
248
|
+
const { data: fetchData } = await gqlQuery(
|
|
249
|
+
request,
|
|
250
|
+
`query GetItem($id: ID!) { item(id: $id) { id title } }`,
|
|
251
|
+
{ id: "101" }
|
|
252
|
+
);
|
|
253
|
+
expect(fetchData.item.title).toBeDefined();
|
|
254
|
+
|
|
255
|
+
const { data: updateData, errors } = await gqlMutation(
|
|
256
|
+
request,
|
|
257
|
+
`mutation UpdateItem($id: ID!, $title: String!) {
|
|
258
|
+
updateItem(id: $id, title: $title) { id title }
|
|
259
|
+
}`,
|
|
260
|
+
{ id: "101", title: "Updated Title" }
|
|
261
|
+
);
|
|
262
|
+
expect(errors).toBeUndefined();
|
|
263
|
+
expect(updateData.updateItem.title).toBe("Updated Title");
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Anti-Patterns
|
|
268
|
+
|
|
269
|
+
| Don't Do This | Problem | Do This Instead |
|
|
270
|
+
| --- | --- | --- |
|
|
271
|
+
| Check only `response.ok()` | GraphQL returns 200 even on errors — `errors` array is the real signal | Always check both `data` and `errors` in the response body |
|
|
272
|
+
| Ignore `errors` array | Validation and auth errors appear in `errors`, not HTTP status | Destructure and assert: `expect(errors).toBeUndefined()` |
|
|
273
|
+
| Hardcode query strings inline everywhere | Duplicated queries are hard to maintain | Extract queries to constants or use a helper function |
|
|
274
|
+
| Skip variable validation | Invalid variables cause cryptic server errors | Validate input shape before sending |
|
|
275
|
+
|
|
276
|
+
## Troubleshooting
|
|
277
|
+
|
|
278
|
+
### GraphQL returns 200 but data is null
|
|
279
|
+
|
|
280
|
+
**Cause**: GraphQL servers return HTTP 200 even when the query has errors. The actual error is in the `errors` array.
|
|
281
|
+
|
|
282
|
+
**Fix**: Always destructure and check both `data` and `errors`.
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
const { data, errors } = await resp.json();
|
|
286
|
+
if (errors) {
|
|
287
|
+
console.error("GraphQL errors:", JSON.stringify(errors, null, 2));
|
|
288
|
+
}
|
|
289
|
+
expect(errors).toBeUndefined();
|
|
290
|
+
expect(data.item).toBeDefined();
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### "Cannot query field X on type Y"
|
|
294
|
+
|
|
295
|
+
**Cause**: The field doesn't exist in the schema, or you're querying the wrong type.
|
|
296
|
+
|
|
297
|
+
**Fix**: Verify the schema. Use introspection or check your GraphQL IDE for available fields.
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// Introspection query to debug schema
|
|
301
|
+
const { data } = await request.post("/graphql", {
|
|
302
|
+
data: {
|
|
303
|
+
query: `{ __type(name: "Item") { fields { name type { name } } } }`,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
console.log(data.__type.fields);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Variables not being applied
|
|
310
|
+
|
|
311
|
+
**Cause**: Variable names in the query don't match the `variables` object keys, or types don't match.
|
|
312
|
+
|
|
313
|
+
**Fix**: Ensure variable names match exactly (case-sensitive) and types align with the schema.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
// Wrong: variable name mismatch
|
|
317
|
+
const resp = await request.post("/graphql", {
|
|
318
|
+
data: {
|
|
319
|
+
query: `query GetItem($itemId: ID!) { item(id: $itemId) { id } }`,
|
|
320
|
+
variables: { id: "101" }, // Should be { itemId: "101" }
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Correct
|
|
325
|
+
const resp = await request.post("/graphql", {
|
|
326
|
+
data: {
|
|
327
|
+
query: `query GetItem($itemId: ID!) { item(id: $itemId) { id } }`,
|
|
328
|
+
variables: { itemId: "101" },
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
```
|