lib-e2e-cypress-for-dummys-ts 0.3.0 → 0.5.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/README.md +634 -545
- package/dist/{selector-picker-VJOLGZ5H.js → chunk-FDMUIHDZ.js} +95 -38
- package/dist/chunk-FDMUIHDZ.js.map +1 -0
- package/dist/index.cjs +925 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +226 -47
- package/dist/index.d.ts +226 -47
- package/dist/index.js +864 -93
- package/dist/index.js.map +1 -1
- package/dist/runner.js +107 -0
- package/dist/selector-picker-NKYFFFGK.js +7 -0
- package/dist/selector-picker-NKYFFFGK.js.map +1 -0
- package/package.json +4 -1
- package/dist/chunk-DRNRKHXN.js +0 -60
- package/dist/chunk-DRNRKHXN.js.map +0 -1
- package/dist/selector-picker-VJOLGZ5H.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,545 +1,634 @@
|
|
|
1
|
-
# lib-e2e-cypress-for-dummys
|
|
2
|
-
|
|
3
|
-
> A floating widget that records real browser interactions and generates ready-to-run **Cypress E2E test code** — no setup required, no Cypress knowledge needed.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## What it does
|
|
8
|
-
|
|
9
|
-
Drop the widget into any web app. Press record. Click around. The widget watches every click, input, select, route change, HTTP call, and navigation event, and translates each one into the matching Cypress command in real time. Stop recording, give the test a name, and the code is saved to IndexedDB — ready to copy into your test suite.
|
|
10
|
-
|
|
11
|
-
**Zero framework coupling.** It is a plain Custom Element (`<lib-e2e-recorder>`). Angular, React, Vue, plain HTML — anything works.
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## Why use it
|
|
16
|
-
|
|
17
|
-
| Without this library | With this library |
|
|
18
|
-
|---|---|
|
|
19
|
-
| Write `cy.get('[data-cy="..."]').click()` by hand | Click in the browser, get the command automatically |
|
|
20
|
-
| Manually craft `cy.intercept()` for every API call | Every fetch/XHR is captured and the interceptor is generated |
|
|
21
|
-
| Guess which selector attribute to use | Choose the strategy once in settings; the recorder handles the rest |
|
|
22
|
-
| Copy-paste commands into `.cy.ts` files | The advanced editor inserts the `it()` block directly into the file |
|
|
23
|
-
| Maintain tests in one language | Interface in ES / EN / FR / IT / DE, auto-detected from the browser |
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Installation
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npm install lib-e2e-cypress-for-dummys-ts idb sweetalert2
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
The package ships three formats:
|
|
34
|
-
|
|
35
|
-
| File | Format | Use |
|
|
36
|
-
|---|---|---|
|
|
37
|
-
| `dist/index.js` | ESM | Modern bundlers (Vite, Angular, etc.) |
|
|
38
|
-
| `dist/index.cjs` | CJS | CommonJS environments |
|
|
39
|
-
| `dist/index.d.ts` | TypeScript declarations | Type-safe imports |
|
|
40
|
-
|
|
41
|
-
`idb` and `sweetalert2` are peer dependencies and must be installed alongside the library.
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
## Quick start — plain HTML
|
|
46
|
-
|
|
47
|
-
```html
|
|
48
|
-
<!DOCTYPE html>
|
|
49
|
-
<html>
|
|
50
|
-
<head>
|
|
51
|
-
<!-- SweetAlert2 CSS (required for modals) -->
|
|
52
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2/dist/sweetalert2.min.css" />
|
|
53
|
-
</head>
|
|
54
|
-
<body>
|
|
55
|
-
<!-- Your app content here -->
|
|
56
|
-
|
|
57
|
-
<lib-e2e-recorder></lib-e2e-recorder>
|
|
58
|
-
|
|
59
|
-
<script type="module">
|
|
60
|
-
import 'path/to/dist/index.js';
|
|
61
|
-
</script>
|
|
62
|
-
</body>
|
|
63
|
-
</html>
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
The widget appears as a small floating panel. Press **⏺** to start recording.
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## Quick start — Angular
|
|
71
|
-
|
|
72
|
-
**1. Register the custom element schema** (in `app.config.ts` or the module):
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
76
|
-
|
|
77
|
-
// In a standalone component or NgModule:
|
|
78
|
-
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
**2. Import the library** in `main.ts` (or any file that is loaded before the widget is used):
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
import 'lib-e2e-cypress-for-dummys-ts/dist/index.js';
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
**3. Add the element** to any template (typically `AppComponent`):
|
|
88
|
-
|
|
89
|
-
```html
|
|
90
|
-
<!-- Only render in non-production environments -->
|
|
91
|
-
@if (!isProduction) {
|
|
92
|
-
<!-- start-hidden: widget is invisible on load; press Ctrl+Shift+E to reveal it -->
|
|
93
|
-
<lib-e2e-recorder start-hidden></lib-e2e-recorder>
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
> The included **example project** (`ejemplo/`) uses `start-hidden` so the floating button does not interfere with the app UI during development. Remove the attribute if you prefer the widget to be visible immediately.
|
|
98
|
-
>
|
|
99
|
-
> The `start-hidden` attribute **overrides** the value stored in the settings panel. See [Start hidden](#start-hidden-invisible-mode) for the full precedence rules.
|
|
100
|
-
|
|
101
|
-
**4. Control it programmatically** (optional):
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
import type { LibE2eRecorderElement } from 'lib-e2e-cypress-for-dummys-ts';
|
|
105
|
-
|
|
106
|
-
const recorder = document.querySelector('lib-e2e-recorder') as LibE2eRecorderElement;
|
|
107
|
-
|
|
108
|
-
// Start / stop recording
|
|
109
|
-
recorder.toggle();
|
|
110
|
-
|
|
111
|
-
// Set the language explicitly
|
|
112
|
-
recorder.setLanguage('en');
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## Features
|
|
118
|
-
|
|
119
|
-
### Recording interactions
|
|
120
|
-
|
|
121
|
-
The recorder automatically captures:
|
|
122
|
-
|
|
123
|
-
| Interaction | Generated Cypress command |
|
|
124
|
-
|---|---|
|
|
125
|
-
| Click on a button or element | `cy.get('[data-cy="submit"]').click()` |
|
|
126
|
-
| Type into a text field | `cy.get('[data-cy="email"]').clear().type('user@example.com')` |
|
|
127
|
-
| Select a `<select>` value | `cy.get('[data-cy="country"]').select('ES')` |
|
|
128
|
-
| SPA route change (push/replace/popstate) | `cy.url().should('include', '/dashboard')` |
|
|
129
|
-
| Page load | `cy.visit('/current-path')` |
|
|
130
|
-
|
|
131
|
-
Input events are **debounced by 1 second** so only the final value is captured, not every keystroke.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
### HTTP monitoring
|
|
136
|
-
|
|
137
|
-
Every `fetch` and `XMLHttpRequest` call made while recording is captured automatically.
|
|
138
|
-
|
|
139
|
-
For **GET, POST and PUT** requests the recorder generates a pair of commands:
|
|
140
|
-
|
|
141
|
-
```javascript
|
|
142
|
-
// Interceptor (placed at the top of the test, in beforeEach)
|
|
143
|
-
cy.intercept('GET', '**/api/users').as('get-api-users')
|
|
144
|
-
|
|
145
|
-
// Wait command (placed inline with the test actions)
|
|
146
|
-
cy.wait('@get-api-users').then((interception) => { })
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
**DELETE** requests are intentionally ignored (read-only guard).
|
|
150
|
-
|
|
151
|
-
#### Advanced HTTP mode
|
|
152
|
-
|
|
153
|
-
Enable **Validaciones de body** in the settings panel to get auto-generated body assertions:
|
|
154
|
-
|
|
155
|
-
```javascript
|
|
156
|
-
// GET — validates response body fields
|
|
157
|
-
cy.wait('@get-api-users').then((interception) => {
|
|
158
|
-
if (interception.response) {
|
|
159
|
-
expect(interception.response.body.name).to.equal("Alice");
|
|
160
|
-
expect(interception.response.body.role).to.equal("admin");
|
|
161
|
-
}
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
// POST / PUT — validates request body fields
|
|
165
|
-
cy.wait('@post-api-users').then((interception) => {
|
|
166
|
-
expect(interception.request.body.name).to.equal("Alice");
|
|
167
|
-
})
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Fields named `id` or `uid` are always skipped (they change between environments). Array responses fall back to the default empty `.then()` block.
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
### Selector strategy
|
|
175
|
-
|
|
176
|
-
The recorder needs to choose an attribute to generate `cy.get()` selectors. Four strategies are available:
|
|
177
|
-
|
|
178
|
-
| Strategy | Generated selector |
|
|
179
|
-
|---|---|
|
|
180
|
-
| `data-cy` (default) | `[data-cy="login-btn"]` |
|
|
181
|
-
| `data-testid` | `[data-testid="login-btn"]` |
|
|
182
|
-
| `aria-label` | `[aria-label="Login"]` |
|
|
183
|
-
| `id` | `#login-btn` |
|
|
184
|
-
|
|
185
|
-
Change the strategy at any time in **⚙️ Config → Estrategia de selector**. The recorder always falls back through the chain (`data-cy` → `id`, etc.) if the preferred attribute is missing on the target element.
|
|
186
|
-
|
|
187
|
-
Forbidden id prefixes (`cdk-`, `mat-`, `ng-`, `mdc-`, `p-`, and others) are automatically excluded to avoid Angular Material / PrimeNG generated ids.
|
|
188
|
-
|
|
189
|
-
---
|
|
190
|
-
|
|
191
|
-
### Smart selector picker
|
|
192
|
-
|
|
193
|
-
When the recorder cannot generate a reliable selector for a clicked element (no `data-cy`, `data-testid`, `aria-label`, or clean `id`), the **Smart Selector Picker** appears as an overlay.
|
|
194
|
-
|
|
195
|
-
The picker walks the DOM ancestor chain of the clicked element (up to 10 levels) and colour-codes each ancestor by selector quality:
|
|
196
|
-
|
|
197
|
-
| Badge | Quality | Criteria |
|
|
198
|
-
|---|---|---|
|
|
199
|
-
| 🟢 Excellent | Best | Has `data-cy`, `data-testid`, or `aria-label` |
|
|
200
|
-
| 🔵 Good | Reliable | Has a valid `id` (no framework-generated prefix) |
|
|
201
|
-
| 🟡 Acceptable | Fragile | Has CSS classes — no testing attribute |
|
|
202
|
-
| 🔴 Not recommended | Avoid | Only tag name or inline `style` |
|
|
203
|
-
|
|
204
|
-
**How to use:**
|
|
205
|
-
1. The picker auto-selects the best available ancestor
|
|
206
|
-
2. Use **↑ / ↓** to navigate the ancestor list
|
|
207
|
-
3. Press **Enter** (or click a row) to record `cy.get('<selector>').click()`
|
|
208
|
-
4. Press **Escape** or click outside to dismiss without recording
|
|
209
|
-
|
|
210
|
-
The picker also closes automatically when recording is stopped or paused.
|
|
211
|
-
|
|
212
|
-
**Toggle in settings:** ⚙️ Config → Smart selector. When disabled, unresolvable clicks are silently dropped (the previous behaviour).
|
|
213
|
-
|
|
214
|
-
---
|
|
215
|
-
|
|
216
|
-
### Saving and managing tests
|
|
217
|
-
|
|
218
|
-
When recording stops, a dialog prompts you to:
|
|
219
|
-
|
|
220
|
-
- Give the test a **description** (becomes the `it('…')` label)
|
|
221
|
-
- Optionally add **tags** for filtering later (e.g. `smoke`, `login`, `regression`)
|
|
222
|
-
- Choose **Save** (keeps it in IndexedDB) or **Save and edit** (opens the advanced editor immediately)
|
|
223
|
-
|
|
224
|
-
The **📋 Tests** panel lets you:
|
|
225
|
-
|
|
226
|
-
- Browse all saved tests filtered by tag
|
|
227
|
-
- Expand a test to see its commands and interceptors
|
|
228
|
-
- Copy the full `describe()` block to the clipboard with a custom suite name
|
|
229
|
-
- Copy just the commands or just the interceptors separately
|
|
230
|
-
- Multi-select tests and generate a combined `describe()` block
|
|
231
|
-
- Delete individual tests
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
### Command previewer
|
|
236
|
-
|
|
237
|
-
The **⌨️ Commands** panel shows the commands captured in the current (unsaved) recording in real time. While the panel is open you can:
|
|
238
|
-
|
|
239
|
-
- Reorder commands with the up/down arrows
|
|
240
|
-
- Delete individual commands
|
|
241
|
-
- Add a custom assertion with the **assertion builder**
|
|
242
|
-
|
|
243
|
-
#### Assertion builder
|
|
244
|
-
|
|
245
|
-
The assertion builder at the bottom of the command panel lets you add `cy.get().should()` commands without typing:
|
|
246
|
-
|
|
247
|
-
1. Enter the CSS selector (e.g. `[data-cy="error-msg"]`)
|
|
248
|
-
2. Choose the assertion type from the dropdown:
|
|
249
|
-
- `be.visible`, `not.exist`, `be.disabled`, `be.checked` — no value required
|
|
250
|
-
- `contain.text`, `have.value`, `have.length`, `have.class`, `have.attr` — enter the expected value
|
|
251
|
-
3. Click **➕ Añadir**
|
|
252
|
-
|
|
253
|
-
---
|
|
254
|
-
|
|
255
|
-
### Advanced editor (direct file insertion)
|
|
256
|
-
|
|
257
|
-
The **📁 Files** panel uses the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API) to browse your local Cypress folder and insert tests directly into `.cy.ts` files — no copy-paste needed.
|
|
258
|
-
|
|
259
|
-
**Setup (one time):**
|
|
260
|
-
|
|
261
|
-
1. Open **⚙️ Config → Carpeta Cypress**
|
|
262
|
-
2. Click **📁 Seleccionar carpeta** and pick the folder that contains the `cypress/` directory
|
|
263
|
-
3. The browser stores the permission — you won't be asked again
|
|
264
|
-
|
|
265
|
-
**Expected folder structure:**
|
|
266
|
-
|
|
267
|
-
```
|
|
268
|
-
cypress/ ← select this folder
|
|
269
|
-
└── e2e/ ← the recorder reads .cy.ts files from here
|
|
270
|
-
└── login.cy.ts
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
**Using the editor:**
|
|
274
|
-
|
|
275
|
-
1. Open **📁 Files** → select a `.cy.ts` file from the tree
|
|
276
|
-
2. The `it()` block for the selected test appears in the panel
|
|
277
|
-
3. Click **💾 Insertar en archivo** — the block is appended inside the last `})` of the describe
|
|
278
|
-
4. Interceptors are automatically wrapped in a `beforeEach()`
|
|
279
|
-
5. Alternatively, click **✏️ Editar manualmente** to open a full diff editor with save support
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
recorder
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
1
|
+
# lib-e2e-cypress-for-dummys
|
|
2
|
+
|
|
3
|
+
> A floating widget that records real browser interactions and generates ready-to-run **Cypress E2E test code** — no setup required, no Cypress knowledge needed.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
Drop the widget into any web app. Press record. Click around. The widget watches every click, input, select, route change, HTTP call, and navigation event, and translates each one into the matching Cypress command in real time. Stop recording, give the test a name, and the code is saved to IndexedDB — ready to copy into your test suite.
|
|
10
|
+
|
|
11
|
+
**Zero framework coupling.** It is a plain Custom Element (`<lib-e2e-recorder>`). Angular, React, Vue, plain HTML — anything works.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Why use it
|
|
16
|
+
|
|
17
|
+
| Without this library | With this library |
|
|
18
|
+
|---|---|
|
|
19
|
+
| Write `cy.get('[data-cy="..."]').click()` by hand | Click in the browser, get the command automatically |
|
|
20
|
+
| Manually craft `cy.intercept()` for every API call | Every fetch/XHR is captured and the interceptor is generated |
|
|
21
|
+
| Guess which selector attribute to use | Choose the strategy once in settings; the recorder handles the rest |
|
|
22
|
+
| Copy-paste commands into `.cy.ts` files | The advanced editor inserts the `it()` block directly into the file |
|
|
23
|
+
| Maintain tests in one language | Interface in ES / EN / FR / IT / DE, auto-detected from the browser |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install lib-e2e-cypress-for-dummys-ts idb sweetalert2
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The package ships three formats:
|
|
34
|
+
|
|
35
|
+
| File | Format | Use |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `dist/index.js` | ESM | Modern bundlers (Vite, Angular, etc.) |
|
|
38
|
+
| `dist/index.cjs` | CJS | CommonJS environments |
|
|
39
|
+
| `dist/index.d.ts` | TypeScript declarations | Type-safe imports |
|
|
40
|
+
|
|
41
|
+
`idb` and `sweetalert2` are peer dependencies and must be installed alongside the library.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Quick start — plain HTML
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<!DOCTYPE html>
|
|
49
|
+
<html>
|
|
50
|
+
<head>
|
|
51
|
+
<!-- SweetAlert2 CSS (required for modals) -->
|
|
52
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2/dist/sweetalert2.min.css" />
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<!-- Your app content here -->
|
|
56
|
+
|
|
57
|
+
<lib-e2e-recorder></lib-e2e-recorder>
|
|
58
|
+
|
|
59
|
+
<script type="module">
|
|
60
|
+
import 'path/to/dist/index.js';
|
|
61
|
+
</script>
|
|
62
|
+
</body>
|
|
63
|
+
</html>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The widget appears as a small floating panel. Press **⏺** to start recording.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Quick start — Angular
|
|
71
|
+
|
|
72
|
+
**1. Register the custom element schema** (in `app.config.ts` or the module):
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
|
76
|
+
|
|
77
|
+
// In a standalone component or NgModule:
|
|
78
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**2. Import the library** in `main.ts` (or any file that is loaded before the widget is used):
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import 'lib-e2e-cypress-for-dummys-ts/dist/index.js';
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**3. Add the element** to any template (typically `AppComponent`):
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<!-- Only render in non-production environments -->
|
|
91
|
+
@if (!isProduction) {
|
|
92
|
+
<!-- start-hidden: widget is invisible on load; press Ctrl+Shift+E to reveal it -->
|
|
93
|
+
<lib-e2e-recorder start-hidden></lib-e2e-recorder>
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
> The included **example project** (`ejemplo/`) uses `start-hidden` so the floating button does not interfere with the app UI during development. Remove the attribute if you prefer the widget to be visible immediately.
|
|
98
|
+
>
|
|
99
|
+
> The `start-hidden` attribute **overrides** the value stored in the settings panel. See [Start hidden](#start-hidden-invisible-mode) for the full precedence rules.
|
|
100
|
+
|
|
101
|
+
**4. Control it programmatically** (optional):
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import type { LibE2eRecorderElement } from 'lib-e2e-cypress-for-dummys-ts';
|
|
105
|
+
|
|
106
|
+
const recorder = document.querySelector('lib-e2e-recorder') as LibE2eRecorderElement;
|
|
107
|
+
|
|
108
|
+
// Start / stop recording
|
|
109
|
+
recorder.toggle();
|
|
110
|
+
|
|
111
|
+
// Set the language explicitly
|
|
112
|
+
recorder.setLanguage('en');
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Features
|
|
118
|
+
|
|
119
|
+
### Recording interactions
|
|
120
|
+
|
|
121
|
+
The recorder automatically captures:
|
|
122
|
+
|
|
123
|
+
| Interaction | Generated Cypress command |
|
|
124
|
+
|---|---|
|
|
125
|
+
| Click on a button or element | `cy.get('[data-cy="submit"]').click()` |
|
|
126
|
+
| Type into a text field | `cy.get('[data-cy="email"]').clear().type('user@example.com')` |
|
|
127
|
+
| Select a `<select>` value | `cy.get('[data-cy="country"]').select('ES')` |
|
|
128
|
+
| SPA route change (push/replace/popstate) | `cy.url().should('include', '/dashboard')` |
|
|
129
|
+
| Page load | `cy.visit('/current-path')` |
|
|
130
|
+
|
|
131
|
+
Input events are **debounced by 1 second** so only the final value is captured, not every keystroke.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### HTTP monitoring
|
|
136
|
+
|
|
137
|
+
Every `fetch` and `XMLHttpRequest` call made while recording is captured automatically.
|
|
138
|
+
|
|
139
|
+
For **GET, POST and PUT** requests the recorder generates a pair of commands:
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
// Interceptor (placed at the top of the test, in beforeEach)
|
|
143
|
+
cy.intercept('GET', '**/api/users').as('get-api-users')
|
|
144
|
+
|
|
145
|
+
// Wait command (placed inline with the test actions)
|
|
146
|
+
cy.wait('@get-api-users').then((interception) => { })
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**DELETE** requests are intentionally ignored (read-only guard).
|
|
150
|
+
|
|
151
|
+
#### Advanced HTTP mode
|
|
152
|
+
|
|
153
|
+
Enable **Validaciones de body** in the settings panel to get auto-generated body assertions:
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
// GET — validates response body fields
|
|
157
|
+
cy.wait('@get-api-users').then((interception) => {
|
|
158
|
+
if (interception.response) {
|
|
159
|
+
expect(interception.response.body.name).to.equal("Alice");
|
|
160
|
+
expect(interception.response.body.role).to.equal("admin");
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// POST / PUT — validates request body fields
|
|
165
|
+
cy.wait('@post-api-users').then((interception) => {
|
|
166
|
+
expect(interception.request.body.name).to.equal("Alice");
|
|
167
|
+
})
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Fields named `id` or `uid` are always skipped (they change between environments). Array responses fall back to the default empty `.then()` block.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Selector strategy
|
|
175
|
+
|
|
176
|
+
The recorder needs to choose an attribute to generate `cy.get()` selectors. Four strategies are available:
|
|
177
|
+
|
|
178
|
+
| Strategy | Generated selector |
|
|
179
|
+
|---|---|
|
|
180
|
+
| `data-cy` (default) | `[data-cy="login-btn"]` |
|
|
181
|
+
| `data-testid` | `[data-testid="login-btn"]` |
|
|
182
|
+
| `aria-label` | `[aria-label="Login"]` |
|
|
183
|
+
| `id` | `#login-btn` |
|
|
184
|
+
|
|
185
|
+
Change the strategy at any time in **⚙️ Config → Estrategia de selector**. The recorder always falls back through the chain (`data-cy` → `id`, etc.) if the preferred attribute is missing on the target element.
|
|
186
|
+
|
|
187
|
+
Forbidden id prefixes (`cdk-`, `mat-`, `ng-`, `mdc-`, `p-`, and others) are automatically excluded to avoid Angular Material / PrimeNG generated ids.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
### Smart selector picker
|
|
192
|
+
|
|
193
|
+
When the recorder cannot generate a reliable selector for a clicked element (no `data-cy`, `data-testid`, `aria-label`, or clean `id`), the **Smart Selector Picker** appears as an overlay.
|
|
194
|
+
|
|
195
|
+
The picker walks the DOM ancestor chain of the clicked element (up to 10 levels) and colour-codes each ancestor by selector quality:
|
|
196
|
+
|
|
197
|
+
| Badge | Quality | Criteria |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| 🟢 Excellent | Best | Has `data-cy`, `data-testid`, or `aria-label` |
|
|
200
|
+
| 🔵 Good | Reliable | Has a valid `id` (no framework-generated prefix) |
|
|
201
|
+
| 🟡 Acceptable | Fragile | Has CSS classes — no testing attribute |
|
|
202
|
+
| 🔴 Not recommended | Avoid | Only tag name or inline `style` |
|
|
203
|
+
|
|
204
|
+
**How to use:**
|
|
205
|
+
1. The picker auto-selects the best available ancestor
|
|
206
|
+
2. Use **↑ / ↓** to navigate the ancestor list
|
|
207
|
+
3. Press **Enter** (or click a row) to record `cy.get('<selector>').click()`
|
|
208
|
+
4. Press **Escape** or click outside to dismiss without recording
|
|
209
|
+
|
|
210
|
+
The picker also closes automatically when recording is stopped or paused.
|
|
211
|
+
|
|
212
|
+
**Toggle in settings:** ⚙️ Config → Smart selector. When disabled, unresolvable clicks are silently dropped (the previous behaviour).
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
### Saving and managing tests
|
|
217
|
+
|
|
218
|
+
When recording stops, a dialog prompts you to:
|
|
219
|
+
|
|
220
|
+
- Give the test a **description** (becomes the `it('…')` label)
|
|
221
|
+
- Optionally add **tags** for filtering later (e.g. `smoke`, `login`, `regression`)
|
|
222
|
+
- Choose **Save** (keeps it in IndexedDB) or **Save and edit** (opens the advanced editor immediately)
|
|
223
|
+
|
|
224
|
+
The **📋 Tests** panel lets you:
|
|
225
|
+
|
|
226
|
+
- Browse all saved tests filtered by tag
|
|
227
|
+
- Expand a test to see its commands and interceptors
|
|
228
|
+
- Copy the full `describe()` block to the clipboard with a custom suite name
|
|
229
|
+
- Copy just the commands or just the interceptors separately
|
|
230
|
+
- Multi-select tests and generate a combined `describe()` block
|
|
231
|
+
- Delete individual tests
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### Command previewer
|
|
236
|
+
|
|
237
|
+
The **⌨️ Commands** panel shows the commands captured in the current (unsaved) recording in real time. While the panel is open you can:
|
|
238
|
+
|
|
239
|
+
- Reorder commands with the up/down arrows
|
|
240
|
+
- Delete individual commands
|
|
241
|
+
- Add a custom assertion with the **assertion builder**
|
|
242
|
+
|
|
243
|
+
#### Assertion builder
|
|
244
|
+
|
|
245
|
+
The assertion builder at the bottom of the command panel lets you add `cy.get().should()` commands without typing:
|
|
246
|
+
|
|
247
|
+
1. Enter the CSS selector (e.g. `[data-cy="error-msg"]`)
|
|
248
|
+
2. Choose the assertion type from the dropdown:
|
|
249
|
+
- `be.visible`, `not.exist`, `be.disabled`, `be.checked` — no value required
|
|
250
|
+
- `contain.text`, `have.value`, `have.length`, `have.class`, `have.attr` — enter the expected value
|
|
251
|
+
3. Click **➕ Añadir**
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
### Advanced editor (direct file insertion)
|
|
256
|
+
|
|
257
|
+
The **📁 Files** panel uses the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API) to browse your local Cypress folder and insert tests directly into `.cy.ts` files — no copy-paste needed.
|
|
258
|
+
|
|
259
|
+
**Setup (one time):**
|
|
260
|
+
|
|
261
|
+
1. Open **⚙️ Config → Carpeta Cypress**
|
|
262
|
+
2. Click **📁 Seleccionar carpeta** and pick the folder that contains the `cypress/` directory
|
|
263
|
+
3. The browser stores the permission — you won't be asked again
|
|
264
|
+
|
|
265
|
+
**Expected folder structure:**
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
cypress/ ← select this folder
|
|
269
|
+
└── e2e/ ← the recorder reads .cy.ts files from here
|
|
270
|
+
└── login.cy.ts
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Using the editor:**
|
|
274
|
+
|
|
275
|
+
1. Open **📁 Files** → select a `.cy.ts` file from the tree
|
|
276
|
+
2. The `it()` block for the selected test appears in the panel
|
|
277
|
+
3. Click **💾 Insertar en archivo** — the block is appended inside the last `})` of the describe
|
|
278
|
+
4. Interceptors are automatically wrapped in a `beforeEach()`
|
|
279
|
+
5. Alternatively, click **✏️ Editar manualmente** to open a full diff editor with save support
|
|
280
|
+
- Inside the manual editor, click **🪄 Insertar bloques** to auto-merge the `it()` and `beforeEach()` blocks into the editor content (same placement as the automatic insert), then review the diff before saving — no copy/paste needed. The 📋 copy buttons remain available if you prefer to paste manually.
|
|
281
|
+
|
|
282
|
+
**Sidebar toolbar:**
|
|
283
|
+
|
|
284
|
+
| Button | Action |
|
|
285
|
+
|---|---|
|
|
286
|
+
| **+ Nuevo archivo** | Creates a new `.cy.ts` file with a basic `describe/it` scaffold in `cypress/e2e/`. Type the name (no extension needed) and press Enter or click **Crear**. |
|
|
287
|
+
| **+ Nueva carpeta** | Creates a new folder in `cypress/e2e/`. Type the name and press Enter or click **Crear**. Path separators are stripped from the name. |
|
|
288
|
+
| **↻ Actualizar** | Rescans the `cypress/e2e/` directory. Useful after a `git pull`, a drag-and-drop into the folder, or any external file change. |
|
|
289
|
+
|
|
290
|
+
> The File System Access API is supported in Chromium-based browsers (Chrome, Edge, Opera). Firefox and Safari do not support it.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
### Running a spec from the editor (local runner)
|
|
295
|
+
|
|
296
|
+
Inside the manual editor (**✏️ Editar manualmente**) the **▶ Lanzar test** button runs
|
|
297
|
+
**only the spec you are editing**, headless (no Cypress GUI), and shows the result
|
|
298
|
+
(pass/fail + output) right in the editor — for a fast record → tweak → run loop.
|
|
299
|
+
|
|
300
|
+
The browser can't spawn Cypress, so it talks to a tiny **local runner** over HTTP.
|
|
301
|
+
Start it once in your Cypress project:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
npx lib-e2e-cypress-runner # listens on http://127.0.0.1:8123
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Options:
|
|
308
|
+
|
|
309
|
+
| Flag | Default | Description |
|
|
310
|
+
|---|---|---|
|
|
311
|
+
| `--port` | `8123` | Port to listen on. |
|
|
312
|
+
| `--command` | `npx cypress run --spec {spec}` | Command to run. `{spec}` is replaced by the spec (passed as a single argv, never through a shell). |
|
|
313
|
+
| `--cwd` | current dir | Working directory the command runs in. |
|
|
314
|
+
| `--host` | `127.0.0.1` | Bind address (local only). |
|
|
315
|
+
|
|
316
|
+
The widget POSTs `{ specPath }` to `http://localhost:8123/run-test`; configure a
|
|
317
|
+
different endpoint via the `runnerUrl` property of `<file-preview>` if needed.
|
|
318
|
+
|
|
319
|
+
Notes:
|
|
320
|
+
- The button is **enabled only when the app is served from localhost**. On a deployed
|
|
321
|
+
environment it is disabled with a *"muévelo a local para poder probar"* hint —
|
|
322
|
+
running a local Cypress against a remote build makes no sense.
|
|
323
|
+
- If no runner is reachable you get a clear "no runner detected" message (no silent
|
|
324
|
+
failure).
|
|
325
|
+
- The runner is a **dev-only** tool: it binds to `127.0.0.1` and passes the spec as an
|
|
326
|
+
argument (no shell interpolation). Don't expose it beyond your machine.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
### Recording history
|
|
331
|
+
|
|
332
|
+
The last **5 recordings** are automatically saved to `localStorage` so you never lose work if you accidentally close the dialog without saving. Use `recorder.recoverLastRecording()` to restore the most recent recording programmatically.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
### Cross-app recording (single-spa / Module Federation)
|
|
337
|
+
|
|
338
|
+
Recording a flow that spans **several micro-frontends** under one domain (login app → dashboard app → admin app) used to be impossible: navigating from one project to another tore the widget down and wiped the in-progress recording. Now the **live recording session is persisted** (in IndexedDB, same origin), so it **survives the crossing** and recording continues seamlessly — the captured commands and interceptors are all still there.
|
|
339
|
+
|
|
340
|
+
It also survives an accidental **page reload** of the same origin: the recording is recovered automatically.
|
|
341
|
+
|
|
342
|
+
> **Scope:** this covers micro-frontends served from the **same origin** (same scheme + host + port, the typical single-spa setup) with **client-side** navigation. Different origins/subdomains and cross-origin `cy.origin` test generation are out of scope (see `docs/specs/006-cross-app-recording-continuity.md`).
|
|
343
|
+
|
|
344
|
+
#### Where to mount the widget
|
|
345
|
+
|
|
346
|
+
Pick **exactly one** placement — never both, or the HTTP monitor installs twice and commands get double-recorded:
|
|
347
|
+
|
|
348
|
+
- **Option A — a single instance in the shell (recommended).** Mount one `<lib-e2e-recorder>` in the shell/root application. The shell is not unmounted on app swaps, so a recording naturally spans every micro-frontend. Simplest and most robust.
|
|
349
|
+
- **Option B — one instance per sub-project, never in the shell.** Each micro-frontend mounts its own widget; continuity across crossings is provided by the persisted session. Do **not** also put one in the shell. *Caveat:* during a single-spa transition two apps can be briefly mounted at once, so an API call fired in that window may produce a duplicate `cy.wait` — just delete the stray line, or prefer Option A.
|
|
350
|
+
|
|
351
|
+
#### Resuming
|
|
352
|
+
|
|
353
|
+
When the recorder loads and finds an active recording session:
|
|
354
|
+
|
|
355
|
+
- **Recent** (within the resume window) → it **continues silently**.
|
|
356
|
+
- **Stale** (older than the window) → it asks **continue or discard**, so a forgotten session never resumes silently (important when deployed with `start-hidden`).
|
|
357
|
+
|
|
358
|
+
The resume window defaults to **30 minutes** and is editable in **⚙️ Config → ⏱ Recording continuity**. Crossing apps is instant, so it is always "recent"; the window only gates the come-back-later case.
|
|
359
|
+
|
|
360
|
+
Stopping a recording (save **or** discard) ends the session, so a finished recording never resurrects on the next navigation.
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
### Keyboard shortcuts
|
|
365
|
+
|
|
366
|
+
| Shortcut | Action |
|
|
367
|
+
|---|---|
|
|
368
|
+
| `Ctrl + Shift + E` | **Show / hide the widget** (works even when it is invisible) |
|
|
369
|
+
| `Ctrl + R` | Start / stop recording |
|
|
370
|
+
| `Ctrl + P` | Pause / resume recording |
|
|
371
|
+
| `Ctrl + 1` | Open saved tests panel |
|
|
372
|
+
| `Ctrl + 2` | Open command previewer |
|
|
373
|
+
| `Ctrl + 3` | Open settings |
|
|
374
|
+
|
|
375
|
+
> `Ctrl + R` / `Ctrl + P` and the panel shortcuts only fire when the widget is **visible**. `Ctrl + Shift + E` always works regardless of visibility state.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
### Invisible mode — record in production, stay hidden from users
|
|
380
|
+
|
|
381
|
+
Most test recorders force you to choose: use them in a safe dev environment, or ship to real users and lose the tool. **Not this one.**
|
|
382
|
+
|
|
383
|
+
With `start-hidden`, you can deploy the recorder to **any environment — including production** — and it stays completely invisible to your users. No floating button, no visual clutter, no accidental clicks. When *you* need to record a test against the real app, with real data, real API responses, and real edge cases that are impossible to reproduce locally, just press the secret shortcut and the widget appears. Record, save, hide. Nobody saw a thing.
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
Ctrl + Shift + E → show / hide the widget (works even when invisible)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
This means you can:
|
|
390
|
+
- **Record against production data** — capture flows that only happen with real users and real back-ends.
|
|
391
|
+
- **Debug flaky tests in the wild** — reproduce the exact sequence of events that broke your CI pipeline.
|
|
392
|
+
- **Onboard QA teams without a local setup** — testers open the live app, hit the shortcut, and record directly.
|
|
393
|
+
- **Keep staging clean** — no visible dev tooling leaked to clients or stakeholders reviewing the environment.
|
|
394
|
+
|
|
395
|
+
#### Option A — HTML attribute (deploy-time, recommended)
|
|
396
|
+
|
|
397
|
+
Add `start-hidden` to the element. This takes **precedence over the settings panel** and is the right choice for environment-level control:
|
|
398
|
+
|
|
399
|
+
```html
|
|
400
|
+
<!-- Production / staging: invisible by default -->
|
|
401
|
+
<lib-e2e-recorder start-hidden></lib-e2e-recorder>
|
|
402
|
+
|
|
403
|
+
<!-- Dev: always visible (or omit the attribute entirely) -->
|
|
404
|
+
<lib-e2e-recorder start-hidden="false"></lib-e2e-recorder>
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Works in every framework:
|
|
408
|
+
|
|
409
|
+
```html
|
|
410
|
+
<!-- Angular -->
|
|
411
|
+
<lib-e2e-recorder start-hidden></lib-e2e-recorder>
|
|
412
|
+
|
|
413
|
+
<!-- React (JSX) -->
|
|
414
|
+
<lib-e2e-recorder start-hidden="true" />
|
|
415
|
+
|
|
416
|
+
<!-- Vue -->
|
|
417
|
+
<lib-e2e-recorder start-hidden />
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
A typical Angular setup driven by an environment variable:
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
// app.component.ts
|
|
424
|
+
get startHidden(): boolean {
|
|
425
|
+
return environment.production; // invisible in prod, visible in dev
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
```html
|
|
430
|
+
<!-- app.component.html -->
|
|
431
|
+
<lib-e2e-recorder [attr.start-hidden]="startHidden ? '' : null"></lib-e2e-recorder>
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
#### Option B — Settings panel (runtime, per user)
|
|
435
|
+
|
|
436
|
+
Enable **⚙️ Config → 👁 Widget visibility → Start hidden**. The preference is stored in IndexedDB and persists across sessions. Useful when individual team members want to tailor the behaviour without a redeployment.
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
**Either way, when the widget is hidden:**
|
|
441
|
+
- Zero DOM rendered, zero event listeners added beyond the keyboard shortcut — it is truly invisible and inert.
|
|
442
|
+
- Your Cypress selectors are never blocked by a floating element.
|
|
443
|
+
- Press **`Ctrl + Shift + E`** to reveal it instantly, no page reload needed.
|
|
444
|
+
- Hide it again with the same shortcut when you are done.
|
|
445
|
+
|
|
446
|
+
> **Running under Cypress?** The widget auto-disables itself when `window.Cypress` is detected — it does not render at all, regardless of the `start-hidden` attribute or the settings panel value. No extra setup, no `beforeEach` cleanup, no risk of the widget covering a selector mid-test.
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
### Import / Export
|
|
451
|
+
|
|
452
|
+
Back up tests to a JSON file or restore from a previous backup via **⚙️ Config → Datos**:
|
|
453
|
+
|
|
454
|
+
- **⬆️ Export tests** — opens a dialog to choose **what** to export, then downloads `e2e-cypress-export.json`:
|
|
455
|
+
- **Todo** — every saved test.
|
|
456
|
+
- **Selección manual** — tick individual tests (multiple allowed); only the checked ones are exported.
|
|
457
|
+
- **Por tags** — pick one or more tags; exports every test carrying **at least one** of them (OR). The dialog lists the matching tests so you can see exactly what will be exported before confirming.
|
|
458
|
+
|
|
459
|
+
A live count shows how many tests the current selection will export, and the export button is disabled when that count is `0`.
|
|
460
|
+
- **⬇️ Import tests** — uploads a JSON file and **merges** its tests into the current database (existing tests are kept, never wiped).
|
|
461
|
+
|
|
462
|
+
The output format and filename are identical in every mode, so any exported file (full or partial) can be re-imported.
|
|
463
|
+
|
|
464
|
+
JSON format:
|
|
465
|
+
|
|
466
|
+
```json
|
|
467
|
+
{
|
|
468
|
+
"tests": [ { "name": "Login correcto", "createdAt": 1234567890 } ],
|
|
469
|
+
"interceptors": []
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
### Internationalisation
|
|
476
|
+
|
|
477
|
+
The interface auto-detects the browser language. Supported languages:
|
|
478
|
+
|
|
479
|
+
| Code | Language |
|
|
480
|
+
|---|---|
|
|
481
|
+
| `es` | Spanish (default fallback) |
|
|
482
|
+
| `en` | English |
|
|
483
|
+
| `fr` | French |
|
|
484
|
+
| `it` | Italian |
|
|
485
|
+
| `de` | German |
|
|
486
|
+
|
|
487
|
+
Override the language programmatically:
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
recorder.setLanguage('en'); // accepts: 'es' | 'en' | 'fr' | 'it' | 'de'
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Public API
|
|
496
|
+
|
|
497
|
+
### `<lib-e2e-recorder>` element
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
class LibE2eRecorderElement extends HTMLElement {
|
|
501
|
+
// Injected services (auto-created if not set)
|
|
502
|
+
recording: RecordingService;
|
|
503
|
+
persistence: PersistenceService;
|
|
504
|
+
translation: TranslationService;
|
|
505
|
+
|
|
506
|
+
// State (read-only in normal use)
|
|
507
|
+
isRecording: boolean;
|
|
508
|
+
isPaused: boolean;
|
|
509
|
+
cypressCommands: string[];
|
|
510
|
+
interceptors: string[];
|
|
511
|
+
|
|
512
|
+
// Dialog flags
|
|
513
|
+
isCommandsDialogOpen: boolean;
|
|
514
|
+
isSavedTestsDialogOpen: boolean;
|
|
515
|
+
isSaveTestDialogOpen: boolean;
|
|
516
|
+
isSettingsDialogOpen: boolean;
|
|
517
|
+
isAdvancedEditorDialogOpen: boolean;
|
|
518
|
+
|
|
519
|
+
// Methods
|
|
520
|
+
toggle(): void; // start / stop recording
|
|
521
|
+
togglePause(): void; // pause / resume
|
|
522
|
+
setLanguage(lang?: string): void; // set UI language
|
|
523
|
+
|
|
524
|
+
showCommandsDialog(): void;
|
|
525
|
+
showSavedTestsDialog(): void;
|
|
526
|
+
showSaveTestDialog(): void;
|
|
527
|
+
showSettingsDialog(): void;
|
|
528
|
+
showAdvancedEditorDialog(testId?: number): void;
|
|
529
|
+
|
|
530
|
+
getRecordingHistory(): Array<{ commands: string[]; interceptors: string[]; savedAt: number }>;
|
|
531
|
+
recoverLastRecording(): void;
|
|
532
|
+
clearRecordingHistory(): void;
|
|
533
|
+
|
|
534
|
+
// Cross-app session (micro-frontends)
|
|
535
|
+
hasActiveSession(): boolean; // is a recording session persisted/active?
|
|
536
|
+
resumeSession(): void; // rehydrate the persisted session
|
|
537
|
+
discardSession(): void; // drop the persisted session
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### `RecordingService`
|
|
542
|
+
|
|
543
|
+
```typescript
|
|
544
|
+
class RecordingService {
|
|
545
|
+
selectorStrategy: 'data-cy' | 'data-testid' | 'aria-label' | 'id';
|
|
546
|
+
|
|
547
|
+
startRecording(): void;
|
|
548
|
+
stopRecording(): void;
|
|
549
|
+
pauseRecording(): void;
|
|
550
|
+
resumeRecording(): void;
|
|
551
|
+
toggleRecording(): void;
|
|
552
|
+
togglePause(): void;
|
|
553
|
+
|
|
554
|
+
addCommand(cmd: string): void; // respects recording/paused state
|
|
555
|
+
appendCommand(cmd: string): void; // always appends (for manual assertions)
|
|
556
|
+
removeCommand(index: number): void;
|
|
557
|
+
moveCommand(from: number, to: number): void;
|
|
558
|
+
|
|
559
|
+
registerInterceptor(method: string, url: string, alias: string): void;
|
|
560
|
+
removeInterceptor(index: number): void;
|
|
561
|
+
|
|
562
|
+
clearCommands(): void;
|
|
563
|
+
|
|
564
|
+
// Cross-app session (micro-frontends) — see spec 006
|
|
565
|
+
sessionId: string | null;
|
|
566
|
+
restoreSession(state: ActiveSessionState): void; // rehydrate WITHOUT re-running the bootstrap
|
|
567
|
+
getSessionSnapshot(): ActiveSessionState;
|
|
568
|
+
onSessionChange(fn: (state: ActiveSessionState) => void): () => void;
|
|
569
|
+
|
|
570
|
+
getCommandsSnapshot(): string[];
|
|
571
|
+
getInterceptorsSnapshot(): string[];
|
|
572
|
+
|
|
573
|
+
onCommandsChange(fn: (cmds: string[]) => void): () => void;
|
|
574
|
+
onInterceptorsChange(fn: (ints: string[]) => void): () => void;
|
|
575
|
+
onRecordingChange(fn: (isRecording: boolean) => void): () => void;
|
|
576
|
+
onPauseChange(fn: (isPaused: boolean) => void): () => void;
|
|
577
|
+
|
|
578
|
+
destroy(): void;
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### `PersistenceService`
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
class PersistenceService {
|
|
586
|
+
insertTest(name: string, commands?: string[], interceptors?: string[], tags?: string[]): Promise<number>;
|
|
587
|
+
getAllTests(): Promise<TestWithDetails[]>;
|
|
588
|
+
getTestById(testId: number): Promise<TestDetail | null>;
|
|
589
|
+
deleteTest(id: number): Promise<void>;
|
|
590
|
+
|
|
591
|
+
setConfig(config: Record<string, unknown>): Promise<void>;
|
|
592
|
+
setConfigKey(key: string, value: unknown): Promise<void>;
|
|
593
|
+
getConfig(key: string): Promise<Record<string, unknown> | null>;
|
|
594
|
+
getGeneralConfig(): Promise<ConfigRecord | null>;
|
|
595
|
+
|
|
596
|
+
// Live recording session (cross-app continuity — see spec 006)
|
|
597
|
+
saveActiveSession(state: ActiveSessionState): Promise<void>;
|
|
598
|
+
getActiveSession(): Promise<ActiveSessionState | null>;
|
|
599
|
+
clearActiveSession(): Promise<void>;
|
|
600
|
+
|
|
601
|
+
clearAllData(): Promise<void>;
|
|
602
|
+
requestDirectoryPermissions(): Promise<void>;
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## Technical notes
|
|
609
|
+
|
|
610
|
+
- **Storage:** IndexedDB via [`idb`](https://github.com/jakearchibald/idb). Data survives page reloads and browser restarts.
|
|
611
|
+
- **Modals:** [`SweetAlert2`](https://sweetalert2.github.io/) with a custom dark theme injected as a `<style>` tag.
|
|
612
|
+
- **Shadow DOM:** All components use `mode: 'open'` shadow roots, so widget styles never bleed into the host application.
|
|
613
|
+
- **Bundle:** ESM + CJS, compiled to ES2022. No framework runtime included.
|
|
614
|
+
- **Browser support:** Chromium 105+ for full feature set (File System Access API). Recording and saving work in all modern browsers.
|
|
615
|
+
|
|
616
|
+
---
|
|
617
|
+
|
|
618
|
+
## Development
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
npm install
|
|
622
|
+
npm run build # compile with tsup
|
|
623
|
+
npm test # run vitest unit tests
|
|
624
|
+
npm run test:coverage # coverage report (≥80% gate)
|
|
625
|
+
npm run lint # ESLint
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
See [`CLAUDE.md`](./CLAUDE.md) for the full development workflow (Spec-Driven Development, TDD, commit conventions).
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
## Licence
|
|
633
|
+
|
|
634
|
+
MIT
|