testblocks 0.1.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +333 -0
  3. package/dist/cli/executor.d.ts +32 -0
  4. package/dist/cli/executor.js +517 -0
  5. package/dist/cli/index.d.ts +2 -0
  6. package/dist/cli/index.js +411 -0
  7. package/dist/cli/reporters.d.ts +62 -0
  8. package/dist/cli/reporters.js +451 -0
  9. package/dist/client/assets/index-4hbFPUhP.js +2087 -0
  10. package/dist/client/assets/index-4hbFPUhP.js.map +1 -0
  11. package/dist/client/assets/index-Dnk1ti7l.css +1 -0
  12. package/dist/client/index.html +25 -0
  13. package/dist/core/blocks/api.d.ts +2 -0
  14. package/dist/core/blocks/api.js +610 -0
  15. package/dist/core/blocks/data-driven.d.ts +2 -0
  16. package/dist/core/blocks/data-driven.js +245 -0
  17. package/dist/core/blocks/index.d.ts +15 -0
  18. package/dist/core/blocks/index.js +71 -0
  19. package/dist/core/blocks/lifecycle.d.ts +2 -0
  20. package/dist/core/blocks/lifecycle.js +199 -0
  21. package/dist/core/blocks/logic.d.ts +2 -0
  22. package/dist/core/blocks/logic.js +357 -0
  23. package/dist/core/blocks/playwright.d.ts +2 -0
  24. package/dist/core/blocks/playwright.js +764 -0
  25. package/dist/core/blocks/procedures.d.ts +5 -0
  26. package/dist/core/blocks/procedures.js +321 -0
  27. package/dist/core/index.d.ts +5 -0
  28. package/dist/core/index.js +44 -0
  29. package/dist/core/plugins.d.ts +66 -0
  30. package/dist/core/plugins.js +118 -0
  31. package/dist/core/types.d.ts +153 -0
  32. package/dist/core/types.js +2 -0
  33. package/dist/server/codegenManager.d.ts +54 -0
  34. package/dist/server/codegenManager.js +259 -0
  35. package/dist/server/codegenParser.d.ts +17 -0
  36. package/dist/server/codegenParser.js +598 -0
  37. package/dist/server/executor.d.ts +37 -0
  38. package/dist/server/executor.js +672 -0
  39. package/dist/server/globals.d.ts +85 -0
  40. package/dist/server/globals.js +273 -0
  41. package/dist/server/index.d.ts +2 -0
  42. package/dist/server/index.js +361 -0
  43. package/dist/server/plugins.d.ts +55 -0
  44. package/dist/server/plugins.js +206 -0
  45. package/package.json +103 -0
