@testsmith/testblocks 0.9.0 → 0.9.2
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/executor.js +29 -18
- package/dist/cli/index.js +9 -2
- package/dist/cli/reporters/HTMLReporter.js +21 -8
- package/dist/cli/reporters/JUnitReporter.js +16 -8
- package/dist/cli/reporters/types.d.ts +1 -0
- package/dist/client/assets/{index-Ivy7T1Qk.js → index-CVn_B7zc.js} +71 -71
- package/dist/client/assets/{index-Ivy7T1Qk.js.map → index-CVn_B7zc.js.map} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/core/blocks/playwright/assertions.js +10 -20
- package/dist/core/blocks/playwright/interactions.js +13 -15
- package/dist/core/blocks/playwright/navigation.js +2 -4
- package/dist/core/blocks/playwright/retrieval.js +3 -6
- package/dist/core/blocks/playwright/types.d.ts +4 -0
- package/dist/core/blocks/playwright/utils.d.ts +4 -0
- package/dist/core/blocks/playwright/utils.js +8 -0
- package/dist/core/types.d.ts +1 -0
- package/dist/server/executor.js +30 -17
- package/dist/server/globals.d.ts +4 -0
- package/dist/server/globals.js +7 -0
- package/dist/server/index.js +4 -2
- package/dist/server/startServer.js +6 -4
- package/package.json +1 -1
package/dist/client/index.html
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
overflow: hidden;
|
|
17
17
|
}
|
|
18
18
|
</style>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-CVn_B7zc.js"></script>
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/index-C5yUtTzz.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
@@ -14,7 +14,6 @@ exports.assertionBlocks = [
|
|
|
14
14
|
tooltip: 'Assert that an element is visible (auto-waits)',
|
|
15
15
|
inputs: [
|
|
16
16
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
17
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
18
17
|
],
|
|
19
18
|
previousStatement: true,
|
|
20
19
|
nextStatement: true,
|
|
@@ -22,7 +21,7 @@ exports.assertionBlocks = [
|
|
|
22
21
|
const page = context.page;
|
|
23
22
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
24
23
|
const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
|
|
25
|
-
const timeout =
|
|
24
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
26
25
|
const expect = await (0, utils_1.getExpect)();
|
|
27
26
|
const locator = page.locator(selector);
|
|
28
27
|
context.logger.info(`Asserting ${displaySelector} is visible`);
|
|
@@ -43,7 +42,6 @@ exports.assertionBlocks = [
|
|
|
43
42
|
tooltip: 'Assert that an element is not visible (auto-waits)',
|
|
44
43
|
inputs: [
|
|
45
44
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
46
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
47
45
|
],
|
|
48
46
|
previousStatement: true,
|
|
49
47
|
nextStatement: true,
|
|
@@ -51,7 +49,7 @@ exports.assertionBlocks = [
|
|
|
51
49
|
const page = context.page;
|
|
52
50
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
53
51
|
const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
|
|
54
|
-
const timeout =
|
|
52
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
55
53
|
const expect = await (0, utils_1.getExpect)();
|
|
56
54
|
const locator = page.locator(selector);
|
|
57
55
|
context.logger.info(`Asserting ${displaySelector} is not visible`);
|
|
@@ -73,7 +71,6 @@ exports.assertionBlocks = [
|
|
|
73
71
|
inputs: [
|
|
74
72
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
75
73
|
{ name: 'TEXT', type: 'field', fieldType: 'text', required: true },
|
|
76
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
77
74
|
],
|
|
78
75
|
previousStatement: true,
|
|
79
76
|
nextStatement: true,
|
|
@@ -82,7 +79,7 @@ exports.assertionBlocks = [
|
|
|
82
79
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
83
80
|
const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
|
|
84
81
|
const expectedText = (0, utils_1.resolveVariables)(params.TEXT, context);
|
|
85
|
-
const timeout =
|
|
82
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
86
83
|
const expect = await (0, utils_1.getExpect)();
|
|
87
84
|
const locator = page.locator(selector);
|
|
88
85
|
context.logger.info(`Asserting ${displaySelector} contains "${expectedText}"`);
|
|
@@ -104,7 +101,6 @@ exports.assertionBlocks = [
|
|
|
104
101
|
inputs: [
|
|
105
102
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
106
103
|
{ name: 'TEXT', type: 'field', fieldType: 'text', required: true },
|
|
107
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
108
104
|
],
|
|
109
105
|
previousStatement: true,
|
|
110
106
|
nextStatement: true,
|
|
@@ -113,7 +109,7 @@ exports.assertionBlocks = [
|
|
|
113
109
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
114
110
|
const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
|
|
115
111
|
const expectedText = (0, utils_1.resolveVariables)(params.TEXT, context);
|
|
116
|
-
const timeout =
|
|
112
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
117
113
|
const expect = await (0, utils_1.getExpect)();
|
|
118
114
|
const locator = page.locator(selector);
|
|
119
115
|
context.logger.info(`Asserting ${displaySelector} text equals "${expectedText}"`);
|
|
@@ -134,14 +130,13 @@ exports.assertionBlocks = [
|
|
|
134
130
|
tooltip: 'Assert that current URL contains expected value (auto-waits)',
|
|
135
131
|
inputs: [
|
|
136
132
|
{ name: 'TEXT', type: 'field', fieldType: 'text', required: true },
|
|
137
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
138
133
|
],
|
|
139
134
|
previousStatement: true,
|
|
140
135
|
nextStatement: true,
|
|
141
136
|
execute: async (params, context) => {
|
|
142
137
|
const page = context.page;
|
|
143
138
|
const expectedText = (0, utils_1.resolveVariables)(params.TEXT, context);
|
|
144
|
-
const timeout =
|
|
139
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
145
140
|
const expect = await (0, utils_1.getExpect)();
|
|
146
141
|
// Escape special regex characters and create a regex pattern
|
|
147
142
|
const escapedText = expectedText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -164,14 +159,13 @@ exports.assertionBlocks = [
|
|
|
164
159
|
tooltip: 'Assert that page title contains expected value (auto-waits)',
|
|
165
160
|
inputs: [
|
|
166
161
|
{ name: 'TEXT', type: 'field', fieldType: 'text', required: true },
|
|
167
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
168
162
|
],
|
|
169
163
|
previousStatement: true,
|
|
170
164
|
nextStatement: true,
|
|
171
165
|
execute: async (params, context) => {
|
|
172
166
|
const page = context.page;
|
|
173
167
|
const expectedText = (0, utils_1.resolveVariables)(params.TEXT, context);
|
|
174
|
-
const timeout =
|
|
168
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
175
169
|
const expect = await (0, utils_1.getExpect)();
|
|
176
170
|
// Escape special regex characters and create a regex pattern
|
|
177
171
|
const escapedText = expectedText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -193,7 +187,6 @@ exports.assertionBlocks = [
|
|
|
193
187
|
tooltip: 'Assert that an element is enabled (auto-waits)',
|
|
194
188
|
inputs: [
|
|
195
189
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
196
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
197
190
|
],
|
|
198
191
|
previousStatement: true,
|
|
199
192
|
nextStatement: true,
|
|
@@ -201,7 +194,7 @@ exports.assertionBlocks = [
|
|
|
201
194
|
const page = context.page;
|
|
202
195
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
203
196
|
const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
|
|
204
|
-
const timeout =
|
|
197
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
205
198
|
const expect = await (0, utils_1.getExpect)();
|
|
206
199
|
const locator = page.locator(selector);
|
|
207
200
|
context.logger.info(`Asserting ${displaySelector} is enabled`);
|
|
@@ -223,7 +216,6 @@ exports.assertionBlocks = [
|
|
|
223
216
|
inputs: [
|
|
224
217
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
225
218
|
{ name: 'EXPECTED', type: 'field', fieldType: 'checkbox', default: true },
|
|
226
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
227
219
|
],
|
|
228
220
|
previousStatement: true,
|
|
229
221
|
nextStatement: true,
|
|
@@ -232,7 +224,7 @@ exports.assertionBlocks = [
|
|
|
232
224
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
233
225
|
const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
|
|
234
226
|
const expected = params.EXPECTED;
|
|
235
|
-
const timeout =
|
|
227
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
236
228
|
const expect = await (0, utils_1.getExpect)();
|
|
237
229
|
const locator = page.locator(selector);
|
|
238
230
|
context.logger.info(`Asserting ${displaySelector} is ${expected ? 'checked' : 'unchecked'}`);
|
|
@@ -261,7 +253,6 @@ exports.assertionBlocks = [
|
|
|
261
253
|
inputs: [
|
|
262
254
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
263
255
|
{ name: 'VALUE', type: 'field', fieldType: 'text', required: true },
|
|
264
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
265
256
|
],
|
|
266
257
|
previousStatement: true,
|
|
267
258
|
nextStatement: true,
|
|
@@ -270,7 +261,7 @@ exports.assertionBlocks = [
|
|
|
270
261
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
271
262
|
const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
|
|
272
263
|
const expectedValue = (0, utils_1.resolveVariables)(params.VALUE, context);
|
|
273
|
-
const timeout =
|
|
264
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
274
265
|
const expect = await (0, utils_1.getExpect)();
|
|
275
266
|
const locator = page.locator(selector);
|
|
276
267
|
context.logger.info(`Asserting ${displaySelector} has value "${expectedValue}"`);
|
|
@@ -294,7 +285,6 @@ exports.assertionBlocks = [
|
|
|
294
285
|
inputs: [
|
|
295
286
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
296
287
|
{ name: 'VALUE', type: 'field', fieldType: 'text', required: true },
|
|
297
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
298
288
|
],
|
|
299
289
|
previousStatement: true,
|
|
300
290
|
nextStatement: true,
|
|
@@ -303,7 +293,7 @@ exports.assertionBlocks = [
|
|
|
303
293
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
304
294
|
const displaySelector = (0, utils_1.getDisplaySelector)(params, context);
|
|
305
295
|
const expectedValue = (0, utils_1.resolveVariables)(params.VALUE, context);
|
|
306
|
-
const timeout =
|
|
296
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
307
297
|
const expect = await (0, utils_1.getExpect)();
|
|
308
298
|
const locator = page.locator(selector);
|
|
309
299
|
context.logger.info(`Asserting ${displaySelector} value contains "${expectedValue}"`);
|
|
@@ -11,19 +11,23 @@ exports.interactionBlocks = [
|
|
|
11
11
|
type: 'web_click',
|
|
12
12
|
category: 'Web',
|
|
13
13
|
color: '#E91E63',
|
|
14
|
-
tooltip: 'Click on an element (auto-waits for element)',
|
|
14
|
+
tooltip: 'Click on an element (auto-waits for element to be visible and stable)',
|
|
15
15
|
inputs: [
|
|
16
16
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
17
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
18
17
|
],
|
|
19
18
|
previousStatement: true,
|
|
20
19
|
nextStatement: true,
|
|
21
20
|
execute: async (params, context) => {
|
|
22
21
|
const page = context.page;
|
|
23
22
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
24
|
-
const timeout =
|
|
23
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
25
24
|
context.logger.info(`Clicking: ${selector}`);
|
|
26
25
|
const locator = page.locator(selector);
|
|
26
|
+
// Wait for element to be visible and stable before clicking
|
|
27
|
+
await locator.waitFor({ state: 'visible', timeout });
|
|
28
|
+
// Scroll into view to ensure element is in viewport
|
|
29
|
+
await locator.scrollIntoViewIfNeeded({ timeout });
|
|
30
|
+
// Click with retry on intercept (handles overlays/animations)
|
|
27
31
|
await locator.click({ timeout });
|
|
28
32
|
return {
|
|
29
33
|
_summary: params.SELECTOR,
|
|
@@ -40,7 +44,6 @@ exports.interactionBlocks = [
|
|
|
40
44
|
inputs: [
|
|
41
45
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
42
46
|
{ name: 'VALUE', type: 'field', fieldType: 'text', required: true },
|
|
43
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
44
47
|
],
|
|
45
48
|
previousStatement: true,
|
|
46
49
|
nextStatement: true,
|
|
@@ -49,7 +52,7 @@ exports.interactionBlocks = [
|
|
|
49
52
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
50
53
|
const rawValue = params.VALUE;
|
|
51
54
|
const value = (0, utils_1.resolveVariables)(rawValue, context);
|
|
52
|
-
const timeout =
|
|
55
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
53
56
|
context.logger.info(`Filling ${selector} with "${value}"`);
|
|
54
57
|
const locator = page.locator(selector);
|
|
55
58
|
await locator.fill(value, { timeout });
|
|
@@ -71,7 +74,6 @@ exports.interactionBlocks = [
|
|
|
71
74
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
72
75
|
{ name: 'TEXT', type: 'field', fieldType: 'text', required: true },
|
|
73
76
|
{ name: 'DELAY', type: 'field', fieldType: 'number', default: 50 },
|
|
74
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
75
77
|
],
|
|
76
78
|
previousStatement: true,
|
|
77
79
|
nextStatement: true,
|
|
@@ -80,7 +82,7 @@ exports.interactionBlocks = [
|
|
|
80
82
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
81
83
|
const text = (0, utils_1.resolveVariables)(params.TEXT, context);
|
|
82
84
|
const delay = params.DELAY;
|
|
83
|
-
const timeout =
|
|
85
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
84
86
|
context.logger.info(`Typing "${text}" into ${selector}`);
|
|
85
87
|
const locator = page.locator(selector);
|
|
86
88
|
await locator.pressSequentially(text, { delay, timeout });
|
|
@@ -102,7 +104,6 @@ exports.interactionBlocks = [
|
|
|
102
104
|
inputs: [
|
|
103
105
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
104
106
|
{ name: 'KEY', type: 'field', fieldType: 'dropdown', options: [['Enter', 'Enter'], ['Tab', 'Tab'], ['Escape', 'Escape'], ['Backspace', 'Backspace'], ['ArrowUp', 'ArrowUp'], ['ArrowDown', 'ArrowDown'], ['ArrowLeft', 'ArrowLeft'], ['ArrowRight', 'ArrowRight']] },
|
|
105
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
106
107
|
],
|
|
107
108
|
previousStatement: true,
|
|
108
109
|
nextStatement: true,
|
|
@@ -110,7 +111,7 @@ exports.interactionBlocks = [
|
|
|
110
111
|
const page = context.page;
|
|
111
112
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
112
113
|
const key = params.KEY;
|
|
113
|
-
const timeout =
|
|
114
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
114
115
|
context.logger.info(`Pressing ${key} on ${selector}`);
|
|
115
116
|
const locator = page.locator(selector);
|
|
116
117
|
await locator.press(key, { timeout });
|
|
@@ -130,7 +131,6 @@ exports.interactionBlocks = [
|
|
|
130
131
|
inputs: [
|
|
131
132
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
132
133
|
{ name: 'VALUE', type: 'field', fieldType: 'text', required: true },
|
|
133
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
134
134
|
],
|
|
135
135
|
previousStatement: true,
|
|
136
136
|
nextStatement: true,
|
|
@@ -138,7 +138,7 @@ exports.interactionBlocks = [
|
|
|
138
138
|
const page = context.page;
|
|
139
139
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
140
140
|
const value = (0, utils_1.resolveVariables)(params.VALUE, context);
|
|
141
|
-
const timeout =
|
|
141
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
142
142
|
context.logger.info(`Selecting "${value}" in ${selector}`);
|
|
143
143
|
const locator = page.locator(selector);
|
|
144
144
|
await locator.selectOption(value, { timeout });
|
|
@@ -158,7 +158,6 @@ exports.interactionBlocks = [
|
|
|
158
158
|
inputs: [
|
|
159
159
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
160
160
|
{ name: 'ACTION', type: 'field', fieldType: 'dropdown', options: [['Check', 'check'], ['Uncheck', 'uncheck']] },
|
|
161
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
162
161
|
],
|
|
163
162
|
previousStatement: true,
|
|
164
163
|
nextStatement: true,
|
|
@@ -166,7 +165,7 @@ exports.interactionBlocks = [
|
|
|
166
165
|
const page = context.page;
|
|
167
166
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
168
167
|
const action = params.ACTION;
|
|
169
|
-
const timeout =
|
|
168
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
170
169
|
context.logger.info(`${action === 'check' ? 'Checking' : 'Unchecking'} ${selector}`);
|
|
171
170
|
const locator = page.locator(selector);
|
|
172
171
|
if (action === 'check') {
|
|
@@ -190,14 +189,13 @@ exports.interactionBlocks = [
|
|
|
190
189
|
tooltip: 'Hover over an element (auto-waits)',
|
|
191
190
|
inputs: [
|
|
192
191
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
193
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
194
192
|
],
|
|
195
193
|
previousStatement: true,
|
|
196
194
|
nextStatement: true,
|
|
197
195
|
execute: async (params, context) => {
|
|
198
196
|
const page = context.page;
|
|
199
197
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
200
|
-
const timeout =
|
|
198
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
201
199
|
context.logger.info(`Hovering over ${selector}`);
|
|
202
200
|
const locator = page.locator(selector);
|
|
203
201
|
await locator.hover({ timeout });
|
|
@@ -40,7 +40,6 @@ exports.navigationBlocks = [
|
|
|
40
40
|
inputs: [
|
|
41
41
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
42
42
|
{ name: 'STATE', type: 'field', fieldType: 'dropdown', options: [['Visible', 'visible'], ['Hidden', 'hidden'], ['Attached', 'attached'], ['Detached', 'detached']] },
|
|
43
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
44
43
|
],
|
|
45
44
|
previousStatement: true,
|
|
46
45
|
nextStatement: true,
|
|
@@ -48,7 +47,7 @@ exports.navigationBlocks = [
|
|
|
48
47
|
const page = context.page;
|
|
49
48
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
50
49
|
const state = params.STATE;
|
|
51
|
-
const timeout =
|
|
50
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
52
51
|
context.logger.info(`Waiting for ${selector} to be ${state}`);
|
|
53
52
|
await page.waitForSelector(selector, { state, timeout });
|
|
54
53
|
return {
|
|
@@ -66,14 +65,13 @@ exports.navigationBlocks = [
|
|
|
66
65
|
tooltip: 'Wait for URL to match',
|
|
67
66
|
inputs: [
|
|
68
67
|
{ name: 'URL', type: 'field', fieldType: 'text', required: true },
|
|
69
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
70
68
|
],
|
|
71
69
|
previousStatement: true,
|
|
72
70
|
nextStatement: true,
|
|
73
71
|
execute: async (params, context) => {
|
|
74
72
|
const page = context.page;
|
|
75
73
|
const url = (0, utils_1.resolveVariables)(params.URL, context);
|
|
76
|
-
const timeout =
|
|
74
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
77
75
|
context.logger.info(`Waiting for URL to match: ${url}`);
|
|
78
76
|
await page.waitForURL(url, { timeout });
|
|
79
77
|
return {
|
|
@@ -14,13 +14,12 @@ exports.retrievalBlocks = [
|
|
|
14
14
|
tooltip: 'Get text content of an element (auto-waits)',
|
|
15
15
|
inputs: [
|
|
16
16
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
17
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
18
17
|
],
|
|
19
18
|
output: { type: 'String' },
|
|
20
19
|
execute: async (params, context) => {
|
|
21
20
|
const page = context.page;
|
|
22
21
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
23
|
-
const timeout =
|
|
22
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
24
23
|
const locator = page.locator(selector);
|
|
25
24
|
await locator.waitFor({ state: 'visible', timeout });
|
|
26
25
|
const text = await locator.textContent({ timeout });
|
|
@@ -43,14 +42,13 @@ exports.retrievalBlocks = [
|
|
|
43
42
|
inputs: [
|
|
44
43
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
45
44
|
{ name: 'ATTRIBUTE', type: 'field', fieldType: 'text', required: true },
|
|
46
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
47
45
|
],
|
|
48
46
|
output: { type: 'String' },
|
|
49
47
|
execute: async (params, context) => {
|
|
50
48
|
const page = context.page;
|
|
51
49
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
52
50
|
const attribute = params.ATTRIBUTE;
|
|
53
|
-
const timeout =
|
|
51
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
54
52
|
const locator = page.locator(selector);
|
|
55
53
|
await locator.waitFor({ state: 'attached', timeout });
|
|
56
54
|
const value = await locator.getAttribute(attribute, { timeout });
|
|
@@ -72,13 +70,12 @@ exports.retrievalBlocks = [
|
|
|
72
70
|
tooltip: 'Get current value of an input field (auto-waits)',
|
|
73
71
|
inputs: [
|
|
74
72
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
75
|
-
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
76
73
|
],
|
|
77
74
|
output: { type: 'String' },
|
|
78
75
|
execute: async (params, context) => {
|
|
79
76
|
const page = context.page;
|
|
80
77
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
81
|
-
const timeout =
|
|
78
|
+
const timeout = (0, utils_1.getTimeout)(context);
|
|
82
79
|
const locator = page.locator(selector);
|
|
83
80
|
await locator.waitFor({ state: 'visible', timeout });
|
|
84
81
|
const value = await locator.inputValue({ timeout });
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
export interface PlaywrightLocator {
|
|
5
5
|
click(options?: {
|
|
6
6
|
timeout?: number;
|
|
7
|
+
force?: boolean;
|
|
7
8
|
}): Promise<void>;
|
|
8
9
|
fill(value: string, options?: {
|
|
9
10
|
timeout?: number;
|
|
@@ -51,6 +52,9 @@ export interface PlaywrightLocator {
|
|
|
51
52
|
state?: 'attached' | 'detached' | 'visible' | 'hidden';
|
|
52
53
|
timeout?: number;
|
|
53
54
|
}): Promise<void>;
|
|
55
|
+
scrollIntoViewIfNeeded(options?: {
|
|
56
|
+
timeout?: number;
|
|
57
|
+
}): Promise<void>;
|
|
54
58
|
}
|
|
55
59
|
export interface PlaywrightPage {
|
|
56
60
|
goto(url: string, options?: {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { ExecutionContext } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Get the global web timeout from context
|
|
4
|
+
*/
|
|
5
|
+
export declare function getTimeout(context: ExecutionContext): number;
|
|
2
6
|
export declare function getExpect(): Promise<any>;
|
|
3
7
|
/**
|
|
4
8
|
* Execute a web assertion with soft assertion support
|
|
@@ -33,11 +33,19 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getTimeout = getTimeout;
|
|
36
37
|
exports.getExpect = getExpect;
|
|
37
38
|
exports.executeWebAssertion = executeWebAssertion;
|
|
38
39
|
exports.resolveVariables = resolveVariables;
|
|
39
40
|
exports.resolveSelector = resolveSelector;
|
|
40
41
|
exports.getDisplaySelector = getDisplaySelector;
|
|
42
|
+
const DEFAULT_WEB_TIMEOUT = 30000;
|
|
43
|
+
/**
|
|
44
|
+
* Get the global web timeout from context
|
|
45
|
+
*/
|
|
46
|
+
function getTimeout(context) {
|
|
47
|
+
return context.webTimeout ?? DEFAULT_WEB_TIMEOUT;
|
|
48
|
+
}
|
|
41
49
|
// Import Playwright's expect for assertions with auto-waiting
|
|
42
50
|
// We use dynamic import with string concatenation to prevent Vite from
|
|
43
51
|
// trying to bundle the playwright package (which is Node.js-only)
|
package/dist/core/types.d.ts
CHANGED
package/dist/server/executor.js
CHANGED
|
@@ -196,7 +196,32 @@ class TestExecutor {
|
|
|
196
196
|
plugins: this.plugins,
|
|
197
197
|
testIdAttribute: this.options.testIdAttribute,
|
|
198
198
|
procedures: mergedProcedures,
|
|
199
|
+
webTimeout: this.options.timeout,
|
|
199
200
|
};
|
|
201
|
+
// Check if there are any enabled tests
|
|
202
|
+
const enabledTests = testFile.tests.filter(t => !t.disabled);
|
|
203
|
+
const hasEnabledTests = enabledTests.length > 0;
|
|
204
|
+
// Add skipped results for disabled tests first
|
|
205
|
+
for (const test of testFile.tests) {
|
|
206
|
+
if (test.disabled) {
|
|
207
|
+
results.push({
|
|
208
|
+
testId: test.id,
|
|
209
|
+
testName: test.name,
|
|
210
|
+
status: 'skipped',
|
|
211
|
+
duration: 0,
|
|
212
|
+
steps: [],
|
|
213
|
+
error: { message: 'Test is disabled' },
|
|
214
|
+
startedAt: new Date().toISOString(),
|
|
215
|
+
finishedAt: new Date().toISOString(),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Only run hooks and tests if there are enabled tests
|
|
220
|
+
if (!hasEnabledTests) {
|
|
221
|
+
sharedContext.logger.info('All tests disabled, skipping hooks');
|
|
222
|
+
await this.cleanup();
|
|
223
|
+
return results;
|
|
224
|
+
}
|
|
200
225
|
let beforeAllFailed = false;
|
|
201
226
|
try {
|
|
202
227
|
// Run beforeAll hooks
|
|
@@ -211,22 +236,8 @@ class TestExecutor {
|
|
|
211
236
|
}
|
|
212
237
|
// Only run tests if beforeAll succeeded
|
|
213
238
|
if (!beforeAllFailed) {
|
|
214
|
-
// Run each test with beforeEach/afterEach
|
|
215
|
-
for (const test of
|
|
216
|
-
// Skip disabled tests
|
|
217
|
-
if (test.disabled) {
|
|
218
|
-
results.push({
|
|
219
|
-
testId: test.id,
|
|
220
|
-
testName: test.name,
|
|
221
|
-
status: 'skipped',
|
|
222
|
-
duration: 0,
|
|
223
|
-
steps: [],
|
|
224
|
-
error: { message: 'Test is disabled' },
|
|
225
|
-
startedAt: new Date().toISOString(),
|
|
226
|
-
finishedAt: new Date().toISOString(),
|
|
227
|
-
});
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
239
|
+
// Run each enabled test with beforeEach/afterEach
|
|
240
|
+
for (const test of enabledTests) {
|
|
230
241
|
// Load data from file if specified
|
|
231
242
|
let testData = test.data;
|
|
232
243
|
if (test.dataFile && !testData) {
|
|
@@ -416,6 +427,7 @@ class TestExecutor {
|
|
|
416
427
|
// Enable soft assertions if configured on the test
|
|
417
428
|
softAssertions: test.softAssertions || false,
|
|
418
429
|
softAssertionErrors: [],
|
|
430
|
+
webTimeout: this.options.timeout,
|
|
419
431
|
};
|
|
420
432
|
// Run beforeTest hooks
|
|
421
433
|
for (const plugin of this.plugins.values()) {
|
|
@@ -507,6 +519,7 @@ class TestExecutor {
|
|
|
507
519
|
// Enable soft assertions if configured on the test
|
|
508
520
|
softAssertions: test.softAssertions || false,
|
|
509
521
|
softAssertionErrors: [],
|
|
522
|
+
webTimeout: this.options.timeout,
|
|
510
523
|
};
|
|
511
524
|
// Inject data values into variables
|
|
512
525
|
for (const [key, value] of Object.entries(dataSet.values)) {
|
|
@@ -720,7 +733,7 @@ class TestExecutor {
|
|
|
720
733
|
if (status === 'failed' && isWebStep && context.page) {
|
|
721
734
|
try {
|
|
722
735
|
const page = context.page;
|
|
723
|
-
const screenshotBuffer = await page.screenshot({ fullPage:
|
|
736
|
+
const screenshotBuffer = await page.screenshot({ fullPage: true });
|
|
724
737
|
screenshot = `data:image/png;base64,${screenshotBuffer.toString('base64')}`;
|
|
725
738
|
}
|
|
726
739
|
catch (screenshotErr) {
|
package/dist/server/globals.d.ts
CHANGED
|
@@ -56,6 +56,10 @@ export declare function getGlobalProcedures(): Record<string, ProcedureDefinitio
|
|
|
56
56
|
* Get the configured test ID attribute (defaults to 'data-testid')
|
|
57
57
|
*/
|
|
58
58
|
export declare function getTestIdAttribute(): string;
|
|
59
|
+
/**
|
|
60
|
+
* Get the configured global timeout in milliseconds (defaults to 30000)
|
|
61
|
+
*/
|
|
62
|
+
export declare function getGlobalTimeout(): number;
|
|
59
63
|
/**
|
|
60
64
|
* Set the test ID attribute and persist to globals.json
|
|
61
65
|
*/
|
package/dist/server/globals.js
CHANGED
|
@@ -47,6 +47,7 @@ exports.getGlobals = getGlobals;
|
|
|
47
47
|
exports.getGlobalVariables = getGlobalVariables;
|
|
48
48
|
exports.getGlobalProcedures = getGlobalProcedures;
|
|
49
49
|
exports.getTestIdAttribute = getTestIdAttribute;
|
|
50
|
+
exports.getGlobalTimeout = getGlobalTimeout;
|
|
50
51
|
exports.setTestIdAttribute = setTestIdAttribute;
|
|
51
52
|
exports.discoverSnippets = discoverSnippets;
|
|
52
53
|
exports.loadSnippet = loadSnippet;
|
|
@@ -118,6 +119,12 @@ function getGlobalProcedures() {
|
|
|
118
119
|
function getTestIdAttribute() {
|
|
119
120
|
return loadedGlobals.testIdAttribute || 'data-testid';
|
|
120
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Get the configured global timeout in milliseconds (defaults to 30000)
|
|
124
|
+
*/
|
|
125
|
+
function getGlobalTimeout() {
|
|
126
|
+
return loadedGlobals.timeout || 30000;
|
|
127
|
+
}
|
|
121
128
|
/**
|
|
122
129
|
* Set the test ID attribute and persist to globals.json
|
|
123
130
|
*/
|
package/dist/server/index.js
CHANGED
|
@@ -384,7 +384,8 @@ app.post('/api/reports/html', (req, res) => {
|
|
|
384
384
|
summary: {
|
|
385
385
|
totalTests: results.length,
|
|
386
386
|
passed: results.filter(r => r.status === 'passed').length,
|
|
387
|
-
failed: results.filter(r => r.status !== 'passed').length,
|
|
387
|
+
failed: results.filter(r => r.status !== 'passed' && r.status !== 'skipped').length,
|
|
388
|
+
skipped: results.filter(r => r.status === 'skipped').length,
|
|
388
389
|
duration: results.reduce((sum, r) => sum + r.duration, 0),
|
|
389
390
|
},
|
|
390
391
|
testFiles: [{
|
|
@@ -420,7 +421,8 @@ app.post('/api/reports/junit', (req, res) => {
|
|
|
420
421
|
summary: {
|
|
421
422
|
totalTests: results.length,
|
|
422
423
|
passed: results.filter(r => r.status === 'passed').length,
|
|
423
|
-
failed: results.filter(r => r.status !== 'passed').length,
|
|
424
|
+
failed: results.filter(r => r.status !== 'passed' && r.status !== 'skipped').length,
|
|
425
|
+
skipped: results.filter(r => r.status === 'skipped').length,
|
|
424
426
|
duration: results.reduce((sum, r) => sum + r.duration, 0),
|
|
425
427
|
},
|
|
426
428
|
testFiles: [{
|
|
@@ -244,7 +244,7 @@ async function startServer(options = {}) {
|
|
|
244
244
|
const testIdAttr = (0, globals_1.getTestIdAttribute)();
|
|
245
245
|
const executor = new executor_1.TestExecutor({
|
|
246
246
|
headless: req.query.headless !== 'false',
|
|
247
|
-
timeout: Number(req.query.timeout) ||
|
|
247
|
+
timeout: Number(req.query.timeout) || (0, globals_1.getGlobalTimeout)(),
|
|
248
248
|
variables: globalVars,
|
|
249
249
|
procedures: globalProcs,
|
|
250
250
|
testIdAttribute: testIdAttr,
|
|
@@ -294,7 +294,7 @@ async function startServer(options = {}) {
|
|
|
294
294
|
const testIdAttr = (0, globals_1.getTestIdAttribute)();
|
|
295
295
|
const executor = new executor_1.TestExecutor({
|
|
296
296
|
headless: req.query.headless !== 'false',
|
|
297
|
-
timeout: Number(req.query.timeout) ||
|
|
297
|
+
timeout: Number(req.query.timeout) || (0, globals_1.getGlobalTimeout)(),
|
|
298
298
|
variables: globalVars,
|
|
299
299
|
procedures: globalProcs,
|
|
300
300
|
testIdAttribute: testIdAttr,
|
|
@@ -429,7 +429,8 @@ async function startServer(options = {}) {
|
|
|
429
429
|
summary: {
|
|
430
430
|
totalTests: results.length,
|
|
431
431
|
passed: results.filter(r => r.status === 'passed').length,
|
|
432
|
-
failed: results.filter(r => r.status !== 'passed').length,
|
|
432
|
+
failed: results.filter(r => r.status !== 'passed' && r.status !== 'skipped').length,
|
|
433
|
+
skipped: results.filter(r => r.status === 'skipped').length,
|
|
433
434
|
duration: results.reduce((sum, r) => sum + r.duration, 0),
|
|
434
435
|
},
|
|
435
436
|
testFiles: [{
|
|
@@ -464,7 +465,8 @@ async function startServer(options = {}) {
|
|
|
464
465
|
summary: {
|
|
465
466
|
totalTests: results.length,
|
|
466
467
|
passed: results.filter(r => r.status === 'passed').length,
|
|
467
|
-
failed: results.filter(r => r.status !== 'passed').length,
|
|
468
|
+
failed: results.filter(r => r.status !== 'passed' && r.status !== 'skipped').length,
|
|
469
|
+
skipped: results.filter(r => r.status === 'skipped').length,
|
|
468
470
|
duration: results.reduce((sum, r) => sum + r.duration, 0),
|
|
469
471
|
},
|
|
470
472
|
testFiles: [{
|