qa360 1.4.5 → 2.0.1

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 (209) hide show
  1. package/README.md +1 -1
  2. package/dist/commands/ai.d.ts +41 -0
  3. package/dist/commands/ai.js +499 -0
  4. package/dist/commands/ask.js +12 -12
  5. package/dist/commands/coverage.d.ts +8 -0
  6. package/dist/commands/coverage.js +252 -0
  7. package/dist/commands/explain.d.ts +27 -0
  8. package/dist/commands/explain.js +630 -0
  9. package/dist/commands/flakiness.d.ts +73 -0
  10. package/dist/commands/flakiness.js +435 -0
  11. package/dist/commands/generate.d.ts +66 -0
  12. package/dist/commands/generate.js +438 -0
  13. package/dist/commands/init.d.ts +56 -9
  14. package/dist/commands/init.js +217 -10
  15. package/dist/commands/monitor.d.ts +27 -0
  16. package/dist/commands/monitor.js +225 -0
  17. package/dist/commands/ollama.d.ts +40 -0
  18. package/dist/commands/ollama.js +301 -0
  19. package/dist/commands/pack.d.ts +37 -9
  20. package/dist/commands/pack.js +240 -141
  21. package/dist/commands/regression.d.ts +8 -0
  22. package/dist/commands/regression.js +340 -0
  23. package/dist/commands/repair.d.ts +26 -0
  24. package/dist/commands/repair.js +307 -0
  25. package/dist/commands/retry.d.ts +43 -0
  26. package/dist/commands/retry.js +275 -0
  27. package/dist/commands/run.d.ts +8 -3
  28. package/dist/commands/run.js +45 -31
  29. package/dist/commands/slo.d.ts +8 -0
  30. package/dist/commands/slo.js +327 -0
  31. package/dist/core/adapters/playwright-native-api.d.ts +183 -0
  32. package/dist/core/adapters/playwright-native-api.js +461 -0
  33. package/dist/core/adapters/playwright-ui.d.ts +7 -0
  34. package/dist/core/adapters/playwright-ui.js +29 -1
  35. package/dist/core/ai/anthropic-provider.d.ts +50 -0
  36. package/dist/core/ai/anthropic-provider.js +211 -0
  37. package/dist/core/ai/deepseek-provider.d.ts +81 -0
  38. package/dist/core/ai/deepseek-provider.js +254 -0
  39. package/dist/core/ai/index.d.ts +60 -0
  40. package/dist/core/ai/index.js +18 -0
  41. package/dist/core/ai/llm-client.d.ts +45 -0
  42. package/dist/core/ai/llm-client.js +7 -0
  43. package/dist/core/ai/mock-provider.d.ts +49 -0
  44. package/dist/core/ai/mock-provider.js +121 -0
  45. package/dist/core/ai/ollama-provider.d.ts +78 -0
  46. package/dist/core/ai/ollama-provider.js +192 -0
  47. package/dist/core/ai/openai-provider.d.ts +48 -0
  48. package/dist/core/ai/openai-provider.js +188 -0
  49. package/dist/core/ai/provider-factory.d.ts +160 -0
  50. package/dist/core/ai/provider-factory.js +269 -0
  51. package/dist/core/auth/api-key-provider.d.ts +16 -0
  52. package/dist/core/auth/api-key-provider.js +63 -0
  53. package/dist/core/auth/aws-iam-provider.d.ts +35 -0
  54. package/dist/core/auth/aws-iam-provider.js +177 -0
  55. package/dist/core/auth/azure-ad-provider.d.ts +15 -0
  56. package/dist/core/auth/azure-ad-provider.js +99 -0
  57. package/dist/core/auth/basic-auth-provider.d.ts +26 -0
  58. package/dist/core/auth/basic-auth-provider.js +111 -0
  59. package/dist/core/auth/gcp-adc-provider.d.ts +27 -0
  60. package/dist/core/auth/gcp-adc-provider.js +126 -0
  61. package/dist/core/auth/index.d.ts +238 -0
  62. package/dist/core/auth/index.js +82 -0
  63. package/dist/core/auth/jwt-provider.d.ts +19 -0
  64. package/dist/core/auth/jwt-provider.js +160 -0
  65. package/dist/core/auth/manager.d.ts +84 -0
  66. package/dist/core/auth/manager.js +230 -0
  67. package/dist/core/auth/oauth2-provider.d.ts +17 -0
  68. package/dist/core/auth/oauth2-provider.js +114 -0
  69. package/dist/core/auth/totp-provider.d.ts +31 -0
  70. package/dist/core/auth/totp-provider.js +134 -0
  71. package/dist/core/auth/ui-login-provider.d.ts +26 -0
  72. package/dist/core/auth/ui-login-provider.js +198 -0
  73. package/dist/core/cache/index.d.ts +7 -0
  74. package/dist/core/cache/index.js +6 -0
  75. package/dist/core/cache/lru-cache.d.ts +203 -0
  76. package/dist/core/cache/lru-cache.js +397 -0
  77. package/dist/core/coverage/analyzer.d.ts +101 -0
  78. package/dist/core/coverage/analyzer.js +415 -0
  79. package/dist/core/coverage/collector.d.ts +74 -0
  80. package/dist/core/coverage/collector.js +459 -0
  81. package/dist/core/coverage/config.d.ts +37 -0
  82. package/dist/core/coverage/config.js +156 -0
  83. package/dist/core/coverage/index.d.ts +11 -0
  84. package/dist/core/coverage/index.js +15 -0
  85. package/dist/core/coverage/types.d.ts +267 -0
  86. package/dist/core/coverage/types.js +6 -0
  87. package/dist/core/coverage/vault.d.ts +95 -0
  88. package/dist/core/coverage/vault.js +405 -0
  89. package/dist/core/dashboard/assets.d.ts +6 -0
  90. package/dist/core/dashboard/assets.js +690 -0
  91. package/dist/core/dashboard/index.d.ts +6 -0
  92. package/dist/core/dashboard/index.js +5 -0
  93. package/dist/core/dashboard/server.d.ts +72 -0
  94. package/dist/core/dashboard/server.js +354 -0
  95. package/dist/core/dashboard/types.d.ts +70 -0
  96. package/dist/core/dashboard/types.js +5 -0
  97. package/dist/core/discoverer/index.d.ts +115 -0
  98. package/dist/core/discoverer/index.js +250 -0
  99. package/dist/core/flakiness/index.d.ts +228 -0
  100. package/dist/core/flakiness/index.js +384 -0
  101. package/dist/core/generation/code-formatter.d.ts +111 -0
  102. package/dist/core/generation/code-formatter.js +307 -0
  103. package/dist/core/generation/code-generator.d.ts +144 -0
  104. package/dist/core/generation/code-generator.js +293 -0
  105. package/dist/core/generation/generator.d.ts +40 -0
  106. package/dist/core/generation/generator.js +76 -0
  107. package/dist/core/generation/index.d.ts +30 -0
  108. package/dist/core/generation/index.js +28 -0
  109. package/dist/core/generation/pack-generator.d.ts +107 -0
  110. package/dist/core/generation/pack-generator.js +416 -0
  111. package/dist/core/generation/prompt-builder.d.ts +132 -0
  112. package/dist/core/generation/prompt-builder.js +672 -0
  113. package/dist/core/generation/source-analyzer.d.ts +213 -0
  114. package/dist/core/generation/source-analyzer.js +657 -0
  115. package/dist/core/generation/test-optimizer.d.ts +117 -0
  116. package/dist/core/generation/test-optimizer.js +328 -0
  117. package/dist/core/generation/types.d.ts +214 -0
  118. package/dist/core/generation/types.js +4 -0
  119. package/dist/core/index.d.ts +23 -1
  120. package/dist/core/index.js +39 -0
  121. package/dist/core/pack/validator.js +31 -1
  122. package/dist/core/pack-v2/index.d.ts +9 -0
  123. package/dist/core/pack-v2/index.js +8 -0
  124. package/dist/core/pack-v2/loader.d.ts +62 -0
  125. package/dist/core/pack-v2/loader.js +231 -0
  126. package/dist/core/pack-v2/migrator.d.ts +56 -0
  127. package/dist/core/pack-v2/migrator.js +455 -0
  128. package/dist/core/pack-v2/validator.d.ts +61 -0
  129. package/dist/core/pack-v2/validator.js +577 -0
  130. package/dist/core/regression/detector.d.ts +107 -0
  131. package/dist/core/regression/detector.js +497 -0
  132. package/dist/core/regression/index.d.ts +9 -0
  133. package/dist/core/regression/index.js +11 -0
  134. package/dist/core/regression/trend-analyzer.d.ts +102 -0
  135. package/dist/core/regression/trend-analyzer.js +345 -0
  136. package/dist/core/regression/types.d.ts +222 -0
  137. package/dist/core/regression/types.js +7 -0
  138. package/dist/core/regression/vault.d.ts +87 -0
  139. package/dist/core/regression/vault.js +289 -0
  140. package/dist/core/repair/engine/fixer.d.ts +24 -0
  141. package/dist/core/repair/engine/fixer.js +226 -0
  142. package/dist/core/repair/engine/suggestion-engine.d.ts +18 -0
  143. package/dist/core/repair/engine/suggestion-engine.js +187 -0
  144. package/dist/core/repair/index.d.ts +10 -0
  145. package/dist/core/repair/index.js +13 -0
  146. package/dist/core/repair/repairer.d.ts +90 -0
  147. package/dist/core/repair/repairer.js +284 -0
  148. package/dist/core/repair/types.d.ts +91 -0
  149. package/dist/core/repair/types.js +6 -0
  150. package/dist/core/repair/utils/error-analyzer.d.ts +28 -0
  151. package/dist/core/repair/utils/error-analyzer.js +264 -0
  152. package/dist/core/retry/flakiness-integration.d.ts +60 -0
  153. package/dist/core/retry/flakiness-integration.js +228 -0
  154. package/dist/core/retry/index.d.ts +14 -0
  155. package/dist/core/retry/index.js +16 -0
  156. package/dist/core/retry/retry-engine.d.ts +80 -0
  157. package/dist/core/retry/retry-engine.js +296 -0
  158. package/dist/core/retry/types.d.ts +178 -0
  159. package/dist/core/retry/types.js +52 -0
  160. package/dist/core/retry/vault.d.ts +77 -0
  161. package/dist/core/retry/vault.js +304 -0
  162. package/dist/core/runner/e2e-helpers.d.ts +102 -0
  163. package/dist/core/runner/e2e-helpers.js +153 -0
  164. package/dist/core/runner/phase3-runner.d.ts +101 -2
  165. package/dist/core/runner/phase3-runner.js +559 -24
  166. package/dist/core/self-healing/assertion-healer.d.ts +97 -0
  167. package/dist/core/self-healing/assertion-healer.js +371 -0
  168. package/dist/core/self-healing/engine.d.ts +122 -0
  169. package/dist/core/self-healing/engine.js +538 -0
  170. package/dist/core/self-healing/index.d.ts +10 -0
  171. package/dist/core/self-healing/index.js +11 -0
  172. package/dist/core/self-healing/selector-healer.d.ts +103 -0
  173. package/dist/core/self-healing/selector-healer.js +372 -0
  174. package/dist/core/self-healing/types.d.ts +152 -0
  175. package/dist/core/self-healing/types.js +6 -0
  176. package/dist/core/slo/config.d.ts +107 -0
  177. package/dist/core/slo/config.js +360 -0
  178. package/dist/core/slo/index.d.ts +11 -0
  179. package/dist/core/slo/index.js +15 -0
  180. package/dist/core/slo/sli-calculator.d.ts +92 -0
  181. package/dist/core/slo/sli-calculator.js +364 -0
  182. package/dist/core/slo/slo-tracker.d.ts +148 -0
  183. package/dist/core/slo/slo-tracker.js +379 -0
  184. package/dist/core/slo/types.d.ts +281 -0
  185. package/dist/core/slo/types.js +7 -0
  186. package/dist/core/slo/vault.d.ts +102 -0
  187. package/dist/core/slo/vault.js +427 -0
  188. package/dist/core/tui/index.d.ts +7 -0
  189. package/dist/core/tui/index.js +6 -0
  190. package/dist/core/tui/monitor.d.ts +92 -0
  191. package/dist/core/tui/monitor.js +271 -0
  192. package/dist/core/tui/renderer.d.ts +33 -0
  193. package/dist/core/tui/renderer.js +218 -0
  194. package/dist/core/tui/types.d.ts +63 -0
  195. package/dist/core/tui/types.js +5 -0
  196. package/dist/core/types/pack-v2.d.ts +425 -0
  197. package/dist/core/types/pack-v2.js +8 -0
  198. package/dist/core/vault/index.d.ts +116 -0
  199. package/dist/core/vault/index.js +400 -5
  200. package/dist/core/watch/index.d.ts +7 -0
  201. package/dist/core/watch/index.js +6 -0
  202. package/dist/core/watch/watch-mode.d.ts +213 -0
  203. package/dist/core/watch/watch-mode.js +389 -0
  204. package/dist/index.js +68 -68
  205. package/dist/utils/config.d.ts +5 -0
  206. package/dist/utils/config.js +136 -0
  207. package/package.json +5 -1
  208. package/dist/core/adapters/playwright-api.d.ts +0 -82
  209. package/dist/core/adapters/playwright-api.js +0 -264
