@capillarytech/cap-ui-dev-tools 1.0.0 → 1.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.
- package/CAPVISION_USAGE.md +488 -0
- package/README.md +199 -48
- package/package.json +39 -10
- package/src/LibraryWatcherPlugin.js +53 -0
- package/src/capvision-recorder/adapters/WebdriverIOAdapter.js +312 -0
- package/src/capvision-recorder/assets/capvision-player.css +1 -0
- package/src/capvision-recorder/assets/capvision-player.min.js +31 -0
- package/src/capvision-recorder/assets/capvision-plugins/console-record.min.js +93 -0
- package/src/capvision-recorder/assets/capvision-plugins/console-replay.min.js +85 -0
- package/src/capvision-recorder/assets/capvision-plugins/network-record.min.js +542 -0
- package/src/capvision-recorder/assets/capvision-plugins/network-replay.min.js +434 -0
- package/src/capvision-recorder/assets/capvision.min.js +19 -0
- package/src/capvision-recorder/core/CapVisionRecorder.js +1338 -0
- package/src/capvision-recorder/core/ReportEnhancer.js +506 -0
- package/src/capvision-recorder/index.js +58 -0
- package/src/index.js +34 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
const { CapVisionRecorder } = require('../core/CapVisionRecorder');
|
|
2
|
+
const { ReportEnhancer } = require('../core/ReportEnhancer');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* WebdriverIO Browser Executor Adapter
|
|
6
|
+
* Adapts WebdriverIO's browser object to the BrowserExecutor interface
|
|
7
|
+
* @class
|
|
8
|
+
*/
|
|
9
|
+
class WebdriverIOBrowserExecutor {
|
|
10
|
+
/**
|
|
11
|
+
* Create a new WebdriverIO Browser Executor
|
|
12
|
+
* @param {Object} browser - WebdriverIO browser instance
|
|
13
|
+
*/
|
|
14
|
+
constructor(browser) {
|
|
15
|
+
/** @type {Object} */
|
|
16
|
+
this.browser = browser;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Execute script in browser context
|
|
21
|
+
* @template T
|
|
22
|
+
* @param {string|Function} script - Script to execute
|
|
23
|
+
* @param {...*} args - Arguments to pass to script
|
|
24
|
+
* @returns {Promise<T>} Result of script execution
|
|
25
|
+
*/
|
|
26
|
+
async execute(script, ...args) {
|
|
27
|
+
return this.browser.execute(script, ...args);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Pause execution for specified milliseconds
|
|
32
|
+
* @param {number} ms - Milliseconds to pause
|
|
33
|
+
* @returns {Promise<void>}
|
|
34
|
+
*/
|
|
35
|
+
async pause(ms) {
|
|
36
|
+
return this.browser.pause(ms);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* WebdriverIO CapVision Recorder Adapter
|
|
42
|
+
* Provides WebdriverIO-specific helpers and integration
|
|
43
|
+
* @class
|
|
44
|
+
*/
|
|
45
|
+
class WebdriverIOCapVisionRecorder {
|
|
46
|
+
/**
|
|
47
|
+
* Create a new WebdriverIO CapVision Recorder
|
|
48
|
+
* @param {import('../core/CapVisionRecorder').CapVisionRecorderConfig} [recorderConfig={}] - Recorder configuration
|
|
49
|
+
* @param {import('../core/ReportEnhancer').ReportEnhancerConfig} [enhancerConfig={}] - Enhancer configuration
|
|
50
|
+
*/
|
|
51
|
+
constructor(recorderConfig = {}, enhancerConfig = {}) {
|
|
52
|
+
/** @type {import('../core/CapVisionRecorder').CapVisionRecorder} */
|
|
53
|
+
this.recorder = new CapVisionRecorder(recorderConfig);
|
|
54
|
+
|
|
55
|
+
/** @type {import('../core/ReportEnhancer').ReportEnhancer} */
|
|
56
|
+
this.enhancer = new ReportEnhancer(enhancerConfig);
|
|
57
|
+
|
|
58
|
+
/** @type {WebdriverIOBrowserExecutor|null} */
|
|
59
|
+
this.browserExecutor = null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Initialize with browser object
|
|
64
|
+
* Call this in beforeTest or beforeSuite hook
|
|
65
|
+
* @param {Object} browser - WebdriverIO browser instance
|
|
66
|
+
*/
|
|
67
|
+
init(browser) {
|
|
68
|
+
this.browserExecutor = new WebdriverIOBrowserExecutor(browser);
|
|
69
|
+
this.recorder.setBrowserExecutor(this.browserExecutor);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get recorder instance
|
|
74
|
+
* @returns {import('../core/CapVisionRecorder').CapVisionRecorder} Recorder instance
|
|
75
|
+
*/
|
|
76
|
+
getRecorder() {
|
|
77
|
+
return this.recorder;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get enhancer instance
|
|
82
|
+
* @returns {import('../core/ReportEnhancer').ReportEnhancer} Enhancer instance
|
|
83
|
+
*/
|
|
84
|
+
getEnhancer() {
|
|
85
|
+
return this.enhancer;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Update recorder configuration
|
|
90
|
+
* @param {Partial<import('../core/CapVisionRecorder').CapVisionRecorderConfig>} config - Configuration updates
|
|
91
|
+
*/
|
|
92
|
+
updateRecorderConfig(config) {
|
|
93
|
+
this.recorder.updateConfig(config);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Update enhancer configuration
|
|
98
|
+
* @param {Partial<import('../core/ReportEnhancer').ReportEnhancerConfig>} config - Configuration updates
|
|
99
|
+
*/
|
|
100
|
+
updateEnhancerConfig(config) {
|
|
101
|
+
this.enhancer.updateConfig(config);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Set test type from test file path
|
|
106
|
+
* @param {string} testFilePath - Path to test file
|
|
107
|
+
* @returns {string|null} Detected test type or null
|
|
108
|
+
*/
|
|
109
|
+
setTestTypeFromPath(testFilePath) {
|
|
110
|
+
return this.recorder.setTestTypeFromPath(testFilePath);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if recording is enabled
|
|
115
|
+
* @param {string} [cluster] - Override cluster check
|
|
116
|
+
* @param {string} [module] - Override module check
|
|
117
|
+
* @returns {boolean} True if recording enabled
|
|
118
|
+
*/
|
|
119
|
+
isCapVisionEnabled(cluster, module) {
|
|
120
|
+
return this.recorder.isCapVisionEnabled(cluster, module);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Clear recordings folder
|
|
125
|
+
*/
|
|
126
|
+
clearRecordingsFolder() {
|
|
127
|
+
this.recorder.clearRecordingsFolder();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Start recording
|
|
132
|
+
* @async
|
|
133
|
+
* @returns {Promise<void>}
|
|
134
|
+
* @throws {Error} If browser executor not initialized
|
|
135
|
+
*/
|
|
136
|
+
async startRecording() {
|
|
137
|
+
if (!this.browserExecutor) {
|
|
138
|
+
throw new Error('Browser executor not initialized. Call init(browser) first.');
|
|
139
|
+
}
|
|
140
|
+
return this.recorder.startRecording();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Stop recording and save
|
|
145
|
+
* @async
|
|
146
|
+
* @param {string} [testName='test'] - Name of the test
|
|
147
|
+
* @param {boolean} [testPassed=true] - Whether the test passed. If true, recording is not saved permanently but temp file is still deleted
|
|
148
|
+
* @returns {Promise<import('../core/CapVisionRecorder').RecordingMetadata>} Recording metadata
|
|
149
|
+
* @throws {Error} If browser executor not initialized
|
|
150
|
+
*/
|
|
151
|
+
async stopRecordingAndSave(testName = 'test', testPassed = true) {
|
|
152
|
+
if (!this.browserExecutor) {
|
|
153
|
+
throw new Error('Browser executor not initialized. Call init(browser) first.');
|
|
154
|
+
}
|
|
155
|
+
return this.recorder.stopRecordingAndSave(testName, testPassed);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Ensure CapVision is active after page refresh
|
|
160
|
+
* @async
|
|
161
|
+
* @returns {Promise<void>}
|
|
162
|
+
* @throws {Error} If browser executor not initialized
|
|
163
|
+
*/
|
|
164
|
+
async ensureCapVisionIsActive() {
|
|
165
|
+
if (!this.browserExecutor) {
|
|
166
|
+
throw new Error('Browser executor not initialized. Call init(browser) first.');
|
|
167
|
+
}
|
|
168
|
+
return this.recorder.ensureCapVisionIsActive();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Enhance all reports
|
|
173
|
+
* @async
|
|
174
|
+
* @returns {Promise<void>}
|
|
175
|
+
*/
|
|
176
|
+
async enhanceAllReports() {
|
|
177
|
+
return this.enhancer.enhanceAllReports();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Enhance a specific report
|
|
182
|
+
* @async
|
|
183
|
+
* @param {string} reportPath - Path to HTML report file
|
|
184
|
+
* @returns {Promise<void>}
|
|
185
|
+
*/
|
|
186
|
+
async enhanceReport(reportPath) {
|
|
187
|
+
return this.enhancer.enhanceReport(reportPath);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Create WebdriverIO hooks for easy integration
|
|
193
|
+
* Usage: Add these hooks to your wdio.conf.js
|
|
194
|
+
*
|
|
195
|
+
* @param {import('../core/CapVisionRecorder').CapVisionRecorderConfig} [recorderConfig={}] - Recorder configuration
|
|
196
|
+
* @param {import('../core/ReportEnhancer').ReportEnhancerConfig} [enhancerConfig={}] - Enhancer configuration
|
|
197
|
+
* @returns {Object} Hooks object with onPrepare, beforeTest, afterTest, onComplete, and getRecorder methods
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* const { createWDIOCapVisionHooks } = require('@capillarytech/cap-ui-dev-tools/capvision');
|
|
201
|
+
*
|
|
202
|
+
* const hooks = createWDIOCapVisionHooks({
|
|
203
|
+
* enabledClusters: ['staging'],
|
|
204
|
+
* recordingsOutputDir: './reports/recordings'
|
|
205
|
+
* });
|
|
206
|
+
*
|
|
207
|
+
* exports.config = {
|
|
208
|
+
* onPrepare: hooks.onPrepare,
|
|
209
|
+
* beforeTest: hooks.beforeTest,
|
|
210
|
+
* afterTest: hooks.afterTest,
|
|
211
|
+
* onComplete: hooks.onComplete
|
|
212
|
+
* };
|
|
213
|
+
*/
|
|
214
|
+
function createWDIOCapVisionHooks(recorderConfig = {}, enhancerConfig = {}) {
|
|
215
|
+
const capVisionRecorder = new WebdriverIOCapVisionRecorder(recorderConfig, enhancerConfig);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
/**
|
|
219
|
+
* Call in onPrepare hook to clear old recordings
|
|
220
|
+
* @returns {void}
|
|
221
|
+
*/
|
|
222
|
+
onPrepare: () => {
|
|
223
|
+
capVisionRecorder.clearRecordingsFolder();
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Call in beforeTest hook to start recording
|
|
228
|
+
* @async
|
|
229
|
+
* @param {Object} test - Test object
|
|
230
|
+
* @param {Object} context - Test context
|
|
231
|
+
* @param {Object} browser - WebdriverIO browser instance
|
|
232
|
+
* @returns {Promise<void>}
|
|
233
|
+
*/
|
|
234
|
+
beforeTest: async (test, context, browser) => {
|
|
235
|
+
// Initialize browser executor
|
|
236
|
+
capVisionRecorder.init(browser);
|
|
237
|
+
|
|
238
|
+
// Detect test type from file path
|
|
239
|
+
if (test.file) {
|
|
240
|
+
capVisionRecorder.setTestTypeFromPath(test.file);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Start recording if enabled
|
|
244
|
+
if (capVisionRecorder.isCapVisionEnabled()) {
|
|
245
|
+
try {
|
|
246
|
+
console.log('🟡 Starting CapVision recording for test:', test.title);
|
|
247
|
+
await capVisionRecorder.startRecording();
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error('🔴 Failed to start CapVision recording:', error.message);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Call in afterTest hook to stop recording and save
|
|
256
|
+
* @async
|
|
257
|
+
* @param {Object} test - Test object
|
|
258
|
+
* @param {Object} context - Test context
|
|
259
|
+
* @param {Object} result - Test result object
|
|
260
|
+
* @returns {Promise<void>}
|
|
261
|
+
*/
|
|
262
|
+
afterTest: async (test, context, result) => {
|
|
263
|
+
if (capVisionRecorder.isCapVisionEnabled()) {
|
|
264
|
+
try {
|
|
265
|
+
const testName = test.title || 'unknown_test';
|
|
266
|
+
console.log('🟡 Test result:', result.passed);
|
|
267
|
+
const testPassed = result.passed !== false; // Default to true if not specified
|
|
268
|
+
const metadata = await capVisionRecorder.stopRecordingAndSave(testName, testPassed);
|
|
269
|
+
|
|
270
|
+
if (metadata && metadata.totalEvents > 0) {
|
|
271
|
+
if (metadata.filename) {
|
|
272
|
+
console.log(`🟢 CapVision recording saved: ${metadata.totalEvents} events -> ${metadata.filename}`);
|
|
273
|
+
} else {
|
|
274
|
+
console.log(`🟡 CapVision recording discarded (test passed): ${metadata.totalEvents} events`);
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
console.log('🟨 No CapVision events were captured during test execution');
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error('🔴 Failed to save CapVision recording:', error.message);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Call in onComplete hook to enhance reports
|
|
287
|
+
* @async
|
|
288
|
+
* @returns {Promise<void>}
|
|
289
|
+
*/
|
|
290
|
+
onComplete: async () => {
|
|
291
|
+
try {
|
|
292
|
+
await capVisionRecorder.enhanceAllReports();
|
|
293
|
+
} catch (error) {
|
|
294
|
+
console.error('🔴 Failed to enhance reports:', error.message);
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Get recorder instance for manual control
|
|
300
|
+
* @returns {WebdriverIOCapVisionRecorder} Recorder instance
|
|
301
|
+
*/
|
|
302
|
+
getRecorder: () => capVisionRecorder
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Export all components
|
|
307
|
+
module.exports = {
|
|
308
|
+
WebdriverIOBrowserExecutor,
|
|
309
|
+
WebdriverIOCapVisionRecorder,
|
|
310
|
+
createWDIOCapVisionHooks
|
|
311
|
+
};
|
|
312
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.replayer-wrapper{position:relative}.replayer-mouse{position:absolute;width:20px;height:20px;transition:left .05s linear,top .05s linear;background-size:contain;background-position:50%;background-repeat:no-repeat;background-image:url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjMwMCIgd2lkdGg9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBkYXRhLW5hbWU9IkxheWVyIDEiIHZpZXdCb3g9IjAgMCA1MCA1MCI+PHBhdGggZD0iTTQ4LjcxIDQyLjkxTDM0LjA4IDI4LjI5IDQ0LjMzIDE4YTEgMSAwIDAwLS4zMy0xLjYxTDIuMzUgMS4wNmExIDEgMCAwMC0xLjI5IDEuMjlMMTYuMzkgNDRhMSAxIDAgMDAxLjY1LjM2bDEwLjI1LTEwLjI4IDE0LjYyIDE0LjYzYTEgMSAwIDAwMS40MSAwbDQuMzgtNC4zOGExIDEgMCAwMC4wMS0xLjQyem0tNS4wOSAzLjY3TDI5IDMyYTEgMSAwIDAwLTEuNDEgMGwtOS44NSA5Ljg1TDMuNjkgMy42OWwzOC4xMiAxNEwzMiAyNy41OEExIDEgMCAwMDMyIDI5bDE0LjU5IDE0LjYyeiIvPjwvc3ZnPg==");border-color:transparent}.replayer-mouse:after{content:"";display:inline-block;width:20px;height:20px;background:#4950f6;border-radius:100%;transform:translate(-50%,-50%);opacity:.3}.replayer-mouse.active:after{animation:click .2s ease-in-out 1}.replayer-mouse.touch-device{background-image:none;width:70px;height:70px;border-radius:100%;margin-left:-37px;margin-top:-37px;border:4px solid rgba(73,80,246,0);transition:left 0s linear,top 0s linear,border-color .2s ease-in-out}.replayer-mouse.touch-device.touch-active{border-color:#4950f6;transition:left .25s linear,top .25s linear,border-color .2s ease-in-out}.replayer-mouse.touch-device:after{opacity:0}.replayer-mouse.touch-device.active:after{animation:touch-click .2s ease-in-out 1}.replayer-mouse-tail{position:absolute;pointer-events:none}@keyframes click{0%{opacity:.3;width:20px;height:20px}50%{opacity:.5;width:10px;height:10px}}@keyframes touch-click{0%{opacity:0;width:20px;height:20px}50%{opacity:.5;width:10px;height:10px}}.rr-player{position:relative;background:white;float:left;border-radius:5px;box-shadow:0 24px 48px rgba(17, 16, 62, 0.12)}.rr-player__frame{overflow:hidden}.replayer-wrapper{float:left;clear:both;transform-origin:top left;left:50%;top:50%}.replayer-wrapper>iframe{border:none}.rr-controller.svelte-19ke1iv.svelte-19ke1iv{width:100%;height:80px;background:#fff;display:flex;flex-direction:column;justify-content:space-around;align-items:center;border-radius:0 0 5px 5px}.rr-timeline.svelte-19ke1iv.svelte-19ke1iv{width:80%;display:flex;align-items:center}.rr-timeline__time.svelte-19ke1iv.svelte-19ke1iv{display:inline-block;width:100px;text-align:center;color:#11103e}.rr-progress.svelte-19ke1iv.svelte-19ke1iv{flex:1;height:12px;background:#eee;position:relative;border-radius:3px;cursor:pointer;box-sizing:border-box;border-top:solid 4px #fff;border-bottom:solid 4px #fff}.rr-progress.disabled.svelte-19ke1iv.svelte-19ke1iv{cursor:not-allowed}.rr-progress__step.svelte-19ke1iv.svelte-19ke1iv{height:100%;position:absolute;left:0;top:0;background:#e0e1fe}.rr-progress__handler.svelte-19ke1iv.svelte-19ke1iv{width:20px;height:20px;border-radius:10px;position:absolute;top:2px;transform:translate(-50%, -50%);background:rgb(73, 80, 246)}.rr-controller__btns.svelte-19ke1iv.svelte-19ke1iv{display:flex;align-items:center;justify-content:center;font-size:13px}.rr-controller__btns.svelte-19ke1iv button.svelte-19ke1iv{width:32px;height:32px;display:flex;padding:0;align-items:center;justify-content:center;background:none;border:none;border-radius:50%;cursor:pointer}.rr-controller__btns.svelte-19ke1iv button.svelte-19ke1iv:active{background:#e0e1fe}.rr-controller__btns.svelte-19ke1iv button.active.svelte-19ke1iv{color:#fff;background:rgb(73, 80, 246)}.rr-controller__btns.svelte-19ke1iv button.svelte-19ke1iv:disabled{cursor:not-allowed}.switch.svelte-9brlez.svelte-9brlez.svelte-9brlez{height:1em;display:flex;align-items:center}.switch.disabled.svelte-9brlez.svelte-9brlez.svelte-9brlez{opacity:0.5}.label.svelte-9brlez.svelte-9brlez.svelte-9brlez{margin:0 8px}.switch.svelte-9brlez input[type='checkbox'].svelte-9brlez.svelte-9brlez{position:absolute;opacity:0}.switch.svelte-9brlez label.svelte-9brlez.svelte-9brlez{width:2em;height:1em;position:relative;cursor:pointer;display:block}.switch.disabled.svelte-9brlez label.svelte-9brlez.svelte-9brlez{cursor:not-allowed}.switch.svelte-9brlez label.svelte-9brlez.svelte-9brlez:before{content:'';position:absolute;width:2em;height:1em;left:0.1em;transition:background 0.1s ease;background:rgba(73, 80, 246, 0.5);border-radius:50px}.switch.svelte-9brlez label.svelte-9brlez.svelte-9brlez:after{content:'';position:absolute;width:1em;height:1em;border-radius:50px;left:0;transition:all 0.2s ease;box-shadow:0px 2px 5px 0px rgba(0, 0, 0, 0.3);background:#fcfff4;animation:switch-off 0.2s ease-out;z-index:2}.switch.svelte-9brlez input[type='checkbox'].svelte-9brlez:checked+label.svelte-9brlez:before{background:rgb(73, 80, 246)}.switch.svelte-9brlez input[type='checkbox'].svelte-9brlez:checked+label.svelte-9brlez:after{animation:switch-on 0.2s ease-out;left:1.1em}
|