@testsmith/testblocks 0.9.5 → 0.9.8
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/dist/cli/index.js +4 -1
- package/dist/client/assets/index-CDAzk2fI.css +1 -0
- package/dist/client/assets/index-CYoJ_6bl.js +2231 -0
- package/dist/client/assets/index-CYoJ_6bl.js.map +1 -0
- package/dist/client/index.html +22 -2
- package/dist/core/blocks/api/ApiDeleteBlock.js +1 -1
- package/dist/core/blocks/api/ApiGetBlock.js +1 -1
- package/dist/core/blocks/api/ApiPatchBlock.js +2 -1
- package/dist/core/blocks/api/ApiPostBlock.js +2 -1
- package/dist/core/blocks/api/ApiPutBlock.js +2 -1
- package/dist/server/executor.d.ts +14 -0
- package/dist/server/executor.js +27 -7
- package/dist/server/index.js +117 -11
- package/dist/server/openApiParser.d.ts +82 -0
- package/dist/server/openApiParser.js +495 -0
- package/dist/server/startServer.d.ts +1 -0
- package/dist/server/startServer.js +216 -6
- package/package.json +4 -2
- package/dist/client/assets/index-BzcQ5WS6.css +0 -1
- package/dist/client/assets/index-CJ8vFqNf.js +0 -2197
- package/dist/client/assets/index-CJ8vFqNf.js.map +0 -1
package/dist/client/index.html
CHANGED
|
@@ -16,10 +16,30 @@
|
|
|
16
16
|
overflow: hidden;
|
|
17
17
|
}
|
|
18
18
|
</style>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
20
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-CYoJ_6bl.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CDAzk2fI.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
|
+
<div id="chrome-warning" style="display:none;align-items:center;justify-content:center;height:100vh;background:#1e1e2e;color:#cdd6f4;font-family:system-ui,sans-serif;">
|
|
24
|
+
<div style="text-align:center;max-width:480px;padding:2rem;">
|
|
25
|
+
<div style="font-size:3rem;margin-bottom:1rem;">⚠️</div>
|
|
26
|
+
<h1 style="margin:0 0 1rem;font-size:1.5rem;">Chrome Required</h1>
|
|
27
|
+
<p style="margin:0 0 1.5rem;color:#a6adc8;line-height:1.6;">
|
|
28
|
+
TestBlocks only works in Google Chrome. Please open this page in Chrome to continue.
|
|
29
|
+
</p>
|
|
30
|
+
<a href="https://www.google.com/chrome/" target="_blank" rel="noopener noreferrer"
|
|
31
|
+
style="color:#89b4fa;text-decoration:underline;">Download Chrome</a>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
23
34
|
<div id="root"></div>
|
|
35
|
+
<script>
|
|
36
|
+
(function() {
|
|
37
|
+
var isChrome = /Chrome/.test(navigator.userAgent) && !/Edg/.test(navigator.userAgent);
|
|
38
|
+
if (!isChrome) {
|
|
39
|
+
document.getElementById('chrome-warning').style.display = 'flex';
|
|
40
|
+
document.getElementById('root').style.display = 'none';
|
|
41
|
+
}
|
|
42
|
+
})();
|
|
43
|
+
</script>
|
|
24
44
|
</body>
|
|
25
45
|
</html>
|
|
@@ -31,7 +31,7 @@ class ApiDeleteBlock extends base_1.StatementBlock {
|
|
|
31
31
|
});
|
|
32
32
|
const parsed = await (0, utils_1.parseResponse)(response);
|
|
33
33
|
(0, utils_1.storeResponse)(context, parsed);
|
|
34
|
-
return parsed;
|
|
34
|
+
return { ...parsed, _summary: `DELETE ${url}`, _requestHeaders: headers };
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
exports.ApiDeleteBlock = ApiDeleteBlock;
|
|
@@ -30,7 +30,7 @@ class ApiGetBlock extends base_1.StatementBlock {
|
|
|
30
30
|
});
|
|
31
31
|
const parsed = await (0, utils_1.parseResponse)(response);
|
|
32
32
|
(0, utils_1.storeResponse)(context, parsed);
|
|
33
|
-
return parsed;
|
|
33
|
+
return { ...parsed, _summary: `GET ${url}`, _requestHeaders: headers };
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
exports.ApiGetBlock = ApiGetBlock;
|
|
@@ -41,7 +41,8 @@ class ApiPatchBlock extends base_1.StatementBlock {
|
|
|
41
41
|
});
|
|
42
42
|
const parsed = await (0, utils_1.parseResponse)(response);
|
|
43
43
|
(0, utils_1.storeResponse)(context, parsed);
|
|
44
|
-
|
|
44
|
+
const requestHeaders = { 'Content-Type': 'application/json', ...inlineHeaders };
|
|
45
|
+
return { ...parsed, _summary: `PATCH ${url}`, _requestHeaders: requestHeaders, _requestBody: body };
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
exports.ApiPatchBlock = ApiPatchBlock;
|
|
@@ -41,7 +41,8 @@ class ApiPostBlock extends base_1.StatementBlock {
|
|
|
41
41
|
});
|
|
42
42
|
const parsed = await (0, utils_1.parseResponse)(response);
|
|
43
43
|
(0, utils_1.storeResponse)(context, parsed);
|
|
44
|
-
|
|
44
|
+
const requestHeaders = { 'Content-Type': 'application/json', ...inlineHeaders };
|
|
45
|
+
return { ...parsed, _summary: `POST ${url}`, _requestHeaders: requestHeaders, _requestBody: body };
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
exports.ApiPostBlock = ApiPostBlock;
|
|
@@ -41,7 +41,8 @@ class ApiPutBlock extends base_1.StatementBlock {
|
|
|
41
41
|
});
|
|
42
42
|
const parsed = await (0, utils_1.parseResponse)(response);
|
|
43
43
|
(0, utils_1.storeResponse)(context, parsed);
|
|
44
|
-
|
|
44
|
+
const requestHeaders = { 'Content-Type': 'application/json', ...inlineHeaders };
|
|
45
|
+
return { ...parsed, _summary: `PUT ${url}`, _requestHeaders: requestHeaders, _requestBody: body };
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
exports.ApiPutBlock = ApiPutBlock;
|
|
@@ -8,6 +8,20 @@ export interface ExecutorOptions {
|
|
|
8
8
|
testIdAttribute?: string;
|
|
9
9
|
baseDir?: string;
|
|
10
10
|
procedures?: Record<string, ProcedureDefinition>;
|
|
11
|
+
locale?: string;
|
|
12
|
+
timezoneId?: string;
|
|
13
|
+
geolocation?: {
|
|
14
|
+
latitude: number;
|
|
15
|
+
longitude: number;
|
|
16
|
+
};
|
|
17
|
+
viewport?: {
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
21
|
+
localStorage?: {
|
|
22
|
+
name: string;
|
|
23
|
+
value: string;
|
|
24
|
+
}[];
|
|
11
25
|
}
|
|
12
26
|
export declare class TestExecutor {
|
|
13
27
|
private options;
|
package/dist/server/executor.js
CHANGED
|
@@ -121,10 +121,30 @@ class TestExecutor {
|
|
|
121
121
|
this.browser = await playwright_1.chromium.launch({
|
|
122
122
|
headless: this.options.headless,
|
|
123
123
|
});
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
viewport: { width: 1920, height: 1080 },
|
|
127
|
-
}
|
|
124
|
+
// Build browser context options
|
|
125
|
+
const contextOptions = {
|
|
126
|
+
viewport: this.options.viewport || { width: 1920, height: 1080 },
|
|
127
|
+
};
|
|
128
|
+
if (this.options.locale) {
|
|
129
|
+
contextOptions.locale = this.options.locale;
|
|
130
|
+
}
|
|
131
|
+
if (this.options.timezoneId) {
|
|
132
|
+
contextOptions.timezoneId = this.options.timezoneId;
|
|
133
|
+
}
|
|
134
|
+
if (this.options.geolocation) {
|
|
135
|
+
contextOptions.geolocation = this.options.geolocation;
|
|
136
|
+
contextOptions.permissions = ['geolocation'];
|
|
137
|
+
}
|
|
138
|
+
console.log('Browser context options:', JSON.stringify(contextOptions));
|
|
139
|
+
this.context = await this.browser.newContext(contextOptions);
|
|
140
|
+
if (this.options.localStorage && this.options.localStorage.length > 0) {
|
|
141
|
+
const items = this.options.localStorage;
|
|
142
|
+
await this.context.addInitScript((storageItems) => {
|
|
143
|
+
for (const item of storageItems) {
|
|
144
|
+
localStorage.setItem(item.name, item.value);
|
|
145
|
+
}
|
|
146
|
+
}, items);
|
|
147
|
+
}
|
|
128
148
|
this.page = await this.context.newPage();
|
|
129
149
|
if (this.options.timeout) {
|
|
130
150
|
this.page.setDefaultTimeout(this.options.timeout);
|
|
@@ -189,8 +209,8 @@ class TestExecutor {
|
|
|
189
209
|
}
|
|
190
210
|
const sharedContext = {
|
|
191
211
|
variables: new Map(Object.entries({
|
|
192
|
-
...this.resolveVariableDefaults(testFile.variables),
|
|
193
212
|
...this.resolveVariableDefaults(this.options.variables),
|
|
213
|
+
...this.resolveVariableDefaults(testFile.variables),
|
|
194
214
|
})),
|
|
195
215
|
results: [],
|
|
196
216
|
browser: this.browser,
|
|
@@ -414,8 +434,8 @@ class TestExecutor {
|
|
|
414
434
|
const baseVariables = sharedContext
|
|
415
435
|
? Object.fromEntries(sharedContext.variables)
|
|
416
436
|
: {
|
|
417
|
-
...this.resolveVariableDefaults(fileVariables),
|
|
418
437
|
...this.resolveVariableDefaults(this.options.variables),
|
|
438
|
+
...this.resolveVariableDefaults(fileVariables),
|
|
419
439
|
};
|
|
420
440
|
const context = {
|
|
421
441
|
variables: new Map(Object.entries(baseVariables)),
|
|
@@ -504,8 +524,8 @@ class TestExecutor {
|
|
|
504
524
|
const baseVariables = sharedContext
|
|
505
525
|
? Object.fromEntries(sharedContext.variables)
|
|
506
526
|
: {
|
|
507
|
-
...this.resolveVariableDefaults(fileVariables),
|
|
508
527
|
...this.resolveVariableDefaults(this.options.variables),
|
|
528
|
+
...this.resolveVariableDefaults(fileVariables),
|
|
509
529
|
};
|
|
510
530
|
const context = {
|
|
511
531
|
variables: new Map(Object.entries(baseVariables)),
|
package/dist/server/index.js
CHANGED
|
@@ -48,6 +48,7 @@ const reporters_1 = require("../cli/reporters");
|
|
|
48
48
|
const plugins_1 = require("./plugins");
|
|
49
49
|
const globals_1 = require("./globals");
|
|
50
50
|
const codegenManager_1 = require("./codegenManager");
|
|
51
|
+
const openApiParser_1 = require("./openApiParser");
|
|
51
52
|
// Set plugins directory (default to examples/plugins or can be overridden via env)
|
|
52
53
|
const pluginsDir = process.env.PLUGINS_DIR || path_1.default.join(process.cwd(), 'examples', 'plugins');
|
|
53
54
|
(0, plugins_1.setPluginsDirectory)(pluginsDir);
|
|
@@ -370,6 +371,108 @@ app.get('/api/record/status/:sessionId', (req, res) => {
|
|
|
370
371
|
error: session.error,
|
|
371
372
|
});
|
|
372
373
|
});
|
|
374
|
+
// ===== OpenAPI Import Endpoints =====
|
|
375
|
+
// Parse an OpenAPI/Swagger spec from URL
|
|
376
|
+
app.post('/api/openapi/parse', async (req, res) => {
|
|
377
|
+
try {
|
|
378
|
+
const { url } = req.body;
|
|
379
|
+
if (!url) {
|
|
380
|
+
return res.status(400).json({ error: 'URL is required' });
|
|
381
|
+
}
|
|
382
|
+
console.log(`Parsing OpenAPI spec from URL: ${url}`);
|
|
383
|
+
const spec = await (0, openApiParser_1.parseOpenApiSpec)(url, true);
|
|
384
|
+
res.json({
|
|
385
|
+
info: spec.info,
|
|
386
|
+
servers: spec.servers,
|
|
387
|
+
endpoints: spec.endpoints.map(e => ({
|
|
388
|
+
operationId: e.operationId,
|
|
389
|
+
method: e.method,
|
|
390
|
+
path: e.path,
|
|
391
|
+
summary: e.summary,
|
|
392
|
+
description: e.description,
|
|
393
|
+
tags: e.tags,
|
|
394
|
+
deprecated: e.deprecated,
|
|
395
|
+
hasRequestBody: !!e.requestBody,
|
|
396
|
+
responses: e.responses.map(r => r.statusCode),
|
|
397
|
+
})),
|
|
398
|
+
securitySchemes: spec.securitySchemes,
|
|
399
|
+
tags: spec.tags,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
console.error('Failed to parse OpenAPI spec:', error);
|
|
404
|
+
res.status(500).json({
|
|
405
|
+
error: 'Failed to parse OpenAPI spec',
|
|
406
|
+
message: error.message,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
// Parse an OpenAPI/Swagger spec from content
|
|
411
|
+
app.post('/api/openapi/parse-content', async (req, res) => {
|
|
412
|
+
try {
|
|
413
|
+
const { content } = req.body;
|
|
414
|
+
if (!content) {
|
|
415
|
+
return res.status(400).json({ error: 'Content is required' });
|
|
416
|
+
}
|
|
417
|
+
console.log('Parsing OpenAPI spec from content');
|
|
418
|
+
const spec = await (0, openApiParser_1.parseOpenApiSpec)(content, false);
|
|
419
|
+
res.json({
|
|
420
|
+
info: spec.info,
|
|
421
|
+
servers: spec.servers,
|
|
422
|
+
endpoints: spec.endpoints.map(e => ({
|
|
423
|
+
operationId: e.operationId,
|
|
424
|
+
method: e.method,
|
|
425
|
+
path: e.path,
|
|
426
|
+
summary: e.summary,
|
|
427
|
+
description: e.description,
|
|
428
|
+
tags: e.tags,
|
|
429
|
+
deprecated: e.deprecated,
|
|
430
|
+
hasRequestBody: !!e.requestBody,
|
|
431
|
+
responses: e.responses.map(r => r.statusCode),
|
|
432
|
+
})),
|
|
433
|
+
securitySchemes: spec.securitySchemes,
|
|
434
|
+
tags: spec.tags,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
console.error('Failed to parse OpenAPI spec:', error);
|
|
439
|
+
res.status(500).json({
|
|
440
|
+
error: 'Failed to parse OpenAPI spec',
|
|
441
|
+
message: error.message,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
// Generate test files from selected endpoints
|
|
446
|
+
app.post('/api/openapi/generate', async (req, res) => {
|
|
447
|
+
try {
|
|
448
|
+
const { url, content, selectedEndpoints, options } = req.body;
|
|
449
|
+
if (!url && !content) {
|
|
450
|
+
return res.status(400).json({ error: 'Either URL or content is required' });
|
|
451
|
+
}
|
|
452
|
+
if (!selectedEndpoints || selectedEndpoints.length === 0) {
|
|
453
|
+
return res.status(400).json({ error: 'No endpoints selected' });
|
|
454
|
+
}
|
|
455
|
+
console.log(`Generating tests for ${selectedEndpoints.length} endpoints`);
|
|
456
|
+
const spec = url
|
|
457
|
+
? await (0, openApiParser_1.parseOpenApiSpec)(url, true)
|
|
458
|
+
: await (0, openApiParser_1.parseOpenApiSpec)(content, false);
|
|
459
|
+
const files = (0, openApiParser_1.generateTestFiles)(spec, selectedEndpoints, options);
|
|
460
|
+
res.json({
|
|
461
|
+
files: files.map(f => ({
|
|
462
|
+
fileName: f.fileName,
|
|
463
|
+
testFile: f.testFile,
|
|
464
|
+
testCount: f.testFile.tests.length,
|
|
465
|
+
})),
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
console.error('Failed to generate test files:', error);
|
|
470
|
+
res.status(500).json({
|
|
471
|
+
error: 'Failed to generate test files',
|
|
472
|
+
message: error.message,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
});
|
|
373
476
|
// ===== Report Generation Endpoints =====
|
|
374
477
|
// Generate HTML report from test results
|
|
375
478
|
app.post('/api/reports/html', (req, res) => {
|
|
@@ -459,15 +562,18 @@ process.on('SIGINT', () => {
|
|
|
459
562
|
app.listen(PORT, () => {
|
|
460
563
|
console.log(`TestBlocks server running on http://localhost:${PORT}`);
|
|
461
564
|
console.log('API endpoints:');
|
|
462
|
-
console.log(' GET /api/health
|
|
463
|
-
console.log(' GET /api/plugins
|
|
464
|
-
console.log(' GET /api/globals
|
|
465
|
-
console.log(' POST /api/run
|
|
466
|
-
console.log(' POST /api/run/:id
|
|
467
|
-
console.log(' POST /api/validate
|
|
468
|
-
console.log(' POST /api/record/start
|
|
469
|
-
console.log(' POST /api/record/stop
|
|
470
|
-
console.log(' GET /api/record/status
|
|
471
|
-
console.log(' POST /api/
|
|
472
|
-
console.log(' POST /api/
|
|
565
|
+
console.log(' GET /api/health - Health check');
|
|
566
|
+
console.log(' GET /api/plugins - List plugins');
|
|
567
|
+
console.log(' GET /api/globals - Get globals and snippets');
|
|
568
|
+
console.log(' POST /api/run - Run all tests');
|
|
569
|
+
console.log(' POST /api/run/:id - Run single test');
|
|
570
|
+
console.log(' POST /api/validate - Validate test file');
|
|
571
|
+
console.log(' POST /api/record/start - Start recording session');
|
|
572
|
+
console.log(' POST /api/record/stop - Stop recording and get steps');
|
|
573
|
+
console.log(' GET /api/record/status - Get recording session status');
|
|
574
|
+
console.log(' POST /api/openapi/parse - Parse OpenAPI spec from URL');
|
|
575
|
+
console.log(' POST /api/openapi/parse-content - Parse OpenAPI spec from content');
|
|
576
|
+
console.log(' POST /api/openapi/generate - Generate tests from OpenAPI spec');
|
|
577
|
+
console.log(' POST /api/reports/html - Generate HTML report');
|
|
578
|
+
console.log(' POST /api/reports/junit - Generate JUnit XML report');
|
|
473
579
|
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { TestFile } from '../core';
|
|
2
|
+
export interface ParsedParameter {
|
|
3
|
+
name: string;
|
|
4
|
+
in: 'path' | 'query' | 'header' | 'cookie';
|
|
5
|
+
required: boolean;
|
|
6
|
+
description?: string;
|
|
7
|
+
schema?: {
|
|
8
|
+
type: string;
|
|
9
|
+
format?: string;
|
|
10
|
+
default?: unknown;
|
|
11
|
+
example?: unknown;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export interface ParsedRequestBody {
|
|
15
|
+
required: boolean;
|
|
16
|
+
contentType: string;
|
|
17
|
+
schema?: Record<string, unknown>;
|
|
18
|
+
example?: unknown;
|
|
19
|
+
}
|
|
20
|
+
export interface ParsedResponse {
|
|
21
|
+
statusCode: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
schema?: Record<string, unknown>;
|
|
24
|
+
example?: unknown;
|
|
25
|
+
}
|
|
26
|
+
export interface ParsedEndpoint {
|
|
27
|
+
operationId: string;
|
|
28
|
+
method: 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
29
|
+
path: string;
|
|
30
|
+
summary?: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
tags: string[];
|
|
33
|
+
parameters: ParsedParameter[];
|
|
34
|
+
requestBody?: ParsedRequestBody;
|
|
35
|
+
responses: ParsedResponse[];
|
|
36
|
+
security?: Record<string, string[]>[];
|
|
37
|
+
deprecated?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface ParsedSecurityScheme {
|
|
40
|
+
type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
|
|
41
|
+
name?: string;
|
|
42
|
+
in?: 'header' | 'query' | 'cookie';
|
|
43
|
+
scheme?: string;
|
|
44
|
+
bearerFormat?: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface ParsedSpec {
|
|
48
|
+
info: {
|
|
49
|
+
title: string;
|
|
50
|
+
version: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
};
|
|
53
|
+
servers: {
|
|
54
|
+
url: string;
|
|
55
|
+
description?: string;
|
|
56
|
+
}[];
|
|
57
|
+
endpoints: ParsedEndpoint[];
|
|
58
|
+
securitySchemes: Record<string, ParsedSecurityScheme>;
|
|
59
|
+
tags: {
|
|
60
|
+
name: string;
|
|
61
|
+
description?: string;
|
|
62
|
+
}[];
|
|
63
|
+
}
|
|
64
|
+
export interface ImportOptions {
|
|
65
|
+
baseUrl: string;
|
|
66
|
+
fileStrategy: 'single' | 'per-tag' | 'per-path';
|
|
67
|
+
includeExamples: boolean;
|
|
68
|
+
generateAssertions: boolean;
|
|
69
|
+
authVariablePrefix: string;
|
|
70
|
+
}
|
|
71
|
+
export interface GeneratedTestFile {
|
|
72
|
+
fileName: string;
|
|
73
|
+
testFile: TestFile;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Parse an OpenAPI/Swagger spec from a URL or content string
|
|
77
|
+
*/
|
|
78
|
+
export declare function parseOpenApiSpec(source: string, isUrl?: boolean): Promise<ParsedSpec>;
|
|
79
|
+
/**
|
|
80
|
+
* Generate test files from selected endpoints
|
|
81
|
+
*/
|
|
82
|
+
export declare function generateTestFiles(spec: ParsedSpec, selectedEndpoints: string[], options: ImportOptions): GeneratedTestFile[];
|