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
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import colors from 'chalk'
|
|
2
|
-
import crypto from 'crypto'
|
|
3
|
-
import figures from 'figures'
|
|
4
|
-
import fs from 'fs'
|
|
5
|
-
import mkdirp from 'mkdirp'
|
|
6
|
-
import path from 'path'
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
1
|
+
import colors from 'chalk'
|
|
2
|
+
import crypto from 'crypto'
|
|
3
|
+
import figures from 'figures'
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
import { mkdirp } from 'mkdirp'
|
|
6
|
+
import path from 'path'
|
|
7
|
+
import cheerio from 'cheerio'
|
|
8
|
+
|
|
9
|
+
import Container from '../container.js'
|
|
10
|
+
import recorder from '../recorder.js'
|
|
11
|
+
import event from '../event.js'
|
|
12
|
+
import output from '../output.js'
|
|
13
|
+
import { template, deleteDir } from '../utils.js'
|
|
14
|
+
|
|
15
|
+
const supportedHelpers = Container.STANDARD_ACTING_HELPERS
|
|
13
16
|
|
|
14
17
|
const defaultConfig = {
|
|
15
18
|
deleteSuccessful: true,
|
|
@@ -19,9 +22,9 @@ const defaultConfig = {
|
|
|
19
22
|
output: global.output_dir,
|
|
20
23
|
screenshotsForAllureReport: false,
|
|
21
24
|
disableScreenshotOnFail: true,
|
|
22
|
-
}
|
|
25
|
+
}
|
|
23
26
|
|
|
24
|
-
const templates = {}
|
|
27
|
+
const templates = {}
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* 
|
|
@@ -61,134 +64,189 @@ const templates = {};
|
|
|
61
64
|
*/
|
|
62
65
|
|
|
63
66
|
export default function (config) {
|
|
64
|
-
const helpers = Container.helpers()
|
|
65
|
-
let helper
|
|
67
|
+
const helpers = Container.helpers()
|
|
68
|
+
let helper
|
|
66
69
|
|
|
67
|
-
config = Object.assign(defaultConfig, config)
|
|
70
|
+
config = Object.assign(defaultConfig, config)
|
|
68
71
|
|
|
69
72
|
for (const helperName of supportedHelpers) {
|
|
70
73
|
if (Object.keys(helpers).indexOf(helperName) > -1) {
|
|
71
|
-
helper = helpers[helperName]
|
|
74
|
+
helper = helpers[helperName]
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
|
|
75
|
-
if (!helper) return
|
|
76
|
-
|
|
77
|
-
let dir
|
|
78
|
-
let stepNum
|
|
79
|
-
let slides = {}
|
|
80
|
-
let error
|
|
81
|
-
let savedStep = null
|
|
82
|
-
let currentTest = null
|
|
83
|
-
let scenarioFailed = false
|
|
84
|
-
|
|
85
|
-
const recordedTests = {}
|
|
86
|
-
const pad = '0000'
|
|
87
|
-
const reportDir = config.output ? path.resolve(global.codecept_dir, config.output) : defaultConfig.output
|
|
88
|
-
|
|
89
|
-
event.dispatcher.on(event.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
78
|
+
if (!helper) return // no helpers for screenshot
|
|
79
|
+
|
|
80
|
+
let dir
|
|
81
|
+
let stepNum
|
|
82
|
+
let slides = {}
|
|
83
|
+
let error
|
|
84
|
+
let savedStep = null
|
|
85
|
+
let currentTest = null
|
|
86
|
+
let scenarioFailed = false
|
|
87
|
+
|
|
88
|
+
const recordedTests = {}
|
|
89
|
+
const pad = '0000'
|
|
90
|
+
const reportDir = config.output ? path.resolve(global.codecept_dir, config.output) : defaultConfig.output
|
|
91
|
+
|
|
92
|
+
event.dispatcher.on(event.suite.before, suite => {
|
|
93
|
+
stepNum = -1
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
event.dispatcher.on(event.test.before, test => {
|
|
97
|
+
const sha256hash = crypto
|
|
98
|
+
.createHash('sha256')
|
|
99
|
+
.update(test.file + test.title)
|
|
100
|
+
.digest('hex')
|
|
101
|
+
dir = path.join(reportDir, `record_${sha256hash}`)
|
|
102
|
+
mkdirp.sync(dir)
|
|
103
|
+
stepNum = 0
|
|
104
|
+
error = null
|
|
105
|
+
slides = {}
|
|
106
|
+
savedStep = null
|
|
107
|
+
currentTest = test
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
event.dispatcher.on(event.step.failed, step => {
|
|
111
|
+
recorder.add('screenshot of failed test', async () => persistStep(step), true)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
event.dispatcher.on(event.step.after, step => {
|
|
115
|
+
recorder.add('screenshot of step of test', async () => persistStep(step), true)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
event.dispatcher.on(event.test.passed, test => {
|
|
119
|
+
if (!config.deleteSuccessful) return persist(test)
|
|
108
120
|
// cleanup
|
|
109
|
-
deleteDir(dir)
|
|
110
|
-
})
|
|
121
|
+
deleteDir(dir)
|
|
122
|
+
})
|
|
111
123
|
|
|
112
|
-
event.dispatcher.on(event.test.failed, (test,
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
return
|
|
124
|
+
event.dispatcher.on(event.test.failed, (test, _err, hookName) => {
|
|
125
|
+
if (hookName === 'BeforeSuite' || hookName === 'AfterSuite') {
|
|
126
|
+
// no browser here
|
|
127
|
+
return
|
|
116
128
|
}
|
|
117
|
-
|
|
129
|
+
|
|
130
|
+
persist(test)
|
|
131
|
+
})
|
|
118
132
|
|
|
119
133
|
event.dispatcher.on(event.all.result, () => {
|
|
120
|
-
if (!Object.keys(slides).length) return
|
|
134
|
+
if (Object.keys(recordedTests).length === 0 || !Object.keys(slides).length) return
|
|
135
|
+
generateRecordsHtml(recordedTests)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
event.dispatcher.on(event.workers.result, async () => {
|
|
139
|
+
await recorder.add(() => {
|
|
140
|
+
const recordedTests = getRecordFoldersWithDetails(reportDir)
|
|
141
|
+
generateRecordsHtml(recordedTests)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
function getRecordFoldersWithDetails(dirPath) {
|
|
146
|
+
let results = {}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const items = fs.readdirSync(dirPath, { withFileTypes: true })
|
|
150
|
+
|
|
151
|
+
items.forEach(item => {
|
|
152
|
+
if (item.isDirectory() && item.name.startsWith('record_')) {
|
|
153
|
+
const recordFolderPath = path.join(dirPath, item.name)
|
|
154
|
+
const indexPath = path.join(recordFolderPath, 'index.html')
|
|
155
|
+
|
|
156
|
+
let name = ''
|
|
157
|
+
if (fs.existsSync(indexPath)) {
|
|
158
|
+
try {
|
|
159
|
+
const htmlContent = fs.readFileSync(indexPath, 'utf-8')
|
|
160
|
+
const $ = cheerio.load(htmlContent)
|
|
161
|
+
name = $('.navbar-brand').text().trim()
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.error(`Error reading index.html in ${recordFolderPath}:`, err.message)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
results[name || 'Unkown'] = `${item.name}/index.html`
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(`Error reading directory ${dirPath}:`, err.message)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return results
|
|
175
|
+
}
|
|
121
176
|
|
|
122
|
-
|
|
177
|
+
function generateRecordsHtml(recordedTests) {
|
|
178
|
+
let links = ''
|
|
123
179
|
|
|
124
180
|
for (const link in recordedTests) {
|
|
125
|
-
links += `<li><a href="${recordedTests[link]}">${link}</a></li>\n
|
|
181
|
+
links += `<li><a href="${recordedTests[link]}">${link}</a></li>\n`
|
|
126
182
|
}
|
|
127
183
|
|
|
128
184
|
const indexHTML = template(templates.index, {
|
|
129
185
|
time: Date().toString(),
|
|
130
186
|
records: links,
|
|
131
|
-
})
|
|
187
|
+
})
|
|
132
188
|
|
|
133
|
-
fs.writeFileSync(path.join(reportDir, 'records.html'), indexHTML)
|
|
189
|
+
fs.writeFileSync(path.join(reportDir, 'records.html'), indexHTML)
|
|
134
190
|
|
|
135
|
-
output.print(`${figures.circleFilled} Step-by-step preview: ${colors.white.bold(`file://${reportDir}/records.html`)}`)
|
|
136
|
-
}
|
|
191
|
+
output.print(`${figures.circleFilled} Step-by-step preview: ${colors.white.bold(`file://${reportDir}/records.html`)}`)
|
|
192
|
+
}
|
|
137
193
|
|
|
138
194
|
async function persistStep(step) {
|
|
139
|
-
if (
|
|
140
|
-
if (
|
|
195
|
+
if (stepNum === -1) return // Ignore steps from BeforeSuite function
|
|
196
|
+
if (isStepIgnored(step)) return
|
|
197
|
+
if (savedStep === step) return // already saved
|
|
141
198
|
// Ignore steps from BeforeSuite function
|
|
142
|
-
if (scenarioFailed && config.disableScreenshotOnFail) return
|
|
143
|
-
if (step.metaStep && step.metaStep.name === 'BeforeSuite') return
|
|
199
|
+
if (scenarioFailed && config.disableScreenshotOnFail) return
|
|
200
|
+
if (step.metaStep && step.metaStep.name === 'BeforeSuite') return
|
|
201
|
+
if (!step.test) return // Ignore steps from AfterSuite
|
|
144
202
|
|
|
145
|
-
const fileName = `${pad.substring(0, pad.length - stepNum.toString().length) + stepNum.toString()}.png
|
|
203
|
+
const fileName = `${pad.substring(0, pad.length - stepNum.toString().length) + stepNum.toString()}.png`
|
|
146
204
|
if (step.status === 'failed') {
|
|
147
|
-
scenarioFailed = true
|
|
205
|
+
scenarioFailed = true
|
|
148
206
|
}
|
|
149
|
-
stepNum
|
|
150
|
-
slides[fileName] = step
|
|
207
|
+
stepNum++
|
|
208
|
+
slides[fileName] = step
|
|
151
209
|
try {
|
|
152
|
-
await helper.saveScreenshot(path.
|
|
210
|
+
await helper.saveScreenshot(path.join(dir, fileName), config.fullPageScreenshots)
|
|
153
211
|
} catch (err) {
|
|
154
|
-
output.
|
|
155
|
-
error = err
|
|
156
|
-
return
|
|
212
|
+
output.plugin(`Can't save step screenshot: ${err}`)
|
|
213
|
+
error = err
|
|
214
|
+
return
|
|
157
215
|
} finally {
|
|
158
|
-
savedStep = step
|
|
216
|
+
savedStep = step
|
|
159
217
|
}
|
|
160
218
|
|
|
161
|
-
if (!currentTest.artifacts.screenshots) currentTest.artifacts.screenshots = []
|
|
219
|
+
if (!currentTest.artifacts.screenshots) currentTest.artifacts.screenshots = []
|
|
162
220
|
// added attachments to test
|
|
163
|
-
currentTest.artifacts.screenshots.push(path.join(dir, fileName))
|
|
221
|
+
currentTest.artifacts.screenshots.push(path.join(dir, fileName))
|
|
164
222
|
|
|
165
|
-
const allureReporter = Container.plugins('allure')
|
|
223
|
+
const allureReporter = Container.plugins('allure')
|
|
166
224
|
if (allureReporter && config.screenshotsForAllureReport) {
|
|
167
|
-
output.
|
|
168
|
-
allureReporter.addAttachment(`Screenshot of step ${step}`, fs.readFileSync(path.join(dir, fileName)), 'image/png')
|
|
225
|
+
output.plugin('stepByStepReport', 'Adding screenshot to Allure')
|
|
226
|
+
allureReporter.addAttachment(`Screenshot of step ${step}`, fs.readFileSync(path.join(dir, fileName)), 'image/png')
|
|
169
227
|
}
|
|
170
228
|
}
|
|
171
229
|
|
|
172
230
|
function persist(test) {
|
|
173
|
-
if (error) return
|
|
231
|
+
if (error) return
|
|
174
232
|
|
|
175
|
-
let indicatorHtml = ''
|
|
176
|
-
let slideHtml = ''
|
|
233
|
+
let indicatorHtml = ''
|
|
234
|
+
let slideHtml = ''
|
|
177
235
|
|
|
178
236
|
for (const i in slides) {
|
|
179
|
-
const step = slides[i]
|
|
180
|
-
const stepNum = parseInt(i, 10)
|
|
237
|
+
const step = slides[i]
|
|
238
|
+
const stepNum = parseInt(i, 10)
|
|
181
239
|
indicatorHtml += template(templates.indicator, {
|
|
182
240
|
step: stepNum,
|
|
183
241
|
isActive: stepNum ? '' : 'class="active"',
|
|
184
|
-
})
|
|
242
|
+
})
|
|
185
243
|
|
|
186
244
|
slideHtml += template(templates.slides, {
|
|
187
245
|
image: i,
|
|
188
246
|
caption: step.toString().replace(/\[\d{2}m/g, ''), // remove ANSI escape sequence
|
|
189
247
|
isActive: stepNum ? '' : 'active',
|
|
190
248
|
isError: step.status === 'failed' ? 'error' : '',
|
|
191
|
-
})
|
|
249
|
+
})
|
|
192
250
|
}
|
|
193
251
|
|
|
194
252
|
const html = template(templates.global, {
|
|
@@ -197,19 +255,19 @@ export default function (config) {
|
|
|
197
255
|
feature: test.parent && test.parent.title,
|
|
198
256
|
test: test.title,
|
|
199
257
|
carousel_class: config.animateSlides ? ' slide' : '',
|
|
200
|
-
})
|
|
258
|
+
})
|
|
201
259
|
|
|
202
|
-
const index = path.join(dir, 'index.html')
|
|
203
|
-
fs.writeFileSync(index, html)
|
|
204
|
-
recordedTests[`${test.parent.title}: ${test.title}`] = path.relative(reportDir, index)
|
|
260
|
+
const index = path.join(dir, 'index.html')
|
|
261
|
+
fs.writeFileSync(index, html)
|
|
262
|
+
recordedTests[`${test.parent.title}: ${test.title}`] = path.relative(reportDir, index)
|
|
205
263
|
}
|
|
206
264
|
|
|
207
265
|
function isStepIgnored(step) {
|
|
208
|
-
if (!config.ignoreSteps) return
|
|
266
|
+
if (!config.ignoreSteps) return
|
|
209
267
|
for (const pattern of config.ignoreSteps || []) {
|
|
210
|
-
if (step.name.match(pattern)) return true
|
|
268
|
+
if (step.name.match(pattern)) return true
|
|
211
269
|
}
|
|
212
|
-
return false
|
|
270
|
+
return false
|
|
213
271
|
}
|
|
214
272
|
}
|
|
215
273
|
|
|
@@ -223,11 +281,11 @@ templates.slides = `
|
|
|
223
281
|
<small>scroll up and down to see the full page</small>
|
|
224
282
|
</div>
|
|
225
283
|
</div>
|
|
226
|
-
|
|
284
|
+
`
|
|
227
285
|
|
|
228
286
|
templates.indicator = `
|
|
229
287
|
<li data-target="#steps" data-slide-to="{{step}}" {{isActive}}></li>
|
|
230
|
-
|
|
288
|
+
`
|
|
231
289
|
|
|
232
290
|
templates.index = `
|
|
233
291
|
<!DOCTYPE html>
|
|
@@ -256,7 +314,7 @@ templates.index = `
|
|
|
256
314
|
|
|
257
315
|
</body>
|
|
258
316
|
</html>
|
|
259
|
-
|
|
317
|
+
`
|
|
260
318
|
|
|
261
319
|
templates.global = `
|
|
262
320
|
<!DOCTYPE html>
|
|
@@ -366,4 +424,4 @@ templates.global = `
|
|
|
366
424
|
</body>
|
|
367
425
|
|
|
368
426
|
</html>
|
|
369
|
-
|
|
427
|
+
`
|
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import event from '../event.js'
|
|
2
|
+
|
|
3
|
+
import { TIMEOUT_ORDER } from '../timeout.js'
|
|
3
4
|
|
|
4
5
|
const defaultConfig = {
|
|
5
6
|
timeout: 150,
|
|
6
7
|
overrideStepLimits: false,
|
|
7
|
-
noTimeoutSteps: [
|
|
8
|
-
'amOnPage',
|
|
9
|
-
'wait*',
|
|
10
|
-
],
|
|
8
|
+
noTimeoutSteps: ['amOnPage', 'wait*'],
|
|
11
9
|
customTimeoutSteps: [],
|
|
12
|
-
}
|
|
10
|
+
}
|
|
13
11
|
|
|
14
12
|
/**
|
|
15
13
|
* Set timeout for test steps globally.
|
|
@@ -64,28 +62,25 @@ const defaultConfig = {
|
|
|
64
62
|
* ```
|
|
65
63
|
*
|
|
66
64
|
*/
|
|
67
|
-
export default (config)
|
|
68
|
-
config = Object.assign(defaultConfig, config)
|
|
65
|
+
export default function(config) {
|
|
66
|
+
config = Object.assign(defaultConfig, config)
|
|
69
67
|
// below override rule makes sure customTimeoutSteps go first but then they override noTimeoutSteps in case of exact pattern match
|
|
70
|
-
config.customTimeoutSteps = config.customTimeoutSteps.concat(config.noTimeoutSteps).concat(config.customTimeoutSteps)
|
|
68
|
+
config.customTimeoutSteps = config.customTimeoutSteps.concat(config.noTimeoutSteps).concat(config.customTimeoutSteps)
|
|
71
69
|
|
|
72
|
-
event.dispatcher.on(event.step.before,
|
|
73
|
-
let stepTimeout
|
|
70
|
+
event.dispatcher.on(event.step.before, step => {
|
|
71
|
+
let stepTimeout
|
|
74
72
|
for (let stepRule of config.customTimeoutSteps) {
|
|
75
|
-
let customTimeout = 0
|
|
73
|
+
let customTimeout = 0
|
|
76
74
|
if (Array.isArray(stepRule)) {
|
|
77
|
-
if (stepRule.length > 1) customTimeout = stepRule[1]
|
|
78
|
-
stepRule = stepRule[0]
|
|
75
|
+
if (stepRule.length > 1) customTimeout = stepRule[1]
|
|
76
|
+
stepRule = stepRule[0]
|
|
79
77
|
}
|
|
80
|
-
if (stepRule instanceof RegExp
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
) {
|
|
84
|
-
stepTimeout = customTimeout;
|
|
85
|
-
break;
|
|
78
|
+
if (stepRule instanceof RegExp ? step.name.match(stepRule) : step.name === stepRule || (stepRule.indexOf('*') && step.name.startsWith(stepRule.slice(0, -1)))) {
|
|
79
|
+
stepTimeout = customTimeout
|
|
80
|
+
break
|
|
86
81
|
}
|
|
87
82
|
}
|
|
88
|
-
stepTimeout = stepTimeout === undefined ? config.timeout : stepTimeout
|
|
89
|
-
step.setTimeout(stepTimeout * 1000, config.overrideStepLimits ?
|
|
90
|
-
})
|
|
91
|
-
}
|
|
83
|
+
stepTimeout = stepTimeout === undefined ? config.timeout : stepTimeout
|
|
84
|
+
step.setTimeout(stepTimeout * 1000, config.overrideStepLimits ? TIMEOUT_ORDER.stepTimeoutHard : TIMEOUT_ORDER.stepTimeoutSoft)
|
|
85
|
+
})
|
|
86
|
+
}
|
package/lib/plugin/subtitles.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { v4 as uuidv4 } from 'uuid'
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
const fsPromise = fs.promises
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import event from '../event.js'
|
|
5
6
|
|
|
6
7
|
// This will convert a given timestamp in milliseconds to
|
|
7
8
|
// an SRT recognized timestamp, ie HH:mm:ss,SSS
|
|
8
9
|
function formatTimestamp(timestampInMs) {
|
|
9
|
-
const date = new Date(0, 0, 0, 0, 0, 0, timestampInMs)
|
|
10
|
-
const hours = date.getHours()
|
|
11
|
-
const minutes = date.getMinutes()
|
|
12
|
-
const seconds = date.getSeconds()
|
|
13
|
-
const ms = timestampInMs - (hours * 3600000 + minutes * 60000 + seconds * 1000)
|
|
14
|
-
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')},${ms.toString().padStart(3, '0')}
|
|
10
|
+
const date = new Date(0, 0, 0, 0, 0, 0, timestampInMs)
|
|
11
|
+
const hours = date.getHours()
|
|
12
|
+
const minutes = date.getMinutes()
|
|
13
|
+
const seconds = date.getSeconds()
|
|
14
|
+
const ms = timestampInMs - (hours * 3600000 + minutes * 60000 + seconds * 1000)
|
|
15
|
+
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')},${ms.toString().padStart(3, '0')}`
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
let steps = {}
|
|
18
|
-
let testStartedAt
|
|
19
|
-
|
|
18
|
+
let steps = {}
|
|
19
|
+
let testStartedAt
|
|
20
20
|
/**
|
|
21
21
|
* Automatically captures steps as subtitle, and saves it as an artifact when a video is found for a failed test
|
|
22
22
|
*
|
|
@@ -30,41 +30,41 @@ let testStartedAt;
|
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
32
|
export default function () {
|
|
33
|
-
event.dispatcher.on(event.test.before,
|
|
34
|
-
testStartedAt = Date.now()
|
|
35
|
-
steps = {}
|
|
36
|
-
})
|
|
33
|
+
event.dispatcher.on(event.test.before, _ => {
|
|
34
|
+
testStartedAt = Date.now()
|
|
35
|
+
steps = {}
|
|
36
|
+
})
|
|
37
37
|
|
|
38
|
-
event.dispatcher.on(event.step.started,
|
|
39
|
-
const stepStartedAt = Date.now()
|
|
40
|
-
step.id = uuidv4()
|
|
38
|
+
event.dispatcher.on(event.step.started, step => {
|
|
39
|
+
const stepStartedAt = Date.now()
|
|
40
|
+
step.id = uuidv4()
|
|
41
41
|
|
|
42
|
-
let title = `${step.actor}.${step.name}(${step.args ? step.args.join(',') : ''})
|
|
42
|
+
let title = `${step.actor}.${step.name}(${step.args ? step.args.join(',') : ''})`
|
|
43
43
|
if (title.length > 100) {
|
|
44
|
-
title = `${title.substring(0, 100)}
|
|
44
|
+
title = `${title.substring(0, 100)}...`
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
steps[step.id] = {
|
|
48
48
|
start: formatTimestamp(stepStartedAt - testStartedAt),
|
|
49
49
|
startedAt: stepStartedAt,
|
|
50
50
|
title,
|
|
51
|
-
}
|
|
52
|
-
})
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
53
|
|
|
54
|
-
event.dispatcher.on(event.step.finished,
|
|
54
|
+
event.dispatcher.on(event.step.finished, step => {
|
|
55
55
|
if (step && step.id && steps[step.id]) {
|
|
56
|
-
steps[step.id].end = formatTimestamp(Date.now() - testStartedAt)
|
|
56
|
+
steps[step.id].end = formatTimestamp(Date.now() - testStartedAt)
|
|
57
57
|
}
|
|
58
|
-
})
|
|
58
|
+
})
|
|
59
59
|
|
|
60
|
-
event.dispatcher.on(event.test.after, async
|
|
60
|
+
event.dispatcher.on(event.test.after, async test => {
|
|
61
61
|
if (test && test.artifacts && test.artifacts.video) {
|
|
62
|
-
const stepsSortedByStartTime = Object.values(steps)
|
|
62
|
+
const stepsSortedByStartTime = Object.values(steps)
|
|
63
63
|
stepsSortedByStartTime.sort((stepA, stepB) => {
|
|
64
|
-
return stepA.startedAt - stepB.startedAt
|
|
65
|
-
})
|
|
64
|
+
return stepA.startedAt - stepB.startedAt
|
|
65
|
+
})
|
|
66
66
|
|
|
67
|
-
let subtitle = ''
|
|
67
|
+
let subtitle = ''
|
|
68
68
|
|
|
69
69
|
// For an SRT file, every subtitle has to be in the format as mentioned below:
|
|
70
70
|
//
|
|
@@ -77,13 +77,13 @@ export default function () {
|
|
|
77
77
|
${step.start} --> ${step.end}
|
|
78
78
|
${step.title}
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
`
|
|
81
81
|
}
|
|
82
|
-
})
|
|
82
|
+
})
|
|
83
83
|
|
|
84
|
-
const { dir: artifactsDirectory, name: fileName } = path.parse(test.artifacts.video)
|
|
85
|
-
await fsPromise.writeFile(`${artifactsDirectory}/${fileName}.srt`, subtitle)
|
|
86
|
-
test.artifacts.subtitle = `${artifactsDirectory}/${fileName}.srt
|
|
84
|
+
const { dir: artifactsDirectory, name: fileName } = path.parse(test.artifacts.video)
|
|
85
|
+
await fsPromise.writeFile(`${artifactsDirectory}/${fileName}.srt`, subtitle)
|
|
86
|
+
test.artifacts.subtitle = `${artifactsDirectory}/${fileName}.srt`
|
|
87
87
|
}
|
|
88
|
-
})
|
|
88
|
+
})
|
|
89
89
|
}
|