codeceptjs 4.0.0-beta.7.esm-aria → 4.0.0-beta.9.esm-aria
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 +46 -3
- package/bin/codecept.js +9 -0
- package/bin/test-server.js +64 -0
- package/docs/webapi/click.mustache +5 -1
- package/lib/ai.js +66 -102
- package/lib/codecept.js +99 -24
- package/lib/command/generate.js +33 -1
- package/lib/command/init.js +7 -3
- package/lib/command/run-workers.js +31 -2
- package/lib/command/run.js +15 -0
- package/lib/command/workers/runTests.js +331 -58
- package/lib/config.js +16 -5
- package/lib/container.js +15 -13
- package/lib/effects.js +1 -1
- package/lib/element/WebElement.js +327 -0
- package/lib/event.js +10 -1
- package/lib/helper/AI.js +11 -11
- package/lib/helper/ApiDataFactory.js +34 -6
- package/lib/helper/Appium.js +156 -42
- package/lib/helper/GraphQL.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +4 -4
- package/lib/helper/JSONResponse.js +48 -40
- package/lib/helper/Mochawesome.js +24 -2
- package/lib/helper/Playwright.js +841 -153
- package/lib/helper/Puppeteer.js +263 -67
- package/lib/helper/REST.js +21 -0
- package/lib/helper/WebDriver.js +105 -16
- package/lib/helper/clientscripts/PollyWebDriverExt.js +1 -1
- package/lib/helper/extras/PlaywrightReactVueLocator.js +52 -0
- package/lib/helper/extras/PlaywrightRestartOpts.js +12 -1
- package/lib/helper/network/actions.js +8 -6
- package/lib/listener/config.js +11 -3
- package/lib/listener/enhancedGlobalRetry.js +110 -0
- package/lib/listener/globalTimeout.js +19 -4
- package/lib/listener/helpers.js +8 -2
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +12 -0
- package/lib/mocha/asyncWrapper.js +13 -3
- package/lib/mocha/cli.js +1 -1
- package/lib/mocha/factory.js +3 -0
- package/lib/mocha/gherkin.js +1 -1
- package/lib/mocha/test.js +6 -0
- package/lib/mocha/ui.js +13 -0
- package/lib/output.js +62 -18
- package/lib/plugin/coverage.js +16 -3
- package/lib/plugin/enhancedRetryFailedStep.js +99 -0
- package/lib/plugin/htmlReporter.js +3648 -0
- package/lib/plugin/retryFailedStep.js +1 -0
- package/lib/plugin/stepByStepReport.js +1 -1
- package/lib/recorder.js +28 -3
- package/lib/result.js +100 -23
- package/lib/retryCoordinator.js +207 -0
- package/lib/step/base.js +1 -1
- package/lib/step/comment.js +2 -2
- package/lib/step/meta.js +1 -1
- package/lib/template/heal.js +1 -1
- package/lib/template/prompts/generatePageObject.js +31 -0
- package/lib/template/prompts/healStep.js +13 -0
- package/lib/template/prompts/writeStep.js +9 -0
- package/lib/test-server.js +334 -0
- package/lib/utils/mask_data.js +47 -0
- package/lib/utils.js +87 -6
- package/lib/workerStorage.js +2 -1
- package/lib/workers.js +179 -23
- package/package.json +59 -47
- package/typings/index.d.ts +19 -7
- package/typings/promiseBasedTypes.d.ts +5534 -3764
- package/typings/types.d.ts +5789 -3775
package/lib/container.js
CHANGED
|
@@ -30,6 +30,7 @@ let container = {
|
|
|
30
30
|
translation: {},
|
|
31
31
|
/** @type {Result | null} */
|
|
32
32
|
result: null,
|
|
33
|
+
sharedKeys: new Set() // Track keys shared via share() function
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
/**
|
|
@@ -220,6 +221,7 @@ class Container {
|
|
|
220
221
|
container.translation = await loadTranslation()
|
|
221
222
|
container.proxySupport = createSupportObjects(newSupport)
|
|
222
223
|
container.plugins = newPlugins
|
|
224
|
+
container.sharedKeys = new Set() // Clear shared keys
|
|
223
225
|
asyncHelperPromise = Promise.resolve()
|
|
224
226
|
store.actor = null
|
|
225
227
|
debug('container cleared')
|
|
@@ -243,7 +245,13 @@ class Container {
|
|
|
243
245
|
* @param {Object} options - set {local: true} to not share among workers
|
|
244
246
|
*/
|
|
245
247
|
static share(data, options = {}) {
|
|
246
|
-
|
|
248
|
+
// Instead of using append which replaces the entire container,
|
|
249
|
+
// directly update the support object to maintain proxy references
|
|
250
|
+
Object.assign(container.support, data)
|
|
251
|
+
|
|
252
|
+
// Track which keys were explicitly shared
|
|
253
|
+
Object.keys(data).forEach(key => container.sharedKeys.add(key))
|
|
254
|
+
|
|
247
255
|
if (!options.local) {
|
|
248
256
|
WorkerStorage.share(data)
|
|
249
257
|
}
|
|
@@ -508,10 +516,11 @@ function createSupportObjects(config) {
|
|
|
508
516
|
{},
|
|
509
517
|
{
|
|
510
518
|
has(target, key) {
|
|
511
|
-
return keys.includes(key)
|
|
519
|
+
return keys.includes(key) || container.sharedKeys.has(key)
|
|
512
520
|
},
|
|
513
521
|
ownKeys() {
|
|
514
|
-
|
|
522
|
+
// Return both original config keys and explicitly shared keys
|
|
523
|
+
return [...new Set([...keys, ...container.sharedKeys])]
|
|
515
524
|
},
|
|
516
525
|
getOwnPropertyDescriptor(target, prop) {
|
|
517
526
|
return {
|
|
@@ -521,16 +530,9 @@ function createSupportObjects(config) {
|
|
|
521
530
|
}
|
|
522
531
|
},
|
|
523
532
|
get(target, key) {
|
|
524
|
-
if
|
|
525
|
-
|
|
526
|
-
return
|
|
527
|
-
}
|
|
528
|
-
// Allow special I even if not declared in includes
|
|
529
|
-
if (key === 'I') {
|
|
530
|
-
return lazyLoad('I')
|
|
531
|
-
}
|
|
532
|
-
if (!keys.includes(key)) {
|
|
533
|
-
throw new Error(`Support object "${String(key)}" is not defined`)
|
|
533
|
+
// First check if this is an explicitly shared property
|
|
534
|
+
if (container.sharedKeys.has(key) && key in container.support) {
|
|
535
|
+
return container.support[key]
|
|
534
536
|
}
|
|
535
537
|
return lazyLoad(key)
|
|
536
538
|
},
|
package/lib/effects.js
CHANGED
|
@@ -171,7 +171,7 @@ async function hopeThat(callback) {
|
|
|
171
171
|
* - Restores the session state after each attempt, whether successful or not.
|
|
172
172
|
*
|
|
173
173
|
* @example
|
|
174
|
-
* const {
|
|
174
|
+
* const { retryTo } = require('codeceptjs/effects')
|
|
175
175
|
* await retryTo((tries) => {
|
|
176
176
|
* if (tries < 3) {
|
|
177
177
|
* I.see('Non-existent element'); // Simulates a failure
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Unified WebElement class that wraps native element instances from different helpers
|
|
5
|
+
* and provides a consistent API across all supported helpers (Playwright, WebDriver, Puppeteer).
|
|
6
|
+
*/
|
|
7
|
+
class WebElement {
|
|
8
|
+
constructor(element, helper) {
|
|
9
|
+
this.element = element
|
|
10
|
+
this.helper = helper
|
|
11
|
+
this.helperType = this._detectHelperType(helper)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_detectHelperType(helper) {
|
|
15
|
+
if (!helper) return 'unknown'
|
|
16
|
+
|
|
17
|
+
const className = helper.constructor.name
|
|
18
|
+
if (className === 'Playwright') return 'playwright'
|
|
19
|
+
if (className === 'WebDriver') return 'webdriver'
|
|
20
|
+
if (className === 'Puppeteer') return 'puppeteer'
|
|
21
|
+
|
|
22
|
+
return 'unknown'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the native element instance
|
|
27
|
+
* @returns {ElementHandle|WebElement|ElementHandle} Native element
|
|
28
|
+
*/
|
|
29
|
+
getNativeElement() {
|
|
30
|
+
return this.element
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get the helper instance
|
|
35
|
+
* @returns {Helper} Helper instance
|
|
36
|
+
*/
|
|
37
|
+
getHelper() {
|
|
38
|
+
return this.helper
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get text content of the element
|
|
43
|
+
* @returns {Promise<string>} Element text content
|
|
44
|
+
*/
|
|
45
|
+
async getText() {
|
|
46
|
+
switch (this.helperType) {
|
|
47
|
+
case 'playwright':
|
|
48
|
+
return this.element.textContent()
|
|
49
|
+
case 'webdriver':
|
|
50
|
+
return this.element.getText()
|
|
51
|
+
case 'puppeteer':
|
|
52
|
+
return this.element.evaluate(el => el.textContent)
|
|
53
|
+
default:
|
|
54
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get attribute value of the element
|
|
60
|
+
* @param {string} name Attribute name
|
|
61
|
+
* @returns {Promise<string|null>} Attribute value
|
|
62
|
+
*/
|
|
63
|
+
async getAttribute(name) {
|
|
64
|
+
switch (this.helperType) {
|
|
65
|
+
case 'playwright':
|
|
66
|
+
return this.element.getAttribute(name)
|
|
67
|
+
case 'webdriver':
|
|
68
|
+
return this.element.getAttribute(name)
|
|
69
|
+
case 'puppeteer':
|
|
70
|
+
return this.element.evaluate((el, attrName) => el.getAttribute(attrName), name)
|
|
71
|
+
default:
|
|
72
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get property value of the element
|
|
78
|
+
* @param {string} name Property name
|
|
79
|
+
* @returns {Promise<any>} Property value
|
|
80
|
+
*/
|
|
81
|
+
async getProperty(name) {
|
|
82
|
+
switch (this.helperType) {
|
|
83
|
+
case 'playwright':
|
|
84
|
+
return this.element.evaluate((el, propName) => el[propName], name)
|
|
85
|
+
case 'webdriver':
|
|
86
|
+
return this.element.getProperty(name)
|
|
87
|
+
case 'puppeteer':
|
|
88
|
+
return this.element.evaluate((el, propName) => el[propName], name)
|
|
89
|
+
default:
|
|
90
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get innerHTML of the element
|
|
96
|
+
* @returns {Promise<string>} Element innerHTML
|
|
97
|
+
*/
|
|
98
|
+
async getInnerHTML() {
|
|
99
|
+
switch (this.helperType) {
|
|
100
|
+
case 'playwright':
|
|
101
|
+
return this.element.innerHTML()
|
|
102
|
+
case 'webdriver':
|
|
103
|
+
return this.element.getProperty('innerHTML')
|
|
104
|
+
case 'puppeteer':
|
|
105
|
+
return this.element.evaluate(el => el.innerHTML)
|
|
106
|
+
default:
|
|
107
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get value of the element (for input elements)
|
|
113
|
+
* @returns {Promise<string>} Element value
|
|
114
|
+
*/
|
|
115
|
+
async getValue() {
|
|
116
|
+
switch (this.helperType) {
|
|
117
|
+
case 'playwright':
|
|
118
|
+
return this.element.inputValue()
|
|
119
|
+
case 'webdriver':
|
|
120
|
+
return this.element.getValue()
|
|
121
|
+
case 'puppeteer':
|
|
122
|
+
return this.element.evaluate(el => el.value)
|
|
123
|
+
default:
|
|
124
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if element is visible
|
|
130
|
+
* @returns {Promise<boolean>} True if element is visible
|
|
131
|
+
*/
|
|
132
|
+
async isVisible() {
|
|
133
|
+
switch (this.helperType) {
|
|
134
|
+
case 'playwright':
|
|
135
|
+
return this.element.isVisible()
|
|
136
|
+
case 'webdriver':
|
|
137
|
+
return this.element.isDisplayed()
|
|
138
|
+
case 'puppeteer':
|
|
139
|
+
return this.element.evaluate(el => {
|
|
140
|
+
const style = window.getComputedStyle(el)
|
|
141
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'
|
|
142
|
+
})
|
|
143
|
+
default:
|
|
144
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if element is enabled
|
|
150
|
+
* @returns {Promise<boolean>} True if element is enabled
|
|
151
|
+
*/
|
|
152
|
+
async isEnabled() {
|
|
153
|
+
switch (this.helperType) {
|
|
154
|
+
case 'playwright':
|
|
155
|
+
return this.element.isEnabled()
|
|
156
|
+
case 'webdriver':
|
|
157
|
+
return this.element.isEnabled()
|
|
158
|
+
case 'puppeteer':
|
|
159
|
+
return this.element.evaluate(el => !el.disabled)
|
|
160
|
+
default:
|
|
161
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if element exists in DOM
|
|
167
|
+
* @returns {Promise<boolean>} True if element exists
|
|
168
|
+
*/
|
|
169
|
+
async exists() {
|
|
170
|
+
try {
|
|
171
|
+
switch (this.helperType) {
|
|
172
|
+
case 'playwright':
|
|
173
|
+
// For Playwright, if we have the element, it exists
|
|
174
|
+
return await this.element.evaluate(el => !!el)
|
|
175
|
+
case 'webdriver':
|
|
176
|
+
// For WebDriver, if we have the element, it exists
|
|
177
|
+
return true
|
|
178
|
+
case 'puppeteer':
|
|
179
|
+
// For Puppeteer, if we have the element, it exists
|
|
180
|
+
return await this.element.evaluate(el => !!el)
|
|
181
|
+
default:
|
|
182
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
183
|
+
}
|
|
184
|
+
} catch (e) {
|
|
185
|
+
return false
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get bounding box of the element
|
|
191
|
+
* @returns {Promise<Object>} Bounding box with x, y, width, height properties
|
|
192
|
+
*/
|
|
193
|
+
async getBoundingBox() {
|
|
194
|
+
switch (this.helperType) {
|
|
195
|
+
case 'playwright':
|
|
196
|
+
return this.element.boundingBox()
|
|
197
|
+
case 'webdriver':
|
|
198
|
+
const rect = await this.element.getRect()
|
|
199
|
+
return {
|
|
200
|
+
x: rect.x,
|
|
201
|
+
y: rect.y,
|
|
202
|
+
width: rect.width,
|
|
203
|
+
height: rect.height,
|
|
204
|
+
}
|
|
205
|
+
case 'puppeteer':
|
|
206
|
+
return this.element.boundingBox()
|
|
207
|
+
default:
|
|
208
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Click the element
|
|
214
|
+
* @param {Object} options Click options
|
|
215
|
+
* @returns {Promise<void>}
|
|
216
|
+
*/
|
|
217
|
+
async click(options = {}) {
|
|
218
|
+
switch (this.helperType) {
|
|
219
|
+
case 'playwright':
|
|
220
|
+
return this.element.click(options)
|
|
221
|
+
case 'webdriver':
|
|
222
|
+
return this.element.click()
|
|
223
|
+
case 'puppeteer':
|
|
224
|
+
return this.element.click(options)
|
|
225
|
+
default:
|
|
226
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Type text into the element
|
|
232
|
+
* @param {string} text Text to type
|
|
233
|
+
* @param {Object} options Type options
|
|
234
|
+
* @returns {Promise<void>}
|
|
235
|
+
*/
|
|
236
|
+
async type(text, options = {}) {
|
|
237
|
+
switch (this.helperType) {
|
|
238
|
+
case 'playwright':
|
|
239
|
+
return this.element.type(text, options)
|
|
240
|
+
case 'webdriver':
|
|
241
|
+
return this.element.setValue(text)
|
|
242
|
+
case 'puppeteer':
|
|
243
|
+
return this.element.type(text, options)
|
|
244
|
+
default:
|
|
245
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Find first child element matching the locator
|
|
251
|
+
* @param {string|Object} locator Element locator
|
|
252
|
+
* @returns {Promise<WebElement|null>} WebElement instance or null if not found
|
|
253
|
+
*/
|
|
254
|
+
async $(locator) {
|
|
255
|
+
let childElement
|
|
256
|
+
|
|
257
|
+
switch (this.helperType) {
|
|
258
|
+
case 'playwright':
|
|
259
|
+
childElement = await this.element.$(this._normalizeLocator(locator))
|
|
260
|
+
break
|
|
261
|
+
case 'webdriver':
|
|
262
|
+
try {
|
|
263
|
+
childElement = await this.element.$(this._normalizeLocator(locator))
|
|
264
|
+
} catch (e) {
|
|
265
|
+
return null
|
|
266
|
+
}
|
|
267
|
+
break
|
|
268
|
+
case 'puppeteer':
|
|
269
|
+
childElement = await this.element.$(this._normalizeLocator(locator))
|
|
270
|
+
break
|
|
271
|
+
default:
|
|
272
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return childElement ? new WebElement(childElement, this.helper) : null
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Find all child elements matching the locator
|
|
280
|
+
* @param {string|Object} locator Element locator
|
|
281
|
+
* @returns {Promise<WebElement[]>} Array of WebElement instances
|
|
282
|
+
*/
|
|
283
|
+
async $$(locator) {
|
|
284
|
+
let childElements
|
|
285
|
+
|
|
286
|
+
switch (this.helperType) {
|
|
287
|
+
case 'playwright':
|
|
288
|
+
childElements = await this.element.$$(this._normalizeLocator(locator))
|
|
289
|
+
break
|
|
290
|
+
case 'webdriver':
|
|
291
|
+
childElements = await this.element.$$(this._normalizeLocator(locator))
|
|
292
|
+
break
|
|
293
|
+
case 'puppeteer':
|
|
294
|
+
childElements = await this.element.$$(this._normalizeLocator(locator))
|
|
295
|
+
break
|
|
296
|
+
default:
|
|
297
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return childElements.map(el => new WebElement(el, this.helper))
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Normalize locator for element search
|
|
305
|
+
* @param {string|Object} locator Locator to normalize
|
|
306
|
+
* @returns {string} Normalized CSS selector
|
|
307
|
+
* @private
|
|
308
|
+
*/
|
|
309
|
+
_normalizeLocator(locator) {
|
|
310
|
+
if (typeof locator === 'string') {
|
|
311
|
+
return locator
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (typeof locator === 'object') {
|
|
315
|
+
// Handle CodeceptJS locator objects
|
|
316
|
+
if (locator.css) return locator.css
|
|
317
|
+
if (locator.xpath) return locator.xpath
|
|
318
|
+
if (locator.id) return `#${locator.id}`
|
|
319
|
+
if (locator.name) return `[name="${locator.name}"]`
|
|
320
|
+
if (locator.className) return `.${locator.className}`
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return locator.toString()
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export default WebElement
|
package/lib/event.js
CHANGED
|
@@ -3,13 +3,22 @@ const debug = debugModule('codeceptjs:event')
|
|
|
3
3
|
import events from 'events'
|
|
4
4
|
import output from './output.js'
|
|
5
5
|
|
|
6
|
+
const MAX_LISTENERS = 200
|
|
7
|
+
|
|
6
8
|
const dispatcher = new events.EventEmitter()
|
|
7
9
|
|
|
8
|
-
dispatcher.setMaxListeners(
|
|
10
|
+
dispatcher.setMaxListeners(MAX_LISTENERS)
|
|
11
|
+
|
|
12
|
+
// Increase process max listeners to prevent warnings for beforeExit and other events
|
|
13
|
+
if (typeof process.setMaxListeners === 'function') {
|
|
14
|
+
process.setMaxListeners(MAX_LISTENERS)
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
/**
|
|
10
18
|
* @namespace
|
|
11
19
|
* @alias event
|
|
12
20
|
*/
|
|
21
|
+
|
|
13
22
|
export default {
|
|
14
23
|
/**
|
|
15
24
|
* @type {NodeJS.EventEmitter}
|
package/lib/helper/AI.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import HelperModule from '@codeceptjs/helper'
|
|
2
|
+
import ora from 'ora-classic'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import ai from '../ai.js'
|
|
6
|
+
import Container from '../container.js'
|
|
7
|
+
import { splitByChunks, minifyHtml } from '../html.js'
|
|
8
|
+
import { beautify } from '../utils.js'
|
|
9
|
+
import output from '../output.js'
|
|
10
|
+
import { registerVariable } from '../pause.js'
|
|
11
11
|
|
|
12
12
|
const standardActingHelpers = Container.STANDARD_ACTING_HELPERS
|
|
13
13
|
|
|
@@ -211,4 +211,4 @@ class AI extends Helper {
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
|
|
214
|
+
export default AI
|
|
@@ -241,10 +241,25 @@ class ApiDataFactory extends Helper {
|
|
|
241
241
|
if (!createdItems.length) continue
|
|
242
242
|
this.debug(`Deleting ${createdItems.length} ${factoryName}(s)`)
|
|
243
243
|
for (const id in createdItems) {
|
|
244
|
-
|
|
244
|
+
const deletePromise = this._requestDelete(factoryName, createdItems[id])
|
|
245
|
+
if (deletePromise) {
|
|
246
|
+
promises.push(deletePromise.catch(err => {
|
|
247
|
+
this.debugSection('Delete Error', `Failed to delete ${factoryName} with id ${createdItems[id]}: ${err.message}`)
|
|
248
|
+
// Don't reject Promise.all, just log the error
|
|
249
|
+
return Promise.resolve()
|
|
250
|
+
}))
|
|
251
|
+
}
|
|
245
252
|
}
|
|
246
253
|
}
|
|
247
|
-
return Promise.all(promises)
|
|
254
|
+
return Promise.all(promises).then(() => {
|
|
255
|
+
// Clear the created items after successful cleanup
|
|
256
|
+
for (const factoryName in this.created) {
|
|
257
|
+
this.created[factoryName] = []
|
|
258
|
+
}
|
|
259
|
+
// Add a small delay to ensure file system changes propagate in Docker environments
|
|
260
|
+
// This helps avoid race conditions where GET requests after DELETE don't see the changes yet
|
|
261
|
+
return new Promise(resolve => setTimeout(resolve, 100))
|
|
262
|
+
})
|
|
248
263
|
}
|
|
249
264
|
|
|
250
265
|
/**
|
|
@@ -386,21 +401,34 @@ Current file error: ${err.message}`)
|
|
|
386
401
|
const url = this.factories[factory].delete[method].replace('{id}', id)
|
|
387
402
|
|
|
388
403
|
request = {
|
|
389
|
-
method,
|
|
404
|
+
method: method.toUpperCase(), // Ensure HTTP method is uppercase
|
|
390
405
|
url,
|
|
391
406
|
}
|
|
392
407
|
}
|
|
393
408
|
|
|
409
|
+
// Ensure method is uppercase (some servers require uppercase HTTP methods)
|
|
410
|
+
if (request.method) {
|
|
411
|
+
request.method = request.method.toUpperCase()
|
|
412
|
+
}
|
|
413
|
+
|
|
394
414
|
request.baseURL = this.config.endpoint
|
|
395
415
|
|
|
396
416
|
if (request.url.match(/^undefined/)) {
|
|
397
417
|
return this.debugSection('Please configure the delete request in your ApiDataFactory helper', "delete: () => ({ method: 'DELETE', url: '/api/users' })")
|
|
398
418
|
}
|
|
399
419
|
|
|
400
|
-
|
|
420
|
+
this.debugSection('Deleting', `${request.method} ${request.baseURL}${request.url} (ID: ${id})`)
|
|
421
|
+
|
|
422
|
+
return this.restHelper._executeRequest(request).then((resp) => {
|
|
401
423
|
const idx = this.created[factory].indexOf(id)
|
|
402
|
-
this.debugSection('Deleted
|
|
403
|
-
|
|
424
|
+
this.debugSection('Deleted Successfully', `${factory} ID: ${id}, Status: ${resp.status || 'OK'}`)
|
|
425
|
+
if (idx !== -1) {
|
|
426
|
+
this.created[factory].splice(idx, 1)
|
|
427
|
+
}
|
|
428
|
+
return resp
|
|
429
|
+
}).catch(err => {
|
|
430
|
+
this.debugSection('Delete Failed', `${factory} ID: ${id}, Error: ${err.message}`)
|
|
431
|
+
throw err
|
|
404
432
|
})
|
|
405
433
|
}
|
|
406
434
|
}
|