@@ -0,0 +1,411 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ const commander_1 = require("commander");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const glob_1 = require("glob");
41
+ const executor_1 = require("./executor");
42
+ const reporters_1 = require("./reporters");
43
+ const program = new commander_1.Command();
44
+ program
45
+ .name('testblocks')
46
+ .description('CLI runner for TestBlocks visual test automation')
47
+ .version('1.0.0');
48
+ program
49
+ .command('run')
50
+ .description('Run test files')
51
+ .argument('<patterns...>', 'Test file patterns (glob supported)')
52
+ .option('-H, --headed', 'Run tests in headed mode (show browser)', false)
53
+ .option('-t, --timeout <ms>', 'Test timeout in milliseconds', '30000')
54
+ .option('-r, --reporter <type>', 'Reporter type: console, json, junit, html', 'console')
55
+ .option('-o, --output <dir>', 'Output directory for reports', './testblocks-results')
56
+ .option('-b, --base-url <url>', 'Base URL for relative URLs')
57
+ .option('-v, --var <vars...>', 'Variables in key=value format')
58
+ .option('--fail-fast', 'Stop on first test failure', false)
59
+ .option('-p, --parallel <count>', 'Number of parallel workers', '1')
60
+ .option('--filter <pattern>', 'Only run tests matching pattern')
61
+ .action(async (patterns, options) => {
62
+ try {
63
+ // Find test files
64
+ const files = [];
65
+ for (const pattern of patterns) {
66
+ const matches = await (0, glob_1.glob)(pattern, { absolute: true });
67
+ files.push(...matches);
68
+ }
69
+ if (files.length === 0) {
70
+ console.error('No test files found matching patterns:', patterns);
71
+ process.exit(1);
72
+ }
73
+ console.log(`Found ${files.length} test file(s)\n`);
74
+ // Parse variables
75
+ const variables = {};
76
+ if (options.var) {
77
+ for (const v of options.var) {
78
+ const [key, ...valueParts] = v.split('=');
79
+ const value = valueParts.join('=');
80
+ // Try to parse as JSON, otherwise use as string
81
+ try {
82
+ variables[key] = JSON.parse(value);
83
+ }
84
+ catch {
85
+ variables[key] = value;
86
+ }
87
+ }
88
+ }
89
+ // Create executor options
90
+ const executorOptions = {
91
+ headless: !options.headed,
92
+ timeout: parseInt(options.timeout, 10),
93
+ baseUrl: options.baseUrl,
94
+ variables,
95
+ };
96
+ // Create reporter
97
+ const reporter = createReporter(options.reporter, options.output);
98
+ // Run tests
99
+ const allResults = [];
100
+ let hasFailures = false;
101
+ for (const file of files) {
102
+ console.log(`Running: ${path.basename(file)}`);
103
+ const content = fs.readFileSync(file, 'utf-8');
104
+ const testFile = JSON.parse(content);
105
+ // Apply filter if specified
106
+ if (options.filter) {
107
+ const filterRegex = new RegExp(options.filter, 'i');
108
+ testFile.tests = testFile.tests.filter(t => filterRegex.test(t.name));
109
+ }
110
+ if (testFile.tests.length === 0) {
111
+ console.log(' (no tests match filter)\n');
112
+ continue;
113
+ }
114
+ const executor = new executor_1.TestExecutor(executorOptions);
115
+ const results = await executor.runTestFile(testFile);
116
+ allResults.push({ file, results });
117
+ // Report results
118
+ reporter.onTestFileComplete(file, testFile, results);
119
+ // Check for failures
120
+ const failed = results.some(r => r.status !== 'passed');
121
+ if (failed) {
122
+ hasFailures = true;
123
+ if (options.failFast) {
124
+ console.log('\nStopping due to --fail-fast\n');
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ // Generate final report
130
+ reporter.onComplete(allResults);
131
+ // Exit with appropriate code
132
+ process.exit(hasFailures ? 1 : 0);
133
+ }
134
+ catch (error) {
135
+ console.error('Error:', error.message);
136
+ process.exit(1);
137
+ }
138
+ });
139
+ program
140
+ .command('validate')
141
+ .description('Validate test files without running them')
142
+ .argument('<patterns...>', 'Test file patterns (glob supported)')
143
+ .action(async (patterns) => {
144
+ try {
145
+ const files = [];
146
+ for (const pattern of patterns) {
147
+ const matches = await (0, glob_1.glob)(pattern, { absolute: true });
148
+ files.push(...matches);
149
+ }
150
+ if (files.length === 0) {
151
+ console.error('No test files found matching patterns:', patterns);
152
+ process.exit(1);
153
+ }
154
+ let hasErrors = false;
155
+ for (const file of files) {
156
+ console.log(`Validating: ${path.basename(file)}`);
157
+ try {
158
+ const content = fs.readFileSync(file, 'utf-8');
159
+ const testFile = JSON.parse(content);
160
+ const errors = validateTestFile(testFile);
161
+ if (errors.length > 0) {
162
+ hasErrors = true;
163
+ console.log(' ✗ Invalid');
164
+ errors.forEach(err => console.log(` - ${err}`));
165
+ }
166
+ else {
167
+ console.log(` ✓ Valid (${testFile.tests.length} tests)`);
168
+ }
169
+ }
170
+ catch (error) {
171
+ hasErrors = true;
172
+ console.log(` ✗ Parse error: ${error.message}`);
173
+ }
174
+ }
175
+ process.exit(hasErrors ? 1 : 0);
176
+ }
177
+ catch (error) {
178
+ console.error('Error:', error.message);
179
+ process.exit(1);
180
+ }
181
+ });
182
+ program
183
+ .command('init')
184
+ .description('Initialize a new TestBlocks project')
185
+ .argument('[directory]', 'Directory to initialize (default: current directory)', '.')
186
+ .option('--name <name>', 'Project name', 'my-testblocks-project')
187
+ .action((directory, options) => {
188
+ const projectDir = path.resolve(directory);
189
+ const projectName = options.name;
190
+ console.log(`\nInitializing TestBlocks project in ${projectDir}...\n`);
191
+ // Create directories
192
+ const dirs = ['tests', 'snippets', 'plugins', 'reports'];
193
+ dirs.forEach(dir => {
194
+ const dirPath = path.join(projectDir, dir);
195
+ if (!fs.existsSync(dirPath)) {
196
+ fs.mkdirSync(dirPath, { recursive: true });
197
+ console.log(` Created: ${dir}/`);
198
+ }
199
+ });
200
+ // Create globals.json
201
+ const globalsPath = path.join(projectDir, 'globals.json');
202
+ if (!fs.existsSync(globalsPath)) {
203
+ const globals = {
204
+ variables: {
205
+ baseUrl: 'https://example.com',
206
+ credentials: {
207
+ validUser: {
208
+ email: 'test@example.com',
209
+ password: 'password123',
210
+ },
211
+ },
212
+ },
213
+ testIdAttribute: 'data-testid',
214
+ };
215
+ fs.writeFileSync(globalsPath, JSON.stringify(globals, null, 2));
216
+ console.log(' Created: globals.json');
217
+ }
218
+ // Create package.json
219
+ const packagePath = path.join(projectDir, 'package.json');
220
+ if (!fs.existsSync(packagePath)) {
221
+ const packageJson = {
222
+ name: projectName,
223
+ version: '1.0.0',
224
+ description: 'TestBlocks test automation project',
225
+ scripts: {
226
+ test: 'testblocks run tests/**/*.testblocks.json',
227
+ 'test:headed': 'testblocks run tests/**/*.testblocks.json --headed',
228
+ 'test:html': 'testblocks run tests/**/*.testblocks.json -r html -o reports',
229
+ 'test:junit': 'testblocks run tests/**/*.testblocks.json -r junit -o reports',
230
+ },
231
+ devDependencies: {
232
+ testblocks: '^1.0.0',
233
+ },
234
+ };
235
+ fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
236
+ console.log(' Created: package.json');
237
+ }
238
+ // Create example test file
239
+ const exampleTestPath = path.join(projectDir, 'tests', 'example.testblocks.json');
240
+ if (!fs.existsSync(exampleTestPath)) {
241
+ const exampleTest = {
242
+ version: '1.0.0',
243
+ name: 'Example Test Suite',
244
+ description: 'Sample tests demonstrating TestBlocks features',
245
+ variables: {},
246
+ tests: [
247
+ {
248
+ id: 'test-web-1',
249
+ name: 'Example Web Test',
250
+ description: 'Navigate to a page and verify content',
251
+ steps: [
252
+ {
253
+ id: 'step-1',
254
+ type: 'web_navigate',
255
+ params: { URL: '${baseUrl}' },
256
+ },
257
+ {
258
+ id: 'step-2',
259
+ type: 'web_expect_title',
260
+ params: { EXPECTED: 'Example Domain' },
261
+ },
262
+ ],
263
+ tags: ['web', 'smoke'],
264
+ },
265
+ ],
266
+ };
267
+ fs.writeFileSync(exampleTestPath, JSON.stringify(exampleTest, null, 2));
268
+ console.log(' Created: tests/example.testblocks.json');
269
+ }
270
+ // Create data-driven example
271
+ const dataDrivenPath = path.join(projectDir, 'tests', 'login-data-driven.testblocks.json');
272
+ if (!fs.existsSync(dataDrivenPath)) {
273
+ const dataDrivenTest = {
274
+ version: '1.0.0',
275
+ name: 'Login Tests (Data-Driven)',
276
+ description: 'Data-driven login tests with multiple user credentials',
277
+ variables: {},
278
+ tests: [
279
+ {
280
+ id: 'test-login-dd',
281
+ name: 'Login with credentials',
282
+ description: 'Tests login with different user types',
283
+ data: [
284
+ { name: 'Valid Admin', values: { username: 'admin@example.com', password: 'admin123', shouldSucceed: true } },
285
+ { name: 'Valid User', values: { username: 'user@example.com', password: 'user123', shouldSucceed: true } },
286
+ { name: 'Invalid Password', values: { username: 'user@example.com', password: 'wrong', shouldSucceed: false } },
287
+ { name: 'Invalid User', values: { username: 'nobody@example.com', password: 'test', shouldSucceed: false } },
288
+ ],
289
+ steps: [
290
+ {
291
+ id: 'step-1',
292
+ type: 'web_navigate',
293
+ params: { URL: '${baseUrl}/login' },
294
+ },
295
+ {
296
+ id: 'step-2',
297
+ type: 'web_fill',
298
+ params: { SELECTOR: '#email', VALUE: '${username}' },
299
+ },
300
+ {
301
+ id: 'step-3',
302
+ type: 'web_fill',
303
+ params: { SELECTOR: '#password', VALUE: '${password}' },
304
+ },
305
+ {
306
+ id: 'step-4',
307
+ type: 'web_click',
308
+ params: { SELECTOR: 'button[type="submit"]' },
309
+ },
310
+ ],
311
+ tags: ['web', 'login', 'data-driven'],
312
+ },
313
+ ],
314
+ };
315
+ fs.writeFileSync(dataDrivenPath, JSON.stringify(dataDrivenTest, null, 2));
316
+ console.log(' Created: tests/login-data-driven.testblocks.json');
317
+ }
318
+ // Create .gitignore
319
+ const gitignorePath = path.join(projectDir, '.gitignore');
320
+ if (!fs.existsSync(gitignorePath)) {
321
+ const gitignore = `# Dependencies
322
+ node_modules/
323
+
324
+ # Reports
325
+ reports/
326
+
327
+ # IDE
328
+ .idea/
329
+ .vscode/
330
+
331
+ # OS
332
+ .DS_Store
333
+ Thumbs.db
334
+ `;
335
+ fs.writeFileSync(gitignorePath, gitignore);
336
+ console.log(' Created: .gitignore');
337
+ }
338
+ console.log('\n✓ Project initialized successfully!\n');
339
+ console.log('Next steps:');
340
+ console.log(' 1. cd ' + (directory === '.' ? '' : directory));
341
+ console.log(' 2. npm install');
342
+ console.log(' 3. npm test\n');
343
+ console.log('Or open the folder in TestBlocks web editor to create tests visually.\n');
344
+ });
345
+ program
346
+ .command('list')
347
+ .description('List tests in test files')
348
+ .argument('<patterns...>', 'Test file patterns (glob supported)')
349
+ .action(async (patterns) => {
350
+ try {
351
+ const files = [];
352
+ for (const pattern of patterns) {
353
+ const matches = await (0, glob_1.glob)(pattern, { absolute: true });
354
+ files.push(...matches);
355
+ }
356
+ if (files.length === 0) {
357
+ console.error('No test files found matching patterns:', patterns);
358
+ process.exit(1);
359
+ }
360
+ for (const file of files) {
361
+ console.log(`\n${path.basename(file)}:`);
362
+ const content = fs.readFileSync(file, 'utf-8');
363
+ const testFile = JSON.parse(content);
364
+ testFile.tests.forEach((test, index) => {
365
+ const tags = test.tags?.length ? ` [${test.tags.join(', ')}]` : '';
366
+ console.log(` ${index + 1}. ${test.name}${tags}`);
367
+ });
368
+ }
369
+ }
370
+ catch (error) {
371
+ console.error('Error:', error.message);
372
+ process.exit(1);
373
+ }
374
+ });
375
+ function createReporter(type, outputDir) {
376
+ switch (type) {
377
+ case 'json':
378
+ return new reporters_1.JSONReporter(outputDir);
379
+ case 'junit':
380
+ return new reporters_1.JUnitReporter(outputDir);
381
+ case 'html':
382
+ return new reporters_1.HTMLReporter(outputDir);
383
+ case 'console':
384
+ default:
385
+ return new reporters_1.ConsoleReporter();
386
+ }
387
+ }
388
+ function validateTestFile(testFile) {
389
+ const errors = [];
390
+ if (!testFile.version) {
391
+ errors.push('Missing version field');
392
+ }
393
+ if (!testFile.name) {
394
+ errors.push('Missing name field');
395
+ }
396
+ if (!testFile.tests || !Array.isArray(testFile.tests)) {
397
+ errors.push('Missing or invalid tests array');
398
+ }
399
+ else {
400
+ testFile.tests.forEach((test, index) => {
401
+ if (!test.id) {
402
+ errors.push(`Test at index ${index} is missing an id`);
403
+ }
404
+ if (!test.name) {
405
+ errors.push(`Test at index ${index} is missing a name`);
406
+ }
407
+ });
408
+ }
409
+ return errors;
410
+ }
411
+ program.parse();
@@ -0,0 +1,62 @@
1
+ import { TestFile, TestResult } from '../core';
2
+ export interface Reporter {
3
+ onTestFileComplete(file: string, testFile: TestFile, results: TestResult[]): void;
4
+ onComplete(allResults: {
5
+ file: string;
6
+ results: TestResult[];
7
+ }[]): void;
8
+ }
9
+ export declare function getTimestamp(): string;
10
+ export declare class ConsoleReporter implements Reporter {
11
+ onTestFileComplete(file: string, testFile: TestFile, results: TestResult[]): void;
12
+ onComplete(allResults: {
13
+ file: string;
14
+ results: TestResult[];
15
+ }[]): void;
16
+ }
17
+ export declare class JSONReporter implements Reporter {
18
+ private outputDir;
19
+ private allResults;
20
+ constructor(outputDir: string);
21
+ onTestFileComplete(file: string, testFile: TestFile, results: TestResult[]): void;
22
+ onComplete(allResults: {
23
+ file: string;
24
+ results: TestResult[];
25
+ }[]): void;
26
+ }
27
+ export declare class JUnitReporter implements Reporter {
28
+ private outputDir;
29
+ private allResults;
30
+ constructor(outputDir: string);
31
+ onTestFileComplete(file: string, testFile: TestFile, results: TestResult[]): void;
32
+ onComplete(allResults: {
33
+ file: string;
34
+ results: TestResult[];
35
+ }[]): void;
36
+ }
37
+ export declare class HTMLReporter implements Reporter {
38
+ private outputDir;
39
+ private allResults;
40
+ constructor(outputDir: string);
41
+ onTestFileComplete(file: string, testFile: TestFile, results: TestResult[]): void;
42
+ onComplete(allResults: {
43
+ file: string;
44
+ results: TestResult[];
45
+ }[]): void;
46
+ }
47
+ export interface ReportData {
48
+ timestamp: string;
49
+ summary: {
50
+ totalTests: number;
51
+ passed: number;
52
+ failed: number;
53
+ duration: number;
54
+ };
55
+ testFiles: {
56
+ file: string;
57
+ testFile: TestFile;
58
+ results: TestResult[];
59
+ }[];
60
+ }
61
+ export declare function generateHTMLReport(data: ReportData): string;
62
+ export declare function generateJUnitXML(data: ReportData): string;