@@ -0,0 +1,461 @@
1
+ /**
2
+ * QA360 Playwright Native API Adapter
3
+ *
4
+ * Uses Playwright's native APIRequestContext for HTTP testing.
5
+ * No browser launch required - lighter and faster than browser-based approach.
6
+ *
7
+ * @see https://playwright.dev/docs/api/class-apirequestcontext
8
+ *
9
+ * Benefits over browser-based approach:
10
+ * - No browser overhead (faster startup, lower memory)
11
+ * - Direct HTTP requests
12
+ * - Same API testing capabilities
13
+ * - Better for CI/CD environments
14
+ */
15
+ import * as playwright from 'playwright';
16
+ import { SecurityRedactor } from '../security/redactor.js';
17
+ import { ResponseCache, generateCacheKey } from '../cache/lru-cache.js';
18
+ /**
19
+ * Playwright Native API Adapter
20
+ *
21
+ * Uses playwright.request.newContext() for direct HTTP requests
22
+ * without launching a browser.
23
+ */
24
+ export class PlaywrightNativeApiAdapter {
25
+ requestContext;
26
+ redactor;
27
+ auth;
28
+ cache;
29
+ constructor() {
30
+ this.redactor = SecurityRedactor.forLogs();
31
+ }
32
+ /**
33
+ * Set authentication credentials for requests
34
+ */
35
+ setAuth(credentials) {
36
+ this.auth = credentials;
37
+ }
38
+ /**
39
+ * Execute API smoke tests using Playwright's native API
40
+ */
41
+ async runSmokeTests(config) {
42
+ try {
43
+ this.auth = config.auth;
44
+ // Initialize cache if enabled
45
+ if (config.cache?.enabled) {
46
+ this.cache = new ResponseCache({
47
+ maxSize: config.cache.maxSize ?? 100,
48
+ ttl: config.cache.ttl ?? 300000,
49
+ maxSizeBytes: config.cache.maxSizeBytes ?? 10 * 1024 * 1024,
50
+ verbose: false
51
+ });
52
+ console.log(`📦 Cache enabled: max=${config.cache.maxSize ?? 100}, ttl=${config.cache.ttl ?? 300000}ms`);
53
+ }
54
+ await this.setupRequestContext(config);
55
+ const results = [];
56
+ const smokeTests = config.target.smoke || [`GET ${config.target.baseUrl}/health -> 200`];
57
+ console.log(`🌐 Running API smoke tests with Playwright Native (${smokeTests.length} endpoints)`);
58
+ for (const test of smokeTests) {
59
+ // Check cache first if enabled
60
+ const cacheKey = this.cache ? generateCacheKey({
61
+ method: test.split(' ')[0],
62
+ url: config.target.baseUrl,
63
+ headers: this.auth?.headers
64
+ }) : '';
65
+ let testResult;
66
+ if (this.cache) {
67
+ testResult = await this.cache.getOrFetch(cacheKey, () => this.executeApiTest(test, config));
68
+ const cached = this.cache.has(cacheKey);
69
+ const cacheIndicator = cached ? '📦' : '🌐';
70
+ if (testResult.success) {
71
+ console.log(` ${cacheIndicator} ✅ ${testResult.method} ${testResult.endpoint} -> ${testResult.status} (${testResult.responseTime}ms)`);
72
+ }
73
+ else {
74
+ const errorMsg = testResult.error || 'Request failed';
75
+ const retryInfo = testResult.attempts && testResult.attempts > 1
76
+ ? ` (after ${testResult.attempts} attempts)`
77
+ : '';
78
+ console.log(` ${cacheIndicator} ❌ ${testResult.method} ${testResult.endpoint} -> ${errorMsg}${retryInfo}`);
79
+ }
80
+ }
81
+ else {
82
+ testResult = await this.executeApiTest(test, config);
83
+ if (testResult.success) {
84
+ console.log(` ✅ ${testResult.method} ${testResult.endpoint} -> ${testResult.status} (${testResult.responseTime}ms)`);
85
+ }
86
+ else {
87
+ const errorMsg = testResult.error || 'Request failed';
88
+ const retryInfo = testResult.attempts && testResult.attempts > 1
89
+ ? ` (after ${testResult.attempts} attempts)`
90
+ : '';
91
+ console.log(` ❌ ${testResult.method} ${testResult.endpoint} -> ${errorMsg}${retryInfo}`);
92
+ }
93
+ }
94
+ results.push(testResult);
95
+ }
96
+ const summary = this.calculateSummary(results);
97
+ const junit = this.generateJUnit(results);
98
+ let error;
99
+ if (summary.failed > 0) {
100
+ const failedTests = results.filter(r => !r.success);
101
+ const errorMessages = failedTests.map(t => t.error).filter(Boolean);
102
+ error = errorMessages.length > 0
103
+ ? `${summary.failed} endpoint(s) failed: ${errorMessages[0]}${errorMessages.length > 1 ? ` (and ${errorMessages.length - 1} more)` : ''}`
104
+ : `${summary.failed} endpoint(s) failed`;
105
+ }
106
+ return {
107
+ success: summary.failed === 0,
108
+ results,
109
+ summary,
110
+ junit,
111
+ error
112
+ };
113
+ }
114
+ finally {
115
+ await this.cleanup();
116
+ }
117
+ }
118
+ /**
119
+ * Setup Playwright APIRequestContext for HTTP requests
120
+ */
121
+ async setupRequestContext(config) {
122
+ const baseOptions = {
123
+ userAgent: 'QA360-Native-API/1.0',
124
+ };
125
+ const headers = {
126
+ 'Accept': 'application/json, text/plain, */*',
127
+ 'Accept-Encoding': 'gzip, deflate, br',
128
+ };
129
+ // Add auth headers if provided
130
+ if (this.auth?.headers) {
131
+ Object.assign(headers, this.auth.headers);
132
+ }
133
+ baseOptions.extraHTTPHeaders = headers;
134
+ // Add storage state if provided
135
+ if (config.storageState) {
136
+ baseOptions.storageState = config.storageState;
137
+ }
138
+ // Set timeout
139
+ if (config.timeout) {
140
+ baseOptions.timeout = config.timeout;
141
+ }
142
+ // Create API request context using Playwright
143
+ this.requestContext = await playwright.request.newContext(baseOptions);
144
+ // Set basic auth if provided in config
145
+ if (config.basicAuth) {
146
+ // Note: setHTTPCredentials may not exist on APIRequestContext
147
+ // Using HTTPCredentials via newContext options instead
148
+ const creds = { username: config.basicAuth.username, password: config.basicAuth.password };
149
+ // Set in context options - basic auth is handled via headers
150
+ const authHeader = 'Basic ' + Buffer.from(`${creds.username}:${creds.password}`).toString('base64');
151
+ headers['Authorization'] = authHeader;
152
+ baseOptions.extraHTTPHeaders = headers;
153
+ // Recreate context with auth headers
154
+ await this.requestContext.dispose();
155
+ this.requestContext = await playwright.request.newContext(baseOptions);
156
+ }
157
+ }
158
+ /**
159
+ * Execute single API test with retry logic using native API
160
+ */
161
+ async executeApiTest(testSpec, config) {
162
+ const { method, endpoint, expectedStatus } = this.parseTestSpec(testSpec, config.target.baseUrl);
163
+ const maxRetries = config.retries || 0;
164
+ let lastError = '';
165
+ let lastResponse = null;
166
+ let attempts = 0;
167
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
168
+ attempts++;
169
+ const startTime = Date.now();
170
+ try {
171
+ // Build request options
172
+ const requestOptions = {
173
+ method: method,
174
+ timeout: config.timeout || 30000,
175
+ failOnStatusCode: false, // Don't throw on non-2xx responses
176
+ };
177
+ // Add body for POST/PUT/PATCH (from config)
178
+ if (['POST', 'PUT', 'PATCH'].includes(method) && config.body) {
179
+ requestOptions.data = config.body;
180
+ }
181
+ // Make the request using Playwright's native API
182
+ lastResponse = await this.requestContext.fetch(endpoint, requestOptions);
183
+ const responseTime = Date.now() - startTime;
184
+ const status = lastResponse.status();
185
+ const success = status === expectedStatus;
186
+ // Get response headers
187
+ const responseHeaders = lastResponse.headers();
188
+ const headers = {};
189
+ for (const [key, value] of Object.entries(responseHeaders)) {
190
+ headers[key] = value;
191
+ }
192
+ // Get response body safely
193
+ let body;
194
+ try {
195
+ const contentType = headers['content-type'] || '';
196
+ if (contentType.includes('application/json')) {
197
+ body = await lastResponse.json();
198
+ }
199
+ else {
200
+ const text = await lastResponse.text();
201
+ body = text.substring(0, 500); // Limit body size
202
+ }
203
+ }
204
+ catch {
205
+ body = '[Response body not readable]';
206
+ }
207
+ return {
208
+ endpoint,
209
+ method,
210
+ status,
211
+ responseTime,
212
+ success,
213
+ error: success ? undefined : `Expected status ${expectedStatus}, got ${status}`,
214
+ headers,
215
+ body: this.redactor.redactObject(body),
216
+ attempts
217
+ };
218
+ }
219
+ catch (error) {
220
+ lastError = error instanceof Error ? error.message : 'Unknown error';
221
+ // Check if this is a retryable error
222
+ const isRetryable = this.isRetryableError(lastError);
223
+ if (isRetryable && attempt < maxRetries) {
224
+ const waitTime = 1000 * Math.pow(2, attempt); // Exponential backoff
225
+ console.log(` 🔄 Retry ${attempt + 1}/${maxRetries} for ${method} ${endpoint} (${lastError}) - waiting ${waitTime}ms`);
226
+ await new Promise(resolve => setTimeout(resolve, waitTime));
227
+ continue;
228
+ }
229
+ break;
230
+ }
231
+ }
232
+ // If we get here, all retries failed or request threw an error
233
+ return {
234
+ endpoint,
235
+ method,
236
+ status: lastResponse?.status() || 0,
237
+ responseTime: 0,
238
+ success: false,
239
+ error: this.redactor.redact(lastError),
240
+ attempts
241
+ };
242
+ }
243
+ /**
244
+ * Parse test specification string
245
+ * Format: "GET /path -> 200" or "POST /api/users -> 201"
246
+ */
247
+ parseTestSpec(spec, baseUrl) {
248
+ const match = spec.match(/^(\w+)\s+(.+?)\s*->\s*(\d+)$/);
249
+ if (!match) {
250
+ throw new Error(`Invalid test spec format: ${spec}. Expected: "METHOD /path -> STATUS"`);
251
+ }
252
+ const [, method, path, statusStr] = match;
253
+ const expectedStatus = parseInt(statusStr, 10);
254
+ // Build full endpoint URL
255
+ let endpoint = path;
256
+ if (!path.startsWith('http')) {
257
+ endpoint = baseUrl.replace(/\/$/, '') + (path.startsWith('/') ? path : `/${path}`);
258
+ }
259
+ return {
260
+ method: method.toUpperCase(),
261
+ endpoint,
262
+ expectedStatus
263
+ };
264
+ }
265
+ /**
266
+ * Check if error is retryable
267
+ */
268
+ isRetryableError(error) {
269
+ const retryablePatterns = [
270
+ /ECONNRESET/,
271
+ /ETIMEDOUT/,
272
+ /ECONNREFUSED/,
273
+ /502 Bad Gateway/,
274
+ /503 Service Unavailable/,
275
+ /504 Gateway Timeout/,
276
+ /timeout/i,
277
+ /network/i,
278
+ /fetch failed/i,
279
+ /EPROTO/,
280
+ ];
281
+ return retryablePatterns.some(pattern => pattern.test(error));
282
+ }
283
+ /**
284
+ * Calculate test summary
285
+ */
286
+ calculateSummary(results) {
287
+ const total = results.length;
288
+ const passed = results.filter(r => r.success).length;
289
+ const failed = total - passed;
290
+ const avgResponseTime = total > 0 ?
291
+ Math.round(results.reduce((sum, r) => sum + r.responseTime, 0) / total) : 0;
292
+ const totalAttempts = results.reduce((sum, r) => sum + (r.attempts || 1), 0);
293
+ return { total, passed, failed, avgResponseTime, totalAttempts };
294
+ }
295
+ /**
296
+ * Generate JUnit XML fragment
297
+ */
298
+ generateJUnit(results) {
299
+ const summary = this.calculateSummary(results);
300
+ const timestamp = new Date().toISOString();
301
+ let junit = `<?xml version="1.0" encoding="UTF-8"?>
302
+ <testsuite name="API Smoke Tests (Playwright Native)" tests="${summary.total}" failures="${summary.failed}" time="${summary.avgResponseTime / 1000}" timestamp="${timestamp}">
303
+ `;
304
+ for (const result of results) {
305
+ const testName = `${result.method} ${result.endpoint}`;
306
+ const time = result.responseTime / 1000;
307
+ junit += ` <testcase name="${this.escapeXml(testName)}" time="${time}">
308
+ `;
309
+ if (!result.success) {
310
+ const failureDetails = {
311
+ error: result.error,
312
+ status: result.status,
313
+ attempts: result.attempts || 1
314
+ };
315
+ junit += ` <failure message="${this.escapeXml(result.error || 'Test failed')}">${this.escapeXml(JSON.stringify(failureDetails, null, 2))}</failure>
316
+ `;
317
+ }
318
+ junit += ` </testcase>
319
+ `;
320
+ }
321
+ junit += `</testsuite>`;
322
+ return junit;
323
+ }
324
+ /**
325
+ * Escape XML special characters
326
+ */
327
+ escapeXml(str) {
328
+ return str
329
+ .replace(/&/g, '&amp;')
330
+ .replace(/</g, '&lt;')
331
+ .replace(/>/g, '&gt;')
332
+ .replace(/"/g, '&quot;')
333
+ .replace(/'/g, '&apos;');
334
+ }
335
+ /**
336
+ * Cleanup request context
337
+ */
338
+ async cleanup() {
339
+ if (this.requestContext) {
340
+ await this.requestContext.dispose();
341
+ this.requestContext = undefined;
342
+ }
343
+ }
344
+ /**
345
+ * Validate API target configuration
346
+ */
347
+ static validateConfig(target) {
348
+ const errors = [];
349
+ if (!target.baseUrl) {
350
+ errors.push('API target requires baseUrl');
351
+ }
352
+ else {
353
+ try {
354
+ new URL(target.baseUrl);
355
+ }
356
+ catch {
357
+ errors.push('API target baseUrl must be a valid URL');
358
+ }
359
+ }
360
+ if (target.smoke) {
361
+ for (const test of target.smoke) {
362
+ if (!/^\w+\s+.+\s*->\s*\d+$/.test(test)) {
363
+ errors.push(`Invalid smoke test format: ${test}. Expected: "METHOD /path -> STATUS"`);
364
+ }
365
+ }
366
+ }
367
+ return {
368
+ valid: errors.length === 0,
369
+ errors
370
+ };
371
+ }
372
+ /**
373
+ * Health check using native API
374
+ * Quick check if the API is accessible without running full tests
375
+ */
376
+ async healthCheck(baseUrl, path = '/health', timeout = 5000) {
377
+ try {
378
+ await this.setupRequestContext({
379
+ target: { baseUrl },
380
+ timeout
381
+ });
382
+ const endpoint = baseUrl.replace(/\/$/, '') + (path.startsWith('/') ? path : `/${path}`);
383
+ const response = await this.requestContext.fetch(endpoint, {
384
+ method: 'GET',
385
+ timeout,
386
+ failOnStatusCode: false
387
+ });
388
+ // Consider 2xx and 3xx as healthy
389
+ return response.status() >= 200 && response.status() < 400;
390
+ }
391
+ catch {
392
+ return false;
393
+ }
394
+ finally {
395
+ await this.cleanup();
396
+ }
397
+ }
398
+ /**
399
+ * Get API info (useful for debugging)
400
+ * Returns version and capabilities
401
+ */
402
+ static getCapabilities() {
403
+ return {
404
+ adapter: 'playwright-native',
405
+ browserless: true,
406
+ features: [
407
+ 'REST API testing',
408
+ 'GraphQL support',
409
+ 'Basic auth',
410
+ 'Bearer token auth',
411
+ 'Custom headers',
412
+ 'Request/response interception',
413
+ 'Retry logic with exponential backoff',
414
+ 'Storage state for cookie persistence',
415
+ 'Timeout configuration',
416
+ 'LRU cache for HTTP responses'
417
+ ]
418
+ };
419
+ }
420
+ /**
421
+ * Get cache statistics if cache is enabled
422
+ */
423
+ getCacheStats() {
424
+ if (!this.cache) {
425
+ return { enabled: false };
426
+ }
427
+ return {
428
+ enabled: true,
429
+ stats: this.cache.getStats()
430
+ };
431
+ }
432
+ /**
433
+ * Clear the cache if enabled
434
+ */
435
+ clearCache() {
436
+ if (this.cache) {
437
+ this.cache.clear();
438
+ console.log('📦 Cache cleared');
439
+ }
440
+ }
441
+ }
442
+ /**
443
+ * Factory function to create a native API adapter
444
+ */
445
+ export function createPlaywrightNativeApiAdapter() {
446
+ return new PlaywrightNativeApiAdapter();
447
+ }
448
+ /**
449
+ * Error class for native API adapter
450
+ */
451
+ export class PlaywrightNativeApiError extends Error {
452
+ code = 'PLAYWRIGHT_NATIVE_API_ERROR';
453
+ endpoint;
454
+ method;
455
+ constructor(message, endpoint, method) {
456
+ super(message);
457
+ this.name = 'PlaywrightNativeApiError';
458
+ this.endpoint = endpoint;
459
+ this.method = method;
460
+ }
461
+ }
@@ -3,10 +3,12 @@
3
3
  * UI smoke tests + accessibility via axe-core
4
4
  */
5
5
  import { WebTarget, PackBudgets } from '../types/pack-v1.js';
6
+ import { AuthCredentials } from '../auth/index.js';
6
7
  export interface UiTestConfig {
7
8
  target: WebTarget;
8
9
  budgets?: PackBudgets;
9
10
  timeout?: number;
11
+ auth?: AuthCredentials;
10
12
  login?: {
11
13
  url?: string;
12
14
  username?: string;
@@ -59,7 +61,12 @@ export declare class PlaywrightUiAdapter {
59
61
  private context?;
60
62
  private page?;
61
63
  private redactor;
64
+ private auth?;
62
65
  constructor();
66
+ /**
67
+ * Set authentication credentials for requests
68
+ */
69
+ setAuth(credentials?: AuthCredentials): void;
63
70
  /**
64
71
  * Execute UI smoke tests with accessibility
65
72
  */
@@ -9,14 +9,23 @@ export class PlaywrightUiAdapter {
9
9
  context;
10
10
  page;
11
11
  redactor;
12
+ auth;
12
13
  constructor() {
13
14
  this.redactor = SecurityRedactor.forLogs();
14
15
  }
16
+ /**
17
+ * Set authentication credentials for requests
18
+ */
19
+ setAuth(credentials) {
20
+ this.auth = credentials;
21
+ }
15
22
  /**
16
23
  * Execute UI smoke tests with accessibility
17
24
  */
18
25
  async runSmokeTests(config) {
19
26
  try {
27
+ // Store auth config
28
+ this.auth = config.auth;
20
29
  await this.setupBrowser();
21
30
  const results = [];
22
31
  const pages = config.target.pages || [config.target.baseUrl];
@@ -292,10 +301,29 @@ export class PlaywrightUiAdapter {
292
301
  headless: true,
293
302
  args: ['--no-sandbox', '--disable-dev-shm-usage']
294
303
  });
304
+ // Build extra HTTP headers with auth
305
+ const extraHTTPHeaders = {
306
+ 'User-Agent': 'QA360-UI-Smoke/1.0'
307
+ };
308
+ if (this.auth?.headers) {
309
+ Object.assign(extraHTTPHeaders, this.auth.headers);
310
+ }
295
311
  this.context = await this.browser.newContext({
296
312
  viewport: { width: 1280, height: 720 },
297
- userAgent: 'QA360-UI-Smoke/1.0'
313
+ userAgent: 'QA360-UI-Smoke/1.0',
314
+ extraHTTPHeaders
298
315
  });
316
+ // Add cookies from auth credentials after context creation
317
+ if (this.auth?.cookies && this.auth.cookies.length > 0) {
318
+ await this.context.addCookies(this.auth.cookies.map(c => ({
319
+ name: c.name,
320
+ value: c.value,
321
+ domain: c.domain || '',
322
+ path: c.path || '/',
323
+ httpOnly: c.httpOnly || false,
324
+ secure: c.secure || false
325
+ })));
326
+ }
299
327
  this.page = await this.context.newPage();
300
328
  }
301
329
  /**
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Anthropic LLM Provider
3
+ *
4
+ * Provider for Anthropic API (https://api.anthropic.com).
5
+ * Supports Claude 3.5 Sonnet, Claude 3 Opus, and Claude 3 Haiku.
6
+ *
7
+ * Environment variables:
8
+ * - ANTHROPIC_API_KEY: Required API key
9
+ * - ANTHROPIC_BASE_URL: Optional base URL (default: https://api.anthropic.com)
10
+ * - ANTHROPIC_MODEL: Model to use (default: claude-3-5-sonnet-20241022)
11
+ */
12
+ import type { LLMProvider, GenerationRequest, GenerationResponse } from './index.js';
13
+ export interface AnthropicConfig {
14
+ apiKey?: string;
15
+ baseURL?: string;
16
+ model?: string;
17
+ timeout?: number;
18
+ }
19
+ export declare class AnthropicProvider implements LLMProvider {
20
+ name: string;
21
+ models: string[];
22
+ private readonly apiKey;
23
+ private readonly baseURL;
24
+ private readonly defaultModel;
25
+ private readonly timeout;
26
+ private readonly version;
27
+ constructor(config?: AnthropicConfig);
28
+ isAvailable(): Promise<boolean>;
29
+ generate(request: GenerationRequest): Promise<GenerationResponse>;
30
+ stream(request: GenerationRequest): AsyncIterable<string>;
31
+ countTokens(text: string): number;
32
+ private buildMessages;
33
+ private extractContent;
34
+ }
35
+ /**
36
+ * Anthropic-specific error
37
+ */
38
+ export declare class AnthropicError extends Error {
39
+ code: string;
40
+ details?: Record<string, unknown>;
41
+ constructor(message: string, details?: Record<string, unknown>);
42
+ }
43
+ /**
44
+ * Create Anthropic provider with default settings
45
+ */
46
+ export declare function createAnthropicProvider(config?: AnthropicConfig): AnthropicProvider;
47
+ /**
48
+ * Check if Anthropic is available and configured
49
+ */
50
+ export declare function checkAnthropicSetup(): Promise<boolean>;