@testsmith/testblocks 0.8.9 → 0.9.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.
- package/dist/cli/executor.js +28 -18
- package/dist/cli/index.js +1 -1
- package/dist/cli/reporters/utils.d.ts +4 -0
- package/dist/cli/reporters/utils.js +10 -2
- package/dist/client/assets/{index-DGmSW0q0.js → index-BCKY2YTp.js} +88 -88
- package/dist/client/assets/{index-DGmSW0q0.js.map → index-BCKY2YTp.js.map} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/core/blocks/playwright/interactions.js +39 -15
- package/dist/core/blocks/playwright/retrieval.js +18 -6
- package/dist/core/blocks/playwright/types.d.ts +43 -10
- package/dist/server/executor.js +27 -17
- 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-BCKY2YTp.js"></script>
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/index-C5yUtTzz.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
@@ -11,7 +11,7 @@ exports.interactionBlocks = [
|
|
|
11
11
|
type: 'web_click',
|
|
12
12
|
category: 'Web',
|
|
13
13
|
color: '#E91E63',
|
|
14
|
-
tooltip: 'Click on an 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
17
|
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
@@ -23,7 +23,13 @@ exports.interactionBlocks = [
|
|
|
23
23
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
24
24
|
const timeout = params.TIMEOUT;
|
|
25
25
|
context.logger.info(`Clicking: ${selector}`);
|
|
26
|
-
|
|
26
|
+
const locator = page.locator(selector);
|
|
27
|
+
// Wait for element to be visible and stable before clicking
|
|
28
|
+
await locator.waitFor({ state: 'visible', timeout });
|
|
29
|
+
// Scroll into view to ensure element is in viewport
|
|
30
|
+
await locator.scrollIntoViewIfNeeded({ timeout });
|
|
31
|
+
// Click with retry on intercept (handles overlays/animations)
|
|
32
|
+
await locator.click({ timeout });
|
|
27
33
|
return {
|
|
28
34
|
_summary: params.SELECTOR,
|
|
29
35
|
selector,
|
|
@@ -35,10 +41,11 @@ exports.interactionBlocks = [
|
|
|
35
41
|
type: 'web_fill',
|
|
36
42
|
category: 'Web',
|
|
37
43
|
color: '#E91E63',
|
|
38
|
-
tooltip: 'Fill an input field (clears existing value)',
|
|
44
|
+
tooltip: 'Fill an input field (clears existing value, auto-waits)',
|
|
39
45
|
inputs: [
|
|
40
46
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
41
47
|
{ name: 'VALUE', type: 'field', fieldType: 'text', required: true },
|
|
48
|
+
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
42
49
|
],
|
|
43
50
|
previousStatement: true,
|
|
44
51
|
nextStatement: true,
|
|
@@ -47,8 +54,10 @@ exports.interactionBlocks = [
|
|
|
47
54
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
48
55
|
const rawValue = params.VALUE;
|
|
49
56
|
const value = (0, utils_1.resolveVariables)(rawValue, context);
|
|
57
|
+
const timeout = params.TIMEOUT;
|
|
50
58
|
context.logger.info(`Filling ${selector} with "${value}"`);
|
|
51
|
-
|
|
59
|
+
const locator = page.locator(selector);
|
|
60
|
+
await locator.fill(value, { timeout });
|
|
52
61
|
const displayValue = value.length > 30 ? value.substring(0, 30) + '...' : value;
|
|
53
62
|
return {
|
|
54
63
|
_summary: `${params.SELECTOR} = "${displayValue}"`,
|
|
@@ -62,11 +71,12 @@ exports.interactionBlocks = [
|
|
|
62
71
|
type: 'web_type',
|
|
63
72
|
category: 'Web',
|
|
64
73
|
color: '#E91E63',
|
|
65
|
-
tooltip: 'Type text character by character',
|
|
74
|
+
tooltip: 'Type text character by character (auto-waits)',
|
|
66
75
|
inputs: [
|
|
67
76
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
68
77
|
{ name: 'TEXT', type: 'field', fieldType: 'text', required: true },
|
|
69
78
|
{ name: 'DELAY', type: 'field', fieldType: 'number', default: 50 },
|
|
79
|
+
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
70
80
|
],
|
|
71
81
|
previousStatement: true,
|
|
72
82
|
nextStatement: true,
|
|
@@ -75,8 +85,10 @@ exports.interactionBlocks = [
|
|
|
75
85
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
76
86
|
const text = (0, utils_1.resolveVariables)(params.TEXT, context);
|
|
77
87
|
const delay = params.DELAY;
|
|
88
|
+
const timeout = params.TIMEOUT;
|
|
78
89
|
context.logger.info(`Typing "${text}" into ${selector}`);
|
|
79
|
-
|
|
90
|
+
const locator = page.locator(selector);
|
|
91
|
+
await locator.pressSequentially(text, { delay, timeout });
|
|
80
92
|
const displayText = text.length > 30 ? text.substring(0, 30) + '...' : text;
|
|
81
93
|
return {
|
|
82
94
|
_summary: `${params.SELECTOR} = "${displayText}"`,
|
|
@@ -91,10 +103,11 @@ exports.interactionBlocks = [
|
|
|
91
103
|
type: 'web_press_key',
|
|
92
104
|
category: 'Web',
|
|
93
105
|
color: '#E91E63',
|
|
94
|
-
tooltip: 'Press a keyboard key',
|
|
106
|
+
tooltip: 'Press a keyboard key (auto-waits)',
|
|
95
107
|
inputs: [
|
|
96
108
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
97
109
|
{ name: 'KEY', type: 'field', fieldType: 'dropdown', options: [['Enter', 'Enter'], ['Tab', 'Tab'], ['Escape', 'Escape'], ['Backspace', 'Backspace'], ['ArrowUp', 'ArrowUp'], ['ArrowDown', 'ArrowDown'], ['ArrowLeft', 'ArrowLeft'], ['ArrowRight', 'ArrowRight']] },
|
|
110
|
+
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
98
111
|
],
|
|
99
112
|
previousStatement: true,
|
|
100
113
|
nextStatement: true,
|
|
@@ -102,8 +115,10 @@ exports.interactionBlocks = [
|
|
|
102
115
|
const page = context.page;
|
|
103
116
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
104
117
|
const key = params.KEY;
|
|
118
|
+
const timeout = params.TIMEOUT;
|
|
105
119
|
context.logger.info(`Pressing ${key} on ${selector}`);
|
|
106
|
-
|
|
120
|
+
const locator = page.locator(selector);
|
|
121
|
+
await locator.press(key, { timeout });
|
|
107
122
|
return {
|
|
108
123
|
_summary: `${key} on ${params.SELECTOR}`,
|
|
109
124
|
selector,
|
|
@@ -116,10 +131,11 @@ exports.interactionBlocks = [
|
|
|
116
131
|
type: 'web_select',
|
|
117
132
|
category: 'Web',
|
|
118
133
|
color: '#E91E63',
|
|
119
|
-
tooltip: 'Select an option from a dropdown',
|
|
134
|
+
tooltip: 'Select an option from a dropdown (auto-waits)',
|
|
120
135
|
inputs: [
|
|
121
136
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
122
137
|
{ name: 'VALUE', type: 'field', fieldType: 'text', required: true },
|
|
138
|
+
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
123
139
|
],
|
|
124
140
|
previousStatement: true,
|
|
125
141
|
nextStatement: true,
|
|
@@ -127,8 +143,10 @@ exports.interactionBlocks = [
|
|
|
127
143
|
const page = context.page;
|
|
128
144
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
129
145
|
const value = (0, utils_1.resolveVariables)(params.VALUE, context);
|
|
146
|
+
const timeout = params.TIMEOUT;
|
|
130
147
|
context.logger.info(`Selecting "${value}" in ${selector}`);
|
|
131
|
-
|
|
148
|
+
const locator = page.locator(selector);
|
|
149
|
+
await locator.selectOption(value, { timeout });
|
|
132
150
|
return {
|
|
133
151
|
_summary: `${params.SELECTOR} = "${value}"`,
|
|
134
152
|
selector,
|
|
@@ -141,10 +159,11 @@ exports.interactionBlocks = [
|
|
|
141
159
|
type: 'web_checkbox',
|
|
142
160
|
category: 'Web',
|
|
143
161
|
color: '#E91E63',
|
|
144
|
-
tooltip: 'Check or uncheck a checkbox',
|
|
162
|
+
tooltip: 'Check or uncheck a checkbox (auto-waits)',
|
|
145
163
|
inputs: [
|
|
146
164
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
147
165
|
{ name: 'ACTION', type: 'field', fieldType: 'dropdown', options: [['Check', 'check'], ['Uncheck', 'uncheck']] },
|
|
166
|
+
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
148
167
|
],
|
|
149
168
|
previousStatement: true,
|
|
150
169
|
nextStatement: true,
|
|
@@ -152,12 +171,14 @@ exports.interactionBlocks = [
|
|
|
152
171
|
const page = context.page;
|
|
153
172
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
154
173
|
const action = params.ACTION;
|
|
174
|
+
const timeout = params.TIMEOUT;
|
|
155
175
|
context.logger.info(`${action === 'check' ? 'Checking' : 'Unchecking'} ${selector}`);
|
|
176
|
+
const locator = page.locator(selector);
|
|
156
177
|
if (action === 'check') {
|
|
157
|
-
await
|
|
178
|
+
await locator.check({ timeout });
|
|
158
179
|
}
|
|
159
180
|
else {
|
|
160
|
-
await
|
|
181
|
+
await locator.uncheck({ timeout });
|
|
161
182
|
}
|
|
162
183
|
return {
|
|
163
184
|
_summary: `${action} ${params.SELECTOR}`,
|
|
@@ -171,17 +192,20 @@ exports.interactionBlocks = [
|
|
|
171
192
|
type: 'web_hover',
|
|
172
193
|
category: 'Web',
|
|
173
194
|
color: '#E91E63',
|
|
174
|
-
tooltip: 'Hover over an element',
|
|
195
|
+
tooltip: 'Hover over an element (auto-waits)',
|
|
175
196
|
inputs: [
|
|
176
197
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
198
|
+
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
177
199
|
],
|
|
178
200
|
previousStatement: true,
|
|
179
201
|
nextStatement: true,
|
|
180
202
|
execute: async (params, context) => {
|
|
181
203
|
const page = context.page;
|
|
182
204
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
205
|
+
const timeout = params.TIMEOUT;
|
|
183
206
|
context.logger.info(`Hovering over ${selector}`);
|
|
184
|
-
|
|
207
|
+
const locator = page.locator(selector);
|
|
208
|
+
await locator.hover({ timeout });
|
|
185
209
|
return {
|
|
186
210
|
_summary: params.SELECTOR,
|
|
187
211
|
selector,
|
|
@@ -11,15 +11,19 @@ exports.retrievalBlocks = [
|
|
|
11
11
|
type: 'web_get_text',
|
|
12
12
|
category: 'Web',
|
|
13
13
|
color: '#2196F3',
|
|
14
|
-
tooltip: 'Get text content of an element',
|
|
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 },
|
|
17
18
|
],
|
|
18
19
|
output: { type: 'String' },
|
|
19
20
|
execute: async (params, context) => {
|
|
20
21
|
const page = context.page;
|
|
21
22
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
22
|
-
const
|
|
23
|
+
const timeout = params.TIMEOUT;
|
|
24
|
+
const locator = page.locator(selector);
|
|
25
|
+
await locator.waitFor({ state: 'visible', timeout });
|
|
26
|
+
const text = await locator.textContent({ timeout });
|
|
23
27
|
context.logger.debug(`Text content of ${selector}: "${text}"`);
|
|
24
28
|
const displayText = text && text.length > 40 ? text.substring(0, 40) + '...' : text;
|
|
25
29
|
return {
|
|
@@ -35,17 +39,21 @@ exports.retrievalBlocks = [
|
|
|
35
39
|
type: 'web_get_attribute',
|
|
36
40
|
category: 'Web',
|
|
37
41
|
color: '#2196F3',
|
|
38
|
-
tooltip: 'Get attribute value of an element',
|
|
42
|
+
tooltip: 'Get attribute value of an element (auto-waits)',
|
|
39
43
|
inputs: [
|
|
40
44
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
41
45
|
{ name: 'ATTRIBUTE', type: 'field', fieldType: 'text', required: true },
|
|
46
|
+
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
42
47
|
],
|
|
43
48
|
output: { type: 'String' },
|
|
44
49
|
execute: async (params, context) => {
|
|
45
50
|
const page = context.page;
|
|
46
51
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
47
52
|
const attribute = params.ATTRIBUTE;
|
|
48
|
-
const
|
|
53
|
+
const timeout = params.TIMEOUT;
|
|
54
|
+
const locator = page.locator(selector);
|
|
55
|
+
await locator.waitFor({ state: 'attached', timeout });
|
|
56
|
+
const value = await locator.getAttribute(attribute, { timeout });
|
|
49
57
|
context.logger.debug(`Attribute ${attribute} of ${selector}: "${value}"`);
|
|
50
58
|
return {
|
|
51
59
|
_summary: `${attribute} = "${value}"`,
|
|
@@ -61,15 +69,19 @@ exports.retrievalBlocks = [
|
|
|
61
69
|
type: 'web_get_input_value',
|
|
62
70
|
category: 'Web',
|
|
63
71
|
color: '#2196F3',
|
|
64
|
-
tooltip: 'Get current value of an input field',
|
|
72
|
+
tooltip: 'Get current value of an input field (auto-waits)',
|
|
65
73
|
inputs: [
|
|
66
74
|
{ name: 'SELECTOR', type: 'field', fieldType: 'text', required: true },
|
|
75
|
+
{ name: 'TIMEOUT', type: 'field', fieldType: 'number', default: 30000 },
|
|
67
76
|
],
|
|
68
77
|
output: { type: 'String' },
|
|
69
78
|
execute: async (params, context) => {
|
|
70
79
|
const page = context.page;
|
|
71
80
|
const selector = (0, utils_1.resolveSelector)(params, context);
|
|
72
|
-
const
|
|
81
|
+
const timeout = params.TIMEOUT;
|
|
82
|
+
const locator = page.locator(selector);
|
|
83
|
+
await locator.waitFor({ state: 'visible', timeout });
|
|
84
|
+
const value = await locator.inputValue({ timeout });
|
|
73
85
|
context.logger.debug(`Input value of ${selector}: "${value}"`);
|
|
74
86
|
const displayValue = value.length > 40 ? value.substring(0, 40) + '...' : value;
|
|
75
87
|
return {
|
|
@@ -4,24 +4,57 @@
|
|
|
4
4
|
export interface PlaywrightLocator {
|
|
5
5
|
click(options?: {
|
|
6
6
|
timeout?: number;
|
|
7
|
+
force?: boolean;
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
fill(value: string, options?: {
|
|
10
|
+
timeout?: number;
|
|
7
11
|
}): Promise<void>;
|
|
8
|
-
fill(value: string): Promise<void>;
|
|
9
12
|
type(text: string, options?: {
|
|
10
13
|
delay?: number;
|
|
14
|
+
timeout?: number;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
pressSequentially(text: string, options?: {
|
|
17
|
+
delay?: number;
|
|
18
|
+
timeout?: number;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
selectOption(values: string | string[], options?: {
|
|
21
|
+
timeout?: number;
|
|
22
|
+
}): Promise<string[]>;
|
|
23
|
+
check(options?: {
|
|
24
|
+
timeout?: number;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
uncheck(options?: {
|
|
27
|
+
timeout?: number;
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
hover(options?: {
|
|
30
|
+
timeout?: number;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
focus(options?: {
|
|
33
|
+
timeout?: number;
|
|
11
34
|
}): Promise<void>;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
press(key: string, options?: {
|
|
36
|
+
timeout?: number;
|
|
37
|
+
}): Promise<void>;
|
|
38
|
+
textContent(options?: {
|
|
39
|
+
timeout?: number;
|
|
40
|
+
}): Promise<string | null>;
|
|
41
|
+
getAttribute(name: string, options?: {
|
|
42
|
+
timeout?: number;
|
|
43
|
+
}): Promise<string | null>;
|
|
44
|
+
inputValue(options?: {
|
|
45
|
+
timeout?: number;
|
|
46
|
+
}): Promise<string>;
|
|
21
47
|
isVisible(): Promise<boolean>;
|
|
22
48
|
isEnabled(): Promise<boolean>;
|
|
23
49
|
isChecked(): Promise<boolean>;
|
|
24
50
|
count(): Promise<number>;
|
|
51
|
+
waitFor(options?: {
|
|
52
|
+
state?: 'attached' | 'detached' | 'visible' | 'hidden';
|
|
53
|
+
timeout?: number;
|
|
54
|
+
}): Promise<void>;
|
|
55
|
+
scrollIntoViewIfNeeded(options?: {
|
|
56
|
+
timeout?: number;
|
|
57
|
+
}): Promise<void>;
|
|
25
58
|
}
|
|
26
59
|
export interface PlaywrightPage {
|
|
27
60
|
goto(url: string, options?: {
|
package/dist/server/executor.js
CHANGED
|
@@ -197,6 +197,30 @@ class TestExecutor {
|
|
|
197
197
|
testIdAttribute: this.options.testIdAttribute,
|
|
198
198
|
procedures: mergedProcedures,
|
|
199
199
|
};
|
|
200
|
+
// Check if there are any enabled tests
|
|
201
|
+
const enabledTests = testFile.tests.filter(t => !t.disabled);
|
|
202
|
+
const hasEnabledTests = enabledTests.length > 0;
|
|
203
|
+
// Add skipped results for disabled tests first
|
|
204
|
+
for (const test of testFile.tests) {
|
|
205
|
+
if (test.disabled) {
|
|
206
|
+
results.push({
|
|
207
|
+
testId: test.id,
|
|
208
|
+
testName: test.name,
|
|
209
|
+
status: 'skipped',
|
|
210
|
+
duration: 0,
|
|
211
|
+
steps: [],
|
|
212
|
+
error: { message: 'Test is disabled' },
|
|
213
|
+
startedAt: new Date().toISOString(),
|
|
214
|
+
finishedAt: new Date().toISOString(),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Only run hooks and tests if there are enabled tests
|
|
219
|
+
if (!hasEnabledTests) {
|
|
220
|
+
sharedContext.logger.info('All tests disabled, skipping hooks');
|
|
221
|
+
await this.cleanup();
|
|
222
|
+
return results;
|
|
223
|
+
}
|
|
200
224
|
let beforeAllFailed = false;
|
|
201
225
|
try {
|
|
202
226
|
// Run beforeAll hooks
|
|
@@ -211,22 +235,8 @@ class TestExecutor {
|
|
|
211
235
|
}
|
|
212
236
|
// Only run tests if beforeAll succeeded
|
|
213
237
|
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
|
-
}
|
|
238
|
+
// Run each enabled test with beforeEach/afterEach
|
|
239
|
+
for (const test of enabledTests) {
|
|
230
240
|
// Load data from file if specified
|
|
231
241
|
let testData = test.data;
|
|
232
242
|
if (test.dataFile && !testData) {
|
|
@@ -720,7 +730,7 @@ class TestExecutor {
|
|
|
720
730
|
if (status === 'failed' && isWebStep && context.page) {
|
|
721
731
|
try {
|
|
722
732
|
const page = context.page;
|
|
723
|
-
const screenshotBuffer = await page.screenshot({ fullPage:
|
|
733
|
+
const screenshotBuffer = await page.screenshot({ fullPage: true });
|
|
724
734
|
screenshot = `data:image/png;base64,${screenshotBuffer.toString('base64')}`;
|
|
725
735
|
}
|
|
726
736
|
catch (screenshotErr) {
|