codeceptjs 4.2.0 → 4.2.1-beta.1
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 +2 -0
- package/bin/codecept.js +11 -0
- package/docs/agents.md +26 -1
- package/docs/ai.md +57 -0
- package/docs/alternative-browsers.md +175 -148
- package/docs/assertions.md +74 -0
- package/docs/bootstrap.md +2 -0
- package/docs/commands.md +45 -0
- package/docs/configuration.md +37 -0
- package/docs/debugging.md +1 -1
- package/docs/helpers/ApiDataFactory.md +20 -20
- package/docs/helpers/Appium.md +1615 -670
- package/docs/helpers/Decision.md +146 -0
- package/docs/helpers/GraphQL.md +31 -31
- package/docs/helpers/GraphQLDataFactory.md +20 -20
- package/docs/helpers/Kitesurf.md +1808 -25
- package/docs/helpers/Lightpanda.md +139 -0
- package/docs/helpers/Obscura.md +1799 -74
- package/docs/helpers/Playwright.md +490 -428
- package/docs/helpers/Puppeteer.md +317 -255
- package/docs/helpers/REST.md +43 -43
- package/docs/helpers/WebDriver.md +237 -175
- package/docs/installation.md +16 -0
- package/docs/mcp.md +7 -1
- package/docs/parallel.md +35 -1
- package/docs/plugins/pause.md +2 -1
- package/docs/plugins/sitemap.md +48 -0
- package/docs/plugins.md +4 -0
- package/lib/ai.js +81 -2
- package/lib/codecept.js +32 -2
- package/lib/command/init.js +22 -0
- package/lib/command/initAgent.js +78 -0
- package/lib/command/run-multiple.js +3 -0
- package/lib/command/run-workers.js +10 -3
- package/lib/command/run.js +3 -2
- package/lib/command/utils.js +15 -0
- package/lib/command/workers/runTests.js +10 -10
- package/lib/container.js +4 -2
- package/lib/effects.js +19 -1
- package/lib/helper/CDPBrowser.js +349 -1384
- package/lib/helper/Decision.js +256 -0
- package/lib/helper/Kitesurf.js +3 -21
- package/lib/helper/Lightpanda.js +137 -0
- package/lib/helper/Obscura.js +21 -238
- package/lib/helper/Playwright.js +6 -4
- package/lib/helper/Puppeteer.js +12 -7
- package/lib/helper/WebDriver.js +2 -1
- package/lib/listener/result.js +4 -0
- package/lib/mocha/bdd.js +19 -1
- package/lib/plugin/junitReporter.js +4 -1
- package/lib/plugin/pause.js +4 -1
- package/lib/plugin/sitemap.js +228 -0
- package/lib/recorder.js +19 -18
- package/lib/result.js +16 -0
- package/lib/step/base.js +12 -0
- package/lib/utils.js +2 -10
- package/lib/webServer.js +193 -0
- package/lib/workers.js +118 -31
- package/package.json +4 -2
- package/typings/index.d.ts +60 -7
- package/typings/promiseBasedTypes.d.ts +772 -824
- package/typings/types.d.ts +866 -833
- package/docs/helpers/CDPBrowser.md +0 -2138
package/lib/helper/Obscura.js
CHANGED
|
@@ -1,43 +1,26 @@
|
|
|
1
|
-
import { spawn } from 'child_process'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import net from 'net'
|
|
4
|
-
import path from 'path'
|
|
5
|
-
import axios from 'axios'
|
|
6
1
|
import CDPBrowser from './CDPBrowser.js'
|
|
7
|
-
import { isFile, isWindows } from '../utils.js'
|
|
8
2
|
|
|
9
3
|
/**
|
|
10
4
|
* ## Configuration
|
|
11
5
|
*
|
|
12
|
-
* This helper should be configured in codecept.conf.js
|
|
13
|
-
* accepts (see its config table), plus:
|
|
6
|
+
* This helper should be configured in codecept.conf.js
|
|
14
7
|
*
|
|
15
8
|
* @typedef ObscuraConfig
|
|
16
9
|
* @type {object}
|
|
17
|
-
* @prop {string} [endpoint] -
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* @prop {
|
|
21
|
-
* (`endpoint` unset). Checked before `OBSCURA_PATH` and `PATH`.
|
|
22
|
-
* @prop {number} [port] - port `obscura serve` listens on, in SELF-MANAGED mode. When unset, a
|
|
23
|
-
* free port is picked automatically, which is what makes `run-workers` collision-free — every
|
|
24
|
-
* worker gets its own instance on its own port with zero config.
|
|
25
|
-
* @prop {number} [serverStartTimeout=15000] - milliseconds to wait for a spawned `obscura serve`
|
|
26
|
-
* to answer `/json/version` before `_connect` gives up.
|
|
10
|
+
* @prop {string} [endpoint] - connect to a running Obscura instead of starting one (ATTACH mode).
|
|
11
|
+
* @prop {string} [binaryPath] - path to the `obscura` executable. Checked before `OBSCURA_PATH` and `PATH`.
|
|
12
|
+
* @prop {number} [port] - port for `obscura serve`. A free port is picked when unset, so parallel workers never collide.
|
|
13
|
+
* @prop {number} [serverStartTimeout=15000] - how long to wait for `obscura serve` to start, in milliseconds.
|
|
27
14
|
*/
|
|
28
15
|
const config = {}
|
|
29
16
|
|
|
30
17
|
/**
|
|
31
|
-
* Obscura drives [Obscura](https://github.com/h4ckf0r0day/obscura), a
|
|
32
|
-
* browser
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* given binary is in — `CDPBrowser._probeCapabilities` detects `layout`/`screenshot` per binary at
|
|
36
|
-
* runtime, so the same helper works against either.
|
|
18
|
+
* Obscura drives [Obscura](https://github.com/h4ckf0r0day/obscura), a lightweight headless
|
|
19
|
+
* browser controlled over the Chrome DevTools Protocol. Default builds from v0.2.0 render pages
|
|
20
|
+
* (layout, screenshots); `-no-render` builds and v0.1.x run JavaScript and the DOM only. The helper
|
|
21
|
+
* detects which build it runs against, so the same config works for both.
|
|
37
22
|
*
|
|
38
|
-
*
|
|
39
|
-
* elements works, it only pins the config presets Obscura requires and manages the `obscura serve`
|
|
40
|
-
* process lifecycle, the same way Playwright manages its own browser process.
|
|
23
|
+
* The helper starts and stops `obscura serve` for you, the same way Playwright manages its browser.
|
|
41
24
|
*
|
|
42
25
|
* > Obscura support is experimental in CodeceptJS 4.2. Pin the browser version in CI and keep a
|
|
43
26
|
* > Playwright/WebDriver job for browser-compatibility coverage.
|
|
@@ -57,8 +40,8 @@ const config = {}
|
|
|
57
40
|
* - **SELF-LAUNCH** — `endpoint` is unset and a binary can be resolved, in order: `binaryPath` in
|
|
58
41
|
* the config, then the `OBSCURA_PATH` environment variable, then `obscura` on `PATH`. The helper
|
|
59
42
|
* spawns `obscura serve --port <port> --allow-private-network --allow-file-access` (`port` from
|
|
60
|
-
* the config, or a free port picked automatically), waits for it to answer, connects, and
|
|
61
|
-
* it
|
|
43
|
+
* the config, or a free port picked automatically), waits for it to answer, connects, and stops
|
|
44
|
+
* it when tests finish.
|
|
62
45
|
* - **COURTESY-ATTACH** — `endpoint` is unset and no binary can be resolved, but something already
|
|
63
46
|
* answers `http://127.0.0.1:9222/json/version` (e.g. `obscura serve` started by hand, or by CI
|
|
64
47
|
* before this process ever ran). The helper attaches to it and never kills it — it isn't the
|
|
@@ -97,13 +80,11 @@ const config = {}
|
|
|
97
80
|
*
|
|
98
81
|
* | option | value | why |
|
|
99
82
|
* | --- | --- | --- |
|
|
100
|
-
* | `input` | `synthetic` |
|
|
101
|
-
* | `xpathPolyfill` | `auto` |
|
|
83
|
+
* | `input` | `synthetic` | clicks that navigate are unreliable in Obscura, so `click` behaves like `forceClick` |
|
|
84
|
+
* | `xpathPolyfill` | `auto` | Obscura's XPath lacks attribute selection and `not()`, so the polyfill is used when needed |
|
|
102
85
|
*
|
|
103
|
-
* `capabilities
|
|
104
|
-
*
|
|
105
|
-
* (`'real'`/`true` on v0.2.0+ default builds, `'none'`/`false` on `-no-render` builds and v0.1.x).
|
|
106
|
-
* Set them explicitly in your own config to skip probing or to force a mode.
|
|
86
|
+
* `capabilities` are detected at runtime from the Obscura build. Set them in your config to skip
|
|
87
|
+
* detection or to force a mode.
|
|
107
88
|
*
|
|
108
89
|
* ## Limitations
|
|
109
90
|
*
|
|
@@ -153,216 +134,18 @@ class Obscura extends CDPBrowser {
|
|
|
153
134
|
* @param {ObscuraConfig} config
|
|
154
135
|
*/
|
|
155
136
|
constructor(config) {
|
|
156
|
-
const explicitEndpoint = !!(config && config.endpoint)
|
|
157
137
|
super({
|
|
158
138
|
input: 'synthetic',
|
|
159
139
|
xpathPolyfill: 'auto',
|
|
160
140
|
...config,
|
|
161
141
|
})
|
|
162
|
-
this.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this.serverError = null
|
|
168
|
-
this.binaryPath = null
|
|
169
|
-
this._selfManagedResolved = false
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* In ATTACH mode, connects exactly as `CDPBrowser._connect` would. In SELF-MANAGED mode,
|
|
174
|
-
* resolves and spawns `obscura serve` (or courtesy-attaches to an already-running one on
|
|
175
|
-
* :9222) exactly once via `_resolveSelfManaged`, then connects.
|
|
176
|
-
*
|
|
177
|
-
* A spawn failure (e.g. a bad binary) is delivered asynchronously by Node as an `error`
|
|
178
|
-
* event; it is recorded on `this.serverError` and surfaced as a rejection from `_waitForServer`
|
|
179
|
-
* instead of crashing the process as an uncaught exception.
|
|
180
|
-
*
|
|
181
|
-
* @protected
|
|
182
|
-
*/
|
|
183
|
-
async _connect() {
|
|
184
|
-
if (this.mode !== 'attach' && !this._selfManagedResolved) {
|
|
185
|
-
await this._resolveSelfManaged()
|
|
186
|
-
this._selfManagedResolved = true
|
|
187
|
-
}
|
|
188
|
-
return super._connect()
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Resolves how to reach Obscura when no explicit `endpoint` was configured, trying, in order:
|
|
193
|
-
* spawn a binary (`binaryPath` config, then `OBSCURA_PATH` env, then `obscura` on `PATH`),
|
|
194
|
-
* courtesy-attach to `http://127.0.0.1:9222` if something already answers there, or throw a
|
|
195
|
-
* loud, actionable error. Sets `this.options.endpoint` as a side effect.
|
|
196
|
-
*
|
|
197
|
-
* @protected
|
|
198
|
-
*/
|
|
199
|
-
async _resolveSelfManaged() {
|
|
200
|
-
const binaryPath = this._resolveBinary()
|
|
201
|
-
if (binaryPath) {
|
|
202
|
-
this.binaryPath = binaryPath
|
|
203
|
-
const port = this.options.port || (await this._findFreePort())
|
|
204
|
-
this.options.port = port
|
|
205
|
-
this.serverError = null
|
|
206
|
-
this.serverProcess = spawn(binaryPath, ['serve', '--port', String(port), '--allow-private-network', '--allow-file-access'], { stdio: 'ignore' })
|
|
207
|
-
this.serverProcess.on('error', err => {
|
|
208
|
-
this.serverError = err
|
|
209
|
-
})
|
|
210
|
-
await this._waitForServer()
|
|
211
|
-
this.options.endpoint = `ws://127.0.0.1:${port}/devtools/browser`
|
|
212
|
-
return
|
|
213
|
-
}
|
|
214
|
-
if (await this._probeUp('http://127.0.0.1:9222/json/version')) {
|
|
215
|
-
this.debugSection('Obscura', 'no binaryPath/OBSCURA_PATH/obscura-on-PATH found — courtesy-attaching to an already-running obscura serve on 127.0.0.1:9222 (this helper will not manage or kill it)')
|
|
216
|
-
this.options.endpoint = 'http://127.0.0.1:9222'
|
|
217
|
-
return
|
|
218
|
-
}
|
|
219
|
-
throw new Error(
|
|
220
|
-
[
|
|
221
|
-
'Obscura has no endpoint configured, no binary could be resolved (checked config.binaryPath, OBSCURA_PATH, and PATH), and nothing answered http://127.0.0.1:9222/json/version.',
|
|
222
|
-
'Start it yourself: obscura serve --port 9222 --allow-private-network',
|
|
223
|
-
'Or let this helper launch it: put a release binary on PATH, set OBSCURA_PATH=/path/to/obscura, or pass binaryPath in the config.',
|
|
224
|
-
'Releases: https://github.com/h4ckf0r0day/obscura/releases',
|
|
225
|
-
].join('\n '),
|
|
226
|
-
)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Resolves the `obscura` binary to spawn, in priority order: `options.binaryPath`, then the
|
|
231
|
-
* `OBSCURA_PATH` environment variable, then `obscura` on `PATH`. The `PATH` lookup walks the
|
|
232
|
-
* directories itself instead of shelling out to `which`, which does not exist on Windows: on
|
|
233
|
-
* Windows every `PATHEXT` suffix is tried, so an `obscura.exe` on `PATH` is found too.
|
|
234
|
-
*
|
|
235
|
-
* @returns {string|null} an absolute or relative path to the binary, or null if none resolved.
|
|
236
|
-
* @protected
|
|
237
|
-
*/
|
|
238
|
-
_resolveBinary() {
|
|
239
|
-
if (this.options.binaryPath) return this.options.binaryPath
|
|
240
|
-
if (process.env.OBSCURA_PATH) return process.env.OBSCURA_PATH
|
|
241
|
-
const windows = isWindows()
|
|
242
|
-
const extensions = windows ? (process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';') : ['']
|
|
243
|
-
for (const entry of (process.env.PATH || '').split(path.delimiter)) {
|
|
244
|
-
const dir = windows ? entry.replace(/^"|"$/g, '') : entry
|
|
245
|
-
if (!dir) continue
|
|
246
|
-
for (const extension of extensions) {
|
|
247
|
-
const candidate = path.join(dir, `obscura${extension}`)
|
|
248
|
-
if (!isFile(candidate)) continue
|
|
249
|
-
try {
|
|
250
|
-
fs.accessSync(candidate, fs.constants.X_OK)
|
|
251
|
-
return candidate
|
|
252
|
-
} catch (e) {
|
|
253
|
-
continue
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return null
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Picks a free TCP port on 127.0.0.1 by briefly listening on port 0 and reading back the OS-assigned
|
|
262
|
-
* port. Used as the SELF-LAUNCH default when `options.port` isn't explicitly set, so multiple
|
|
263
|
-
* `run-workers` workers never collide on the same port.
|
|
264
|
-
*
|
|
265
|
-
* @returns {Promise<number>} a free port.
|
|
266
|
-
* @protected
|
|
267
|
-
*/
|
|
268
|
-
async _findFreePort() {
|
|
269
|
-
return new Promise((resolve, reject) => {
|
|
270
|
-
const srv = net.createServer()
|
|
271
|
-
srv.unref()
|
|
272
|
-
srv.on('error', reject)
|
|
273
|
-
srv.listen(0, '127.0.0.1', () => {
|
|
274
|
-
const { port } = srv.address()
|
|
275
|
-
srv.close(() => resolve(port))
|
|
276
|
-
})
|
|
142
|
+
this._manageServer(config, {
|
|
143
|
+
name: 'obscura',
|
|
144
|
+
envVar: 'OBSCURA_PATH',
|
|
145
|
+
args: port => ['serve', '--port', String(port), '--allow-private-network', '--allow-file-access'],
|
|
146
|
+
releases: 'https://github.com/h4ckf0r0day/obscura/releases',
|
|
277
147
|
})
|
|
278
148
|
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Probes a `/json/version`-style URL with a short timeout, used for the COURTESY-ATTACH check.
|
|
282
|
-
*
|
|
283
|
-
* @param {string} url
|
|
284
|
-
* @returns {Promise<boolean>} true if the URL answered.
|
|
285
|
-
* @protected
|
|
286
|
-
*/
|
|
287
|
-
async _probeUp(url) {
|
|
288
|
-
try {
|
|
289
|
-
await axios.get(url, { timeout: 1000 })
|
|
290
|
-
return true
|
|
291
|
-
} catch (e) {
|
|
292
|
-
return false
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Polls `http://127.0.0.1:<port>/json/version` until `obscura serve` responds, `this.serverError`
|
|
298
|
-
* is set by the spawned process' `error` event, or `options.serverStartTimeout` elapses. The
|
|
299
|
-
* process typically comes up within tens of milliseconds — a 20ms retry interval (down from a
|
|
300
|
-
* previous 200ms) keeps the wasted tail after the server is actually ready small, since this cost
|
|
301
|
-
* is paid once per run and counts directly toward real-world startup latency.
|
|
302
|
-
*
|
|
303
|
-
* @protected
|
|
304
|
-
*/
|
|
305
|
-
async _waitForServer() {
|
|
306
|
-
const timeout = this.options.serverStartTimeout || 15000
|
|
307
|
-
const deadline = Date.now() + timeout
|
|
308
|
-
while (Date.now() < deadline) {
|
|
309
|
-
if (this.serverError) {
|
|
310
|
-
throw new Error(`Failed to start obscura at ${this.binaryPath}: ${this.serverError.message}`)
|
|
311
|
-
}
|
|
312
|
-
try {
|
|
313
|
-
await axios.get(`http://127.0.0.1:${this.options.port}/json/version`)
|
|
314
|
-
return
|
|
315
|
-
} catch (e) {
|
|
316
|
-
await new Promise(r => setTimeout(r, 20))
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
if (this.serverError) {
|
|
320
|
-
throw new Error(`Failed to start obscura at ${this.binaryPath}: ${this.serverError.message}`)
|
|
321
|
-
}
|
|
322
|
-
throw new Error(`obscura serve did not start on port ${this.options.port} within ${timeout}ms`)
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Closes the CDP connection (via `CDPBrowser._finishTest`), then kills the `obscura serve`
|
|
327
|
-
* process spawned by `_connect`, if any (never runs in ATTACH or COURTESY-ATTACH mode, since
|
|
328
|
-
* `this.serverProcess` is only ever set in SELF-LAUNCH mode). Runs in a `finally` so the process
|
|
329
|
-
* is always reaped even if closing the CDP connection throws. Sends `SIGTERM` first and waits for
|
|
330
|
-
* the process to exit; a process that ignores `SIGTERM` is escalated to `SIGKILL` after 5s. The
|
|
331
|
-
* promise only resolves once the child has actually exited (confirmed via the `exit` event, not
|
|
332
|
-
* merely once `SIGKILL` was sent — the kernel needs a moment to reap it), with a final safety-net
|
|
333
|
-
* timeout so a stuck child can never keep the event loop alive even if that confirmation is
|
|
334
|
-
* somehow lost.
|
|
335
|
-
*
|
|
336
|
-
* @protected
|
|
337
|
-
*/
|
|
338
|
-
async _finishTest() {
|
|
339
|
-
try {
|
|
340
|
-
await super._finishTest()
|
|
341
|
-
} finally {
|
|
342
|
-
if (this.serverProcess) {
|
|
343
|
-
const proc = this.serverProcess
|
|
344
|
-
this.serverProcess = null
|
|
345
|
-
await new Promise(resolve => {
|
|
346
|
-
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
347
|
-
resolve()
|
|
348
|
-
return
|
|
349
|
-
}
|
|
350
|
-
let settled = false
|
|
351
|
-
const finish = () => {
|
|
352
|
-
if (settled) return
|
|
353
|
-
settled = true
|
|
354
|
-
clearTimeout(killTimer)
|
|
355
|
-
clearTimeout(safetyTimer)
|
|
356
|
-
resolve()
|
|
357
|
-
}
|
|
358
|
-
proc.once('exit', finish)
|
|
359
|
-
const killTimer = setTimeout(() => proc.kill('SIGKILL'), 5000)
|
|
360
|
-
const safetyTimer = setTimeout(finish, 5500)
|
|
361
|
-
proc.kill()
|
|
362
|
-
})
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
149
|
}
|
|
367
150
|
|
|
368
151
|
export default Obscura
|
package/lib/helper/Playwright.js
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
normalizeSpacesInString,
|
|
28
28
|
normalizePath,
|
|
29
29
|
resolveUrl,
|
|
30
|
+
appendBaseUrl,
|
|
30
31
|
relativeDir,
|
|
31
32
|
getMimeType,
|
|
32
33
|
base64EncodeFile,
|
|
@@ -1365,7 +1366,7 @@ class Playwright extends Helper {
|
|
|
1365
1366
|
}
|
|
1366
1367
|
|
|
1367
1368
|
if (!/^\w+\:(\/\/|.+)/.test(url)) {
|
|
1368
|
-
url =
|
|
1369
|
+
url = appendBaseUrl(this.options.url, url)
|
|
1369
1370
|
this.debug(`Changed URL to base url + relative path: ${url}`)
|
|
1370
1371
|
}
|
|
1371
1372
|
|
|
@@ -3124,7 +3125,7 @@ class Playwright extends Helper {
|
|
|
3124
3125
|
|
|
3125
3126
|
if (url.startsWith('/')) {
|
|
3126
3127
|
// local url
|
|
3127
|
-
url = this.options.url
|
|
3128
|
+
url = appendBaseUrl(this.options.url, url)
|
|
3128
3129
|
this.debugSection('URL', url)
|
|
3129
3130
|
}
|
|
3130
3131
|
|
|
@@ -4427,7 +4428,8 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
4427
4428
|
description = `element ${locator.toString()}`
|
|
4428
4429
|
const els = await this._locate(locator)
|
|
4429
4430
|
assertElementExists(els, locator.toString())
|
|
4430
|
-
allText = await Promise.all(els.map(el => el.innerText
|
|
4431
|
+
allText = (await Promise.all(els.map(el => el.evaluateAll(nodes => nodes.map(node => node.innerText ?? node.textContent))))).flat()
|
|
4432
|
+
assertElementExists(allText, locator.toString())
|
|
4431
4433
|
}
|
|
4432
4434
|
|
|
4433
4435
|
if (store?.currentStep?.opts?.ignoreCase === true) {
|
|
@@ -4945,7 +4947,7 @@ async function highlightActiveElement(element) {
|
|
|
4945
4947
|
}
|
|
4946
4948
|
|
|
4947
4949
|
async function elToString(el, numberOfElements) {
|
|
4948
|
-
const html = await el.evaluate(node => node.outerHTML)
|
|
4950
|
+
const html = await el.evaluate(node => node.outerHTML, undefined, { timeout: 100 }).catch(() => '<unavailable>')
|
|
4949
4951
|
return (
|
|
4950
4952
|
html
|
|
4951
4953
|
.replace(/\n/g, '')
|
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
normalizeSpacesInString,
|
|
31
31
|
normalizePath,
|
|
32
32
|
resolveUrl,
|
|
33
|
+
appendBaseUrl,
|
|
33
34
|
getMimeType,
|
|
34
35
|
base64EncodeFile,
|
|
35
36
|
} from '../utils.js'
|
|
@@ -751,7 +752,7 @@ class Puppeteer extends Helper {
|
|
|
751
752
|
*/
|
|
752
753
|
async amOnPage(url) {
|
|
753
754
|
if (!/^\w+\:\/\//.test(url)) {
|
|
754
|
-
url = this.options.url
|
|
755
|
+
url = appendBaseUrl(this.options.url, url)
|
|
755
756
|
}
|
|
756
757
|
|
|
757
758
|
if (this.options.basicAuth && this.isAuthenticated !== true) {
|
|
@@ -3216,14 +3217,18 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
3216
3217
|
let description
|
|
3217
3218
|
let allText
|
|
3218
3219
|
if (!context) {
|
|
3219
|
-
|
|
3220
|
+
if (this.withinLocator) {
|
|
3221
|
+
let el = await this.context
|
|
3220
3222
|
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
}
|
|
3223
|
+
if (el && !el.getProperty) {
|
|
3224
|
+
el = await this.context.$('body')
|
|
3225
|
+
}
|
|
3225
3226
|
|
|
3226
|
-
|
|
3227
|
+
allText = [await el.evaluate(node => node.innerText)]
|
|
3228
|
+
} else {
|
|
3229
|
+
const ctx = await this._getContext()
|
|
3230
|
+
allText = [await ctx.evaluate(() => document.body.innerText)]
|
|
3231
|
+
}
|
|
3227
3232
|
description = 'web application'
|
|
3228
3233
|
} else {
|
|
3229
3234
|
const locator = new Locator(context, 'css')
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
modifierKeys,
|
|
28
28
|
normalizePath,
|
|
29
29
|
resolveUrl,
|
|
30
|
+
appendBaseUrl,
|
|
30
31
|
getMimeType,
|
|
31
32
|
base64EncodeFile,
|
|
32
33
|
} from '../utils.js'
|
|
@@ -1063,7 +1064,7 @@ class WebDriver extends Helper {
|
|
|
1063
1064
|
let split_url
|
|
1064
1065
|
if (this.options.basicAuth) {
|
|
1065
1066
|
if (url.startsWith('/')) {
|
|
1066
|
-
url = this.options.url
|
|
1067
|
+
url = appendBaseUrl(this.options.url, url)
|
|
1067
1068
|
}
|
|
1068
1069
|
split_url = url.split('//')
|
|
1069
1070
|
url = `${split_url[0]}//${this.options.basicAuth.username}:${this.options.basicAuth.password}@${split_url[1]}`
|
package/lib/listener/result.js
CHANGED
package/lib/mocha/bdd.js
CHANGED
|
@@ -145,15 +145,33 @@ const Before = fn => {
|
|
|
145
145
|
event.dispatcher.on(event.test.started, fn)
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
const GherkinBefore = Before
|
|
149
|
+
|
|
148
150
|
const After = fn => {
|
|
149
151
|
event.dispatcher.on(event.test.finished, fn)
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
const GherkinAfter = After
|
|
155
|
+
|
|
152
156
|
const Fail = fn => {
|
|
153
157
|
event.dispatcher.on(event.test.failed, fn)
|
|
154
158
|
}
|
|
155
159
|
|
|
156
|
-
export {
|
|
160
|
+
export {
|
|
161
|
+
Given,
|
|
162
|
+
When,
|
|
163
|
+
Then,
|
|
164
|
+
And,
|
|
165
|
+
Before,
|
|
166
|
+
GherkinBefore,
|
|
167
|
+
After,
|
|
168
|
+
GherkinAfter,
|
|
169
|
+
Fail,
|
|
170
|
+
matchStep,
|
|
171
|
+
getSteps,
|
|
172
|
+
clearSteps,
|
|
173
|
+
defineParameterType
|
|
174
|
+
}
|
|
157
175
|
|
|
158
176
|
export default {
|
|
159
177
|
Given: addStep,
|
|
@@ -122,7 +122,10 @@ export default function (config = {}) {
|
|
|
122
122
|
|
|
123
123
|
function buildXml(result, config, hookFailures = []) {
|
|
124
124
|
const doc = new DOMImplementation().createDocument(null, null, null)
|
|
125
|
-
const
|
|
125
|
+
const failureKey = (suiteTitle, message) => `${suiteTitle || ''}::${String(message || '').replace(/\s+/g, ' ').trim()}`
|
|
126
|
+
const hookFailureKeys = new Set(hookFailures.map(hf => failureKey(hf.parent && hf.parent.title, hf.err && hf.err.message)))
|
|
127
|
+
const tests = result.tests.filter(test => !hookFailureKeys.has(failureKey(test.parent && test.parent.title, test.err && test.err.message)))
|
|
128
|
+
const allTests = tests.concat(hookFailures)
|
|
126
129
|
const suites = groupBySuite(allTests)
|
|
127
130
|
|
|
128
131
|
const root = doc.createElement('testsuites')
|
package/lib/plugin/pause.js
CHANGED
|
@@ -2,6 +2,7 @@ import event from '../event.js'
|
|
|
2
2
|
import pause from '../pause.js'
|
|
3
3
|
import recorder from '../recorder.js'
|
|
4
4
|
import output from '../output.js'
|
|
5
|
+
import { isEffectActive } from '../effects.js'
|
|
5
6
|
import {
|
|
6
7
|
parsePluginArgs,
|
|
7
8
|
resolveTrigger,
|
|
@@ -27,7 +28,8 @@ import {
|
|
|
27
28
|
*
|
|
28
29
|
* #### `on=` modes
|
|
29
30
|
*
|
|
30
|
-
* * **fail** — pause when a step fails (default)
|
|
31
|
+
* * **fail** — pause when a step fails (default). Failures inside `tryTo` and
|
|
32
|
+
* non-final `retryTo` attempts are ignored
|
|
31
33
|
* * **test** — pause after each test
|
|
32
34
|
* * **step** — pause before the first step (interactive walk-through)
|
|
33
35
|
* * **file** — pause when execution reaches `path=...[;line=...]`
|
|
@@ -69,6 +71,7 @@ function initFailMode() {
|
|
|
69
71
|
})
|
|
70
72
|
|
|
71
73
|
event.dispatcher.on(event.step.failed, () => {
|
|
74
|
+
if (isEffectActive()) return
|
|
72
75
|
failed = true
|
|
73
76
|
})
|
|
74
77
|
|