@testsmith/testblocks 0.1.0 → 0.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.
@@ -16,8 +16,8 @@
16
16
  overflow: hidden;
17
17
  }
18
18
  </style>
19
- <script type="module" crossorigin src="/assets/index-Cq84-VIf.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-Dnk1ti7l.css">
19
+ <script type="module" crossorigin src="/assets/index-q27f2_ju.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-DbvW1Eh_.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
@@ -1,3 +1,10 @@
1
+ export interface FolderHooks {
2
+ version: string;
3
+ beforeAll?: TestStep[];
4
+ afterAll?: TestStep[];
5
+ beforeEach?: TestStep[];
6
+ afterEach?: TestStep[];
7
+ }
1
8
  export interface TestFile {
2
9
  version: string;
3
10
  name: string;
@@ -17,6 +17,7 @@ export declare class TestExecutor {
17
17
  constructor(options?: ExecutorOptions);
18
18
  initialize(): Promise<void>;
19
19
  cleanup(): Promise<void>;
20
+ private requiresBrowser;
20
21
  runTestFile(testFile: TestFile): Promise<TestResult[]>;
21
22
  private runLifecycleSteps;
22
23
  /**
@@ -130,13 +130,44 @@ class TestExecutor {
130
130
  this.context = null;
131
131
  this.browser = null;
132
132
  }
133
+ requiresBrowser(testFile) {
134
+ const hasWebStep = (steps) => {
135
+ return steps.some(step => step.type.startsWith('web_'));
136
+ };
137
+ const hasWebStepInState = (state) => {
138
+ const steps = this.extractStepsFromBlocklyState(state);
139
+ return hasWebStep(steps);
140
+ };
141
+ // Check beforeAll/afterAll hooks
142
+ if (testFile.beforeAll && hasWebStepInState(testFile.beforeAll))
143
+ return true;
144
+ if (testFile.afterAll && hasWebStepInState(testFile.afterAll))
145
+ return true;
146
+ if (testFile.beforeEach && hasWebStepInState(testFile.beforeEach))
147
+ return true;
148
+ if (testFile.afterEach && hasWebStepInState(testFile.afterEach))
149
+ return true;
150
+ // Check all tests
151
+ for (const test of testFile.tests) {
152
+ if (hasWebStepInState(test.steps))
153
+ return true;
154
+ if (test.beforeEach && hasWebStepInState(test.beforeEach))
155
+ return true;
156
+ if (test.afterEach && hasWebStepInState(test.afterEach))
157
+ return true;
158
+ }
159
+ return false;
160
+ }
133
161
  async runTestFile(testFile) {
134
162
  const results = [];
135
163
  // Register custom blocks from procedures
136
164
  if (testFile.procedures) {
137
165
  this.registerCustomBlocksFromProcedures(testFile.procedures);
138
166
  }
139
- await this.initialize();
167
+ // Only initialize browser if test file contains web steps
168
+ if (this.requiresBrowser(testFile)) {
169
+ await this.initialize();
170
+ }
140
171
  // Create shared execution context for lifecycle hooks
141
172
  const sharedContext = {
142
173
  variables: new Map(Object.entries({
@@ -27,6 +27,59 @@ const globalsDir = process.env.GLOBALS_DIR || path_1.default.join(process.cwd(),
27
27
  }).catch(err => {
28
28
  console.error('Failed to load plugins:', err);
29
29
  });
30
+ /**
31
+ * Merge folder hooks into a test file.
32
+ * Folder hooks are ordered from outermost to innermost folder.
33
+ * - beforeAll: run parent hooks first, then child hooks, then test file hooks
34
+ * - afterAll: run test file hooks first, then child hooks, then parent hooks
35
+ * - beforeEach/afterEach: same pattern
36
+ */
37
+ function mergeFolderHooksIntoTestFile(testFile, folderHooks) {
38
+ if (!folderHooks || folderHooks.length === 0) {
39
+ return testFile;
40
+ }
41
+ // Collect all steps from folder hooks (parent to child order is already provided)
42
+ const beforeAllSteps = [];
43
+ const afterAllSteps = [];
44
+ const beforeEachSteps = [];
45
+ const afterEachSteps = [];
46
+ // Parent to child order for beforeAll/beforeEach
47
+ for (const hooks of folderHooks) {
48
+ if (hooks.beforeAll)
49
+ beforeAllSteps.push(...hooks.beforeAll);
50
+ if (hooks.beforeEach)
51
+ beforeEachSteps.push(...hooks.beforeEach);
52
+ }
53
+ // Child to parent order for afterAll/afterEach
54
+ for (let i = folderHooks.length - 1; i >= 0; i--) {
55
+ const hooks = folderHooks[i];
56
+ if (hooks.afterAll)
57
+ afterAllSteps.unshift(...hooks.afterAll);
58
+ if (hooks.afterEach)
59
+ afterEachSteps.unshift(...hooks.afterEach);
60
+ }
61
+ // Merge with test file hooks
62
+ const merged = {
63
+ ...testFile,
64
+ beforeAll: [
65
+ ...beforeAllSteps,
66
+ ...(testFile.beforeAll || []),
67
+ ],
68
+ afterAll: [
69
+ ...(testFile.afterAll || []),
70
+ ...afterAllSteps,
71
+ ],
72
+ beforeEach: [
73
+ ...beforeEachSteps,
74
+ ...(testFile.beforeEach || []),
75
+ ],
76
+ afterEach: [
77
+ ...(testFile.afterEach || []),
78
+ ...afterEachSteps,
79
+ ],
80
+ };
81
+ return merged;
82
+ }
30
83
  const app = (0, express_1.default)();
31
84
  const PORT = process.env.PORT || 3001;
32
85
  app.use((0, cors_1.default)());
@@ -90,11 +143,13 @@ app.put('/api/globals/test-id-attribute', (req, res) => {
90
143
  // Run tests
91
144
  app.post('/api/run', async (req, res) => {
92
145
  try {
93
- const testFile = req.body;
146
+ const { testFile, folderHooks } = req.body;
94
147
  if (!testFile || !testFile.tests) {
95
148
  return res.status(400).json({ error: 'Invalid test file format' });
96
149
  }
97
150
  console.log(`Running ${testFile.tests.length} tests from "${testFile.name}"...`);
151
+ // Merge folder hooks into test file (parent to child order for beforeAll, child to parent for afterAll)
152
+ const mergedTestFile = mergeFolderHooksIntoTestFile(testFile, folderHooks || []);
98
153
  // Merge global variables with test file variables
99
154
  const globalVars = (0, globals_1.getGlobalVariables)();
100
155
  const testIdAttr = (0, globals_1.getTestIdAttribute)();
@@ -105,7 +160,7 @@ app.post('/api/run', async (req, res) => {
105
160
  testIdAttribute: testIdAttr, // Pass test ID attribute
106
161
  baseDir: globalsDir, // Base directory for resolving relative file paths
107
162
  });
108
- const results = await executor.runTestFile(testFile);
163
+ const results = await executor.runTestFile(mergedTestFile);
109
164
  const passed = results.filter(r => r.status === 'passed').length;
110
165
  const failed = results.filter(r => r.status === 'failed').length;
111
166
  console.log(`Results: ${passed} passed, ${failed} failed`);
@@ -124,12 +179,15 @@ app.post('/api/run', async (req, res) => {
124
179
  // Run a single test
125
180
  app.post('/api/run/:testId', async (req, res) => {
126
181
  try {
127
- const testFile = req.body;
182
+ const { testFile, folderHooks } = req.body;
128
183
  const { testId } = req.params;
129
184
  const test = testFile.tests.find(t => t.id === testId);
130
185
  if (!test) {
131
186
  return res.status(404).json({ error: `Test not found: ${testId}` });
132
187
  }
188
+ // For single test runs, merge folder beforeEach/afterEach hooks
189
+ // (beforeAll/afterAll are handled at suite level)
190
+ const mergedTestFile = mergeFolderHooksIntoTestFile(testFile, folderHooks || []);
133
191
  // Merge global variables
134
192
  const globalVars = (0, globals_1.getGlobalVariables)();
135
193
  const testIdAttr = (0, globals_1.getTestIdAttribute)();
@@ -141,11 +199,11 @@ app.post('/api/run/:testId', async (req, res) => {
141
199
  baseDir: globalsDir, // Base directory for resolving relative file paths
142
200
  });
143
201
  // Register custom blocks from procedures before running the test
144
- if (testFile.procedures) {
145
- executor.registerProcedures(testFile.procedures);
202
+ if (mergedTestFile.procedures) {
203
+ executor.registerProcedures(mergedTestFile.procedures);
146
204
  }
147
205
  await executor.initialize();
148
- const result = await executor.runTest(test, testFile.variables);
206
+ const result = await executor.runTest(test, mergedTestFile.variables);
149
207
  await executor.cleanup();
150
208
  res.json(result);
151
209
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testsmith/testblocks",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Visual test automation tool with Blockly - API and Playwright testing",
5
5
  "author": "Roy de Kleijn",
6
6
  "license": "MIT",