codeceptjs 4.0.0-beta.1 → 4.0.0-beta.10.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 +133 -120
- package/bin/codecept.js +107 -96
- package/bin/test-server.js +64 -0
- package/docs/webapi/clearCookie.mustache +1 -1
- package/docs/webapi/click.mustache +5 -1
- package/lib/actor.js +71 -103
- package/lib/ai.js +159 -188
- package/lib/assert/empty.js +22 -24
- package/lib/assert/equal.js +30 -37
- package/lib/assert/error.js +14 -14
- package/lib/assert/include.js +43 -48
- package/lib/assert/throws.js +11 -11
- package/lib/assert/truth.js +22 -22
- package/lib/assert.js +20 -18
- package/lib/codecept.js +238 -162
- package/lib/colorUtils.js +50 -52
- package/lib/command/check.js +206 -0
- package/lib/command/configMigrate.js +56 -51
- package/lib/command/definitions.js +96 -109
- package/lib/command/dryRun.js +77 -79
- package/lib/command/generate.js +234 -194
- package/lib/command/gherkin/init.js +42 -33
- package/lib/command/gherkin/snippets.js +76 -74
- package/lib/command/gherkin/steps.js +20 -17
- package/lib/command/info.js +74 -38
- package/lib/command/init.js +300 -290
- package/lib/command/interactive.js +41 -32
- package/lib/command/list.js +28 -27
- package/lib/command/run-multiple/chunk.js +51 -48
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +5 -1
- package/lib/command/run-multiple.js +97 -97
- package/lib/command/run-rerun.js +19 -25
- package/lib/command/run-workers.js +68 -92
- package/lib/command/run.js +39 -27
- package/lib/command/utils.js +80 -64
- package/lib/command/workers/runTests.js +388 -226
- package/lib/config.js +124 -50
- package/lib/container.js +751 -260
- package/lib/data/context.js +60 -61
- package/lib/data/dataScenarioConfig.js +47 -47
- package/lib/data/dataTableArgument.js +32 -32
- package/lib/data/table.js +22 -22
- package/lib/effects.js +307 -0
- package/lib/element/WebElement.js +327 -0
- package/lib/els.js +160 -0
- package/lib/event.js +173 -163
- package/lib/globals.js +141 -0
- package/lib/heal.js +89 -85
- package/lib/helper/AI.js +131 -41
- package/lib/helper/ApiDataFactory.js +107 -75
- package/lib/helper/Appium.js +542 -404
- package/lib/helper/FileSystem.js +100 -79
- package/lib/helper/GraphQL.js +44 -43
- package/lib/helper/GraphQLDataFactory.js +52 -52
- package/lib/helper/JSONResponse.js +126 -88
- package/lib/helper/Mochawesome.js +54 -29
- package/lib/helper/Playwright.js +2547 -1316
- package/lib/helper/Puppeteer.js +1578 -1181
- package/lib/helper/REST.js +209 -68
- package/lib/helper/WebDriver.js +1482 -1342
- package/lib/helper/errors/ConnectionRefused.js +6 -6
- package/lib/helper/errors/ElementAssertion.js +11 -16
- package/lib/helper/errors/ElementNotFound.js +5 -9
- package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
- package/lib/helper/extras/Console.js +11 -11
- package/lib/helper/extras/PlaywrightLocator.js +110 -0
- package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
- package/lib/helper/extras/PlaywrightReactVueLocator.js +17 -8
- package/lib/helper/extras/PlaywrightRestartOpts.js +25 -11
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/extras/React.js +27 -28
- package/lib/helper/network/actions.js +36 -42
- package/lib/helper/network/utils.js +78 -84
- package/lib/helper/scripts/blurElement.js +5 -5
- package/lib/helper/scripts/focusElement.js +5 -5
- package/lib/helper/scripts/highlightElement.js +8 -8
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -3
- package/lib/history.js +23 -19
- package/lib/hooks.js +8 -8
- package/lib/html.js +94 -104
- package/lib/index.js +38 -27
- package/lib/listener/config.js +30 -23
- package/lib/listener/emptyRun.js +54 -0
- package/lib/listener/enhancedGlobalRetry.js +110 -0
- package/lib/listener/exit.js +16 -18
- package/lib/listener/globalRetry.js +70 -0
- package/lib/listener/globalTimeout.js +181 -0
- package/lib/listener/helpers.js +76 -51
- package/lib/listener/mocha.js +10 -11
- package/lib/listener/result.js +11 -0
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +71 -59
- package/lib/listener/store.js +20 -0
- package/lib/locator.js +214 -197
- package/lib/mocha/asyncWrapper.js +274 -0
- package/lib/mocha/bdd.js +167 -0
- package/lib/mocha/cli.js +341 -0
- package/lib/mocha/factory.js +163 -0
- package/lib/mocha/featureConfig.js +89 -0
- package/lib/mocha/gherkin.js +231 -0
- package/lib/mocha/hooks.js +121 -0
- package/lib/mocha/index.js +21 -0
- package/lib/mocha/inject.js +46 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +58 -34
- package/lib/mocha/suite.js +89 -0
- package/lib/mocha/test.js +184 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +242 -0
- package/lib/output.js +141 -71
- package/lib/parser.js +47 -44
- package/lib/pause.js +173 -145
- package/lib/plugin/analyze.js +403 -0
- package/lib/plugin/{autoLogin.js → auth.js} +178 -79
- package/lib/plugin/autoDelay.js +36 -40
- package/lib/plugin/coverage.js +131 -78
- package/lib/plugin/customLocator.js +22 -21
- package/lib/plugin/customReporter.js +53 -0
- package/lib/plugin/enhancedRetryFailedStep.js +99 -0
- package/lib/plugin/heal.js +101 -110
- package/lib/plugin/htmlReporter.js +3648 -0
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/pauseOnFail.js +12 -11
- package/lib/plugin/retryFailedStep.js +82 -47
- package/lib/plugin/screenshotOnFail.js +111 -92
- package/lib/plugin/stepByStepReport.js +159 -101
- package/lib/plugin/stepTimeout.js +20 -25
- package/lib/plugin/subtitles.js +38 -38
- package/lib/recorder.js +193 -130
- package/lib/rerun.js +94 -49
- package/lib/result.js +238 -0
- package/lib/retryCoordinator.js +207 -0
- package/lib/secret.js +20 -18
- package/lib/session.js +95 -89
- package/lib/step/base.js +239 -0
- package/lib/step/comment.js +10 -0
- package/lib/step/config.js +50 -0
- package/lib/step/func.js +46 -0
- package/lib/step/helper.js +50 -0
- package/lib/step/meta.js +99 -0
- package/lib/step/record.js +74 -0
- package/lib/step/retry.js +11 -0
- package/lib/step/section.js +55 -0
- package/lib/step.js +18 -329
- package/lib/steps.js +54 -0
- package/lib/store.js +38 -7
- package/lib/template/heal.js +3 -12
- 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/timeout.js +60 -0
- package/lib/transform.js +8 -8
- package/lib/translation.js +34 -21
- package/lib/utils/mask_data.js +47 -0
- package/lib/utils.js +411 -228
- package/lib/workerStorage.js +37 -34
- package/lib/workers.js +532 -296
- package/package.json +115 -95
- package/translations/de-DE.js +5 -3
- package/translations/fr-FR.js +5 -4
- package/translations/index.js +22 -12
- package/translations/it-IT.js +4 -3
- package/translations/ja-JP.js +4 -3
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +4 -3
- package/translations/pt-BR.js +4 -3
- package/translations/ru-RU.js +4 -3
- package/translations/utils.js +10 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/index.d.ts +546 -185
- package/typings/promiseBasedTypes.d.ts +150 -879
- package/typings/types.d.ts +547 -996
- package/lib/cli.js +0 -249
- package/lib/dirname.js +0 -5
- package/lib/helper/Expect.js +0 -425
- package/lib/helper/ExpectHelper.js +0 -399
- package/lib/helper/MockServer.js +0 -223
- package/lib/helper/Nightmare.js +0 -1411
- package/lib/helper/Protractor.js +0 -1835
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/helper/TestCafe.js +0 -1410
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -63
- package/lib/interfaces/bdd.js +0 -98
- package/lib/interfaces/featureConfig.js +0 -69
- package/lib/interfaces/gherkin.js +0 -195
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/retry.js +0 -68
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -110
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/commentStep.js +0 -136
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/retryTo.js +0 -121
- package/lib/plugin/selenoid.js +0 -371
- package/lib/plugin/standardActingHelpers.js +0 -9
- package/lib/plugin/tryTo.js +0 -105
- package/lib/plugin/wdio.js +0 -246
- package/lib/scenario.js +0 -222
- package/lib/ui.js +0 -238
- package/lib/within.js +0 -70
package/lib/utils.js
CHANGED
|
@@ -1,142 +1,134 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import os from 'os'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import os from 'os'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { createRequire } from 'module'
|
|
5
|
+
import chalk from 'chalk'
|
|
6
|
+
import getFunctionArguments from 'fn-args'
|
|
7
|
+
import deepClone from 'lodash.clonedeep'
|
|
8
|
+
import merge from 'lodash.merge'
|
|
9
|
+
import { convertColorToRGBA, isColorProperty } from './colorUtils.js'
|
|
10
|
+
import Fuse from 'fuse.js'
|
|
11
|
+
import crypto from 'crypto'
|
|
12
|
+
import jsBeautify from 'js-beautify'
|
|
13
|
+
import { spawnSync } from 'child_process'
|
|
14
|
+
|
|
15
|
+
function deepMerge(target, source) {
|
|
16
|
+
return merge(target, source)
|
|
17
|
+
}
|
|
13
18
|
|
|
14
|
-
export
|
|
15
|
-
return
|
|
19
|
+
export const genTestId = test => {
|
|
20
|
+
return clearString(crypto.createHash('sha256').update(test.fullTitle()).digest('base64').slice(0, -2))
|
|
16
21
|
}
|
|
17
22
|
|
|
18
|
-
export
|
|
19
|
-
return createHash('sha256').update(test.fullTitle()).digest('base64')
|
|
20
|
-
.slice(0, -2);
|
|
21
|
-
};
|
|
23
|
+
export { deepMerge }
|
|
22
24
|
|
|
23
|
-
export { deepClone }
|
|
25
|
+
export { deepClone }
|
|
24
26
|
|
|
25
27
|
export const isGenerator = function (fn) {
|
|
26
|
-
return fn.constructor.name === 'GeneratorFunction'
|
|
27
|
-
}
|
|
28
|
+
return fn.constructor.name === 'GeneratorFunction'
|
|
29
|
+
}
|
|
28
30
|
|
|
29
31
|
export const isFunction = function (fn) {
|
|
30
|
-
return typeof fn === 'function'
|
|
31
|
-
}
|
|
32
|
+
return typeof fn === 'function'
|
|
33
|
+
}
|
|
32
34
|
|
|
33
|
-
export function
|
|
34
|
-
if (!fn) return false
|
|
35
|
-
return fn[Symbol.toStringTag] === 'AsyncFunction'
|
|
35
|
+
export const isAsyncFunction = function (fn) {
|
|
36
|
+
if (!fn) return false
|
|
37
|
+
return fn[Symbol.toStringTag] === 'AsyncFunction'
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export const fileExists = function (filePath) {
|
|
39
|
-
return fs.existsSync(filePath)
|
|
40
|
-
}
|
|
41
|
+
return fs.existsSync(filePath)
|
|
42
|
+
}
|
|
41
43
|
|
|
42
44
|
export const isFile = function (filePath) {
|
|
43
|
-
let filestat
|
|
45
|
+
let filestat
|
|
44
46
|
try {
|
|
45
|
-
filestat = fs.statSync(filePath)
|
|
47
|
+
filestat = fs.statSync(filePath)
|
|
46
48
|
} catch (err) {
|
|
47
|
-
if (err.code === 'ENOENT') return false
|
|
49
|
+
if (err.code === 'ENOENT') return false
|
|
48
50
|
}
|
|
49
|
-
if (!filestat) return false
|
|
50
|
-
return filestat.isFile()
|
|
51
|
-
}
|
|
51
|
+
if (!filestat) return false
|
|
52
|
+
return filestat.isFile()
|
|
53
|
+
}
|
|
52
54
|
|
|
53
55
|
export const getParamNames = function (fn) {
|
|
54
|
-
if (fn.isSinonProxy) return []
|
|
55
|
-
return getFunctionArguments(fn)
|
|
56
|
-
}
|
|
56
|
+
if (fn.isSinonProxy) return []
|
|
57
|
+
return getFunctionArguments(fn)
|
|
58
|
+
}
|
|
57
59
|
|
|
58
60
|
export const installedLocally = function () {
|
|
59
|
-
return path.resolve(`${
|
|
60
|
-
}
|
|
61
|
+
return path.resolve(`${new URL(import.meta.url).pathname}/../../`).indexOf(process.cwd()) === 0
|
|
62
|
+
}
|
|
61
63
|
|
|
62
64
|
export const methodsOfObject = function (obj, className) {
|
|
63
|
-
const methods = []
|
|
64
|
-
|
|
65
|
-
const standard = [
|
|
66
|
-
'constructor',
|
|
67
|
-
'toString',
|
|
68
|
-
'toLocaleString',
|
|
69
|
-
'valueOf',
|
|
70
|
-
'hasOwnProperty',
|
|
71
|
-
'bind',
|
|
72
|
-
'apply',
|
|
73
|
-
'call',
|
|
74
|
-
'isPrototypeOf',
|
|
75
|
-
'propertyIsEnumerable',
|
|
76
|
-
];
|
|
65
|
+
const methods = []
|
|
66
|
+
|
|
67
|
+
const standard = ['constructor', 'toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'bind', 'apply', 'call', 'isPrototypeOf', 'propertyIsEnumerable']
|
|
77
68
|
|
|
78
69
|
function pushToMethods(prop) {
|
|
79
70
|
try {
|
|
80
|
-
if (!isFunction(obj[prop]) && !isAsyncFunction(obj[prop])) return
|
|
81
|
-
} catch (err) {
|
|
82
|
-
|
|
71
|
+
if (!isFunction(obj[prop]) && !isAsyncFunction(obj[prop])) return
|
|
72
|
+
} catch (err) {
|
|
73
|
+
// can't access property
|
|
74
|
+
return
|
|
83
75
|
}
|
|
84
|
-
if (standard.indexOf(prop) >= 0) return
|
|
85
|
-
if (prop.indexOf('_') === 0) return
|
|
86
|
-
methods.push(prop)
|
|
76
|
+
if (standard.indexOf(prop) >= 0) return
|
|
77
|
+
if (prop.indexOf('_') === 0) return
|
|
78
|
+
methods.push(prop)
|
|
87
79
|
}
|
|
88
80
|
|
|
89
81
|
while (obj.constructor.name !== className) {
|
|
90
|
-
Object.getOwnPropertyNames(obj).forEach(pushToMethods)
|
|
91
|
-
obj = Object.getPrototypeOf(obj)
|
|
82
|
+
Object.getOwnPropertyNames(obj).forEach(pushToMethods)
|
|
83
|
+
obj = Object.getPrototypeOf(obj)
|
|
92
84
|
|
|
93
|
-
if (!obj || !obj.constructor) break
|
|
85
|
+
if (!obj || !obj.constructor) break
|
|
94
86
|
}
|
|
95
|
-
return methods
|
|
96
|
-
}
|
|
87
|
+
return methods
|
|
88
|
+
}
|
|
97
89
|
|
|
98
90
|
export const template = function (template, data) {
|
|
99
91
|
return template.replace(/{{([^{}]*)}}/g, (a, b) => {
|
|
100
|
-
const r = data[b]
|
|
101
|
-
if (r === undefined) return ''
|
|
102
|
-
return r.toString()
|
|
103
|
-
})
|
|
104
|
-
}
|
|
92
|
+
const r = data[b]
|
|
93
|
+
if (r === undefined) return ''
|
|
94
|
+
return r.toString()
|
|
95
|
+
})
|
|
96
|
+
}
|
|
105
97
|
|
|
106
98
|
/**
|
|
107
99
|
* Make first char uppercase.
|
|
108
100
|
* @param {string} str
|
|
109
|
-
* @returns {string}
|
|
101
|
+
* @returns {string | undefined}
|
|
110
102
|
*/
|
|
111
103
|
export const ucfirst = function (str) {
|
|
112
|
-
return str.charAt(0).toUpperCase() + str.substr(1)
|
|
113
|
-
}
|
|
104
|
+
if (str) return str.charAt(0).toUpperCase() + str.substr(1)
|
|
105
|
+
}
|
|
114
106
|
|
|
115
107
|
/**
|
|
116
108
|
* Make first char lowercase.
|
|
117
109
|
* @param {string} str
|
|
118
|
-
* @returns {string}
|
|
110
|
+
* @returns {string | undefined}
|
|
119
111
|
*/
|
|
120
112
|
export const lcfirst = function (str) {
|
|
121
|
-
return str.charAt(0).toLowerCase() + str.substr(1)
|
|
122
|
-
}
|
|
113
|
+
if (str) return str.charAt(0).toLowerCase() + str.substr(1)
|
|
114
|
+
}
|
|
123
115
|
|
|
124
116
|
export const chunkArray = function (arr, chunk) {
|
|
125
|
-
let i
|
|
126
|
-
let j
|
|
127
|
-
const tmp = []
|
|
117
|
+
let i
|
|
118
|
+
let j
|
|
119
|
+
const tmp = []
|
|
128
120
|
for (i = 0, j = arr.length; i < j; i += chunk) {
|
|
129
|
-
tmp.push(arr.slice(i, i + chunk))
|
|
121
|
+
tmp.push(arr.slice(i, i + chunk))
|
|
130
122
|
}
|
|
131
|
-
return tmp
|
|
132
|
-
}
|
|
123
|
+
return tmp
|
|
124
|
+
}
|
|
133
125
|
|
|
134
126
|
export const clearString = function (str) {
|
|
135
|
-
if (!str) return ''
|
|
127
|
+
if (!str) return ''
|
|
136
128
|
/* Replace forbidden symbols in string
|
|
137
129
|
*/
|
|
138
130
|
if (str.endsWith('.')) {
|
|
139
|
-
str = str.slice(0, -1)
|
|
131
|
+
str = str.slice(0, -1)
|
|
140
132
|
}
|
|
141
133
|
return str
|
|
142
134
|
.replace(/ /g, '_')
|
|
@@ -149,26 +141,29 @@ export const clearString = function (str) {
|
|
|
149
141
|
.replace(/\|/g, '_')
|
|
150
142
|
.replace(/\?/g, '.')
|
|
151
143
|
.replace(/\*/g, '^')
|
|
152
|
-
.replace(/'/g, '')
|
|
153
|
-
}
|
|
144
|
+
.replace(/'/g, '')
|
|
145
|
+
}
|
|
154
146
|
|
|
155
147
|
export const decodeUrl = function (url) {
|
|
156
148
|
/* Replace forbidden symbols in string
|
|
157
149
|
*/
|
|
158
|
-
return decodeURIComponent(decodeURIComponent(decodeURIComponent(url)))
|
|
159
|
-
}
|
|
150
|
+
return decodeURIComponent(decodeURIComponent(decodeURIComponent(url)))
|
|
151
|
+
}
|
|
160
152
|
|
|
161
153
|
export const xpathLocator = {
|
|
162
154
|
/**
|
|
163
155
|
* @param {string} string
|
|
164
156
|
* @returns {string}
|
|
165
157
|
*/
|
|
166
|
-
literal:
|
|
158
|
+
literal: string => {
|
|
167
159
|
if (string.indexOf("'") > -1) {
|
|
168
|
-
string = string
|
|
169
|
-
|
|
160
|
+
string = string
|
|
161
|
+
.split("'", -1)
|
|
162
|
+
.map(substr => `'${substr}'`)
|
|
163
|
+
.join(',"\'",')
|
|
164
|
+
return `concat(${string})`
|
|
170
165
|
}
|
|
171
|
-
return `'${string}'
|
|
166
|
+
return `'${string}'`
|
|
172
167
|
},
|
|
173
168
|
|
|
174
169
|
/**
|
|
@@ -177,54 +172,83 @@ export const xpathLocator = {
|
|
|
177
172
|
* @returns {string}
|
|
178
173
|
*/
|
|
179
174
|
combine: locators => locators.join(' | '),
|
|
180
|
-
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const test = {
|
|
178
|
+
grepLines(array, startString, endString) {
|
|
179
|
+
let startIndex = 0
|
|
180
|
+
let endIndex
|
|
181
|
+
array.every((elem, index) => {
|
|
182
|
+
if (elem === startString) {
|
|
183
|
+
startIndex = index
|
|
184
|
+
return true
|
|
185
|
+
}
|
|
186
|
+
if (elem === endString) {
|
|
187
|
+
endIndex = index
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
return true
|
|
191
|
+
})
|
|
192
|
+
return array.slice(startIndex + 1, endIndex)
|
|
193
|
+
},
|
|
181
194
|
|
|
182
|
-
export default {
|
|
183
195
|
submittedData(dataFile) {
|
|
184
196
|
return function (key) {
|
|
185
197
|
if (!fs.existsSync(dataFile)) {
|
|
186
|
-
|
|
187
|
-
|
|
198
|
+
// Extended timeout for CI environments to handle slower processing
|
|
199
|
+
const waitTime = process.env.CI ? 60 * 1000 : 2 * 1000 // 60 seconds in CI, 2 seconds otherwise
|
|
200
|
+
let pollInterval = 100 // Start with 100ms polling interval
|
|
201
|
+
const maxPollInterval = 2000 // Max 2 second intervals
|
|
202
|
+
const startTime = new Date().getTime()
|
|
203
|
+
|
|
204
|
+
// Synchronous polling with exponential backoff to reduce CPU usage
|
|
205
|
+
while (new Date().getTime() - startTime < waitTime) {
|
|
206
|
+
if (fs.existsSync(dataFile)) {
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Use Node.js child_process.spawnSync with platform-specific sleep commands
|
|
211
|
+
// This avoids busy waiting and allows other processes to run
|
|
212
|
+
try {
|
|
213
|
+
if (os.platform() === 'win32') {
|
|
214
|
+
// Windows: use ping with precise timing (ping waits exactly the specified ms)
|
|
215
|
+
spawnSync('ping', ['-n', '1', '-w', pollInterval.toString(), '127.0.0.1'], { stdio: 'ignore' })
|
|
216
|
+
} else {
|
|
217
|
+
// Unix/Linux/macOS: use sleep with fractional seconds
|
|
218
|
+
spawnSync('sleep', [(pollInterval / 1000).toString()], { stdio: 'ignore' })
|
|
219
|
+
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
// If system commands fail, use a simple busy wait with minimal CPU usage
|
|
222
|
+
const end = new Date().getTime() + pollInterval
|
|
223
|
+
while (new Date().getTime() < end) {
|
|
224
|
+
// No-op loop - much lighter than previous approaches
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Exponential backoff: gradually increase polling interval to reduce resource usage
|
|
229
|
+
pollInterval = Math.min(pollInterval * 1.2, maxPollInterval)
|
|
230
|
+
}
|
|
188
231
|
}
|
|
189
232
|
if (!fs.existsSync(dataFile)) {
|
|
190
|
-
throw new Error('Data file was not created in time')
|
|
233
|
+
throw new Error('Data file was not created in time')
|
|
191
234
|
}
|
|
192
|
-
const data = JSON.parse(fs.readFileSync(dataFile, 'utf8'))
|
|
235
|
+
const data = JSON.parse(fs.readFileSync(dataFile, 'utf8'))
|
|
193
236
|
if (key) {
|
|
194
|
-
return data.form[key]
|
|
237
|
+
return data.form[key]
|
|
195
238
|
}
|
|
196
|
-
return data
|
|
197
|
-
};
|
|
198
|
-
},
|
|
199
|
-
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
export function grepLines(array, startString, endString) {
|
|
203
|
-
let startIndex = 0;
|
|
204
|
-
let endIndex;
|
|
205
|
-
array.every((elem, index) => {
|
|
206
|
-
if (elem === startString) {
|
|
207
|
-
startIndex = index;
|
|
208
|
-
return true;
|
|
239
|
+
return data
|
|
209
240
|
}
|
|
210
|
-
|
|
211
|
-
endIndex = index;
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
return true;
|
|
215
|
-
});
|
|
216
|
-
return array.slice(startIndex + 1, endIndex);
|
|
241
|
+
},
|
|
217
242
|
}
|
|
218
243
|
|
|
219
|
-
function
|
|
244
|
+
export const toCamelCase = function (name) {
|
|
220
245
|
if (typeof name !== 'string') {
|
|
221
|
-
return name
|
|
246
|
+
return name
|
|
222
247
|
}
|
|
223
248
|
return name.replace(/-(\w)/gi, (_word, letter) => {
|
|
224
|
-
return letter.toUpperCase()
|
|
225
|
-
})
|
|
249
|
+
return letter.toUpperCase()
|
|
250
|
+
})
|
|
226
251
|
}
|
|
227
|
-
export { toCamelCase };
|
|
228
252
|
|
|
229
253
|
function convertFontWeightToNumber(name) {
|
|
230
254
|
const fontWeightPatterns = [
|
|
@@ -237,105 +261,110 @@ function convertFontWeightToNumber(name) {
|
|
|
237
261
|
{ num: 700, pattern: /^Bold$/i },
|
|
238
262
|
{ num: 800, pattern: /^(Extra|Ultra)-?bold$/i },
|
|
239
263
|
{ num: 900, pattern: /^(Black|Heavy)$/i },
|
|
240
|
-
]
|
|
264
|
+
]
|
|
241
265
|
|
|
242
266
|
if (/^[1-9]00$/.test(name)) {
|
|
243
|
-
return Number(name)
|
|
267
|
+
return Number(name)
|
|
244
268
|
}
|
|
245
269
|
|
|
246
|
-
const matches = fontWeightPatterns.filter(fontWeight => fontWeight.pattern.test(name))
|
|
270
|
+
const matches = fontWeightPatterns.filter(fontWeight => fontWeight.pattern.test(name))
|
|
247
271
|
|
|
248
272
|
if (matches.length) {
|
|
249
|
-
return String(matches[0].num)
|
|
273
|
+
return String(matches[0].num)
|
|
250
274
|
}
|
|
251
|
-
return name
|
|
275
|
+
return name
|
|
252
276
|
}
|
|
253
277
|
|
|
254
278
|
function isFontWeightProperty(prop) {
|
|
255
|
-
return prop === 'fontWeight'
|
|
279
|
+
return prop === 'fontWeight'
|
|
256
280
|
}
|
|
257
281
|
|
|
258
282
|
export const convertCssPropertiesToCamelCase = function (props) {
|
|
259
|
-
const output = {}
|
|
260
|
-
Object.keys(props).forEach(
|
|
261
|
-
const keyCamel = toCamelCase(key)
|
|
283
|
+
const output = {}
|
|
284
|
+
Object.keys(props).forEach(key => {
|
|
285
|
+
const keyCamel = toCamelCase(key)
|
|
262
286
|
|
|
263
287
|
if (isFontWeightProperty(keyCamel)) {
|
|
264
|
-
output[keyCamel] = convertFontWeightToNumber(props[key])
|
|
288
|
+
output[keyCamel] = convertFontWeightToNumber(props[key])
|
|
265
289
|
} else if (isColorProperty(keyCamel)) {
|
|
266
|
-
output[keyCamel] = convertColorToRGBA(props[key])
|
|
290
|
+
output[keyCamel] = convertColorToRGBA(props[key])
|
|
267
291
|
} else {
|
|
268
|
-
output[keyCamel] = props[key]
|
|
292
|
+
output[keyCamel] = props[key]
|
|
269
293
|
}
|
|
270
|
-
})
|
|
271
|
-
return output
|
|
272
|
-
}
|
|
294
|
+
})
|
|
295
|
+
return output
|
|
296
|
+
}
|
|
273
297
|
|
|
274
298
|
export const deleteDir = function (dir_path) {
|
|
275
299
|
if (fs.existsSync(dir_path)) {
|
|
276
300
|
fs.readdirSync(dir_path).forEach(function (entry) {
|
|
277
|
-
const entry_path = path.join(dir_path, entry)
|
|
301
|
+
const entry_path = path.join(dir_path, entry)
|
|
278
302
|
if (fs.lstatSync(entry_path).isDirectory()) {
|
|
279
|
-
|
|
303
|
+
deleteDir(entry_path)
|
|
280
304
|
} else {
|
|
281
|
-
fs.unlinkSync(entry_path)
|
|
305
|
+
fs.unlinkSync(entry_path)
|
|
282
306
|
}
|
|
283
|
-
})
|
|
284
|
-
fs.rmdirSync(dir_path)
|
|
307
|
+
})
|
|
308
|
+
fs.rmdirSync(dir_path)
|
|
285
309
|
}
|
|
286
|
-
}
|
|
310
|
+
}
|
|
287
311
|
|
|
288
312
|
/**
|
|
289
313
|
* Returns absolute filename to save screenshot.
|
|
290
314
|
* @param fileName {string} - filename.
|
|
291
315
|
*/
|
|
292
316
|
export const screenshotOutputFolder = function (fileName) {
|
|
293
|
-
const fileSep = path.sep
|
|
317
|
+
const fileSep = path.sep
|
|
294
318
|
|
|
295
319
|
if (!fileName.includes(fileSep) || fileName.includes('record_')) {
|
|
296
|
-
return path.resolve(global.output_dir, fileName)
|
|
320
|
+
return path.resolve(global.output_dir, fileName)
|
|
297
321
|
}
|
|
298
|
-
return path.resolve(global.codecept_dir, fileName)
|
|
299
|
-
}
|
|
322
|
+
return path.resolve(global.codecept_dir, fileName)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export const relativeDir = function (fileName) {
|
|
326
|
+
return fileName.replace(global.codecept_dir, '').replace(/^\//, '')
|
|
327
|
+
}
|
|
300
328
|
|
|
301
329
|
export const beautify = function (code) {
|
|
302
|
-
|
|
303
|
-
}
|
|
330
|
+
const format = jsBeautify.js
|
|
331
|
+
return format(code, { indent_size: 2, space_in_empty_paren: true })
|
|
332
|
+
}
|
|
304
333
|
|
|
305
334
|
function shouldAppendBaseUrl(url) {
|
|
306
|
-
return !/^\w+\:\/\//.test(url)
|
|
335
|
+
return !/^\w+\:\/\//.test(url)
|
|
307
336
|
}
|
|
308
337
|
|
|
309
338
|
function trimUrl(url) {
|
|
310
|
-
const firstChar = url.substr(1)
|
|
339
|
+
const firstChar = url.substr(1)
|
|
311
340
|
if (firstChar === '/') {
|
|
312
|
-
url = url.slice(1)
|
|
341
|
+
url = url.slice(1)
|
|
313
342
|
}
|
|
314
|
-
return url
|
|
343
|
+
return url
|
|
315
344
|
}
|
|
316
345
|
|
|
317
346
|
function joinUrl(baseUrl, url) {
|
|
318
|
-
return shouldAppendBaseUrl(url) ? `${baseUrl}/${trimUrl(url)}` : url
|
|
347
|
+
return shouldAppendBaseUrl(url) ? `${baseUrl}/${trimUrl(url)}` : url
|
|
319
348
|
}
|
|
320
349
|
|
|
321
350
|
export const appendBaseUrl = function (baseUrl = '', oneOrMoreUrls) {
|
|
322
351
|
if (typeof baseUrl !== 'string') {
|
|
323
|
-
throw new Error(`Invalid value for baseUrl: ${baseUrl}`)
|
|
352
|
+
throw new Error(`Invalid value for baseUrl: ${baseUrl}`)
|
|
324
353
|
}
|
|
325
354
|
if (!(typeof oneOrMoreUrls === 'string' || Array.isArray(oneOrMoreUrls))) {
|
|
326
|
-
throw new Error(`Expected type of Urls is 'string' or 'array', Found '${typeof oneOrMoreUrls}'.`)
|
|
355
|
+
throw new Error(`Expected type of Urls is 'string' or 'array', Found '${typeof oneOrMoreUrls}'.`)
|
|
327
356
|
}
|
|
328
357
|
// Remove '/' if it's at the end of baseUrl
|
|
329
|
-
const lastChar = baseUrl.substr(-1)
|
|
358
|
+
const lastChar = baseUrl.substr(-1)
|
|
330
359
|
if (lastChar === '/') {
|
|
331
|
-
baseUrl = baseUrl.slice(0, -1)
|
|
360
|
+
baseUrl = baseUrl.slice(0, -1)
|
|
332
361
|
}
|
|
333
362
|
|
|
334
363
|
if (!Array.isArray(oneOrMoreUrls)) {
|
|
335
|
-
return joinUrl(baseUrl, oneOrMoreUrls)
|
|
364
|
+
return joinUrl(baseUrl, oneOrMoreUrls)
|
|
336
365
|
}
|
|
337
|
-
return oneOrMoreUrls.map(url => joinUrl(baseUrl, url))
|
|
338
|
-
}
|
|
366
|
+
return oneOrMoreUrls.map(url => joinUrl(baseUrl, url))
|
|
367
|
+
}
|
|
339
368
|
|
|
340
369
|
/**
|
|
341
370
|
* Recursively search key in object and replace it's value.
|
|
@@ -345,56 +374,53 @@ export const appendBaseUrl = function (baseUrl = '', oneOrMoreUrls) {
|
|
|
345
374
|
* @param {*} value value to set for key
|
|
346
375
|
*/
|
|
347
376
|
export const replaceValueDeep = function replaceValueDeep(obj, key, value) {
|
|
348
|
-
if (!obj) return
|
|
377
|
+
if (!obj) return
|
|
349
378
|
|
|
350
379
|
if (obj instanceof Array) {
|
|
351
380
|
for (const i in obj) {
|
|
352
|
-
replaceValueDeep(obj[i], key, value)
|
|
381
|
+
replaceValueDeep(obj[i], key, value)
|
|
353
382
|
}
|
|
354
383
|
}
|
|
355
384
|
|
|
356
385
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
357
|
-
obj[key] = value
|
|
386
|
+
obj[key] = value
|
|
358
387
|
}
|
|
359
388
|
|
|
360
389
|
if (typeof obj === 'object' && obj !== null) {
|
|
361
|
-
const children = Object.values(obj)
|
|
390
|
+
const children = Object.values(obj)
|
|
362
391
|
for (const child of children) {
|
|
363
|
-
replaceValueDeep(child, key, value)
|
|
392
|
+
replaceValueDeep(child, key, value)
|
|
364
393
|
}
|
|
365
394
|
}
|
|
366
|
-
return obj
|
|
367
|
-
}
|
|
395
|
+
return obj
|
|
396
|
+
}
|
|
368
397
|
|
|
369
398
|
export const ansiRegExp = function ({ onlyFirst = false } = {}) {
|
|
370
|
-
const pattern = [
|
|
371
|
-
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
|
372
|
-
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))',
|
|
373
|
-
].join('|');
|
|
399
|
+
const pattern = ['[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))'].join('|')
|
|
374
400
|
|
|
375
|
-
return new RegExp(pattern, onlyFirst ? undefined : 'g')
|
|
376
|
-
}
|
|
401
|
+
return new RegExp(pattern, onlyFirst ? undefined : 'g')
|
|
402
|
+
}
|
|
377
403
|
|
|
378
404
|
export const tryOrDefault = function (fn, defaultValue) {
|
|
379
405
|
try {
|
|
380
|
-
return fn()
|
|
406
|
+
return fn()
|
|
381
407
|
} catch (_) {
|
|
382
|
-
return defaultValue
|
|
408
|
+
return defaultValue
|
|
383
409
|
}
|
|
384
|
-
}
|
|
410
|
+
}
|
|
385
411
|
|
|
386
412
|
function normalizeKeyReplacer(match, prefix, key, suffix, offset, string) {
|
|
387
413
|
if (typeof key !== 'string') {
|
|
388
|
-
return string
|
|
414
|
+
return string
|
|
389
415
|
}
|
|
390
|
-
const normalizedKey = key.charAt(0).toUpperCase() + key.substr(1).toLowerCase()
|
|
391
|
-
let position = ''
|
|
416
|
+
const normalizedKey = key.charAt(0).toUpperCase() + key.substr(1).toLowerCase()
|
|
417
|
+
let position = ''
|
|
392
418
|
if (typeof prefix === 'string') {
|
|
393
|
-
position = prefix
|
|
419
|
+
position = prefix
|
|
394
420
|
} else if (typeof suffix === 'string') {
|
|
395
|
-
position = suffix
|
|
421
|
+
position = suffix
|
|
396
422
|
}
|
|
397
|
-
return normalizedKey + position.charAt(0).toUpperCase() + position.substr(1).toLowerCase()
|
|
423
|
+
return normalizedKey + position.charAt(0).toUpperCase() + position.substr(1).toLowerCase()
|
|
398
424
|
}
|
|
399
425
|
|
|
400
426
|
/**
|
|
@@ -404,77 +430,234 @@ function normalizeKeyReplacer(match, prefix, key, suffix, offset, string) {
|
|
|
404
430
|
*/
|
|
405
431
|
export const getNormalizedKeyAttributeValue = function (key) {
|
|
406
432
|
// Use operation modifier key based on operating system
|
|
407
|
-
key = key.replace(/(Ctrl|Control|Cmd|Command)[ _]?Or[ _]?(Ctrl|Control|Cmd|Command)/i, os.platform() === 'darwin' ? 'Meta' : 'Control')
|
|
433
|
+
key = key.replace(/(Ctrl|Control|Cmd|Command)[ _]?Or[ _]?(Ctrl|Control|Cmd|Command)/i, os.platform() === 'darwin' ? 'Meta' : 'Control')
|
|
408
434
|
// Selection of keys (https://www.w3.org/TR/uievents-key/#named-key-attribute-values)
|
|
409
435
|
// which can be written in various ways and should be normalized.
|
|
410
436
|
// For example 'LEFT ALT', 'ALT_Left', 'alt left' or 'LeftAlt' will be normalized as 'AltLeft'.
|
|
411
|
-
key = key.replace(/^\s*(?:(Down|Left|Right|Up)[ _]?)?(Arrow|Alt|Ctrl|Control|Cmd|Command|Meta|Option|OS|Page|Shift|Super)(?:[ _]?(Down|Left|Right|Up|Gr(?:aph)?))?\s*$/i, normalizeKeyReplacer)
|
|
437
|
+
key = key.replace(/^\s*(?:(Down|Left|Right|Up)[ _]?)?(Arrow|Alt|Ctrl|Control|Cmd|Command|Meta|Option|OS|Page|Shift|Super)(?:[ _]?(Down|Left|Right|Up|Gr(?:aph)?))?\s*$/i, normalizeKeyReplacer)
|
|
412
438
|
// Map alias to corresponding key value
|
|
413
|
-
key = key.replace(/^(Add|Divide|Decimal|Multiply|Subtract)$/, 'Numpad$1')
|
|
414
|
-
key = key.replace(/^AltGr$/, 'AltGraph')
|
|
415
|
-
key = key.replace(/^(Cmd|Command|Os|Super)/, 'Meta')
|
|
416
|
-
key = key.replace('Ctrl', 'Control')
|
|
417
|
-
key = key.replace('Option', 'Alt')
|
|
418
|
-
key = key.replace(/^(NumpadComma|Separator)$/, 'Comma')
|
|
419
|
-
return key
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const modifierKeys = [
|
|
423
|
-
'Alt', 'AltGraph', 'AltLeft', 'AltRight',
|
|
424
|
-
'Control', 'ControlLeft', 'ControlRight',
|
|
425
|
-
'Meta', 'MetaLeft', 'MetaRight',
|
|
426
|
-
'Shift', 'ShiftLeft', 'ShiftRight',
|
|
427
|
-
];
|
|
428
|
-
|
|
429
|
-
export { modifierKeys };
|
|
439
|
+
key = key.replace(/^(Add|Divide|Decimal|Multiply|Subtract)$/, 'Numpad$1')
|
|
440
|
+
key = key.replace(/^AltGr$/, 'AltGraph')
|
|
441
|
+
key = key.replace(/^(Cmd|Command|Os|Super)/, 'Meta')
|
|
442
|
+
key = key.replace('Ctrl', 'Control')
|
|
443
|
+
key = key.replace('Option', 'Alt')
|
|
444
|
+
key = key.replace(/^(NumpadComma|Separator)$/, 'Comma')
|
|
445
|
+
return key
|
|
446
|
+
}
|
|
430
447
|
|
|
448
|
+
export const modifierKeys = ['Alt', 'AltGraph', 'AltLeft', 'AltRight', 'Control', 'ControlLeft', 'ControlRight', 'Meta', 'MetaLeft', 'MetaRight', 'Shift', 'ShiftLeft', 'ShiftRight']
|
|
431
449
|
export const isModifierKey = function (key) {
|
|
432
|
-
return modifierKeys.includes(key)
|
|
433
|
-
}
|
|
450
|
+
return modifierKeys.includes(key)
|
|
451
|
+
}
|
|
434
452
|
|
|
435
453
|
export const requireWithFallback = function (...packages) {
|
|
454
|
+
const require = createRequire(import.meta.url)
|
|
455
|
+
|
|
436
456
|
const exists = function (pkg) {
|
|
437
457
|
try {
|
|
438
|
-
|
|
458
|
+
require.resolve(pkg)
|
|
439
459
|
} catch (e) {
|
|
440
|
-
return false
|
|
460
|
+
return false
|
|
441
461
|
}
|
|
442
462
|
|
|
443
|
-
return true
|
|
444
|
-
}
|
|
463
|
+
return true
|
|
464
|
+
}
|
|
445
465
|
|
|
446
466
|
for (const pkg of packages) {
|
|
447
467
|
if (exists(pkg)) {
|
|
448
|
-
return
|
|
468
|
+
return require(pkg)
|
|
449
469
|
}
|
|
450
470
|
}
|
|
451
471
|
|
|
452
|
-
throw new Error(`Cannot find modules ${packages.join(',')}`)
|
|
453
|
-
}
|
|
472
|
+
throw new Error(`Cannot find modules ${packages.join(',')}`)
|
|
473
|
+
}
|
|
454
474
|
|
|
455
475
|
export const isNotSet = function (obj) {
|
|
456
|
-
if (obj === null) return true
|
|
457
|
-
if (obj === undefined) return true
|
|
458
|
-
return false
|
|
459
|
-
}
|
|
476
|
+
if (obj === null) return true
|
|
477
|
+
if (obj === undefined) return true
|
|
478
|
+
return false
|
|
479
|
+
}
|
|
460
480
|
|
|
461
|
-
export const emptyFolder =
|
|
462
|
-
|
|
463
|
-
|
|
481
|
+
export const emptyFolder = directoryPath => {
|
|
482
|
+
// Do not throw on non-existent directory, since it may be created later
|
|
483
|
+
if (!fs.existsSync(directoryPath)) return
|
|
484
|
+
for (const file of fs.readdirSync(directoryPath)) {
|
|
485
|
+
fs.rmSync(path.join(directoryPath, file), { recursive: true, force: true })
|
|
486
|
+
}
|
|
487
|
+
}
|
|
464
488
|
|
|
465
|
-
export const printObjectProperties =
|
|
489
|
+
export const printObjectProperties = obj => {
|
|
466
490
|
if (typeof obj !== 'object' || obj === null) {
|
|
467
|
-
return obj
|
|
491
|
+
return obj
|
|
468
492
|
}
|
|
469
493
|
|
|
470
|
-
let result = ''
|
|
494
|
+
let result = ''
|
|
471
495
|
for (const [key, value] of Object.entries(obj)) {
|
|
472
|
-
result += `${key}: "${value}";
|
|
496
|
+
result += `${key}: "${value}"; `
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return `{${result}}`
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export const normalizeSpacesInString = string => {
|
|
503
|
+
return string.replace(/\s+/g, ' ')
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export const humanizeFunction = function (fn) {
|
|
507
|
+
const fnStr = fn.toString().trim()
|
|
508
|
+
// Remove arrow function syntax, async, and parentheses
|
|
509
|
+
let simplified = fnStr
|
|
510
|
+
.replace(/^async\s*/, '')
|
|
511
|
+
.replace(/^\([^)]*\)\s*=>/, '')
|
|
512
|
+
.replace(/^function\s*\([^)]*\)/, '')
|
|
513
|
+
// Remove curly braces and any whitespace around them
|
|
514
|
+
.replace(/{\s*(.*)\s*}/, '$1')
|
|
515
|
+
// Remove return statement
|
|
516
|
+
.replace(/return\s+/, '')
|
|
517
|
+
// Remove trailing semicolon
|
|
518
|
+
.replace(/;$/, '')
|
|
519
|
+
.trim()
|
|
520
|
+
|
|
521
|
+
if (simplified.length > 100) {
|
|
522
|
+
simplified = simplified.slice(0, 97) + '...'
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return simplified
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Searches through a given data source using the Fuse.js library for fuzzy searching.
|
|
530
|
+
*
|
|
531
|
+
* @function searchWithFusejs
|
|
532
|
+
* @param {Array|Object} source - The data source to search through. This can be an array of objects or strings.
|
|
533
|
+
* @param {string} searchString - The search query string to match against the source.
|
|
534
|
+
* @param {Object} [opts] - Optional configuration object for Fuse.js.
|
|
535
|
+
* @param {boolean} [opts.includeScore=true] - Whether to include the score of the match in the results.
|
|
536
|
+
* @param {number} [opts.threshold=0.6] - Determines the match threshold; lower values mean stricter matching.
|
|
537
|
+
* @param {boolean} [opts.caseSensitive=false] - Whether the search should be case-sensitive.
|
|
538
|
+
* @param {number} [opts.distance=100] - Determines how far apart the search term is allowed to be from the target.
|
|
539
|
+
* @param {number} [opts.maxPatternLength=32] - The maximum length of the search pattern. Patterns longer than this are ignored.
|
|
540
|
+
* @param {boolean} [opts.ignoreLocation=false] - Whether the location of the match is ignored when scoring.
|
|
541
|
+
* @param {boolean} [opts.ignoreFieldNorm=false] - When true, the field's length is not considered when scoring.
|
|
542
|
+
* @param {Array<string>} [opts.keys=[]] - List of keys to search in the objects of the source array.
|
|
543
|
+
* @param {boolean} [opts.shouldSort=true] - Whether the results should be sorted by score.
|
|
544
|
+
* @param {string} [opts.sortFn] - A custom sorting function for sorting results.
|
|
545
|
+
* @param {number} [opts.minMatchCharLength=1] - The minimum number of characters that must match.
|
|
546
|
+
* @param {boolean} [opts.useExtendedSearch=false] - Enables extended search capabilities.
|
|
547
|
+
*
|
|
548
|
+
* @returns {Array<Object>} - An array of search results. Each result contains an item and, if `includeScore` is true, a score.
|
|
549
|
+
*
|
|
550
|
+
* @example
|
|
551
|
+
* const data = [
|
|
552
|
+
* { title: "Old Man's War", author: "John Scalzi" },
|
|
553
|
+
* { title: "The Lock Artist", author: "Steve Hamilton" },
|
|
554
|
+
* ];
|
|
555
|
+
*
|
|
556
|
+
* const options = {
|
|
557
|
+
* keys: ['title', 'author'],
|
|
558
|
+
* includeScore: true,
|
|
559
|
+
* threshold: 0.4,
|
|
560
|
+
* caseSensitive: false,
|
|
561
|
+
* distance: 50,
|
|
562
|
+
* ignoreLocation: true,
|
|
563
|
+
* };
|
|
564
|
+
*
|
|
565
|
+
* const results = searchWithFusejs(data, 'lock', options);
|
|
566
|
+
* console.log(results);
|
|
567
|
+
*/
|
|
568
|
+
export const searchWithFusejs = function (source, searchString, opts) {
|
|
569
|
+
const fuse = new Fuse(source, opts)
|
|
570
|
+
|
|
571
|
+
return fuse.search(searchString)
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export const humanizeString = function (string) {
|
|
575
|
+
// split strings by words, then make them all lowercase
|
|
576
|
+
const _result = string
|
|
577
|
+
.replace(/([a-z](?=[A-Z]))/g, '$1 ')
|
|
578
|
+
.split(' ')
|
|
579
|
+
.map(word => word.toLowerCase())
|
|
580
|
+
|
|
581
|
+
_result[0] = _result[0] === 'i' ? ucfirst(_result[0]) : _result[0]
|
|
582
|
+
return _result.join(' ').trim()
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Creates a circular-safe replacer function for JSON.stringify
|
|
587
|
+
* @param {string[]} keysToSkip - Keys to skip during serialization to break circular references
|
|
588
|
+
* @returns {Function} Replacer function for JSON.stringify
|
|
589
|
+
*/
|
|
590
|
+
function createCircularSafeReplacer(keysToSkip = []) {
|
|
591
|
+
const seen = new WeakSet()
|
|
592
|
+
const defaultSkipKeys = ['parent', 'tests', 'suite', 'root', 'runner', 'ctx']
|
|
593
|
+
const skipKeys = new Set([...defaultSkipKeys, ...keysToSkip])
|
|
594
|
+
|
|
595
|
+
return function (key, value) {
|
|
596
|
+
// Skip specific keys that commonly cause circular references
|
|
597
|
+
if (key && skipKeys.has(key)) {
|
|
598
|
+
return undefined
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (value === null || typeof value !== 'object') {
|
|
602
|
+
return value
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Handle circular references
|
|
606
|
+
if (seen.has(value)) {
|
|
607
|
+
return `[Circular Reference to ${value.constructor?.name || 'Object'}]`
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
seen.add(value)
|
|
611
|
+
return value
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Safely stringify an object, handling circular references
|
|
617
|
+
* @param {any} obj - Object to stringify
|
|
618
|
+
* @param {string[]} keysToSkip - Additional keys to skip during serialization
|
|
619
|
+
* @param {number} space - Number of spaces for indentation (default: 0)
|
|
620
|
+
* @returns {string} JSON string representation
|
|
621
|
+
*/
|
|
622
|
+
export const safeStringify = function (obj, keysToSkip = [], space = 0) {
|
|
623
|
+
try {
|
|
624
|
+
return JSON.stringify(obj, createCircularSafeReplacer(keysToSkip), space)
|
|
625
|
+
} catch (error) {
|
|
626
|
+
// Fallback for any remaining edge cases
|
|
627
|
+
return JSON.stringify({ error: `Failed to serialize: ${error.message}` }, null, space)
|
|
473
628
|
}
|
|
629
|
+
}
|
|
474
630
|
|
|
475
|
-
|
|
476
|
-
|
|
631
|
+
export const serializeError = function (error) {
|
|
632
|
+
if (error) {
|
|
633
|
+
const { stack, uncaught, message, actual, expected } = error
|
|
634
|
+
return { stack, uncaught, message, actual, expected }
|
|
635
|
+
}
|
|
636
|
+
return null
|
|
637
|
+
}
|
|
477
638
|
|
|
478
|
-
export const
|
|
479
|
-
return
|
|
480
|
-
}
|
|
639
|
+
export const base64EncodeFile = function (filePath) {
|
|
640
|
+
return Buffer.from(fs.readFileSync(filePath)).toString('base64')
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
export const markdownToAnsi = function (markdown) {
|
|
644
|
+
return (
|
|
645
|
+
markdown
|
|
646
|
+
// Headers (# Text) - make blue and bold
|
|
647
|
+
.replace(/^(#{1,6})\s+(.+)$/gm, (_, hashes, text) => {
|
|
648
|
+
return chalk.bold.blue(`${hashes} ${text}`)
|
|
649
|
+
})
|
|
650
|
+
// Bullet points - replace with yellow bullet character
|
|
651
|
+
.replace(/^[-*]\s+(.+)$/gm, (_, text) => {
|
|
652
|
+
return `${chalk.yellow('•')} ${text}`
|
|
653
|
+
})
|
|
654
|
+
// Bold (**text**) - make bold
|
|
655
|
+
.replace(/\*\*(.+?)\*\*/g, (_, text) => {
|
|
656
|
+
return chalk.bold(text)
|
|
657
|
+
})
|
|
658
|
+
// Italic (*text*) - make italic (dim in terminals)
|
|
659
|
+
.replace(/\*(.+?)\*/g, (_, text) => {
|
|
660
|
+
return chalk.italic(text)
|
|
661
|
+
})
|
|
662
|
+
)
|
|
663
|
+
}
|