wick-dom-observer 1.0.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.
@@ -0,0 +1,454 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Modal + Loading Table Demo</title>
8
+ <style>
9
+ :root {
10
+ --bg-a: #0f172a;
11
+ --bg-b: #111827;
12
+ --card: rgba(15, 23, 42, 0.86);
13
+ --line: rgba(148, 163, 184, 0.24);
14
+ --text: #e2e8f0;
15
+ --muted: #94a3b8;
16
+ --primary-a: #0ea5e9;
17
+ --primary-b: #6366f1;
18
+ --success-a: #059669;
19
+ --success-b: #16a34a;
20
+ --shadow: 0 14px 40px rgba(2, 6, 23, 0.55);
21
+ }
22
+
23
+ * {
24
+ box-sizing: border-box;
25
+ }
26
+
27
+ body {
28
+ margin: 0;
29
+ min-height: 100vh;
30
+ font-family: Inter, Segoe UI, Roboto, Arial, sans-serif;
31
+ color: var(--text);
32
+ background:
33
+ radial-gradient(circle at 15% 10%, rgba(14, 165, 233, 0.16), transparent 35%),
34
+ radial-gradient(circle at 85% 5%, rgba(99, 102, 241, 0.18), transparent 40%),
35
+ linear-gradient(170deg, var(--bg-a), var(--bg-b));
36
+ display: grid;
37
+ place-items: center;
38
+ padding: 24px;
39
+ }
40
+
41
+ .app {
42
+ width: min(1080px, 100%);
43
+ background: var(--card);
44
+ border: 1px solid var(--line);
45
+ border-radius: 18px;
46
+ box-shadow: var(--shadow);
47
+ backdrop-filter: blur(6px);
48
+ overflow: hidden;
49
+ }
50
+
51
+ .header {
52
+ padding: 20px 24px 14px;
53
+ border-bottom: 1px solid var(--line);
54
+ background: linear-gradient(180deg, rgba(99, 102, 241, 0.12), transparent);
55
+ }
56
+
57
+ .title {
58
+ margin: 0;
59
+ font-size: 1.26rem;
60
+ font-weight: 750;
61
+ letter-spacing: 0.2px;
62
+ }
63
+
64
+ .subtitle {
65
+ margin: 8px 0 0;
66
+ color: var(--muted);
67
+ font-size: 0.94rem;
68
+ }
69
+
70
+ .content {
71
+ padding: 22px 24px 24px;
72
+ display: grid;
73
+ gap: 16px;
74
+ }
75
+
76
+ .toolbar {
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 12px;
80
+ flex-wrap: wrap;
81
+ }
82
+
83
+ .cta {
84
+ border: 1px solid transparent;
85
+ border-radius: 12px;
86
+ font-size: 0.95rem;
87
+ font-weight: 700;
88
+ color: #eff6ff;
89
+ padding: 11px 16px;
90
+ cursor: pointer;
91
+ background: linear-gradient(135deg, var(--primary-a), var(--primary-b));
92
+ box-shadow: 0 10px 24px rgba(59, 130, 246, 0.35);
93
+ transition: transform 140ms ease, box-shadow 140ms ease, opacity 140ms ease;
94
+ }
95
+
96
+ .cta:hover {
97
+ transform: translateY(-1px);
98
+ box-shadow: 0 13px 28px rgba(59, 130, 246, 0.42);
99
+ }
100
+
101
+ .cta:disabled {
102
+ opacity: 0.72;
103
+ cursor: default;
104
+ transform: none;
105
+ box-shadow: 0 8px 16px rgba(59, 130, 246, 0.2);
106
+ }
107
+
108
+ .status {
109
+ color: var(--muted);
110
+ font-size: 0.9rem;
111
+ margin: 0;
112
+ }
113
+
114
+ .loading-row {
115
+ min-height: 44px;
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 12px;
119
+ }
120
+
121
+ .spinner-image {
122
+ width: 36px;
123
+ height: 36px;
124
+ border-radius: 999px;
125
+ border: 4px solid rgba(148, 163, 184, 0.28);
126
+ border-top-color: #38bdf8;
127
+ border-right-color: #6366f1;
128
+ animation: spin 760ms linear infinite;
129
+ filter: drop-shadow(0 4px 10px rgba(56, 189, 248, 0.25));
130
+ }
131
+
132
+ .loading-label {
133
+ margin: 0;
134
+ font-size: 0.95rem;
135
+ color: #cbd5e1;
136
+ }
137
+
138
+ .table-wrap {
139
+ border: 1px solid var(--line);
140
+ border-radius: 14px;
141
+ overflow: hidden;
142
+ background: rgba(2, 6, 23, 0.38);
143
+ }
144
+
145
+ table {
146
+ width: 100%;
147
+ border-collapse: collapse;
148
+ }
149
+
150
+ thead th {
151
+ text-align: left;
152
+ font-size: 0.84rem;
153
+ letter-spacing: 0.4px;
154
+ color: #cbd5e1;
155
+ padding: 12px 14px;
156
+ background: rgba(30, 41, 59, 0.84);
157
+ border-bottom: 1px solid var(--line);
158
+ }
159
+
160
+ tbody td {
161
+ padding: 11px 14px;
162
+ border-bottom: 1px solid rgba(148, 163, 184, 0.14);
163
+ font-size: 0.91rem;
164
+ color: #e5e7eb;
165
+ }
166
+
167
+ tbody tr:nth-child(even) {
168
+ background: rgba(30, 41, 59, 0.28);
169
+ }
170
+
171
+ tbody tr:hover {
172
+ background: rgba(56, 189, 248, 0.12);
173
+ }
174
+
175
+ .hidden {
176
+ display: none;
177
+ }
178
+
179
+ /* Modal */
180
+ .overlay {
181
+ position: fixed;
182
+ inset: 0;
183
+ background: rgba(2, 6, 23, 0.66);
184
+ display: grid;
185
+ place-items: center;
186
+ z-index: 1000;
187
+ padding: 20px;
188
+ }
189
+
190
+ .overlay.hidden {
191
+ display: none;
192
+ }
193
+
194
+ .modal {
195
+ width: min(560px, 100%);
196
+ border-radius: 18px;
197
+ border: 1px solid var(--line);
198
+ background:
199
+ linear-gradient(160deg, rgba(15, 23, 42, 0.98), rgba(30, 41, 59, 0.98));
200
+ box-shadow: 0 20px 48px rgba(2, 6, 23, 0.62);
201
+ overflow: hidden;
202
+ animation: riseIn 180ms ease-out;
203
+ }
204
+
205
+ .modal-head {
206
+ padding: 16px 18px 12px;
207
+ border-bottom: 1px solid var(--line);
208
+ background: linear-gradient(180deg, rgba(14, 165, 233, 0.14), transparent);
209
+ }
210
+
211
+ .modal-title {
212
+ margin: 0;
213
+ font-size: 1.05rem;
214
+ font-weight: 750;
215
+ color: #e0f2fe;
216
+ }
217
+
218
+ .modal-body {
219
+ padding: 16px 18px;
220
+ color: #cbd5e1;
221
+ line-height: 1.52;
222
+ font-size: 0.94rem;
223
+ }
224
+
225
+ .modal-list {
226
+ margin: 10px 0 0;
227
+ padding-left: 18px;
228
+ }
229
+
230
+ .modal-actions {
231
+ padding: 0 18px 18px;
232
+ display: flex;
233
+ justify-content: flex-end;
234
+ }
235
+
236
+ .close-modal {
237
+ border: 1px solid transparent;
238
+ border-radius: 10px;
239
+ padding: 10px 14px;
240
+ font-weight: 700;
241
+ color: #ecfdf5;
242
+ background: linear-gradient(135deg, var(--success-a), var(--success-b));
243
+ cursor: pointer;
244
+ box-shadow: 0 8px 18px rgba(22, 163, 74, 0.35);
245
+ }
246
+
247
+ /* Keep ad-overlay modal behavior, but style like a top banner. */
248
+ #adOverlay {
249
+ place-items: start center;
250
+ padding: 14px 16px 24px;
251
+ background: rgba(2, 6, 23, 0.6);
252
+ }
253
+
254
+ #adOverlay .modal {
255
+ width: min(1040px, calc(100% - 10px));
256
+ border-radius: 14px;
257
+ border: 1px solid rgba(250, 204, 21, 0.68);
258
+ background:
259
+ repeating-linear-gradient(-45deg,
260
+ rgba(245, 158, 11, 0.16) 0 14px,
261
+ rgba(234, 88, 12, 0.16) 14px 28px),
262
+ linear-gradient(140deg, rgba(30, 41, 59, 0.98), rgba(15, 23, 42, 0.98));
263
+ box-shadow: 0 16px 36px rgba(2, 6, 23, 0.66);
264
+ animation: bannerDrop 220ms ease-out;
265
+ }
266
+
267
+ #adOverlay .modal-head {
268
+ padding: 12px 16px 10px;
269
+ background: linear-gradient(180deg, rgba(250, 204, 21, 0.2), transparent);
270
+ border-bottom: 1px solid rgba(250, 204, 21, 0.32);
271
+ }
272
+
273
+ #adOverlay .modal-title {
274
+ color: #fef3c7;
275
+ font-size: 1.1rem;
276
+ letter-spacing: 0.25px;
277
+ }
278
+
279
+ #adOverlay .modal-body {
280
+ padding: 12px 16px;
281
+ color: #fef9c3;
282
+ }
283
+
284
+ #adOverlay .modal-actions {
285
+ padding: 0 16px 14px;
286
+ }
287
+
288
+ @keyframes spin {
289
+ to {
290
+ transform: rotate(360deg);
291
+ }
292
+ }
293
+
294
+ @keyframes riseIn {
295
+ from {
296
+ opacity: 0;
297
+ transform: translateY(14px) scale(0.98);
298
+ }
299
+
300
+ to {
301
+ opacity: 1;
302
+ transform: translateY(0) scale(1);
303
+ }
304
+ }
305
+
306
+ @keyframes bannerDrop {
307
+ from {
308
+ opacity: 0;
309
+ transform: translateY(-18px);
310
+ }
311
+
312
+ to {
313
+ opacity: 1;
314
+ transform: translateY(0);
315
+ }
316
+ }
317
+ </style>
318
+ </head>
319
+
320
+ <body>
321
+ <div class="app">
322
+ <header class="header">
323
+ <h1 class="title">Campaign Data Loader</h1>
324
+ <p class="subtitle">
325
+ Click the button to load data and validate the loading flow.
326
+ </p>
327
+ </header>
328
+
329
+ <section class="content">
330
+ <div class="toolbar">
331
+ <button id="loadDataBtn" data-cy="load-data-btn" class="cta" type="button">Load Campaign Data Spinner</button>
332
+ <p id="statusText" class="status">Ready to load data.</p>
333
+ </div>
334
+
335
+ <div id="loadingRow" data-cy="loading-row" class="loading-row hidden" aria-live="polite">
336
+ <div class="spinner-image" data-cy="service-spinner"></div>
337
+ <p class="loading-label">Fetching latest campaign analytics...</p>
338
+ </div>
339
+
340
+ <div id="tableWrap" data-cy="table-wrap" class="table-wrap hidden">
341
+ <table>
342
+ <thead>
343
+ <tr>
344
+ <th>Campaign ID</th>
345
+ <th>Channel</th>
346
+ <th>Region</th>
347
+ <th>Daily Budget</th>
348
+ <th>Status</th>
349
+ </tr>
350
+ </thead>
351
+ <tbody id="resultRows" data-cy="result-rows"></tbody>
352
+ </table>
353
+ </div>
354
+ </section>
355
+ </div>
356
+
357
+ <div id="adOverlay" data-cy="ad-overlay" class="overlay hidden" role="dialog" aria-modal="true"
358
+ aria-labelledby="adTitle">
359
+ <div class="modal">
360
+ <div class="modal-head">
361
+ <h2 id="adTitle" class="modal-title">Limited Offer: AI Insights Upgrade</h2>
362
+ </div>
363
+ <div class="modal-body">
364
+ Unlock deeper performance insights with premium analytics features:
365
+ <ul class="modal-list">
366
+ <li>Real-time anomaly detection for active campaigns.</li>
367
+ <li>Predictive recommendations for budget allocation.</li>
368
+ <li>Automated daily summaries delivered to your inbox.</li>
369
+ </ul>
370
+ </div>
371
+ <div class="modal-actions">
372
+ <button id="closeAdBtn" data-cy="close-ad-btn" class="close-modal" type="button">Close and Continue</button>
373
+ </div>
374
+ </div>
375
+ </div>
376
+
377
+ <script>
378
+ (function () {
379
+ var loadDataBtn = document.getElementById('loadDataBtn')
380
+ var statusText = document.getElementById('statusText')
381
+ var loadingRow = document.getElementById('loadingRow')
382
+ var tableWrap = document.getElementById('tableWrap')
383
+ var resultRows = document.getElementById('resultRows')
384
+ var adOverlay = document.getElementById('adOverlay')
385
+ var closeAdBtn = document.getElementById('closeAdBtn')
386
+
387
+ function getMockRows() {
388
+ return [
389
+ ['CMP-1001', 'Search', 'North', '$1,450', 'Active'],
390
+ ['CMP-1002', 'Social', 'West', '$980', 'Active'],
391
+ ['CMP-1003', 'Display', 'South', '$730', 'Paused'],
392
+ ['CMP-1004', 'Email', 'East', '$420', 'Active'],
393
+ ['CMP-1005', 'Search', 'Global', '$1,960', 'Active'],
394
+ ['CMP-1006', 'Social', 'North', '$840', 'Draft'],
395
+ ['CMP-1007', 'Display', 'Central', '$610', 'Active'],
396
+ ['CMP-1008', 'Email', 'West', '$390', 'Paused'],
397
+ ['CMP-1009', 'Search', 'East', '$1,120', 'Active'],
398
+ ['CMP-1010', 'Social', 'South', '$890', 'Active'],
399
+ ]
400
+ }
401
+
402
+ function renderRows(rows) {
403
+ resultRows.innerHTML = ''
404
+ rows.forEach(function (cells) {
405
+ var tr = document.createElement('tr')
406
+ cells.forEach(function (value) {
407
+ var td = document.createElement('td')
408
+ td.textContent = value
409
+ tr.appendChild(td)
410
+ })
411
+ resultRows.appendChild(tr)
412
+ })
413
+ }
414
+
415
+ function startLoad() {
416
+ loadDataBtn.disabled = true
417
+ tableWrap.classList.add('hidden')
418
+ loadingRow.classList.add('hidden')
419
+ statusText.textContent = 'Calling service... preparing request.'
420
+
421
+ var showSpinnerAfterMs = 500 + Math.floor(Math.random() * 1501) // 500-2000
422
+ var spinnerVisibleForMs = 500 + Math.floor(Math.random() * 2501) // 500-3000
423
+
424
+ setTimeout(function () {
425
+ loadingRow.classList.remove('hidden')
426
+ statusText.textContent = 'Fetching latest campaign analytics...'
427
+
428
+ setTimeout(function () {
429
+ renderRows(getMockRows())
430
+ loadingRow.classList.add('hidden')
431
+ tableWrap.classList.remove('hidden')
432
+ statusText.textContent = 'Data loaded successfully.'
433
+ loadDataBtn.disabled = false
434
+ }, spinnerVisibleForMs)
435
+ }, showSpinnerAfterMs)
436
+ }
437
+
438
+ loadDataBtn.addEventListener('click', function () {
439
+ startLoad()
440
+ })
441
+
442
+ closeAdBtn.addEventListener('click', function () {
443
+ adOverlay.classList.add('hidden')
444
+ })
445
+
446
+ // 50% chance to show the advertisement modal at load.
447
+ if (Math.random() < 0.5) {
448
+ adOverlay.classList.remove('hidden')
449
+ }
450
+ })()
451
+ </script>
452
+ </body>
453
+
454
+ </html>
@@ -0,0 +1,25 @@
1
+ // ***********************************************
2
+ // This example commands.js shows you how to
3
+ // create various custom commands and overwrite
4
+ // existing commands.
5
+ //
6
+ // For more comprehensive examples of custom
7
+ // commands please read more here:
8
+ // https://on.cypress.io/custom-commands
9
+ // ***********************************************
10
+ //
11
+ //
12
+ // -- This is a parent command --
13
+ // Cypress.Commands.add('login', (email, password) => { ... })
14
+ //
15
+ //
16
+ // -- This is a child command --
17
+ // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
18
+ //
19
+ //
20
+ // -- This is a dual command --
21
+ // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
22
+ //
23
+ //
24
+ // -- This will overwrite an existing command --
25
+ // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
@@ -0,0 +1,20 @@
1
+ // ***********************************************************
2
+ // This example support/e2e.js is processed and
3
+ // loaded automatically before your test files.
4
+ //
5
+ // This is a great place to put global configuration and
6
+ // behavior that modifies Cypress.
7
+ //
8
+ // You can change the location of this file or turn off
9
+ // automatically serving support files with the
10
+ // 'supportFile' configuration option.
11
+ //
12
+ // You can read more here:
13
+ // https://on.cypress.io/configuration
14
+ // ***********************************************************
15
+
16
+ // Load the wick-dom-observer plugin (use package name when published)
17
+ import '../../src/index.js'
18
+ // Import commands.js using ES2015 syntax:
19
+ import './commands'
20
+
@@ -0,0 +1,13 @@
1
+ const { defineConfig } = require("cypress");
2
+
3
+ module.exports = defineConfig({
4
+ allowCypressEnv: false,
5
+ watchForFileChanges: false,
6
+
7
+ e2e: {
8
+ baseUrl: 'http://localhost:3030',
9
+ setupNodeEvents(on, config) {
10
+ // implement node event listeners here
11
+ },
12
+ },
13
+ });
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "wick-dom-observer",
3
+ "version": "1.0.0",
4
+ "description": "A Cypress plugin that watches DOM changes after clicks to detect fast loading spinners and async UI transitions. It improves test reliability by waiting for real visual completion instead of fixed delays or intercepts.",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "keywords": [
8
+ "cypress",
9
+ "spinner",
10
+ "loading",
11
+ "custom-command",
12
+ "testing"
13
+ ],
14
+ "author": "",
15
+ "license": "MIT",
16
+ "scripts": {
17
+ "demo:serve": "serve cypress/public -p 3030",
18
+ "cy:open": "start-server-and-test serve 3030 cypress:open",
19
+ "cy:run": "start-server-and-test serve 3030 cypress:run",
20
+ "cypress:open": "cypress open",
21
+ "cypress:run": "cypress run",
22
+ "serve": "serve cypress/public -p 3030"
23
+ },
24
+ "devDependencies": {
25
+ "serve": "^14.2.4",
26
+ "start-server-and-test": "^2.0.3"
27
+ },
28
+ "peerDependencies": {
29
+ "cypress": "^15.11.0"
30
+ }
31
+ }
32
+
33
+
package/src/index.d.ts ADDED
@@ -0,0 +1,144 @@
1
+ /// <reference types="cypress" />
2
+
3
+ declare global {
4
+ namespace Cypress {
5
+ type ClickSpinnerAppearMode = 'optional' | 'required'
6
+
7
+ interface ClickSpinnerConfig {
8
+ selector: string
9
+ assert: ($el: JQuery<HTMLElement>) => void
10
+ action?: ($el: JQuery<HTMLElement>) => void
11
+ timeout?: number
12
+ pollingInterval?: number
13
+ appear?: ClickSpinnerAppearMode
14
+ disappear?: boolean
15
+ mustLast?: number
16
+ }
17
+
18
+ interface Chainable {
19
+ /**
20
+ * Cypress custom command that clicks an element and watches for a spinner in the DOM.
21
+ * It is designed for very fast loading indicators that may appear and disappear between
22
+ * normal Cypress assertions.
23
+ *
24
+ * The command supports all common `.click()` signatures after the required `config` object:
25
+ * - `clickAndWatchForElement(config)`
26
+ * - `clickAndWatchForElement(config, options)`
27
+ * - `clickAndWatchForElement(config, position)`
28
+ * - `clickAndWatchForElement(config, position, options)`
29
+ * - `clickAndWatchForElement(config, x, y)`
30
+ * - `clickAndWatchForElement(config, x, y, options)`
31
+ *
32
+ * @param {Cypress.ClickSpinnerConfig} config - Spinner behavior configuration.
33
+ * - {string} selector: CSS selector used to find the spinner.
34
+ * - {Function} assert: Synchronous assertion callback executed when spinner is found.
35
+ * - {Function} [action]: Synchronous callback executed after `assert` passes.
36
+ * - {number} [timeout]: Max wait time in ms for appear/disappear checks.
37
+ * - {number} [pollingInterval]: Polling interval in ms (used for disappear checks).
38
+ * - {'optional'|'required'} [appear='optional']:
39
+ * - `optional`: spinner may not appear.
40
+ * - `required`: command fails if spinner does not appear and satisfy `assert` in time.
41
+ * - {boolean} [disappear=false]: If true, also waits for spinner to be removed from DOM.
42
+ * - {number} [mustLast]: Minimum time in ms spinner must remain in DOM (when `appear: 'required'`).
43
+ * @param {Cypress.ClickOptions} [options] - Optional Cypress click options.
44
+ * @param {Cypress.PositionType} [position] - Optional click position string.
45
+ * @param {number} [x] - Optional x coordinate for click.
46
+ * @param {number} [y] - Optional y coordinate for click.
47
+ * @returns {Cypress.Chainable} Returns the original subject for further chaining.
48
+ *
49
+ * @example
50
+ * // Basic usage
51
+ * cy.get('[data-cy="save-button"]').clickAndWatchForElement({
52
+ * selector: '.loading-spinner',
53
+ * assert: ($el) => {
54
+ * expect($el).to.be.visible()
55
+ * },
56
+ * })
57
+ *
58
+ * @example
59
+ * // Require spinner appearance and disappearance
60
+ * cy.get('[data-cy="save-button"]').clickAndWatchForElement({
61
+ * selector: '.loading-spinner',
62
+ * appear: 'required',
63
+ * disappear: true,
64
+ * timeout: 10000,
65
+ * assert: ($el) => {
66
+ * expect($el).to.have.class('loading')
67
+ * },
68
+ * })
69
+ *
70
+ * @example
71
+ * // Position + click options
72
+ * cy.get('[data-cy="save-button"]').clickAndWatchForElement(
73
+ * {
74
+ * selector: '.loading-spinner',
75
+ * assert: ($el) => {
76
+ * expect($el).to.be.visible()
77
+ * },
78
+ * },
79
+ * 'topRight',
80
+ * { force: true }
81
+ * )
82
+ *
83
+ * @example
84
+ * // Coordinates + mustLast
85
+ * cy.get('canvas').clickAndWatchForElement(
86
+ * {
87
+ * selector: '.spinner',
88
+ * appear: 'required',
89
+ * mustLast: 100,
90
+ * assert: ($el) => {
91
+ * expect($el.text()).to.eq('Loading')
92
+ * },
93
+ * },
94
+ * 20,
95
+ * 40,
96
+ * { force: true }
97
+ * )
98
+ */
99
+ clickAndWatchForElement(
100
+ config: ClickSpinnerConfig
101
+ ): Chainable
102
+ clickAndWatchForElement(
103
+ config: ClickSpinnerConfig,
104
+ options: Partial<ClickOptions>
105
+ ): Chainable
106
+ clickAndWatchForElement(
107
+ config: ClickSpinnerConfig,
108
+ position: PositionType
109
+ ): Chainable
110
+ clickAndWatchForElement(
111
+ config: ClickSpinnerConfig,
112
+ position: PositionType,
113
+ options: Partial<ClickOptions>
114
+ ): Chainable
115
+ clickAndWatchForElement(
116
+ config: ClickSpinnerConfig,
117
+ x: number,
118
+ y: number
119
+ ): Chainable
120
+ clickAndWatchForElement(
121
+ config: ClickSpinnerConfig,
122
+ x: number,
123
+ y: number,
124
+ options: Partial<ClickOptions>
125
+ ): Chainable
126
+
127
+ /**
128
+ * Watches for an element to appear/disappear using the same config behavior as
129
+ * `clickAndWatchForElement`, but without performing any click action.
130
+ *
131
+ * Useful for observing elements that may appear automatically (e.g. on page load).
132
+ *
133
+ * @param {Cypress.ClickSpinnerConfig} config - Element watch configuration.
134
+ * @returns {Cypress.Chainable} Chainable that resolves when watch flow completes.
135
+ */
136
+ watchForElement(
137
+ config: ClickSpinnerConfig
138
+ ): Chainable
139
+ }
140
+ }
141
+ }
142
+
143
+ export {}
144
+
package/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import './watcher'
2
+