codeceptjs 4.0.0-beta.2 → 4.0.0-beta.21
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 +73 -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 +262 -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 +301 -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 +109 -50
- package/lib/container.js +765 -261
- 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 +54 -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/loaderCheck.js +124 -0
- package/lib/utils/mask_data.js +47 -0
- package/lib/utils/typescript.js +237 -0
- package/lib/utils.js +411 -228
- package/lib/workerStorage.js +37 -34
- package/lib/workers.js +532 -296
- package/package.json +124 -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 -875
- package/typings/types.d.ts +547 -992
- 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/history.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import colors from 'chalk'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import
|
|
1
|
+
import colors from 'chalk'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import output from './output.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* REPL history records REPL commands and stores them in
|
|
@@ -10,42 +10,46 @@ import * as output from './output.js';
|
|
|
10
10
|
class ReplHistory {
|
|
11
11
|
constructor() {
|
|
12
12
|
if (global.output_dir) {
|
|
13
|
-
this.historyFile = path.join(global.output_dir, 'cli-history')
|
|
13
|
+
this.historyFile = path.join(global.output_dir, 'cli-history')
|
|
14
14
|
}
|
|
15
|
-
this.commands = []
|
|
15
|
+
this.commands = []
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
push(cmd) {
|
|
19
|
-
this.commands.push(cmd)
|
|
19
|
+
this.commands.push(cmd)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
pop() {
|
|
23
|
-
this.commands.pop()
|
|
23
|
+
this.commands.pop()
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
load() {
|
|
27
|
-
if (!this.historyFile) return
|
|
27
|
+
if (!this.historyFile) return
|
|
28
28
|
if (!fs.existsSync(this.historyFile)) {
|
|
29
|
-
return
|
|
29
|
+
return
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const history = fs.readFileSync(this.historyFile, 'utf-8')
|
|
33
|
-
return history
|
|
32
|
+
const history = fs.readFileSync(this.historyFile, 'utf-8')
|
|
33
|
+
return history
|
|
34
|
+
.split('\n')
|
|
35
|
+
.reverse()
|
|
36
|
+
.filter(line => line.startsWith('I.'))
|
|
37
|
+
.map(line => line.slice(2))
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
save() {
|
|
37
|
-
if (!this.historyFile) return
|
|
41
|
+
if (!this.historyFile) return
|
|
38
42
|
if (this.commands.length === 0) {
|
|
39
|
-
return
|
|
43
|
+
return
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
const commandSnippet = `\n\n<<< Recorded commands on ${new Date()}\n${this.commands.join('\n')}
|
|
43
|
-
fs.appendFileSync(this.historyFile, commandSnippet)
|
|
46
|
+
const commandSnippet = `\n\n<<< Recorded commands on ${new Date()}\n${this.commands.join('\n')}`
|
|
47
|
+
fs.appendFileSync(this.historyFile, commandSnippet)
|
|
44
48
|
|
|
45
|
-
output.print(colors.yellow(` Commands have been saved to ${this.historyFile}`))
|
|
49
|
+
output.print(colors.yellow(` Commands have been saved to ${this.historyFile}`))
|
|
46
50
|
|
|
47
|
-
this.commands = []
|
|
51
|
+
this.commands = []
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
|
|
51
|
-
export default new ReplHistory()
|
|
55
|
+
export default new ReplHistory()
|
package/lib/hooks.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { isFunction, isAsyncFunction } from './utils.js'
|
|
2
|
-
import
|
|
1
|
+
import { isFunction, isAsyncFunction } from './utils.js'
|
|
2
|
+
import output from './output.js'
|
|
3
3
|
|
|
4
4
|
export default async function (hook, stage) {
|
|
5
|
-
if (!hook) return
|
|
5
|
+
if (!hook) return
|
|
6
6
|
if (!isFunction(hook)) {
|
|
7
|
-
throw new Error('CodeceptJS 3 allows bootstrap/teardown hooks only as async functions. More info: https://bit.ly/codecept3Up')
|
|
7
|
+
throw new Error('CodeceptJS 3 allows bootstrap/teardown hooks only as async functions. More info: https://bit.ly/codecept3Up')
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
if (stage) output.
|
|
10
|
+
if (stage) output.log(`started ${stage} hook`)
|
|
11
11
|
if (isAsyncFunction(hook)) {
|
|
12
|
-
await hook()
|
|
12
|
+
await hook()
|
|
13
13
|
} else {
|
|
14
|
-
hook()
|
|
14
|
+
hook()
|
|
15
15
|
}
|
|
16
|
-
if (stage) output.
|
|
16
|
+
if (stage) output.log(`finished ${stage} hook`)
|
|
17
17
|
}
|
package/lib/html.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { parse, serialize } from 'parse5'
|
|
2
|
-
import { minify } from 'html-minifier-terser'
|
|
1
|
+
import { parse, serialize } from 'parse5'
|
|
2
|
+
import { minify } from 'html-minifier-terser'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
async function minifyHtml(html) {
|
|
5
5
|
return minify(html, {
|
|
6
6
|
collapseWhitespace: true,
|
|
7
7
|
removeComments: true,
|
|
@@ -11,7 +11,7 @@ export async function minifyHtml(html) {
|
|
|
11
11
|
removeStyleLinkTypeAttributes: true,
|
|
12
12
|
collapseBooleanAttributes: true,
|
|
13
13
|
useShortDoctype: true,
|
|
14
|
-
})
|
|
14
|
+
})
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const defaultHtmlOpts = {
|
|
@@ -19,240 +19,230 @@ const defaultHtmlOpts = {
|
|
|
19
19
|
textElements: ['label', 'h1', 'h2'],
|
|
20
20
|
allowedAttrs: ['id', 'for', 'class', 'name', 'type', 'value', 'tabindex', 'aria-labelledby', 'aria-label', 'label', 'placeholder', 'title', 'alt', 'src', 'role'],
|
|
21
21
|
allowedRoles: ['button', 'checkbox', 'search', 'textbox', 'tab'],
|
|
22
|
-
}
|
|
22
|
+
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
opts = { ...defaultHtmlOpts, ...opts }
|
|
26
|
-
const {
|
|
27
|
-
interactiveElements,
|
|
28
|
-
textElements,
|
|
29
|
-
allowedAttrs,
|
|
30
|
-
allowedRoles,
|
|
31
|
-
} = opts;
|
|
24
|
+
function removeNonInteractiveElements(html, opts = {}) {
|
|
25
|
+
opts = { ...defaultHtmlOpts, ...opts }
|
|
26
|
+
const { interactiveElements, textElements, allowedAttrs, allowedRoles } = opts
|
|
32
27
|
|
|
33
28
|
// Parse the HTML into a document tree
|
|
34
|
-
const document = parse(html)
|
|
29
|
+
const document = parse(html)
|
|
35
30
|
|
|
36
|
-
const trashHtmlClasses = /^(text-|color-|flex-|float-|v-|ember-|d-|border-)
|
|
31
|
+
const trashHtmlClasses = /^(text-|color-|flex-|float-|v-|ember-|d-|border-)/
|
|
37
32
|
// Array to store interactive elements
|
|
38
|
-
const removeElements = ['path', 'script']
|
|
33
|
+
const removeElements = ['path', 'script']
|
|
39
34
|
|
|
40
35
|
function isFilteredOut(node) {
|
|
41
|
-
if (removeElements.includes(node.nodeName)) return true
|
|
36
|
+
if (removeElements.includes(node.nodeName)) return true
|
|
42
37
|
if (node.attrs) {
|
|
43
|
-
if (node.attrs.find(attr => attr.name === 'role' && attr.value === 'tooltip')) return true
|
|
38
|
+
if (node.attrs.find(attr => attr.name === 'role' && attr.value === 'tooltip')) return true
|
|
44
39
|
}
|
|
45
|
-
return false
|
|
40
|
+
return false
|
|
46
41
|
}
|
|
47
42
|
|
|
48
43
|
// Function to check if an element is interactive
|
|
49
44
|
function isInteractive(element) {
|
|
50
|
-
if (element.nodeName === 'input' && element.attrs.find(attr => attr.name === 'type' && attr.value === 'hidden')) return false
|
|
51
|
-
if (interactiveElements.includes(element.nodeName)) return true
|
|
45
|
+
if (element.nodeName === 'input' && element.attrs.find(attr => attr.name === 'type' && attr.value === 'hidden')) return false
|
|
46
|
+
if (interactiveElements.includes(element.nodeName)) return true
|
|
52
47
|
if (element.attrs) {
|
|
53
|
-
if (element.attrs.find(attr => attr.name === 'contenteditable')) return true
|
|
54
|
-
if (element.attrs.find(attr => attr.name === 'tabindex')) return true
|
|
55
|
-
const role = element.attrs.find(attr => attr.name === 'role')
|
|
56
|
-
if (role && allowedRoles.includes(role.value)) return true
|
|
48
|
+
if (element.attrs.find(attr => attr.name === 'contenteditable')) return true
|
|
49
|
+
if (element.attrs.find(attr => attr.name === 'tabindex')) return true
|
|
50
|
+
const role = element.attrs.find(attr => attr.name === 'role')
|
|
51
|
+
if (role && allowedRoles.includes(role.value)) return true
|
|
57
52
|
}
|
|
58
|
-
return false
|
|
53
|
+
return false
|
|
59
54
|
}
|
|
60
55
|
|
|
61
56
|
function hasMeaningfulText(node) {
|
|
62
|
-
if (textElements.includes(node.nodeName)) return true
|
|
63
|
-
return false
|
|
57
|
+
if (textElements.includes(node.nodeName)) return true
|
|
58
|
+
return false
|
|
64
59
|
}
|
|
65
60
|
|
|
66
61
|
function hasInteractiveDescendant(node) {
|
|
67
|
-
if (!node.childNodes) return false
|
|
68
|
-
let result = false
|
|
62
|
+
if (!node.childNodes) return false
|
|
63
|
+
let result = false
|
|
69
64
|
|
|
70
65
|
for (const childNode of node.childNodes) {
|
|
71
|
-
if (isInteractive(childNode) || hasMeaningfulText(childNode)) return true
|
|
72
|
-
result = result || hasInteractiveDescendant(childNode)
|
|
66
|
+
if (isInteractive(childNode) || hasMeaningfulText(childNode)) return true
|
|
67
|
+
result = result || hasInteractiveDescendant(childNode)
|
|
73
68
|
}
|
|
74
69
|
|
|
75
|
-
return result
|
|
70
|
+
return result
|
|
76
71
|
}
|
|
77
72
|
|
|
78
73
|
// Function to remove non-interactive elements recursively
|
|
79
74
|
function removeNonInteractive(node) {
|
|
80
75
|
if (node.nodeName !== '#document') {
|
|
81
|
-
const parent = node.parentNode
|
|
82
|
-
const index = parent.childNodes.indexOf(node)
|
|
76
|
+
const parent = node.parentNode
|
|
77
|
+
const index = parent.childNodes.indexOf(node)
|
|
83
78
|
|
|
84
79
|
if (isFilteredOut(node)) {
|
|
85
|
-
parent.childNodes.splice(index, 1)
|
|
86
|
-
return true
|
|
80
|
+
parent.childNodes.splice(index, 1)
|
|
81
|
+
return true
|
|
87
82
|
}
|
|
88
83
|
|
|
89
84
|
// keep texts for interactive elements
|
|
90
85
|
if ((isInteractive(parent) || hasMeaningfulText(parent)) && node.nodeName === '#text') {
|
|
91
|
-
node.value = node.value.trim().slice(0, 200)
|
|
92
|
-
if (!node.value) return false
|
|
93
|
-
return true
|
|
86
|
+
node.value = node.value.trim().slice(0, 200)
|
|
87
|
+
if (!node.value) return false
|
|
88
|
+
return true
|
|
94
89
|
}
|
|
95
90
|
|
|
96
91
|
if (
|
|
97
92
|
// if parent is interactive, we may need child element to match
|
|
98
|
-
!isInteractive(parent)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
93
|
+
!isInteractive(parent) &&
|
|
94
|
+
!isInteractive(node) &&
|
|
95
|
+
!hasInteractiveDescendant(node) &&
|
|
96
|
+
!hasMeaningfulText(node)
|
|
97
|
+
) {
|
|
98
|
+
parent.childNodes.splice(index, 1)
|
|
99
|
+
return true
|
|
104
100
|
}
|
|
105
101
|
}
|
|
106
102
|
|
|
107
103
|
if (node.attrs) {
|
|
108
104
|
// Filter and keep allowed attributes, accessibility attributes
|
|
109
105
|
node.attrs = node.attrs.filter(attr => {
|
|
110
|
-
const { name, value } = attr
|
|
106
|
+
const { name, value } = attr
|
|
111
107
|
if (name === 'class') {
|
|
112
108
|
// Remove classes containing digits
|
|
113
|
-
attr.value = value
|
|
109
|
+
attr.value = value
|
|
110
|
+
.split(' ')
|
|
114
111
|
// remove classes containing digits/
|
|
115
112
|
.filter(className => !/\d/.test(className))
|
|
116
113
|
// remove popular trash classes
|
|
117
114
|
.filter(className => !className.match(trashHtmlClasses))
|
|
118
115
|
// remove classes with : and __ in them
|
|
119
116
|
.filter(className => !className.match(/(:|__)/))
|
|
120
|
-
.join(' ')
|
|
117
|
+
.join(' ')
|
|
121
118
|
}
|
|
122
119
|
|
|
123
|
-
return allowedAttrs.includes(name)
|
|
124
|
-
})
|
|
120
|
+
return allowedAttrs.includes(name)
|
|
121
|
+
})
|
|
125
122
|
}
|
|
126
123
|
|
|
127
124
|
if (node.childNodes) {
|
|
128
125
|
for (let i = node.childNodes.length - 1; i >= 0; i--) {
|
|
129
|
-
const childNode = node.childNodes[i]
|
|
130
|
-
removeNonInteractive(childNode)
|
|
126
|
+
const childNode = node.childNodes[i]
|
|
127
|
+
removeNonInteractive(childNode)
|
|
131
128
|
}
|
|
132
129
|
}
|
|
133
|
-
return false
|
|
130
|
+
return false
|
|
134
131
|
}
|
|
135
132
|
|
|
136
133
|
// Remove non-interactive elements starting from the root element
|
|
137
|
-
removeNonInteractive(document)
|
|
134
|
+
removeNonInteractive(document)
|
|
138
135
|
|
|
139
136
|
// Serialize the modified document tree back to HTML
|
|
140
|
-
const serializedHTML = serialize(document)
|
|
137
|
+
const serializedHTML = serialize(document)
|
|
141
138
|
|
|
142
|
-
return serializedHTML
|
|
139
|
+
return serializedHTML
|
|
143
140
|
}
|
|
144
141
|
|
|
145
|
-
|
|
142
|
+
function scanForErrorMessages(html, errorClasses = []) {
|
|
146
143
|
// Parse the HTML into a document tree
|
|
147
|
-
const document = parse(html)
|
|
144
|
+
const document = parse(html)
|
|
148
145
|
|
|
149
146
|
// Array to store error messages
|
|
150
|
-
const errorMessages = []
|
|
147
|
+
const errorMessages = []
|
|
151
148
|
|
|
152
149
|
// Function to recursively scan for error classes and messages
|
|
153
150
|
function scanErrors(node) {
|
|
154
151
|
if (node.attrs) {
|
|
155
|
-
const classAttr = node.attrs.find(attr => attr.name === 'class')
|
|
152
|
+
const classAttr = node.attrs.find(attr => attr.name === 'class')
|
|
156
153
|
if (classAttr && classAttr.value) {
|
|
157
|
-
const classNameChunks = classAttr.value.split(' ')
|
|
158
|
-
const errorClassFound = errorClasses.some(errorClass => classNameChunks.includes(errorClass))
|
|
154
|
+
const classNameChunks = classAttr.value.split(' ')
|
|
155
|
+
const errorClassFound = errorClasses.some(errorClass => classNameChunks.includes(errorClass))
|
|
159
156
|
if (errorClassFound && node.childNodes) {
|
|
160
|
-
const errorMessage = sanitizeTextContent(node)
|
|
161
|
-
errorMessages.push(errorMessage)
|
|
157
|
+
const errorMessage = sanitizeTextContent(node)
|
|
158
|
+
errorMessages.push(errorMessage)
|
|
162
159
|
}
|
|
163
160
|
}
|
|
164
161
|
}
|
|
165
162
|
|
|
166
163
|
if (node.childNodes) {
|
|
167
164
|
for (const childNode of node.childNodes) {
|
|
168
|
-
scanErrors(childNode)
|
|
165
|
+
scanErrors(childNode)
|
|
169
166
|
}
|
|
170
167
|
}
|
|
171
168
|
}
|
|
172
169
|
|
|
173
170
|
// Start scanning for error classes and messages from the root element
|
|
174
|
-
scanErrors(document)
|
|
171
|
+
scanErrors(document)
|
|
175
172
|
|
|
176
|
-
return errorMessages
|
|
173
|
+
return errorMessages
|
|
177
174
|
}
|
|
178
175
|
|
|
179
176
|
function sanitizeTextContent(node) {
|
|
180
177
|
if (node.nodeName === '#text') {
|
|
181
|
-
return node.value.trim()
|
|
178
|
+
return node.value.trim()
|
|
182
179
|
}
|
|
183
180
|
|
|
184
|
-
let sanitizedText = ''
|
|
181
|
+
let sanitizedText = ''
|
|
185
182
|
|
|
186
183
|
if (node.childNodes) {
|
|
187
184
|
for (const childNode of node.childNodes) {
|
|
188
|
-
sanitizedText += sanitizeTextContent(childNode)
|
|
185
|
+
sanitizedText += sanitizeTextContent(childNode)
|
|
189
186
|
}
|
|
190
187
|
}
|
|
191
188
|
|
|
192
|
-
return sanitizedText
|
|
189
|
+
return sanitizedText
|
|
193
190
|
}
|
|
194
191
|
|
|
195
192
|
function buildPath(node, path = '') {
|
|
196
|
-
const tag = node.nodeName
|
|
197
|
-
let attributes = ''
|
|
193
|
+
const tag = node.nodeName
|
|
194
|
+
let attributes = ''
|
|
198
195
|
|
|
199
196
|
if (node.attrs) {
|
|
200
|
-
attributes = node.attrs
|
|
201
|
-
.map(attr => `${attr.name}="${attr.value}"`)
|
|
202
|
-
.join(' ');
|
|
197
|
+
attributes = node.attrs.map(attr => `${attr.name}="${attr.value}"`).join(' ')
|
|
203
198
|
}
|
|
204
199
|
|
|
205
200
|
if (!tag.startsWith('#') && tag !== 'body' && tag !== 'html') {
|
|
206
|
-
path += `<${node.nodeName}${node.attrs ? ` ${attributes}` : ''}
|
|
201
|
+
path += `<${node.nodeName}${node.attrs ? ` ${attributes}` : ''}>`
|
|
207
202
|
}
|
|
208
203
|
|
|
209
|
-
if (!node.childNodes) return path
|
|
204
|
+
if (!node.childNodes) return path
|
|
210
205
|
|
|
211
|
-
const children = node.childNodes.filter(child => !child.nodeName.startsWith('#'))
|
|
206
|
+
const children = node.childNodes.filter(child => !child.nodeName.startsWith('#'))
|
|
212
207
|
|
|
213
208
|
if (children.length) {
|
|
214
|
-
return buildPath(children[children.length - 1], path)
|
|
209
|
+
return buildPath(children[children.length - 1], path)
|
|
215
210
|
}
|
|
216
|
-
return path
|
|
211
|
+
return path
|
|
217
212
|
}
|
|
218
213
|
|
|
219
|
-
|
|
220
|
-
chunkSize -= 20
|
|
221
|
-
const chunks = []
|
|
214
|
+
function splitByChunks(text, chunkSize) {
|
|
215
|
+
chunkSize -= 20
|
|
216
|
+
const chunks = []
|
|
222
217
|
for (let i = 0; i < text.length; i += chunkSize) {
|
|
223
|
-
chunks.push(text.slice(i, i + chunkSize))
|
|
218
|
+
chunks.push(text.slice(i, i + chunkSize))
|
|
224
219
|
}
|
|
225
220
|
|
|
226
|
-
const regex = /<\s*\w+(?:\s+\w+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^>\s]+)))*\s
|
|
221
|
+
const regex = /<\s*\w+(?:\s+\w+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^>\s]+)))*\s*$/
|
|
227
222
|
|
|
228
223
|
// append tag to chunk if it was split out
|
|
229
224
|
for (const index in chunks) {
|
|
230
|
-
const nextIndex = parseInt(index, 10) + 1
|
|
231
|
-
if (!chunks[nextIndex]) break
|
|
225
|
+
const nextIndex = parseInt(index, 10) + 1
|
|
226
|
+
if (!chunks[nextIndex]) break
|
|
232
227
|
|
|
233
|
-
const currentChunk = chunks[index]
|
|
234
|
-
const nextChunk = chunks[nextIndex]
|
|
228
|
+
const currentChunk = chunks[index]
|
|
229
|
+
const nextChunk = chunks[nextIndex]
|
|
235
230
|
|
|
236
|
-
const lastTag = currentChunk.match(regex)
|
|
231
|
+
const lastTag = currentChunk.match(regex)
|
|
237
232
|
if (lastTag) {
|
|
238
|
-
chunks[nextIndex] = lastTag[0] + nextChunk
|
|
233
|
+
chunks[nextIndex] = lastTag[0] + nextChunk
|
|
239
234
|
}
|
|
240
235
|
|
|
241
|
-
const path = buildPath(parse(currentChunk))
|
|
236
|
+
const path = buildPath(parse(currentChunk))
|
|
242
237
|
if (path) {
|
|
243
|
-
chunks[nextIndex] = path + chunks[nextIndex]
|
|
238
|
+
chunks[nextIndex] = path + chunks[nextIndex]
|
|
244
239
|
}
|
|
245
240
|
|
|
246
|
-
if (chunks[nextIndex].includes('<html')) continue
|
|
247
|
-
chunks[nextIndex] = `<html><body>${chunks[nextIndex]}</body></html
|
|
241
|
+
if (chunks[nextIndex].includes('<html')) continue
|
|
242
|
+
chunks[nextIndex] = `<html><body>${chunks[nextIndex]}</body></html>`
|
|
248
243
|
}
|
|
249
244
|
|
|
250
|
-
return chunks.map(chunk => chunk.trim())
|
|
245
|
+
return chunks.map(chunk => chunk.trim())
|
|
251
246
|
}
|
|
252
247
|
|
|
253
|
-
export
|
|
254
|
-
scanForErrorMessages,
|
|
255
|
-
removeNonInteractiveElements,
|
|
256
|
-
splitByChunks,
|
|
257
|
-
minifyHtml,
|
|
258
|
-
};
|
|
248
|
+
export { scanForErrorMessages, removeNonInteractiveElements, splitByChunks, minifyHtml }
|
package/lib/index.js
CHANGED
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
import * as _codecept from './codecept.js';
|
|
2
|
-
import * as workers from './workers.js';
|
|
3
|
-
import * as dataTableArgument from './data/dataTableArgument.js';
|
|
4
|
-
import * as dataTable from './data/table.js';
|
|
5
|
-
import * as within from './within.js';
|
|
6
|
-
import * as pause from './pause.js';
|
|
7
|
-
import * as helper from './helper.js';
|
|
8
|
-
import * as actor from './actor.js';
|
|
9
|
-
import * as config from './config.js';
|
|
10
|
-
import * as output from './output.js';
|
|
11
|
-
import * as recorder from './recorder.js';
|
|
12
|
-
import * as container from './container.js';
|
|
13
|
-
import * as locator from './locator.js';
|
|
14
|
-
import * as event from './event.js';
|
|
15
|
-
import * as store from './store.js';
|
|
16
|
-
import * as heal from './heal.js';
|
|
17
|
-
import * as ai from './ai.js';
|
|
18
1
|
/**
|
|
19
2
|
* Index file for loading CodeceptJS programmatically.
|
|
20
3
|
*
|
|
@@ -22,14 +5,30 @@ import * as ai from './ai.js';
|
|
|
22
5
|
* @alias index
|
|
23
6
|
* @namespace
|
|
24
7
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
8
|
+
import codecept from './codecept.js'
|
|
9
|
+
import output from './output.js'
|
|
10
|
+
import container from './container.js'
|
|
11
|
+
import event from './event.js'
|
|
12
|
+
import recorder from './recorder.js'
|
|
13
|
+
import config from './config.js'
|
|
14
|
+
import actor from './actor.js'
|
|
15
|
+
import helper from './helper.js'
|
|
16
|
+
import pause from './pause.js'
|
|
17
|
+
import { within } from './effects.js'
|
|
18
|
+
import dataTable from './data/table.js'
|
|
19
|
+
import dataTableArgument from './data/dataTableArgument.js'
|
|
20
|
+
import store from './store.js'
|
|
21
|
+
import locator from './locator.js'
|
|
22
|
+
import heal from './heal.js'
|
|
23
|
+
import ai from './ai.js'
|
|
24
|
+
import Workers from './workers.js'
|
|
25
|
+
import Secret, { secret } from './secret.js'
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
/** @type {typeof CodeceptJS.Codecept} */
|
|
29
|
+
codecept,
|
|
30
|
+
/** @type {typeof CodeceptJS.Codecept} */
|
|
31
|
+
Codecept: codecept,
|
|
33
32
|
/** @type {typeof CodeceptJS.output} */
|
|
34
33
|
output,
|
|
35
34
|
/** @type {typeof CodeceptJS.Container} */
|
|
@@ -44,6 +43,8 @@ export {
|
|
|
44
43
|
actor,
|
|
45
44
|
/** @type {typeof CodeceptJS.Helper} */
|
|
46
45
|
helper,
|
|
46
|
+
/** @type {typeof CodeceptJS.Helper} */
|
|
47
|
+
Helper: helper,
|
|
47
48
|
/** @type {typeof CodeceptJS.pause} */
|
|
48
49
|
pause,
|
|
49
50
|
/** @type {typeof CodeceptJS.within} */
|
|
@@ -56,7 +57,17 @@ export {
|
|
|
56
57
|
store,
|
|
57
58
|
/** @type {typeof CodeceptJS.Locator} */
|
|
58
59
|
locator,
|
|
60
|
+
|
|
59
61
|
heal,
|
|
60
62
|
ai,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
|
|
64
|
+
Workers,
|
|
65
|
+
|
|
66
|
+
/** @type {typeof CodeceptJS.Secret} */
|
|
67
|
+
Secret,
|
|
68
|
+
/** @type {typeof CodeceptJS.secret} */
|
|
69
|
+
secret,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Named exports for ESM compatibility
|
|
73
|
+
export { codecept, output, container, event, recorder, config, actor, helper, pause, within, dataTable, dataTableArgument, store, locator, heal, ai, Workers, Secret, secret }
|
package/lib/listener/config.js
CHANGED
|
@@ -1,46 +1,53 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import * as output from '../output.js';
|
|
1
|
+
import event from '../event.js'
|
|
2
|
+
import recorder from '../recorder.js'
|
|
3
|
+
import { deepMerge, deepClone, ucfirst } from '../utils.js'
|
|
4
|
+
import output from '../output.js'
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Enable Helpers to listen to test events
|
|
9
8
|
*/
|
|
10
9
|
export default function () {
|
|
11
|
-
|
|
10
|
+
// Use global flag to prevent duplicate initialization across module re-imports
|
|
11
|
+
if (global.__codeceptConfigListenerInitialized) {
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
global.__codeceptConfigListenerInitialized = true
|
|
15
|
+
|
|
16
|
+
const helpers = global.container.helpers()
|
|
12
17
|
|
|
13
|
-
enableDynamicConfigFor('suite')
|
|
14
|
-
enableDynamicConfigFor('test')
|
|
18
|
+
enableDynamicConfigFor('suite')
|
|
19
|
+
enableDynamicConfigFor('test')
|
|
15
20
|
|
|
16
21
|
function enableDynamicConfigFor(type) {
|
|
17
22
|
event.dispatcher.on(event[type].before, (context = {}) => {
|
|
18
23
|
function updateHelperConfig(helper, config) {
|
|
19
|
-
const oldConfig =
|
|
24
|
+
const oldConfig = deepClone(helper.options)
|
|
20
25
|
try {
|
|
21
|
-
helper._setConfig(deepMerge(deepClone(oldConfig), config))
|
|
22
|
-
debug(`[${ucfirst(type)} Config] ${helper.constructor.name} ${JSON.stringify(config)}`)
|
|
26
|
+
helper._setConfig(deepMerge(deepClone(oldConfig), config))
|
|
27
|
+
output.debug(`[${ucfirst(type)} Config] ${helper.constructor.name} ${JSON.stringify(config)}`)
|
|
23
28
|
} catch (err) {
|
|
24
|
-
recorder.throw(err)
|
|
25
|
-
return
|
|
29
|
+
recorder.throw(err)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
const restoreCallback = () => {
|
|
33
|
+
helper._setConfig(oldConfig)
|
|
34
|
+
output.debug(`[${ucfirst(type)} Config] Reverted for ${helper.constructor.name}`)
|
|
26
35
|
}
|
|
27
|
-
event.dispatcher.once(event[type].after,
|
|
28
|
-
helper._setConfig(oldConfig);
|
|
29
|
-
debug(`[${ucfirst(type)} Config] Reverted for ${helper.constructor.name}`);
|
|
30
|
-
});
|
|
36
|
+
event.dispatcher.once(event[type].after, restoreCallback)
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
// change config
|
|
34
40
|
if (context.config) {
|
|
35
41
|
for (let name in context.config) {
|
|
36
|
-
const config = context.config[name]
|
|
37
|
-
if (name === '0') {
|
|
38
|
-
|
|
42
|
+
const config = context.config[name]
|
|
43
|
+
if (name === '0') {
|
|
44
|
+
// first helper
|
|
45
|
+
name = Object.keys(helpers)[0]
|
|
39
46
|
}
|
|
40
|
-
const helper = helpers[name]
|
|
41
|
-
updateHelperConfig(helper, config)
|
|
47
|
+
const helper = helpers[name]
|
|
48
|
+
updateHelperConfig(helper, config)
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
|
-
})
|
|
51
|
+
})
|
|
45
52
|
}
|
|
46
53
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import figures from 'figures'
|
|
2
|
+
import event from '../event.js'
|
|
3
|
+
import output from '../output.js'
|
|
4
|
+
import { searchWithFusejs } from '../utils.js'
|
|
5
|
+
|
|
6
|
+
export default function () {
|
|
7
|
+
let isEmptyRun = true
|
|
8
|
+
|
|
9
|
+
event.dispatcher.on(event.test.before, test => {
|
|
10
|
+
isEmptyRun = false
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
event.dispatcher.on(event.all.result, () => {
|
|
14
|
+
if (isEmptyRun) {
|
|
15
|
+
const mocha = global.container.mocha()
|
|
16
|
+
|
|
17
|
+
if (mocha.options.grep) {
|
|
18
|
+
output.print()
|
|
19
|
+
output.print('No tests found by pattern: ' + mocha.options.grep)
|
|
20
|
+
|
|
21
|
+
const allTests = []
|
|
22
|
+
mocha.suite.suites.forEach(suite => {
|
|
23
|
+
suite.tests.forEach(test => {
|
|
24
|
+
allTests.push(test.fullTitle())
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const results = searchWithFusejs(allTests, mocha.options.grep.toString(), {
|
|
29
|
+
includeScore: true,
|
|
30
|
+
threshold: 0.6,
|
|
31
|
+
caseSensitive: false,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
if (results.length > 0) {
|
|
35
|
+
output.print()
|
|
36
|
+
output.print('Maybe you wanted to run one of these tests?')
|
|
37
|
+
results.forEach(result => {
|
|
38
|
+
output.print(figures.checkboxOff, output.styles.log(result.item))
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
output.print()
|
|
42
|
+
output.print(output.styles.debug('To run the first test use the following command:'))
|
|
43
|
+
output.print(output.styles.bold('npx codeceptjs run --debug --grep "' + results[0].item + '"'))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (process.env.CI && !process.env.DONT_FAIL_ON_EMPTY_RUN) {
|
|
47
|
+
output.print()
|
|
48
|
+
output.error('No tests were executed. Failing on CI to avoid false positives')
|
|
49
|
+
output.error('To disable this check, set `DONT_FAIL_ON_EMPTY_RUN` environment variable to true in CI config')
|
|
50
|
+
process.exitCode = 1
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
}
|