doc-detective 4.12.1 → 4.13.0
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/dist/common/src/schemas/schemas.json +878 -70
- package/dist/common/src/types/generated/config_v3.d.ts +4 -0
- package/dist/common/src/types/generated/config_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/record_v3.d.ts +4 -0
- package/dist/common/src/types/generated/record_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/report_v3.d.ts +4 -0
- package/dist/common/src/types/generated/report_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/resolvedTests_v3.d.ts +8 -0
- package/dist/common/src/types/generated/resolvedTests_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/spec_v3.d.ts +4 -0
- package/dist/common/src/types/generated/spec_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/step_v3.d.ts +24 -2
- package/dist/common/src/types/generated/step_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/stopRecord_v3.d.ts +20 -2
- package/dist/common/src/types/generated/stopRecord_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/test_v3.d.ts +52 -4
- package/dist/common/src/types/generated/test_v3.d.ts.map +1 -1
- package/dist/core/tests/ffmpegRecorder.d.ts +11 -2
- package/dist/core/tests/ffmpegRecorder.d.ts.map +1 -1
- package/dist/core/tests/ffmpegRecorder.js +155 -8
- package/dist/core/tests/ffmpegRecorder.js.map +1 -1
- package/dist/core/tests/findElement.d.ts.map +1 -1
- package/dist/core/tests/findElement.js +2 -1
- package/dist/core/tests/findElement.js.map +1 -1
- package/dist/core/tests/saveScreenshot.d.ts.map +1 -1
- package/dist/core/tests/saveScreenshot.js +26 -9
- package/dist/core/tests/saveScreenshot.js.map +1 -1
- package/dist/core/tests/startRecording.d.ts.map +1 -1
- package/dist/core/tests/startRecording.js +36 -2
- package/dist/core/tests/startRecording.js.map +1 -1
- package/dist/core/tests/stopRecording.d.ts.map +1 -1
- package/dist/core/tests/stopRecording.js +51 -16
- package/dist/core/tests/stopRecording.js.map +1 -1
- package/dist/core/tests/typeKeys.d.ts.map +1 -1
- package/dist/core/tests/typeKeys.js +3 -2
- package/dist/core/tests/typeKeys.js.map +1 -1
- package/dist/core/tests.d.ts +12 -1
- package/dist/core/tests.d.ts.map +1 -1
- package/dist/core/tests.js +188 -34
- package/dist/core/tests.js.map +1 -1
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/core/utils.js +13 -8
- package/dist/core/utils.js.map +1 -1
- package/dist/debug/provenance.d.ts.map +1 -1
- package/dist/debug/provenance.js +6 -0
- package/dist/debug/provenance.js.map +1 -1
- package/dist/hints/hints.d.ts.map +1 -1
- package/dist/hints/hints.js +19 -0
- package/dist/hints/hints.js.map +1 -1
- package/dist/index.cjs +1588 -558
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +7 -0
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7600,6 +7600,14 @@ var init_schemas = __esm({
|
|
|
7600
7600
|
"false"
|
|
7601
7601
|
]
|
|
7602
7602
|
},
|
|
7603
|
+
name: {
|
|
7604
|
+
type: "string",
|
|
7605
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
7606
|
+
pattern: "\\S",
|
|
7607
|
+
transform: [
|
|
7608
|
+
"trim"
|
|
7609
|
+
]
|
|
7610
|
+
},
|
|
7603
7611
|
engine: {
|
|
7604
7612
|
title: "Recording engine",
|
|
7605
7613
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -7695,6 +7703,14 @@ var init_schemas = __esm({
|
|
|
7695
7703
|
"false"
|
|
7696
7704
|
]
|
|
7697
7705
|
},
|
|
7706
|
+
name: {
|
|
7707
|
+
type: "string",
|
|
7708
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
7709
|
+
pattern: "\\S",
|
|
7710
|
+
transform: [
|
|
7711
|
+
"trim"
|
|
7712
|
+
]
|
|
7713
|
+
},
|
|
7698
7714
|
engine: {
|
|
7699
7715
|
title: "Recording engine",
|
|
7700
7716
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -7930,13 +7946,52 @@ var init_schemas = __esm({
|
|
|
7930
7946
|
stopRecord: {
|
|
7931
7947
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
7932
7948
|
title: "stopRecord",
|
|
7933
|
-
description:
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7949
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
7950
|
+
anyOf: [
|
|
7951
|
+
{
|
|
7952
|
+
type: "boolean",
|
|
7953
|
+
title: "stopRecord (boolean)",
|
|
7954
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
7955
|
+
},
|
|
7956
|
+
{
|
|
7957
|
+
type: "null",
|
|
7958
|
+
title: "stopRecord (null)",
|
|
7959
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
7960
|
+
},
|
|
7961
|
+
{
|
|
7962
|
+
type: "string",
|
|
7963
|
+
title: "stopRecord (name)",
|
|
7964
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
7965
|
+
pattern: "\\S",
|
|
7966
|
+
transform: [
|
|
7967
|
+
"trim"
|
|
7968
|
+
]
|
|
7969
|
+
},
|
|
7970
|
+
{
|
|
7971
|
+
type: "object",
|
|
7972
|
+
title: "stopRecord (detailed)",
|
|
7973
|
+
additionalProperties: false,
|
|
7974
|
+
required: [
|
|
7975
|
+
"name"
|
|
7976
|
+
],
|
|
7977
|
+
properties: {
|
|
7978
|
+
name: {
|
|
7979
|
+
type: "string",
|
|
7980
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
7981
|
+
pattern: "\\S",
|
|
7982
|
+
transform: [
|
|
7983
|
+
"trim"
|
|
7984
|
+
]
|
|
7985
|
+
}
|
|
7986
|
+
}
|
|
7987
|
+
}
|
|
7937
7988
|
],
|
|
7938
7989
|
examples: [
|
|
7939
|
-
true
|
|
7990
|
+
true,
|
|
7991
|
+
"demo",
|
|
7992
|
+
{
|
|
7993
|
+
name: "demo"
|
|
7994
|
+
}
|
|
7940
7995
|
]
|
|
7941
7996
|
}
|
|
7942
7997
|
},
|
|
@@ -10233,6 +10288,11 @@ var init_schemas = __esm({
|
|
|
10233
10288
|
type: "boolean",
|
|
10234
10289
|
default: false
|
|
10235
10290
|
},
|
|
10291
|
+
autoRecord: {
|
|
10292
|
+
description: "If `true`, records a video of every test context that runs in a browser, in addition to any explicit `record` steps. The recording wraps the whole context (it starts before the first step and stops after the last) and always uses the `ffmpeg` engine. Videos are saved in the per-run artifact directory (`<output>/.doc-detective/run-<runId>/`) at paths derived from spec, test, and context IDs (for example, `recordings/<specId>/<testId>/<contextId>.mp4`), so the same context lands on the same relative path in every run's folder for run-over-run comparison. Specs and tests can override this value with their own `autoRecord` fields (test level wins over spec level, which wins over config level). Equivalent to `--auto-record` on the CLI.",
|
|
10293
|
+
type: "boolean",
|
|
10294
|
+
default: false
|
|
10295
|
+
},
|
|
10236
10296
|
autoUpdate: {
|
|
10237
10297
|
description: "If `true` (default), the CLI checks for a newer published `doc-detective` on startup and self-updates before running tests. Updates happen for global (`npm i -g`) and `npx` installs only \u2014 local installs (where `doc-detective` is a project dep) get an informational message instead, since auto-updating would mutate the user's lockfile. CI environments and the `DOC_DETECTIVE_SKIP_AUTO_UPDATE=1` env var also skip the check. Set to `false` to pin to the installed version. Equivalent to `--no-auto-update` on the CLI.",
|
|
10238
10298
|
type: "boolean",
|
|
@@ -16640,6 +16700,14 @@ var init_schemas = __esm({
|
|
|
16640
16700
|
"false"
|
|
16641
16701
|
]
|
|
16642
16702
|
},
|
|
16703
|
+
name: {
|
|
16704
|
+
type: "string",
|
|
16705
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
16706
|
+
pattern: "\\S",
|
|
16707
|
+
transform: [
|
|
16708
|
+
"trim"
|
|
16709
|
+
]
|
|
16710
|
+
},
|
|
16643
16711
|
engine: {
|
|
16644
16712
|
title: "Recording engine",
|
|
16645
16713
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -16735,6 +16803,14 @@ var init_schemas = __esm({
|
|
|
16735
16803
|
"false"
|
|
16736
16804
|
]
|
|
16737
16805
|
},
|
|
16806
|
+
name: {
|
|
16807
|
+
type: "string",
|
|
16808
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
16809
|
+
pattern: "\\S",
|
|
16810
|
+
transform: [
|
|
16811
|
+
"trim"
|
|
16812
|
+
]
|
|
16813
|
+
},
|
|
16738
16814
|
engine: {
|
|
16739
16815
|
title: "Recording engine",
|
|
16740
16816
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -16970,13 +17046,52 @@ var init_schemas = __esm({
|
|
|
16970
17046
|
stopRecord: {
|
|
16971
17047
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
16972
17048
|
title: "stopRecord",
|
|
16973
|
-
description:
|
|
16974
|
-
|
|
16975
|
-
|
|
16976
|
-
|
|
17049
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
17050
|
+
anyOf: [
|
|
17051
|
+
{
|
|
17052
|
+
type: "boolean",
|
|
17053
|
+
title: "stopRecord (boolean)",
|
|
17054
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
17055
|
+
},
|
|
17056
|
+
{
|
|
17057
|
+
type: "null",
|
|
17058
|
+
title: "stopRecord (null)",
|
|
17059
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
17060
|
+
},
|
|
17061
|
+
{
|
|
17062
|
+
type: "string",
|
|
17063
|
+
title: "stopRecord (name)",
|
|
17064
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
17065
|
+
pattern: "\\S",
|
|
17066
|
+
transform: [
|
|
17067
|
+
"trim"
|
|
17068
|
+
]
|
|
17069
|
+
},
|
|
17070
|
+
{
|
|
17071
|
+
type: "object",
|
|
17072
|
+
title: "stopRecord (detailed)",
|
|
17073
|
+
additionalProperties: false,
|
|
17074
|
+
required: [
|
|
17075
|
+
"name"
|
|
17076
|
+
],
|
|
17077
|
+
properties: {
|
|
17078
|
+
name: {
|
|
17079
|
+
type: "string",
|
|
17080
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
17081
|
+
pattern: "\\S",
|
|
17082
|
+
transform: [
|
|
17083
|
+
"trim"
|
|
17084
|
+
]
|
|
17085
|
+
}
|
|
17086
|
+
}
|
|
17087
|
+
}
|
|
16977
17088
|
],
|
|
16978
17089
|
examples: [
|
|
16979
|
-
true
|
|
17090
|
+
true,
|
|
17091
|
+
"demo",
|
|
17092
|
+
{
|
|
17093
|
+
name: "demo"
|
|
17094
|
+
}
|
|
16980
17095
|
]
|
|
16981
17096
|
}
|
|
16982
17097
|
},
|
|
@@ -22955,6 +23070,14 @@ var init_schemas = __esm({
|
|
|
22955
23070
|
"false"
|
|
22956
23071
|
]
|
|
22957
23072
|
},
|
|
23073
|
+
name: {
|
|
23074
|
+
type: "string",
|
|
23075
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
23076
|
+
pattern: "\\S",
|
|
23077
|
+
transform: [
|
|
23078
|
+
"trim"
|
|
23079
|
+
]
|
|
23080
|
+
},
|
|
22958
23081
|
engine: {
|
|
22959
23082
|
title: "Recording engine",
|
|
22960
23083
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -23050,6 +23173,14 @@ var init_schemas = __esm({
|
|
|
23050
23173
|
"false"
|
|
23051
23174
|
]
|
|
23052
23175
|
},
|
|
23176
|
+
name: {
|
|
23177
|
+
type: "string",
|
|
23178
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
23179
|
+
pattern: "\\S",
|
|
23180
|
+
transform: [
|
|
23181
|
+
"trim"
|
|
23182
|
+
]
|
|
23183
|
+
},
|
|
23053
23184
|
engine: {
|
|
23054
23185
|
title: "Recording engine",
|
|
23055
23186
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -30292,6 +30423,14 @@ var init_schemas = __esm({
|
|
|
30292
30423
|
"false"
|
|
30293
30424
|
]
|
|
30294
30425
|
},
|
|
30426
|
+
name: {
|
|
30427
|
+
type: "string",
|
|
30428
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
30429
|
+
pattern: "\\S",
|
|
30430
|
+
transform: [
|
|
30431
|
+
"trim"
|
|
30432
|
+
]
|
|
30433
|
+
},
|
|
30295
30434
|
engine: {
|
|
30296
30435
|
title: "Recording engine",
|
|
30297
30436
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -30387,6 +30526,14 @@ var init_schemas = __esm({
|
|
|
30387
30526
|
"false"
|
|
30388
30527
|
]
|
|
30389
30528
|
},
|
|
30529
|
+
name: {
|
|
30530
|
+
type: "string",
|
|
30531
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
30532
|
+
pattern: "\\S",
|
|
30533
|
+
transform: [
|
|
30534
|
+
"trim"
|
|
30535
|
+
]
|
|
30536
|
+
},
|
|
30390
30537
|
engine: {
|
|
30391
30538
|
title: "Recording engine",
|
|
30392
30539
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -30622,133 +30769,172 @@ var init_schemas = __esm({
|
|
|
30622
30769
|
stopRecord: {
|
|
30623
30770
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
30624
30771
|
title: "stopRecord",
|
|
30625
|
-
description:
|
|
30626
|
-
|
|
30627
|
-
|
|
30628
|
-
|
|
30629
|
-
|
|
30630
|
-
|
|
30631
|
-
true
|
|
30632
|
-
]
|
|
30633
|
-
}
|
|
30634
|
-
},
|
|
30635
|
-
title: "stopRecord"
|
|
30636
|
-
}
|
|
30637
|
-
]
|
|
30638
|
-
},
|
|
30639
|
-
{
|
|
30640
|
-
allOf: [
|
|
30641
|
-
{
|
|
30642
|
-
type: "object",
|
|
30643
|
-
dynamicDefaults: {
|
|
30644
|
-
stepId: "uuid"
|
|
30645
|
-
},
|
|
30646
|
-
properties: {
|
|
30647
|
-
$schema: {
|
|
30648
|
-
description: "JSON Schema for this object.",
|
|
30649
|
-
type: "string",
|
|
30650
|
-
enum: [
|
|
30651
|
-
"https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json"
|
|
30652
|
-
]
|
|
30653
|
-
},
|
|
30654
|
-
stepId: {
|
|
30655
|
-
type: "string",
|
|
30656
|
-
description: "ID of the step."
|
|
30657
|
-
},
|
|
30658
|
-
description: {
|
|
30659
|
-
type: "string",
|
|
30660
|
-
description: "Description of the step."
|
|
30661
|
-
},
|
|
30662
|
-
unsafe: {
|
|
30663
|
-
type: "boolean",
|
|
30664
|
-
description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.",
|
|
30665
|
-
default: false
|
|
30666
|
-
},
|
|
30667
|
-
outputs: {
|
|
30668
|
-
type: "object",
|
|
30669
|
-
description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.",
|
|
30670
|
-
default: {},
|
|
30671
|
-
patternProperties: {
|
|
30672
|
-
"^[A-Za-z0-9_]+$": {
|
|
30673
|
-
type: "string",
|
|
30674
|
-
description: "Runtime expression for a user-defined output value."
|
|
30675
|
-
}
|
|
30676
|
-
},
|
|
30677
|
-
title: "Outputs (step)"
|
|
30678
|
-
},
|
|
30679
|
-
variables: {
|
|
30680
|
-
type: "object",
|
|
30681
|
-
description: "Environment variables to set from user-defined expressions.",
|
|
30682
|
-
default: {},
|
|
30683
|
-
patternProperties: {
|
|
30684
|
-
"^[A-Za-z0-9_]+$": {
|
|
30685
|
-
type: "string",
|
|
30686
|
-
description: "Runtime expression for a user-defined output value."
|
|
30687
|
-
}
|
|
30688
|
-
},
|
|
30689
|
-
title: "Variables (step)"
|
|
30690
|
-
},
|
|
30691
|
-
breakpoint: {
|
|
30692
|
-
type: "boolean",
|
|
30693
|
-
description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.",
|
|
30694
|
-
default: false
|
|
30695
|
-
},
|
|
30696
|
-
location: {
|
|
30697
|
-
type: "object",
|
|
30698
|
-
description: "Source location where this step was detected in the original file. This is system-populated metadata and should not be set manually.",
|
|
30699
|
-
readOnly: true,
|
|
30700
|
-
properties: {
|
|
30701
|
-
line: {
|
|
30702
|
-
type: "integer",
|
|
30703
|
-
description: "1-indexed line number in the source file where the step was detected.",
|
|
30704
|
-
minimum: 1
|
|
30772
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
30773
|
+
anyOf: [
|
|
30774
|
+
{
|
|
30775
|
+
type: "boolean",
|
|
30776
|
+
title: "stopRecord (boolean)",
|
|
30777
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
30705
30778
|
},
|
|
30706
|
-
|
|
30707
|
-
type: "
|
|
30708
|
-
|
|
30709
|
-
|
|
30779
|
+
{
|
|
30780
|
+
type: "null",
|
|
30781
|
+
title: "stopRecord (null)",
|
|
30782
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
30710
30783
|
},
|
|
30711
|
-
|
|
30712
|
-
type: "
|
|
30713
|
-
|
|
30714
|
-
|
|
30784
|
+
{
|
|
30785
|
+
type: "string",
|
|
30786
|
+
title: "stopRecord (name)",
|
|
30787
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
30788
|
+
pattern: "\\S",
|
|
30789
|
+
transform: [
|
|
30790
|
+
"trim"
|
|
30791
|
+
]
|
|
30792
|
+
},
|
|
30793
|
+
{
|
|
30794
|
+
type: "object",
|
|
30795
|
+
title: "stopRecord (detailed)",
|
|
30796
|
+
additionalProperties: false,
|
|
30797
|
+
required: [
|
|
30798
|
+
"name"
|
|
30799
|
+
],
|
|
30800
|
+
properties: {
|
|
30801
|
+
name: {
|
|
30802
|
+
type: "string",
|
|
30803
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
30804
|
+
pattern: "\\S",
|
|
30805
|
+
transform: [
|
|
30806
|
+
"trim"
|
|
30807
|
+
]
|
|
30808
|
+
}
|
|
30809
|
+
}
|
|
30715
30810
|
}
|
|
30716
|
-
},
|
|
30717
|
-
required: [
|
|
30718
|
-
"line",
|
|
30719
|
-
"startIndex",
|
|
30720
|
-
"endIndex"
|
|
30721
30811
|
],
|
|
30722
|
-
additionalProperties: false,
|
|
30723
|
-
title: "Source Location"
|
|
30724
|
-
},
|
|
30725
|
-
autoScreenshot: {
|
|
30726
|
-
type: "string",
|
|
30727
|
-
minLength: 1,
|
|
30728
|
-
pattern: "^(?![A-Za-z]:|[\\\\/])[^\\\\]+$",
|
|
30729
|
-
description: "Path, relative to the run's artifact directory (the report's `runDir`), of the screenshot captured automatically after this step. Always a non-empty, forward-slash, relative path. Present only in test results, when `autoScreenshot` is enabled and the capture succeeded. This is system-populated metadata and should not be set manually.",
|
|
30730
|
-
readOnly: true
|
|
30731
|
-
}
|
|
30732
|
-
},
|
|
30733
|
-
title: "Common"
|
|
30734
|
-
},
|
|
30735
|
-
{
|
|
30736
|
-
title: "loadVariables",
|
|
30737
|
-
type: "object",
|
|
30738
|
-
required: [
|
|
30739
|
-
"loadVariables"
|
|
30740
|
-
],
|
|
30741
|
-
properties: {
|
|
30742
|
-
loadVariables: {
|
|
30743
|
-
$schema: "http://json-schema.org/draft-07/schema#",
|
|
30744
|
-
title: "loadVariables",
|
|
30745
|
-
type: "string",
|
|
30746
|
-
description: "Load environment variables from the specified `.env` file.",
|
|
30747
30812
|
examples: [
|
|
30748
|
-
|
|
30813
|
+
true,
|
|
30814
|
+
"demo",
|
|
30815
|
+
{
|
|
30816
|
+
name: "demo"
|
|
30817
|
+
}
|
|
30749
30818
|
]
|
|
30750
30819
|
}
|
|
30751
|
-
}
|
|
30820
|
+
},
|
|
30821
|
+
title: "stopRecord"
|
|
30822
|
+
}
|
|
30823
|
+
]
|
|
30824
|
+
},
|
|
30825
|
+
{
|
|
30826
|
+
allOf: [
|
|
30827
|
+
{
|
|
30828
|
+
type: "object",
|
|
30829
|
+
dynamicDefaults: {
|
|
30830
|
+
stepId: "uuid"
|
|
30831
|
+
},
|
|
30832
|
+
properties: {
|
|
30833
|
+
$schema: {
|
|
30834
|
+
description: "JSON Schema for this object.",
|
|
30835
|
+
type: "string",
|
|
30836
|
+
enum: [
|
|
30837
|
+
"https://raw.githubusercontent.com/doc-detective/common/refs/heads/main/dist/schemas/step_v3.schema.json"
|
|
30838
|
+
]
|
|
30839
|
+
},
|
|
30840
|
+
stepId: {
|
|
30841
|
+
type: "string",
|
|
30842
|
+
description: "ID of the step."
|
|
30843
|
+
},
|
|
30844
|
+
description: {
|
|
30845
|
+
type: "string",
|
|
30846
|
+
description: "Description of the step."
|
|
30847
|
+
},
|
|
30848
|
+
unsafe: {
|
|
30849
|
+
type: "boolean",
|
|
30850
|
+
description: "Whether or not the step may be unsafe. Unsafe steps may perform actions that could modify the system or environment in unexpected ways. Unsafe steps are only performed within Docker containers or if unsafe steps are enabled with the `allowUnsafeSteps` config property or the `--allow-unsafe` flag.",
|
|
30851
|
+
default: false
|
|
30852
|
+
},
|
|
30853
|
+
outputs: {
|
|
30854
|
+
type: "object",
|
|
30855
|
+
description: "Outputs from step processes and user-defined expressions. Use the `outputs` object to reference outputs in subsequent steps. If a user-defined output matches the key for a step-defined output, the user-defined output takes precedence.",
|
|
30856
|
+
default: {},
|
|
30857
|
+
patternProperties: {
|
|
30858
|
+
"^[A-Za-z0-9_]+$": {
|
|
30859
|
+
type: "string",
|
|
30860
|
+
description: "Runtime expression for a user-defined output value."
|
|
30861
|
+
}
|
|
30862
|
+
},
|
|
30863
|
+
title: "Outputs (step)"
|
|
30864
|
+
},
|
|
30865
|
+
variables: {
|
|
30866
|
+
type: "object",
|
|
30867
|
+
description: "Environment variables to set from user-defined expressions.",
|
|
30868
|
+
default: {},
|
|
30869
|
+
patternProperties: {
|
|
30870
|
+
"^[A-Za-z0-9_]+$": {
|
|
30871
|
+
type: "string",
|
|
30872
|
+
description: "Runtime expression for a user-defined output value."
|
|
30873
|
+
}
|
|
30874
|
+
},
|
|
30875
|
+
title: "Variables (step)"
|
|
30876
|
+
},
|
|
30877
|
+
breakpoint: {
|
|
30878
|
+
type: "boolean",
|
|
30879
|
+
description: "Whether or not this step should act as a breakpoint when debugging is enabled. When `true`, execution will pause at this step when debug mode is enabled.",
|
|
30880
|
+
default: false
|
|
30881
|
+
},
|
|
30882
|
+
location: {
|
|
30883
|
+
type: "object",
|
|
30884
|
+
description: "Source location where this step was detected in the original file. This is system-populated metadata and should not be set manually.",
|
|
30885
|
+
readOnly: true,
|
|
30886
|
+
properties: {
|
|
30887
|
+
line: {
|
|
30888
|
+
type: "integer",
|
|
30889
|
+
description: "1-indexed line number in the source file where the step was detected.",
|
|
30890
|
+
minimum: 1
|
|
30891
|
+
},
|
|
30892
|
+
startIndex: {
|
|
30893
|
+
type: "integer",
|
|
30894
|
+
description: "0-indexed character offset from the start of the source file where the step begins.",
|
|
30895
|
+
minimum: 0
|
|
30896
|
+
},
|
|
30897
|
+
endIndex: {
|
|
30898
|
+
type: "integer",
|
|
30899
|
+
description: "0-indexed character offset from the start of the source file where the step ends (exclusive).",
|
|
30900
|
+
minimum: 0
|
|
30901
|
+
}
|
|
30902
|
+
},
|
|
30903
|
+
required: [
|
|
30904
|
+
"line",
|
|
30905
|
+
"startIndex",
|
|
30906
|
+
"endIndex"
|
|
30907
|
+
],
|
|
30908
|
+
additionalProperties: false,
|
|
30909
|
+
title: "Source Location"
|
|
30910
|
+
},
|
|
30911
|
+
autoScreenshot: {
|
|
30912
|
+
type: "string",
|
|
30913
|
+
minLength: 1,
|
|
30914
|
+
pattern: "^(?![A-Za-z]:|[\\\\/])[^\\\\]+$",
|
|
30915
|
+
description: "Path, relative to the run's artifact directory (the report's `runDir`), of the screenshot captured automatically after this step. Always a non-empty, forward-slash, relative path. Present only in test results, when `autoScreenshot` is enabled and the capture succeeded. This is system-populated metadata and should not be set manually.",
|
|
30916
|
+
readOnly: true
|
|
30917
|
+
}
|
|
30918
|
+
},
|
|
30919
|
+
title: "Common"
|
|
30920
|
+
},
|
|
30921
|
+
{
|
|
30922
|
+
title: "loadVariables",
|
|
30923
|
+
type: "object",
|
|
30924
|
+
required: [
|
|
30925
|
+
"loadVariables"
|
|
30926
|
+
],
|
|
30927
|
+
properties: {
|
|
30928
|
+
loadVariables: {
|
|
30929
|
+
$schema: "http://json-schema.org/draft-07/schema#",
|
|
30930
|
+
title: "loadVariables",
|
|
30931
|
+
type: "string",
|
|
30932
|
+
description: "Load environment variables from the specified `.env` file.",
|
|
30933
|
+
examples: [
|
|
30934
|
+
".env"
|
|
30935
|
+
]
|
|
30936
|
+
}
|
|
30937
|
+
}
|
|
30752
30938
|
}
|
|
30753
30939
|
]
|
|
30754
30940
|
},
|
|
@@ -32925,6 +33111,11 @@ var init_schemas = __esm({
|
|
|
32925
33111
|
type: "boolean",
|
|
32926
33112
|
default: false
|
|
32927
33113
|
},
|
|
33114
|
+
autoRecord: {
|
|
33115
|
+
description: "If `true`, records a video of every test context that runs in a browser, in addition to any explicit `record` steps. The recording wraps the whole context (it starts before the first step and stops after the last) and always uses the `ffmpeg` engine. Videos are saved in the per-run artifact directory (`<output>/.doc-detective/run-<runId>/`) at paths derived from spec, test, and context IDs (for example, `recordings/<specId>/<testId>/<contextId>.mp4`), so the same context lands on the same relative path in every run's folder for run-over-run comparison. Specs and tests can override this value with their own `autoRecord` fields (test level wins over spec level, which wins over config level). Equivalent to `--auto-record` on the CLI.",
|
|
33116
|
+
type: "boolean",
|
|
33117
|
+
default: false
|
|
33118
|
+
},
|
|
32928
33119
|
autoUpdate: {
|
|
32929
33120
|
description: "If `true` (default), the CLI checks for a newer published `doc-detective` on startup and self-updates before running tests. Updates happen for global (`npm i -g`) and `npx` installs only \u2014 local installs (where `doc-detective` is a project dep) get an informational message instead, since auto-updating would mutate the user's lockfile. CI environments and the `DOC_DETECTIVE_SKIP_AUTO_UPDATE=1` env var also skip the check. Set to `false` to pin to the installed version. Equivalent to `--no-auto-update` on the CLI.",
|
|
32930
33121
|
type: "boolean",
|
|
@@ -39332,6 +39523,14 @@ var init_schemas = __esm({
|
|
|
39332
39523
|
"false"
|
|
39333
39524
|
]
|
|
39334
39525
|
},
|
|
39526
|
+
name: {
|
|
39527
|
+
type: "string",
|
|
39528
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
39529
|
+
pattern: "\\S",
|
|
39530
|
+
transform: [
|
|
39531
|
+
"trim"
|
|
39532
|
+
]
|
|
39533
|
+
},
|
|
39335
39534
|
engine: {
|
|
39336
39535
|
title: "Recording engine",
|
|
39337
39536
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -39427,6 +39626,14 @@ var init_schemas = __esm({
|
|
|
39427
39626
|
"false"
|
|
39428
39627
|
]
|
|
39429
39628
|
},
|
|
39629
|
+
name: {
|
|
39630
|
+
type: "string",
|
|
39631
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
39632
|
+
pattern: "\\S",
|
|
39633
|
+
transform: [
|
|
39634
|
+
"trim"
|
|
39635
|
+
]
|
|
39636
|
+
},
|
|
39430
39637
|
engine: {
|
|
39431
39638
|
title: "Recording engine",
|
|
39432
39639
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -39662,13 +39869,52 @@ var init_schemas = __esm({
|
|
|
39662
39869
|
stopRecord: {
|
|
39663
39870
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
39664
39871
|
title: "stopRecord",
|
|
39665
|
-
description:
|
|
39666
|
-
|
|
39667
|
-
|
|
39668
|
-
|
|
39872
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
39873
|
+
anyOf: [
|
|
39874
|
+
{
|
|
39875
|
+
type: "boolean",
|
|
39876
|
+
title: "stopRecord (boolean)",
|
|
39877
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
39878
|
+
},
|
|
39879
|
+
{
|
|
39880
|
+
type: "null",
|
|
39881
|
+
title: "stopRecord (null)",
|
|
39882
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
39883
|
+
},
|
|
39884
|
+
{
|
|
39885
|
+
type: "string",
|
|
39886
|
+
title: "stopRecord (name)",
|
|
39887
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
39888
|
+
pattern: "\\S",
|
|
39889
|
+
transform: [
|
|
39890
|
+
"trim"
|
|
39891
|
+
]
|
|
39892
|
+
},
|
|
39893
|
+
{
|
|
39894
|
+
type: "object",
|
|
39895
|
+
title: "stopRecord (detailed)",
|
|
39896
|
+
additionalProperties: false,
|
|
39897
|
+
required: [
|
|
39898
|
+
"name"
|
|
39899
|
+
],
|
|
39900
|
+
properties: {
|
|
39901
|
+
name: {
|
|
39902
|
+
type: "string",
|
|
39903
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
39904
|
+
pattern: "\\S",
|
|
39905
|
+
transform: [
|
|
39906
|
+
"trim"
|
|
39907
|
+
]
|
|
39908
|
+
}
|
|
39909
|
+
}
|
|
39910
|
+
}
|
|
39669
39911
|
],
|
|
39670
39912
|
examples: [
|
|
39671
|
-
true
|
|
39913
|
+
true,
|
|
39914
|
+
"demo",
|
|
39915
|
+
{
|
|
39916
|
+
name: "demo"
|
|
39917
|
+
}
|
|
39672
39918
|
]
|
|
39673
39919
|
}
|
|
39674
39920
|
},
|
|
@@ -42158,6 +42404,10 @@ var init_schemas = __esm({
|
|
|
42158
42404
|
type: "boolean",
|
|
42159
42405
|
description: "If `true`, captures a screenshot after every step in this spec's tests that runs in a browser. Overrides the config-level `autoScreenshot`; individual tests can override this value with their own `autoScreenshot`. When unset, defers to the config level."
|
|
42160
42406
|
},
|
|
42407
|
+
autoRecord: {
|
|
42408
|
+
type: "boolean",
|
|
42409
|
+
description: "If `true`, records a video of every browser context in this spec's tests. Overrides the config-level `autoRecord`; individual tests can override this value with their own `autoRecord`. When unset, defers to the config level."
|
|
42410
|
+
},
|
|
42161
42411
|
tests: {
|
|
42162
42412
|
description: "[Tests](test) to perform.",
|
|
42163
42413
|
type: "array",
|
|
@@ -42191,6 +42441,10 @@ var init_schemas = __esm({
|
|
|
42191
42441
|
type: "boolean",
|
|
42192
42442
|
description: "If `true`, captures a screenshot after every step in this test that runs in a browser. Overrides `autoScreenshot` set at the spec or config level. When unset, defers to the spec level, then the config level."
|
|
42193
42443
|
},
|
|
42444
|
+
autoRecord: {
|
|
42445
|
+
type: "boolean",
|
|
42446
|
+
description: "If `true`, records a video of every browser context in this test. Overrides `autoRecord` set at the spec or config level. When unset, defers to the spec level, then the config level."
|
|
42447
|
+
},
|
|
42194
42448
|
runOn: {
|
|
42195
42449
|
type: "array",
|
|
42196
42450
|
description: "Contexts to run the test in. Overrides contexts defined at the config and spec levels.",
|
|
@@ -49047,6 +49301,14 @@ var init_schemas = __esm({
|
|
|
49047
49301
|
"false"
|
|
49048
49302
|
]
|
|
49049
49303
|
},
|
|
49304
|
+
name: {
|
|
49305
|
+
type: "string",
|
|
49306
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
49307
|
+
pattern: "\\S",
|
|
49308
|
+
transform: [
|
|
49309
|
+
"trim"
|
|
49310
|
+
]
|
|
49311
|
+
},
|
|
49050
49312
|
engine: {
|
|
49051
49313
|
title: "Recording engine",
|
|
49052
49314
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -49142,6 +49404,14 @@ var init_schemas = __esm({
|
|
|
49142
49404
|
"false"
|
|
49143
49405
|
]
|
|
49144
49406
|
},
|
|
49407
|
+
name: {
|
|
49408
|
+
type: "string",
|
|
49409
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
49410
|
+
pattern: "\\S",
|
|
49411
|
+
transform: [
|
|
49412
|
+
"trim"
|
|
49413
|
+
]
|
|
49414
|
+
},
|
|
49145
49415
|
engine: {
|
|
49146
49416
|
title: "Recording engine",
|
|
49147
49417
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -49377,13 +49647,52 @@ var init_schemas = __esm({
|
|
|
49377
49647
|
stopRecord: {
|
|
49378
49648
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
49379
49649
|
title: "stopRecord",
|
|
49380
|
-
description:
|
|
49381
|
-
|
|
49382
|
-
|
|
49383
|
-
|
|
49650
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
49651
|
+
anyOf: [
|
|
49652
|
+
{
|
|
49653
|
+
type: "boolean",
|
|
49654
|
+
title: "stopRecord (boolean)",
|
|
49655
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
49656
|
+
},
|
|
49657
|
+
{
|
|
49658
|
+
type: "null",
|
|
49659
|
+
title: "stopRecord (null)",
|
|
49660
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
49661
|
+
},
|
|
49662
|
+
{
|
|
49663
|
+
type: "string",
|
|
49664
|
+
title: "stopRecord (name)",
|
|
49665
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
49666
|
+
pattern: "\\S",
|
|
49667
|
+
transform: [
|
|
49668
|
+
"trim"
|
|
49669
|
+
]
|
|
49670
|
+
},
|
|
49671
|
+
{
|
|
49672
|
+
type: "object",
|
|
49673
|
+
title: "stopRecord (detailed)",
|
|
49674
|
+
additionalProperties: false,
|
|
49675
|
+
required: [
|
|
49676
|
+
"name"
|
|
49677
|
+
],
|
|
49678
|
+
properties: {
|
|
49679
|
+
name: {
|
|
49680
|
+
type: "string",
|
|
49681
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
49682
|
+
pattern: "\\S",
|
|
49683
|
+
transform: [
|
|
49684
|
+
"trim"
|
|
49685
|
+
]
|
|
49686
|
+
}
|
|
49687
|
+
}
|
|
49688
|
+
}
|
|
49384
49689
|
],
|
|
49385
49690
|
examples: [
|
|
49386
|
-
true
|
|
49691
|
+
true,
|
|
49692
|
+
"demo",
|
|
49693
|
+
{
|
|
49694
|
+
name: "demo"
|
|
49695
|
+
}
|
|
49387
49696
|
]
|
|
49388
49697
|
}
|
|
49389
49698
|
},
|
|
@@ -57521,6 +57830,14 @@ var init_schemas = __esm({
|
|
|
57521
57830
|
"false"
|
|
57522
57831
|
]
|
|
57523
57832
|
},
|
|
57833
|
+
name: {
|
|
57834
|
+
type: "string",
|
|
57835
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
57836
|
+
pattern: "\\S",
|
|
57837
|
+
transform: [
|
|
57838
|
+
"trim"
|
|
57839
|
+
]
|
|
57840
|
+
},
|
|
57524
57841
|
engine: {
|
|
57525
57842
|
title: "Recording engine",
|
|
57526
57843
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -57616,6 +57933,14 @@ var init_schemas = __esm({
|
|
|
57616
57933
|
"false"
|
|
57617
57934
|
]
|
|
57618
57935
|
},
|
|
57936
|
+
name: {
|
|
57937
|
+
type: "string",
|
|
57938
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
57939
|
+
pattern: "\\S",
|
|
57940
|
+
transform: [
|
|
57941
|
+
"trim"
|
|
57942
|
+
]
|
|
57943
|
+
},
|
|
57619
57944
|
engine: {
|
|
57620
57945
|
title: "Recording engine",
|
|
57621
57946
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -57851,13 +58176,52 @@ var init_schemas = __esm({
|
|
|
57851
58176
|
stopRecord: {
|
|
57852
58177
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
57853
58178
|
title: "stopRecord",
|
|
57854
|
-
description:
|
|
57855
|
-
|
|
57856
|
-
|
|
57857
|
-
|
|
58179
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
58180
|
+
anyOf: [
|
|
58181
|
+
{
|
|
58182
|
+
type: "boolean",
|
|
58183
|
+
title: "stopRecord (boolean)",
|
|
58184
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
58185
|
+
},
|
|
58186
|
+
{
|
|
58187
|
+
type: "null",
|
|
58188
|
+
title: "stopRecord (null)",
|
|
58189
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
58190
|
+
},
|
|
58191
|
+
{
|
|
58192
|
+
type: "string",
|
|
58193
|
+
title: "stopRecord (name)",
|
|
58194
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
58195
|
+
pattern: "\\S",
|
|
58196
|
+
transform: [
|
|
58197
|
+
"trim"
|
|
58198
|
+
]
|
|
58199
|
+
},
|
|
58200
|
+
{
|
|
58201
|
+
type: "object",
|
|
58202
|
+
title: "stopRecord (detailed)",
|
|
58203
|
+
additionalProperties: false,
|
|
58204
|
+
required: [
|
|
58205
|
+
"name"
|
|
58206
|
+
],
|
|
58207
|
+
properties: {
|
|
58208
|
+
name: {
|
|
58209
|
+
type: "string",
|
|
58210
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
58211
|
+
pattern: "\\S",
|
|
58212
|
+
transform: [
|
|
58213
|
+
"trim"
|
|
58214
|
+
]
|
|
58215
|
+
}
|
|
58216
|
+
}
|
|
58217
|
+
}
|
|
57858
58218
|
],
|
|
57859
58219
|
examples: [
|
|
57860
|
-
true
|
|
58220
|
+
true,
|
|
58221
|
+
"demo",
|
|
58222
|
+
{
|
|
58223
|
+
name: "demo"
|
|
58224
|
+
}
|
|
57861
58225
|
]
|
|
57862
58226
|
}
|
|
57863
58227
|
},
|
|
@@ -60834,6 +61198,10 @@ var init_schemas = __esm({
|
|
|
60834
61198
|
type: "boolean",
|
|
60835
61199
|
description: "If `true`, captures a screenshot after every step in this spec's tests that runs in a browser. Overrides the config-level `autoScreenshot`; individual tests can override this value with their own `autoScreenshot`. When unset, defers to the config level."
|
|
60836
61200
|
},
|
|
61201
|
+
autoRecord: {
|
|
61202
|
+
type: "boolean",
|
|
61203
|
+
description: "If `true`, records a video of every browser context in this spec's tests. Overrides the config-level `autoRecord`; individual tests can override this value with their own `autoRecord`. When unset, defers to the config level."
|
|
61204
|
+
},
|
|
60837
61205
|
tests: {
|
|
60838
61206
|
description: "[Tests](test) to perform.",
|
|
60839
61207
|
type: "array",
|
|
@@ -60867,6 +61235,10 @@ var init_schemas = __esm({
|
|
|
60867
61235
|
type: "boolean",
|
|
60868
61236
|
description: "If `true`, captures a screenshot after every step in this test that runs in a browser. Overrides `autoScreenshot` set at the spec or config level. When unset, defers to the spec level, then the config level."
|
|
60869
61237
|
},
|
|
61238
|
+
autoRecord: {
|
|
61239
|
+
type: "boolean",
|
|
61240
|
+
description: "If `true`, records a video of every browser context in this test. Overrides `autoRecord` set at the spec or config level. When unset, defers to the spec level, then the config level."
|
|
61241
|
+
},
|
|
60870
61242
|
runOn: {
|
|
60871
61243
|
type: "array",
|
|
60872
61244
|
description: "Contexts to run the test in. Overrides contexts defined at the config and spec levels.",
|
|
@@ -67723,6 +68095,14 @@ var init_schemas = __esm({
|
|
|
67723
68095
|
"false"
|
|
67724
68096
|
]
|
|
67725
68097
|
},
|
|
68098
|
+
name: {
|
|
68099
|
+
type: "string",
|
|
68100
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
68101
|
+
pattern: "\\S",
|
|
68102
|
+
transform: [
|
|
68103
|
+
"trim"
|
|
68104
|
+
]
|
|
68105
|
+
},
|
|
67726
68106
|
engine: {
|
|
67727
68107
|
title: "Recording engine",
|
|
67728
68108
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -67818,6 +68198,14 @@ var init_schemas = __esm({
|
|
|
67818
68198
|
"false"
|
|
67819
68199
|
]
|
|
67820
68200
|
},
|
|
68201
|
+
name: {
|
|
68202
|
+
type: "string",
|
|
68203
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
68204
|
+
pattern: "\\S",
|
|
68205
|
+
transform: [
|
|
68206
|
+
"trim"
|
|
68207
|
+
]
|
|
68208
|
+
},
|
|
67821
68209
|
engine: {
|
|
67822
68210
|
title: "Recording engine",
|
|
67823
68211
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -68053,13 +68441,52 @@ var init_schemas = __esm({
|
|
|
68053
68441
|
stopRecord: {
|
|
68054
68442
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
68055
68443
|
title: "stopRecord",
|
|
68056
|
-
description:
|
|
68057
|
-
|
|
68058
|
-
|
|
68059
|
-
|
|
68444
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
68445
|
+
anyOf: [
|
|
68446
|
+
{
|
|
68447
|
+
type: "boolean",
|
|
68448
|
+
title: "stopRecord (boolean)",
|
|
68449
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
68450
|
+
},
|
|
68451
|
+
{
|
|
68452
|
+
type: "null",
|
|
68453
|
+
title: "stopRecord (null)",
|
|
68454
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
68455
|
+
},
|
|
68456
|
+
{
|
|
68457
|
+
type: "string",
|
|
68458
|
+
title: "stopRecord (name)",
|
|
68459
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
68460
|
+
pattern: "\\S",
|
|
68461
|
+
transform: [
|
|
68462
|
+
"trim"
|
|
68463
|
+
]
|
|
68464
|
+
},
|
|
68465
|
+
{
|
|
68466
|
+
type: "object",
|
|
68467
|
+
title: "stopRecord (detailed)",
|
|
68468
|
+
additionalProperties: false,
|
|
68469
|
+
required: [
|
|
68470
|
+
"name"
|
|
68471
|
+
],
|
|
68472
|
+
properties: {
|
|
68473
|
+
name: {
|
|
68474
|
+
type: "string",
|
|
68475
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
68476
|
+
pattern: "\\S",
|
|
68477
|
+
transform: [
|
|
68478
|
+
"trim"
|
|
68479
|
+
]
|
|
68480
|
+
}
|
|
68481
|
+
}
|
|
68482
|
+
}
|
|
68060
68483
|
],
|
|
68061
68484
|
examples: [
|
|
68062
|
-
true
|
|
68485
|
+
true,
|
|
68486
|
+
"demo",
|
|
68487
|
+
{
|
|
68488
|
+
name: "demo"
|
|
68489
|
+
}
|
|
68063
68490
|
]
|
|
68064
68491
|
}
|
|
68065
68492
|
},
|
|
@@ -76197,6 +76624,14 @@ var init_schemas = __esm({
|
|
|
76197
76624
|
"false"
|
|
76198
76625
|
]
|
|
76199
76626
|
},
|
|
76627
|
+
name: {
|
|
76628
|
+
type: "string",
|
|
76629
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
76630
|
+
pattern: "\\S",
|
|
76631
|
+
transform: [
|
|
76632
|
+
"trim"
|
|
76633
|
+
]
|
|
76634
|
+
},
|
|
76200
76635
|
engine: {
|
|
76201
76636
|
title: "Recording engine",
|
|
76202
76637
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -76292,6 +76727,14 @@ var init_schemas = __esm({
|
|
|
76292
76727
|
"false"
|
|
76293
76728
|
]
|
|
76294
76729
|
},
|
|
76730
|
+
name: {
|
|
76731
|
+
type: "string",
|
|
76732
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
76733
|
+
pattern: "\\S",
|
|
76734
|
+
transform: [
|
|
76735
|
+
"trim"
|
|
76736
|
+
]
|
|
76737
|
+
},
|
|
76295
76738
|
engine: {
|
|
76296
76739
|
title: "Recording engine",
|
|
76297
76740
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -76527,13 +76970,52 @@ var init_schemas = __esm({
|
|
|
76527
76970
|
stopRecord: {
|
|
76528
76971
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
76529
76972
|
title: "stopRecord",
|
|
76530
|
-
description:
|
|
76531
|
-
|
|
76532
|
-
|
|
76533
|
-
|
|
76973
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
76974
|
+
anyOf: [
|
|
76975
|
+
{
|
|
76976
|
+
type: "boolean",
|
|
76977
|
+
title: "stopRecord (boolean)",
|
|
76978
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
76979
|
+
},
|
|
76980
|
+
{
|
|
76981
|
+
type: "null",
|
|
76982
|
+
title: "stopRecord (null)",
|
|
76983
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
76984
|
+
},
|
|
76985
|
+
{
|
|
76986
|
+
type: "string",
|
|
76987
|
+
title: "stopRecord (name)",
|
|
76988
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
76989
|
+
pattern: "\\S",
|
|
76990
|
+
transform: [
|
|
76991
|
+
"trim"
|
|
76992
|
+
]
|
|
76993
|
+
},
|
|
76994
|
+
{
|
|
76995
|
+
type: "object",
|
|
76996
|
+
title: "stopRecord (detailed)",
|
|
76997
|
+
additionalProperties: false,
|
|
76998
|
+
required: [
|
|
76999
|
+
"name"
|
|
77000
|
+
],
|
|
77001
|
+
properties: {
|
|
77002
|
+
name: {
|
|
77003
|
+
type: "string",
|
|
77004
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
77005
|
+
pattern: "\\S",
|
|
77006
|
+
transform: [
|
|
77007
|
+
"trim"
|
|
77008
|
+
]
|
|
77009
|
+
}
|
|
77010
|
+
}
|
|
77011
|
+
}
|
|
76534
77012
|
],
|
|
76535
77013
|
examples: [
|
|
76536
|
-
true
|
|
77014
|
+
true,
|
|
77015
|
+
"demo",
|
|
77016
|
+
{
|
|
77017
|
+
name: "demo"
|
|
77018
|
+
}
|
|
76537
77019
|
]
|
|
76538
77020
|
}
|
|
76539
77021
|
},
|
|
@@ -80888,6 +81370,10 @@ var init_schemas = __esm({
|
|
|
80888
81370
|
type: "boolean",
|
|
80889
81371
|
description: "If `true`, captures a screenshot after every step in this spec's tests that runs in a browser. Overrides the config-level `autoScreenshot`; individual tests can override this value with their own `autoScreenshot`. When unset, defers to the config level."
|
|
80890
81372
|
},
|
|
81373
|
+
autoRecord: {
|
|
81374
|
+
type: "boolean",
|
|
81375
|
+
description: "If `true`, records a video of every browser context in this spec's tests. Overrides the config-level `autoRecord`; individual tests can override this value with their own `autoRecord`. When unset, defers to the config level."
|
|
81376
|
+
},
|
|
80891
81377
|
tests: {
|
|
80892
81378
|
description: "[Tests](test) to perform.",
|
|
80893
81379
|
type: "array",
|
|
@@ -80921,6 +81407,10 @@ var init_schemas = __esm({
|
|
|
80921
81407
|
type: "boolean",
|
|
80922
81408
|
description: "If `true`, captures a screenshot after every step in this test that runs in a browser. Overrides `autoScreenshot` set at the spec or config level. When unset, defers to the spec level, then the config level."
|
|
80923
81409
|
},
|
|
81410
|
+
autoRecord: {
|
|
81411
|
+
type: "boolean",
|
|
81412
|
+
description: "If `true`, records a video of every browser context in this test. Overrides `autoRecord` set at the spec or config level. When unset, defers to the spec level, then the config level."
|
|
81413
|
+
},
|
|
80924
81414
|
runOn: {
|
|
80925
81415
|
type: "array",
|
|
80926
81416
|
description: "Contexts to run the test in. Overrides contexts defined at the config and spec levels.",
|
|
@@ -87777,6 +88267,14 @@ var init_schemas = __esm({
|
|
|
87777
88267
|
"false"
|
|
87778
88268
|
]
|
|
87779
88269
|
},
|
|
88270
|
+
name: {
|
|
88271
|
+
type: "string",
|
|
88272
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
88273
|
+
pattern: "\\S",
|
|
88274
|
+
transform: [
|
|
88275
|
+
"trim"
|
|
88276
|
+
]
|
|
88277
|
+
},
|
|
87780
88278
|
engine: {
|
|
87781
88279
|
title: "Recording engine",
|
|
87782
88280
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -87872,6 +88370,14 @@ var init_schemas = __esm({
|
|
|
87872
88370
|
"false"
|
|
87873
88371
|
]
|
|
87874
88372
|
},
|
|
88373
|
+
name: {
|
|
88374
|
+
type: "string",
|
|
88375
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
88376
|
+
pattern: "\\S",
|
|
88377
|
+
transform: [
|
|
88378
|
+
"trim"
|
|
88379
|
+
]
|
|
88380
|
+
},
|
|
87875
88381
|
engine: {
|
|
87876
88382
|
title: "Recording engine",
|
|
87877
88383
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -88107,13 +88613,52 @@ var init_schemas = __esm({
|
|
|
88107
88613
|
stopRecord: {
|
|
88108
88614
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
88109
88615
|
title: "stopRecord",
|
|
88110
|
-
description:
|
|
88111
|
-
|
|
88112
|
-
|
|
88113
|
-
|
|
88616
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
88617
|
+
anyOf: [
|
|
88618
|
+
{
|
|
88619
|
+
type: "boolean",
|
|
88620
|
+
title: "stopRecord (boolean)",
|
|
88621
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
88622
|
+
},
|
|
88623
|
+
{
|
|
88624
|
+
type: "null",
|
|
88625
|
+
title: "stopRecord (null)",
|
|
88626
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
88627
|
+
},
|
|
88628
|
+
{
|
|
88629
|
+
type: "string",
|
|
88630
|
+
title: "stopRecord (name)",
|
|
88631
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
88632
|
+
pattern: "\\S",
|
|
88633
|
+
transform: [
|
|
88634
|
+
"trim"
|
|
88635
|
+
]
|
|
88636
|
+
},
|
|
88637
|
+
{
|
|
88638
|
+
type: "object",
|
|
88639
|
+
title: "stopRecord (detailed)",
|
|
88640
|
+
additionalProperties: false,
|
|
88641
|
+
required: [
|
|
88642
|
+
"name"
|
|
88643
|
+
],
|
|
88644
|
+
properties: {
|
|
88645
|
+
name: {
|
|
88646
|
+
type: "string",
|
|
88647
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
88648
|
+
pattern: "\\S",
|
|
88649
|
+
transform: [
|
|
88650
|
+
"trim"
|
|
88651
|
+
]
|
|
88652
|
+
}
|
|
88653
|
+
}
|
|
88654
|
+
}
|
|
88114
88655
|
],
|
|
88115
88656
|
examples: [
|
|
88116
|
-
true
|
|
88657
|
+
true,
|
|
88658
|
+
"demo",
|
|
88659
|
+
{
|
|
88660
|
+
name: "demo"
|
|
88661
|
+
}
|
|
88117
88662
|
]
|
|
88118
88663
|
}
|
|
88119
88664
|
},
|
|
@@ -96251,6 +96796,14 @@ var init_schemas = __esm({
|
|
|
96251
96796
|
"false"
|
|
96252
96797
|
]
|
|
96253
96798
|
},
|
|
96799
|
+
name: {
|
|
96800
|
+
type: "string",
|
|
96801
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
96802
|
+
pattern: "\\S",
|
|
96803
|
+
transform: [
|
|
96804
|
+
"trim"
|
|
96805
|
+
]
|
|
96806
|
+
},
|
|
96254
96807
|
engine: {
|
|
96255
96808
|
title: "Recording engine",
|
|
96256
96809
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -96346,6 +96899,14 @@ var init_schemas = __esm({
|
|
|
96346
96899
|
"false"
|
|
96347
96900
|
]
|
|
96348
96901
|
},
|
|
96902
|
+
name: {
|
|
96903
|
+
type: "string",
|
|
96904
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
96905
|
+
pattern: "\\S",
|
|
96906
|
+
transform: [
|
|
96907
|
+
"trim"
|
|
96908
|
+
]
|
|
96909
|
+
},
|
|
96349
96910
|
engine: {
|
|
96350
96911
|
title: "Recording engine",
|
|
96351
96912
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -96581,13 +97142,52 @@ var init_schemas = __esm({
|
|
|
96581
97142
|
stopRecord: {
|
|
96582
97143
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
96583
97144
|
title: "stopRecord",
|
|
96584
|
-
description:
|
|
96585
|
-
|
|
96586
|
-
|
|
96587
|
-
|
|
97145
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
97146
|
+
anyOf: [
|
|
97147
|
+
{
|
|
97148
|
+
type: "boolean",
|
|
97149
|
+
title: "stopRecord (boolean)",
|
|
97150
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
97151
|
+
},
|
|
97152
|
+
{
|
|
97153
|
+
type: "null",
|
|
97154
|
+
title: "stopRecord (null)",
|
|
97155
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
97156
|
+
},
|
|
97157
|
+
{
|
|
97158
|
+
type: "string",
|
|
97159
|
+
title: "stopRecord (name)",
|
|
97160
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
97161
|
+
pattern: "\\S",
|
|
97162
|
+
transform: [
|
|
97163
|
+
"trim"
|
|
97164
|
+
]
|
|
97165
|
+
},
|
|
97166
|
+
{
|
|
97167
|
+
type: "object",
|
|
97168
|
+
title: "stopRecord (detailed)",
|
|
97169
|
+
additionalProperties: false,
|
|
97170
|
+
required: [
|
|
97171
|
+
"name"
|
|
97172
|
+
],
|
|
97173
|
+
properties: {
|
|
97174
|
+
name: {
|
|
97175
|
+
type: "string",
|
|
97176
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
97177
|
+
pattern: "\\S",
|
|
97178
|
+
transform: [
|
|
97179
|
+
"trim"
|
|
97180
|
+
]
|
|
97181
|
+
}
|
|
97182
|
+
}
|
|
97183
|
+
}
|
|
96588
97184
|
],
|
|
96589
97185
|
examples: [
|
|
96590
|
-
true
|
|
97186
|
+
true,
|
|
97187
|
+
"demo",
|
|
97188
|
+
{
|
|
97189
|
+
name: "demo"
|
|
97190
|
+
}
|
|
96591
97191
|
]
|
|
96592
97192
|
}
|
|
96593
97193
|
},
|
|
@@ -105005,6 +105605,14 @@ var init_schemas = __esm({
|
|
|
105005
105605
|
"false"
|
|
105006
105606
|
]
|
|
105007
105607
|
},
|
|
105608
|
+
name: {
|
|
105609
|
+
type: "string",
|
|
105610
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
105611
|
+
pattern: "\\S",
|
|
105612
|
+
transform: [
|
|
105613
|
+
"trim"
|
|
105614
|
+
]
|
|
105615
|
+
},
|
|
105008
105616
|
engine: {
|
|
105009
105617
|
title: "Recording engine",
|
|
105010
105618
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -105100,6 +105708,14 @@ var init_schemas = __esm({
|
|
|
105100
105708
|
"false"
|
|
105101
105709
|
]
|
|
105102
105710
|
},
|
|
105711
|
+
name: {
|
|
105712
|
+
type: "string",
|
|
105713
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
105714
|
+
pattern: "\\S",
|
|
105715
|
+
transform: [
|
|
105716
|
+
"trim"
|
|
105717
|
+
]
|
|
105718
|
+
},
|
|
105103
105719
|
engine: {
|
|
105104
105720
|
title: "Recording engine",
|
|
105105
105721
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -105335,13 +105951,52 @@ var init_schemas = __esm({
|
|
|
105335
105951
|
stopRecord: {
|
|
105336
105952
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
105337
105953
|
title: "stopRecord",
|
|
105338
|
-
description:
|
|
105339
|
-
|
|
105340
|
-
|
|
105341
|
-
|
|
105954
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
105955
|
+
anyOf: [
|
|
105956
|
+
{
|
|
105957
|
+
type: "boolean",
|
|
105958
|
+
title: "stopRecord (boolean)",
|
|
105959
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
105960
|
+
},
|
|
105961
|
+
{
|
|
105962
|
+
type: "null",
|
|
105963
|
+
title: "stopRecord (null)",
|
|
105964
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
105965
|
+
},
|
|
105966
|
+
{
|
|
105967
|
+
type: "string",
|
|
105968
|
+
title: "stopRecord (name)",
|
|
105969
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
105970
|
+
pattern: "\\S",
|
|
105971
|
+
transform: [
|
|
105972
|
+
"trim"
|
|
105973
|
+
]
|
|
105974
|
+
},
|
|
105975
|
+
{
|
|
105976
|
+
type: "object",
|
|
105977
|
+
title: "stopRecord (detailed)",
|
|
105978
|
+
additionalProperties: false,
|
|
105979
|
+
required: [
|
|
105980
|
+
"name"
|
|
105981
|
+
],
|
|
105982
|
+
properties: {
|
|
105983
|
+
name: {
|
|
105984
|
+
type: "string",
|
|
105985
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
105986
|
+
pattern: "\\S",
|
|
105987
|
+
transform: [
|
|
105988
|
+
"trim"
|
|
105989
|
+
]
|
|
105990
|
+
}
|
|
105991
|
+
}
|
|
105992
|
+
}
|
|
105342
105993
|
],
|
|
105343
105994
|
examples: [
|
|
105344
|
-
true
|
|
105995
|
+
true,
|
|
105996
|
+
"demo",
|
|
105997
|
+
{
|
|
105998
|
+
name: "demo"
|
|
105999
|
+
}
|
|
105345
106000
|
]
|
|
105346
106001
|
}
|
|
105347
106002
|
},
|
|
@@ -106944,13 +107599,52 @@ var init_schemas = __esm({
|
|
|
106944
107599
|
stopRecord_v3: {
|
|
106945
107600
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
106946
107601
|
title: "stopRecord",
|
|
106947
|
-
description:
|
|
106948
|
-
|
|
106949
|
-
|
|
106950
|
-
|
|
107602
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
107603
|
+
anyOf: [
|
|
107604
|
+
{
|
|
107605
|
+
type: "boolean",
|
|
107606
|
+
title: "stopRecord (boolean)",
|
|
107607
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
107608
|
+
},
|
|
107609
|
+
{
|
|
107610
|
+
type: "null",
|
|
107611
|
+
title: "stopRecord (null)",
|
|
107612
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
107613
|
+
},
|
|
107614
|
+
{
|
|
107615
|
+
type: "string",
|
|
107616
|
+
title: "stopRecord (name)",
|
|
107617
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
107618
|
+
pattern: "\\S",
|
|
107619
|
+
transform: [
|
|
107620
|
+
"trim"
|
|
107621
|
+
]
|
|
107622
|
+
},
|
|
107623
|
+
{
|
|
107624
|
+
type: "object",
|
|
107625
|
+
title: "stopRecord (detailed)",
|
|
107626
|
+
additionalProperties: false,
|
|
107627
|
+
required: [
|
|
107628
|
+
"name"
|
|
107629
|
+
],
|
|
107630
|
+
properties: {
|
|
107631
|
+
name: {
|
|
107632
|
+
type: "string",
|
|
107633
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
107634
|
+
pattern: "\\S",
|
|
107635
|
+
transform: [
|
|
107636
|
+
"trim"
|
|
107637
|
+
]
|
|
107638
|
+
}
|
|
107639
|
+
}
|
|
107640
|
+
}
|
|
106951
107641
|
],
|
|
106952
107642
|
examples: [
|
|
106953
|
-
true
|
|
107643
|
+
true,
|
|
107644
|
+
"demo",
|
|
107645
|
+
{
|
|
107646
|
+
name: "demo"
|
|
107647
|
+
}
|
|
106954
107648
|
]
|
|
106955
107649
|
},
|
|
106956
107650
|
test_v3: {
|
|
@@ -106980,6 +107674,10 @@ var init_schemas = __esm({
|
|
|
106980
107674
|
type: "boolean",
|
|
106981
107675
|
description: "If `true`, captures a screenshot after every step in this test that runs in a browser. Overrides `autoScreenshot` set at the spec or config level. When unset, defers to the spec level, then the config level."
|
|
106982
107676
|
},
|
|
107677
|
+
autoRecord: {
|
|
107678
|
+
type: "boolean",
|
|
107679
|
+
description: "If `true`, records a video of every browser context in this test. Overrides `autoRecord` set at the spec or config level. When unset, defers to the spec level, then the config level."
|
|
107680
|
+
},
|
|
106983
107681
|
runOn: {
|
|
106984
107682
|
type: "array",
|
|
106985
107683
|
description: "Contexts to run the test in. Overrides contexts defined at the config and spec levels.",
|
|
@@ -113836,6 +114534,14 @@ var init_schemas = __esm({
|
|
|
113836
114534
|
"false"
|
|
113837
114535
|
]
|
|
113838
114536
|
},
|
|
114537
|
+
name: {
|
|
114538
|
+
type: "string",
|
|
114539
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
114540
|
+
pattern: "\\S",
|
|
114541
|
+
transform: [
|
|
114542
|
+
"trim"
|
|
114543
|
+
]
|
|
114544
|
+
},
|
|
113839
114545
|
engine: {
|
|
113840
114546
|
title: "Recording engine",
|
|
113841
114547
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -113931,6 +114637,14 @@ var init_schemas = __esm({
|
|
|
113931
114637
|
"false"
|
|
113932
114638
|
]
|
|
113933
114639
|
},
|
|
114640
|
+
name: {
|
|
114641
|
+
type: "string",
|
|
114642
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
114643
|
+
pattern: "\\S",
|
|
114644
|
+
transform: [
|
|
114645
|
+
"trim"
|
|
114646
|
+
]
|
|
114647
|
+
},
|
|
113934
114648
|
engine: {
|
|
113935
114649
|
title: "Recording engine",
|
|
113936
114650
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -114166,13 +114880,52 @@ var init_schemas = __esm({
|
|
|
114166
114880
|
stopRecord: {
|
|
114167
114881
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
114168
114882
|
title: "stopRecord",
|
|
114169
|
-
description:
|
|
114170
|
-
|
|
114171
|
-
|
|
114172
|
-
|
|
114883
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
114884
|
+
anyOf: [
|
|
114885
|
+
{
|
|
114886
|
+
type: "boolean",
|
|
114887
|
+
title: "stopRecord (boolean)",
|
|
114888
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
114889
|
+
},
|
|
114890
|
+
{
|
|
114891
|
+
type: "null",
|
|
114892
|
+
title: "stopRecord (null)",
|
|
114893
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
114894
|
+
},
|
|
114895
|
+
{
|
|
114896
|
+
type: "string",
|
|
114897
|
+
title: "stopRecord (name)",
|
|
114898
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
114899
|
+
pattern: "\\S",
|
|
114900
|
+
transform: [
|
|
114901
|
+
"trim"
|
|
114902
|
+
]
|
|
114903
|
+
},
|
|
114904
|
+
{
|
|
114905
|
+
type: "object",
|
|
114906
|
+
title: "stopRecord (detailed)",
|
|
114907
|
+
additionalProperties: false,
|
|
114908
|
+
required: [
|
|
114909
|
+
"name"
|
|
114910
|
+
],
|
|
114911
|
+
properties: {
|
|
114912
|
+
name: {
|
|
114913
|
+
type: "string",
|
|
114914
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
114915
|
+
pattern: "\\S",
|
|
114916
|
+
transform: [
|
|
114917
|
+
"trim"
|
|
114918
|
+
]
|
|
114919
|
+
}
|
|
114920
|
+
}
|
|
114921
|
+
}
|
|
114173
114922
|
],
|
|
114174
114923
|
examples: [
|
|
114175
|
-
true
|
|
114924
|
+
true,
|
|
114925
|
+
"demo",
|
|
114926
|
+
{
|
|
114927
|
+
name: "demo"
|
|
114928
|
+
}
|
|
114176
114929
|
]
|
|
114177
114930
|
}
|
|
114178
114931
|
},
|
|
@@ -122310,6 +123063,14 @@ var init_schemas = __esm({
|
|
|
122310
123063
|
"false"
|
|
122311
123064
|
]
|
|
122312
123065
|
},
|
|
123066
|
+
name: {
|
|
123067
|
+
type: "string",
|
|
123068
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
123069
|
+
pattern: "\\S",
|
|
123070
|
+
transform: [
|
|
123071
|
+
"trim"
|
|
123072
|
+
]
|
|
123073
|
+
},
|
|
122313
123074
|
engine: {
|
|
122314
123075
|
title: "Recording engine",
|
|
122315
123076
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -122405,6 +123166,14 @@ var init_schemas = __esm({
|
|
|
122405
123166
|
"false"
|
|
122406
123167
|
]
|
|
122407
123168
|
},
|
|
123169
|
+
name: {
|
|
123170
|
+
type: "string",
|
|
123171
|
+
description: 'Identifier for this recording. A later `stopRecord` step can target it by name (`stopRecord: "<name>"`), which is how you stop a specific recording when several overlap. Names must be unique among recordings that are active at the same time. If omitted, the recording is anonymous and is stopped LIFO by an untargeted `stopRecord`.',
|
|
123172
|
+
pattern: "\\S",
|
|
123173
|
+
transform: [
|
|
123174
|
+
"trim"
|
|
123175
|
+
]
|
|
123176
|
+
},
|
|
122408
123177
|
engine: {
|
|
122409
123178
|
title: "Recording engine",
|
|
122410
123179
|
description: "Recording engine to use. Either a string shorthand selecting the engine with defaults, or an object for full control. If unset, defaults to the `browser` engine when a visible Chrome context is available and to `ffmpeg` otherwise.",
|
|
@@ -122640,13 +123409,52 @@ var init_schemas = __esm({
|
|
|
122640
123409
|
stopRecord: {
|
|
122641
123410
|
$schema: "http://json-schema.org/draft-07/schema#",
|
|
122642
123411
|
title: "stopRecord",
|
|
122643
|
-
description:
|
|
122644
|
-
|
|
122645
|
-
|
|
122646
|
-
|
|
123412
|
+
description: 'Stop a recording started by an earlier `record` step. With no target (`true`/`null`), stops the most recently started recording that is still active (LIFO). To stop a specific recording when several overlap, target it by name with a string (`stopRecord: "<name>"`) or an object (`stopRecord: { name: "<name>" }`).',
|
|
123413
|
+
anyOf: [
|
|
123414
|
+
{
|
|
123415
|
+
type: "boolean",
|
|
123416
|
+
title: "stopRecord (boolean)",
|
|
123417
|
+
description: "If `true`, stops the most recently started active recording (LIFO). If `false`, does nothing \u2014 an explicit no-op (mirrors `record: false`)."
|
|
123418
|
+
},
|
|
123419
|
+
{
|
|
123420
|
+
type: "null",
|
|
123421
|
+
title: "stopRecord (null)",
|
|
123422
|
+
description: "Stops the most recently started active recording (LIFO)."
|
|
123423
|
+
},
|
|
123424
|
+
{
|
|
123425
|
+
type: "string",
|
|
123426
|
+
title: "stopRecord (name)",
|
|
123427
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
123428
|
+
pattern: "\\S",
|
|
123429
|
+
transform: [
|
|
123430
|
+
"trim"
|
|
123431
|
+
]
|
|
123432
|
+
},
|
|
123433
|
+
{
|
|
123434
|
+
type: "object",
|
|
123435
|
+
title: "stopRecord (detailed)",
|
|
123436
|
+
additionalProperties: false,
|
|
123437
|
+
required: [
|
|
123438
|
+
"name"
|
|
123439
|
+
],
|
|
123440
|
+
properties: {
|
|
123441
|
+
name: {
|
|
123442
|
+
type: "string",
|
|
123443
|
+
description: "Name of the recording to stop. Matches the `name` given to a `record` step.",
|
|
123444
|
+
pattern: "\\S",
|
|
123445
|
+
transform: [
|
|
123446
|
+
"trim"
|
|
123447
|
+
]
|
|
123448
|
+
}
|
|
123449
|
+
}
|
|
123450
|
+
}
|
|
122647
123451
|
],
|
|
122648
123452
|
examples: [
|
|
122649
|
-
true
|
|
123453
|
+
true,
|
|
123454
|
+
"demo",
|
|
123455
|
+
{
|
|
123456
|
+
name: "demo"
|
|
123457
|
+
}
|
|
122650
123458
|
]
|
|
122651
123459
|
}
|
|
122652
123460
|
},
|
|
@@ -134404,10 +135212,10 @@ function getRunOutputDir(config, { create = true } = {}) {
|
|
|
134404
135212
|
function runArchivesArtifacts(config = {}, specs = []) {
|
|
134405
135213
|
const list = Array.isArray(specs) ? specs : [];
|
|
134406
135214
|
if (list.length > 0) {
|
|
134407
|
-
const
|
|
134408
|
-
if (
|
|
135215
|
+
const writesArtifact = list.some((spec) => (spec?.tests ?? []).some((test) => Boolean(test?.autoScreenshot ?? spec?.autoScreenshot ?? config?.autoScreenshot) || Boolean(test?.autoRecord ?? spec?.autoRecord ?? config?.autoRecord)));
|
|
135216
|
+
if (writesArtifact)
|
|
134409
135217
|
return true;
|
|
134410
|
-
} else if (Boolean(config?.autoScreenshot)) {
|
|
135218
|
+
} else if (Boolean(config?.autoScreenshot) || Boolean(config?.autoRecord)) {
|
|
134411
135219
|
return true;
|
|
134412
135220
|
}
|
|
134413
135221
|
const active = Array.isArray(config?.reporters) && config.reporters.length > 0 ? config.reporters : ["terminal", "json", "runFolder"];
|
|
@@ -139774,6 +140582,375 @@ async function findElementByCriteria({ selector, elementText, elementId, element
|
|
|
139774
140582
|
// dist/core/tests/typeKeys.js
|
|
139775
140583
|
init_validate();
|
|
139776
140584
|
init_loader();
|
|
140585
|
+
|
|
140586
|
+
// dist/core/tests/ffmpegRecorder.js
|
|
140587
|
+
var import_node_child_process4 = require("node:child_process");
|
|
140588
|
+
var import_node_os6 = __toESM(require("node:os"), 1);
|
|
140589
|
+
var import_node_path10 = __toESM(require("node:path"), 1);
|
|
140590
|
+
var import_node_fs10 = __toESM(require("node:fs"), 1);
|
|
140591
|
+
var import_node_crypto5 = __toESM(require("node:crypto"), 1);
|
|
140592
|
+
init_loader();
|
|
140593
|
+
init_utils();
|
|
140594
|
+
var XVFB_SCREEN_SIZE = "1920x1080";
|
|
140595
|
+
function isRecordingActive(driver) {
|
|
140596
|
+
return Array.isArray(driver?.state?.recordings) && driver.state.recordings.length > 0;
|
|
140597
|
+
}
|
|
140598
|
+
function recordStepName(record) {
|
|
140599
|
+
if (record && typeof record === "object" && typeof record.name === "string") {
|
|
140600
|
+
const trimmed = record.name.trim();
|
|
140601
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
140602
|
+
}
|
|
140603
|
+
return void 0;
|
|
140604
|
+
}
|
|
140605
|
+
function stopRecordTargetName(stopRecord) {
|
|
140606
|
+
if (typeof stopRecord === "string") {
|
|
140607
|
+
const trimmed = stopRecord.trim();
|
|
140608
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
140609
|
+
}
|
|
140610
|
+
if (stopRecord && typeof stopRecord === "object" && typeof stopRecord.name === "string") {
|
|
140611
|
+
const trimmed = stopRecord.name.trim();
|
|
140612
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
140613
|
+
}
|
|
140614
|
+
return void 0;
|
|
140615
|
+
}
|
|
140616
|
+
function selectRecordingToStop(recordings, stopRecord, { includeSynthetic = false } = {}) {
|
|
140617
|
+
if (!Array.isArray(recordings) || recordings.length === 0)
|
|
140618
|
+
return void 0;
|
|
140619
|
+
const target = stopRecordTargetName(stopRecord);
|
|
140620
|
+
if (target !== void 0) {
|
|
140621
|
+
return recordings.find((r) => r && r.name === target);
|
|
140622
|
+
}
|
|
140623
|
+
for (let i = recordings.length - 1; i >= 0; i--) {
|
|
140624
|
+
const r = recordings[i];
|
|
140625
|
+
if (includeSynthetic || !r?.synthetic)
|
|
140626
|
+
return r;
|
|
140627
|
+
}
|
|
140628
|
+
return void 0;
|
|
140629
|
+
}
|
|
140630
|
+
function detectRecordingNameConflict(steps) {
|
|
140631
|
+
if (!Array.isArray(steps))
|
|
140632
|
+
return null;
|
|
140633
|
+
const active = [];
|
|
140634
|
+
for (const step of steps) {
|
|
140635
|
+
if (!step || typeof step !== "object")
|
|
140636
|
+
continue;
|
|
140637
|
+
const isStart = typeof step.record !== "undefined" && step.record !== false;
|
|
140638
|
+
const isStop = typeof step.stopRecord !== "undefined" && step.stopRecord !== false;
|
|
140639
|
+
if (isStart) {
|
|
140640
|
+
const name = recordStepName(step.record);
|
|
140641
|
+
if (name !== void 0 && active.includes(name))
|
|
140642
|
+
return name;
|
|
140643
|
+
active.push(name);
|
|
140644
|
+
} else if (isStop) {
|
|
140645
|
+
const target = stopRecordTargetName(step.stopRecord);
|
|
140646
|
+
if (target !== void 0) {
|
|
140647
|
+
const idx = active.lastIndexOf(target);
|
|
140648
|
+
if (idx !== -1)
|
|
140649
|
+
active.splice(idx, 1);
|
|
140650
|
+
} else if (active.length > 0) {
|
|
140651
|
+
active.pop();
|
|
140652
|
+
}
|
|
140653
|
+
}
|
|
140654
|
+
}
|
|
140655
|
+
return null;
|
|
140656
|
+
}
|
|
140657
|
+
function safeContextId(contextId) {
|
|
140658
|
+
const raw = String(contextId ?? "ctx");
|
|
140659
|
+
const base = sanitizeFilesystemName(raw, "ctx");
|
|
140660
|
+
if (base === raw)
|
|
140661
|
+
return base;
|
|
140662
|
+
const hash = import_node_crypto5.default.createHash("sha1").update(raw).digest("hex").slice(0, 8);
|
|
140663
|
+
return `${base}-${hash}`;
|
|
140664
|
+
}
|
|
140665
|
+
function browserCaptureTitle(contextId) {
|
|
140666
|
+
return `RECORD_ME_${safeContextId(contextId)}`;
|
|
140667
|
+
}
|
|
140668
|
+
function browserDownloadDir(contextId) {
|
|
140669
|
+
return import_node_path10.default.join(import_node_os6.default.tmpdir(), "doc-detective", "recordings", safeContextId(contextId));
|
|
140670
|
+
}
|
|
140671
|
+
function engineFields(record) {
|
|
140672
|
+
const engine = record && typeof record === "object" ? record.engine : void 0;
|
|
140673
|
+
if (typeof engine === "string")
|
|
140674
|
+
return { name: engine };
|
|
140675
|
+
if (engine && typeof engine === "object")
|
|
140676
|
+
return { name: engine.name, target: engine.target, fps: engine.fps };
|
|
140677
|
+
return {};
|
|
140678
|
+
}
|
|
140679
|
+
function resolveRecordPlan({ step, context }) {
|
|
140680
|
+
const { name, target, fps } = engineFields(step?.record);
|
|
140681
|
+
let engineName = name;
|
|
140682
|
+
if (!engineName) {
|
|
140683
|
+
const b = context?.browser;
|
|
140684
|
+
engineName = b?.name === "chrome" && b?.headless === false ? "browser" : "ffmpeg";
|
|
140685
|
+
}
|
|
140686
|
+
return {
|
|
140687
|
+
name: engineName,
|
|
140688
|
+
target: target || "display",
|
|
140689
|
+
fps: fps ?? 30
|
|
140690
|
+
};
|
|
140691
|
+
}
|
|
140692
|
+
function hasRecordStepWithoutEngine(context) {
|
|
140693
|
+
const steps = Array.isArray(context?.steps) ? context.steps : [];
|
|
140694
|
+
return steps.some((s) => {
|
|
140695
|
+
if (!s?.record)
|
|
140696
|
+
return false;
|
|
140697
|
+
return engineFields(s.record).name === void 0;
|
|
140698
|
+
});
|
|
140699
|
+
}
|
|
140700
|
+
function coerceRecordContextBrowser({ context, availableApps }) {
|
|
140701
|
+
if (context?.browser)
|
|
140702
|
+
return null;
|
|
140703
|
+
if (!hasRecordStepWithoutEngine(context))
|
|
140704
|
+
return null;
|
|
140705
|
+
const chromeAvailable = Array.isArray(availableApps) && availableApps.some((a) => a?.name === "chrome");
|
|
140706
|
+
if (!chromeAvailable)
|
|
140707
|
+
return null;
|
|
140708
|
+
return { name: "chrome", headless: false };
|
|
140709
|
+
}
|
|
140710
|
+
function buildCaptureArgs({ platform, fps, displayEnv, outputPath, screenIndex, screenSize }) {
|
|
140711
|
+
const rate = String(fps ?? 30);
|
|
140712
|
+
let input;
|
|
140713
|
+
switch (platform) {
|
|
140714
|
+
case "win32":
|
|
140715
|
+
input = ["-f", "gdigrab", "-framerate", rate, "-i", "desktop"];
|
|
140716
|
+
break;
|
|
140717
|
+
case "darwin":
|
|
140718
|
+
input = [
|
|
140719
|
+
"-f",
|
|
140720
|
+
"avfoundation",
|
|
140721
|
+
"-framerate",
|
|
140722
|
+
rate,
|
|
140723
|
+
"-i",
|
|
140724
|
+
`${screenIndex ?? "0"}:none`
|
|
140725
|
+
];
|
|
140726
|
+
break;
|
|
140727
|
+
case "linux":
|
|
140728
|
+
input = [
|
|
140729
|
+
"-f",
|
|
140730
|
+
"x11grab",
|
|
140731
|
+
"-framerate",
|
|
140732
|
+
rate,
|
|
140733
|
+
...screenSize ? ["-video_size", screenSize] : [],
|
|
140734
|
+
"-i",
|
|
140735
|
+
displayEnv || ":0.0"
|
|
140736
|
+
];
|
|
140737
|
+
break;
|
|
140738
|
+
default:
|
|
140739
|
+
throw new Error(`Screen recording isn't supported on platform '${platform}'.`);
|
|
140740
|
+
}
|
|
140741
|
+
return ["-y", ...input, "-pix_fmt", "yuv420p", outputPath];
|
|
140742
|
+
}
|
|
140743
|
+
async function resolveCropGeometry({ driver, target }) {
|
|
140744
|
+
if (target === "viewport") {
|
|
140745
|
+
const m = await driver.execute(() => {
|
|
140746
|
+
return {
|
|
140747
|
+
sx: window.screenX,
|
|
140748
|
+
sy: window.screenY,
|
|
140749
|
+
iw: window.innerWidth,
|
|
140750
|
+
ih: window.innerHeight,
|
|
140751
|
+
ow: window.outerWidth,
|
|
140752
|
+
oh: window.outerHeight,
|
|
140753
|
+
dpr: window.devicePixelRatio || 1
|
|
140754
|
+
};
|
|
140755
|
+
});
|
|
140756
|
+
const dpr = m.dpr || 1;
|
|
140757
|
+
const border = Math.max(0, (m.ow - m.iw) / 2);
|
|
140758
|
+
const topChrome = Math.max(0, m.oh - m.ih - border);
|
|
140759
|
+
return {
|
|
140760
|
+
x: Math.round((m.sx + border) * dpr),
|
|
140761
|
+
y: Math.round((m.sy + topChrome) * dpr),
|
|
140762
|
+
w: Math.round(m.iw * dpr),
|
|
140763
|
+
h: Math.round(m.ih * dpr)
|
|
140764
|
+
};
|
|
140765
|
+
}
|
|
140766
|
+
if (target === "window") {
|
|
140767
|
+
const r = await driver.getWindowRect();
|
|
140768
|
+
let dpr;
|
|
140769
|
+
try {
|
|
140770
|
+
dpr = await driver.execute(() => window.devicePixelRatio || 1) || 1;
|
|
140771
|
+
} catch {
|
|
140772
|
+
dpr = 1;
|
|
140773
|
+
}
|
|
140774
|
+
return {
|
|
140775
|
+
x: Math.round(r.x * dpr),
|
|
140776
|
+
y: Math.round(r.y * dpr),
|
|
140777
|
+
w: Math.round(r.width * dpr),
|
|
140778
|
+
h: Math.round(r.height * dpr)
|
|
140779
|
+
};
|
|
140780
|
+
}
|
|
140781
|
+
return null;
|
|
140782
|
+
}
|
|
140783
|
+
function jobIsFfmpegRecording(job) {
|
|
140784
|
+
const context = job?.context;
|
|
140785
|
+
const steps = Array.isArray(context?.steps) ? context.steps : [];
|
|
140786
|
+
const activeBrowser = [];
|
|
140787
|
+
for (const s of steps) {
|
|
140788
|
+
const isStop = typeof s?.stopRecord !== "undefined" && s.stopRecord !== false;
|
|
140789
|
+
if (isStop) {
|
|
140790
|
+
const target = stopRecordTargetName(s.stopRecord);
|
|
140791
|
+
if (target !== void 0) {
|
|
140792
|
+
const idx = activeBrowser.lastIndexOf(target);
|
|
140793
|
+
if (idx !== -1)
|
|
140794
|
+
activeBrowser.splice(idx, 1);
|
|
140795
|
+
} else if (activeBrowser.length > 0) {
|
|
140796
|
+
activeBrowser.pop();
|
|
140797
|
+
}
|
|
140798
|
+
continue;
|
|
140799
|
+
}
|
|
140800
|
+
if (!s?.record || s.record === false)
|
|
140801
|
+
continue;
|
|
140802
|
+
const plan = resolveRecordPlan({ step: s, context });
|
|
140803
|
+
if (plan.name === "ffmpeg")
|
|
140804
|
+
return true;
|
|
140805
|
+
const supportsBrowser = context?.browser?.name === "chrome" && !context?.browser?.headless;
|
|
140806
|
+
if (!supportsBrowser)
|
|
140807
|
+
continue;
|
|
140808
|
+
if (activeBrowser.length > 0)
|
|
140809
|
+
return true;
|
|
140810
|
+
activeBrowser.push(recordStepName(s.record));
|
|
140811
|
+
}
|
|
140812
|
+
return false;
|
|
140813
|
+
}
|
|
140814
|
+
function computeEffectiveConcurrency({ requestedLimit, jobs, platform, xvfbAvailable, allowOverlappingCaptures = false }) {
|
|
140815
|
+
const ffmpegJobs = (jobs || []).filter(jobIsFfmpegRecording);
|
|
140816
|
+
if (ffmpegJobs.length === 0) {
|
|
140817
|
+
return { limit: requestedLimit, xvfbContexts: [], forcedSerial: false };
|
|
140818
|
+
}
|
|
140819
|
+
if (platform === "linux" && xvfbAvailable) {
|
|
140820
|
+
return {
|
|
140821
|
+
limit: requestedLimit,
|
|
140822
|
+
xvfbContexts: ffmpegJobs.map((j) => j.context),
|
|
140823
|
+
forcedSerial: false
|
|
140824
|
+
};
|
|
140825
|
+
}
|
|
140826
|
+
if (allowOverlappingCaptures) {
|
|
140827
|
+
return {
|
|
140828
|
+
limit: requestedLimit,
|
|
140829
|
+
xvfbContexts: [],
|
|
140830
|
+
forcedSerial: false,
|
|
140831
|
+
overlappingCaptures: true
|
|
140832
|
+
};
|
|
140833
|
+
}
|
|
140834
|
+
return { limit: 1, xvfbContexts: [], forcedSerial: requestedLimit > 1 };
|
|
140835
|
+
}
|
|
140836
|
+
async function getFfmpegPath(ctx = {}) {
|
|
140837
|
+
const mod = await loadHeavyDep("@ffmpeg-installer/ffmpeg", { ctx });
|
|
140838
|
+
const candidate = mod && (mod.path ?? mod.default?.path);
|
|
140839
|
+
if (typeof candidate !== "string" || candidate.length === 0) {
|
|
140840
|
+
throw new Error("ffmpeg binary path is missing or malformed in the installed @ffmpeg-installer/ffmpeg package. Try `doc-detective install runtime --force` to reinstall.");
|
|
140841
|
+
}
|
|
140842
|
+
return candidate;
|
|
140843
|
+
}
|
|
140844
|
+
function parseMacScreenIndex(listing) {
|
|
140845
|
+
const m = /\[(\d+)\]\s+Capture screen/i.exec(listing || "");
|
|
140846
|
+
return m ? m[1] : null;
|
|
140847
|
+
}
|
|
140848
|
+
async function detectMacScreenIndex(ffmpegPath) {
|
|
140849
|
+
return new Promise((resolve) => {
|
|
140850
|
+
let out = "";
|
|
140851
|
+
let settled = false;
|
|
140852
|
+
let proc = null;
|
|
140853
|
+
const done = (v) => {
|
|
140854
|
+
if (settled)
|
|
140855
|
+
return;
|
|
140856
|
+
settled = true;
|
|
140857
|
+
try {
|
|
140858
|
+
proc?.kill();
|
|
140859
|
+
} catch {
|
|
140860
|
+
}
|
|
140861
|
+
resolve(v);
|
|
140862
|
+
};
|
|
140863
|
+
try {
|
|
140864
|
+
proc = (0, import_node_child_process4.spawn)(ffmpegPath, ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { stdio: ["ignore", "ignore", "pipe"] });
|
|
140865
|
+
proc.stderr?.on("data", (d) => {
|
|
140866
|
+
out += d.toString();
|
|
140867
|
+
});
|
|
140868
|
+
proc.on("error", () => done(null));
|
|
140869
|
+
proc.on("close", () => done(parseMacScreenIndex(out)));
|
|
140870
|
+
setTimeout(() => done(null), 5e3);
|
|
140871
|
+
} catch {
|
|
140872
|
+
done(null);
|
|
140873
|
+
}
|
|
140874
|
+
});
|
|
140875
|
+
}
|
|
140876
|
+
async function detectX11ScreenSize(display) {
|
|
140877
|
+
return new Promise((resolve) => {
|
|
140878
|
+
let out = "";
|
|
140879
|
+
let settled = false;
|
|
140880
|
+
let proc = null;
|
|
140881
|
+
const done = (v) => {
|
|
140882
|
+
if (settled)
|
|
140883
|
+
return;
|
|
140884
|
+
settled = true;
|
|
140885
|
+
try {
|
|
140886
|
+
proc?.kill();
|
|
140887
|
+
} catch {
|
|
140888
|
+
}
|
|
140889
|
+
resolve(v);
|
|
140890
|
+
};
|
|
140891
|
+
try {
|
|
140892
|
+
const env = display ? { ...process.env, DISPLAY: display } : process.env;
|
|
140893
|
+
proc = (0, import_node_child_process4.spawn)("xdpyinfo", [], { env, stdio: ["ignore", "pipe", "ignore"] });
|
|
140894
|
+
proc.stdout?.on("data", (d) => {
|
|
140895
|
+
out += d.toString();
|
|
140896
|
+
});
|
|
140897
|
+
proc.on("error", () => done(null));
|
|
140898
|
+
proc.on("close", () => {
|
|
140899
|
+
const m = /dimensions:\s+(\d+x\d+)\s+pixels/i.exec(out);
|
|
140900
|
+
done(m ? m[1] : null);
|
|
140901
|
+
});
|
|
140902
|
+
setTimeout(() => done(null), 5e3);
|
|
140903
|
+
} catch {
|
|
140904
|
+
done(null);
|
|
140905
|
+
}
|
|
140906
|
+
});
|
|
140907
|
+
}
|
|
140908
|
+
async function checkSystemBinary(name) {
|
|
140909
|
+
return new Promise((resolve) => {
|
|
140910
|
+
try {
|
|
140911
|
+
const proc = (0, import_node_child_process4.spawn)(name, ["-help"], { stdio: "ignore" });
|
|
140912
|
+
proc.on("error", () => resolve(false));
|
|
140913
|
+
proc.on("close", () => resolve(true));
|
|
140914
|
+
} catch {
|
|
140915
|
+
resolve(false);
|
|
140916
|
+
}
|
|
140917
|
+
});
|
|
140918
|
+
}
|
|
140919
|
+
function xvfbDisplay(index) {
|
|
140920
|
+
return `:${99 + index}`;
|
|
140921
|
+
}
|
|
140922
|
+
async function startXvfb(display, opts = {}) {
|
|
140923
|
+
const num = display.replace(/^:/, "").split(".")[0];
|
|
140924
|
+
const [defW, defH] = XVFB_SCREEN_SIZE.split("x").map(Number);
|
|
140925
|
+
const w = opts.width ?? defW;
|
|
140926
|
+
const h = opts.height ?? defH;
|
|
140927
|
+
const startMs = Date.now();
|
|
140928
|
+
const proc = (0, import_node_child_process4.spawn)("Xvfb", [display, "-screen", "0", `${w}x${h}x24`, "-nolisten", "tcp"], { stdio: "ignore" });
|
|
140929
|
+
let spawnErr = null;
|
|
140930
|
+
proc.on("error", (e) => {
|
|
140931
|
+
spawnErr = e;
|
|
140932
|
+
});
|
|
140933
|
+
const lock = `/tmp/.X${num}-lock`;
|
|
140934
|
+
for (let i = 0; i < 50; i++) {
|
|
140935
|
+
if (spawnErr)
|
|
140936
|
+
throw spawnErr;
|
|
140937
|
+
if (proc.exitCode !== null)
|
|
140938
|
+
throw new Error(`Xvfb exited early on ${display} (code ${proc.exitCode})`);
|
|
140939
|
+
try {
|
|
140940
|
+
if (import_node_fs10.default.statSync(lock).mtimeMs >= startMs)
|
|
140941
|
+
return proc;
|
|
140942
|
+
} catch {
|
|
140943
|
+
}
|
|
140944
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
140945
|
+
}
|
|
140946
|
+
try {
|
|
140947
|
+
proc.kill();
|
|
140948
|
+
} catch {
|
|
140949
|
+
}
|
|
140950
|
+
throw new Error(`Xvfb did not become ready on ${display} within 5s.`);
|
|
140951
|
+
}
|
|
140952
|
+
|
|
140953
|
+
// dist/core/tests/typeKeys.js
|
|
139777
140954
|
var _specialKeyMap = null;
|
|
139778
140955
|
async function getSpecialKeyMap(ctx = {}) {
|
|
139779
140956
|
if (_specialKeyMap)
|
|
@@ -139900,7 +141077,7 @@ async function typeKeys({ config, step, driver }) {
|
|
|
139900
141077
|
return result;
|
|
139901
141078
|
}
|
|
139902
141079
|
}
|
|
139903
|
-
if (driver
|
|
141080
|
+
if (isRecordingActive(driver)) {
|
|
139904
141081
|
let keys = [];
|
|
139905
141082
|
step.type.keys.forEach((key) => {
|
|
139906
141083
|
if (key.startsWith("$") && key.endsWith("$")) {
|
|
@@ -139930,7 +141107,7 @@ async function typeKeys({ config, step, driver }) {
|
|
|
139930
141107
|
});
|
|
139931
141108
|
}
|
|
139932
141109
|
try {
|
|
139933
|
-
if (driver
|
|
141110
|
+
if (isRecordingActive(driver)) {
|
|
139934
141111
|
for (let i = 0; i < step.type.keys.length; i++) {
|
|
139935
141112
|
await driver.keys(step.type.keys[i]);
|
|
139936
141113
|
await new Promise((resolve) => setTimeout(resolve, step.type.inputDelay));
|
|
@@ -140094,7 +141271,7 @@ async function findElement({ config, step, driver, click }) {
|
|
|
140094
141271
|
result.description += " Typed keys.";
|
|
140095
141272
|
}
|
|
140096
141273
|
}
|
|
140097
|
-
if (driver
|
|
141274
|
+
if (isRecordingActive(driver)) {
|
|
140098
141275
|
await wait({ config, step: { wait: 2e3 }, driver });
|
|
140099
141276
|
}
|
|
140100
141277
|
return result;
|
|
@@ -140483,8 +141660,8 @@ async function waitForDOMStable(driver, idleTime, timeout) {
|
|
|
140483
141660
|
// dist/core/tests/runShell.js
|
|
140484
141661
|
init_validate();
|
|
140485
141662
|
init_utils();
|
|
140486
|
-
var
|
|
140487
|
-
var
|
|
141663
|
+
var import_node_fs11 = __toESM(require("node:fs"), 1);
|
|
141664
|
+
var import_node_path11 = __toESM(require("node:path"), 1);
|
|
140488
141665
|
async function runShell({ config, step }) {
|
|
140489
141666
|
const result = {
|
|
140490
141667
|
status: "PASS",
|
|
@@ -140557,24 +141734,24 @@ async function runShell({ config, step }) {
|
|
|
140557
141734
|
}
|
|
140558
141735
|
}
|
|
140559
141736
|
if (step.runShell.path) {
|
|
140560
|
-
const dir =
|
|
140561
|
-
if (!
|
|
140562
|
-
|
|
141737
|
+
const dir = import_node_path11.default.dirname(step.runShell.path);
|
|
141738
|
+
if (!import_node_fs11.default.existsSync(dir)) {
|
|
141739
|
+
import_node_fs11.default.mkdirSync(dir, { recursive: true });
|
|
140563
141740
|
}
|
|
140564
141741
|
let filePath = step.runShell.path;
|
|
140565
141742
|
log(config, "debug", `Saving stdio to file: ${filePath}`);
|
|
140566
|
-
if (!
|
|
140567
|
-
|
|
141743
|
+
if (!import_node_fs11.default.existsSync(filePath)) {
|
|
141744
|
+
import_node_fs11.default.writeFileSync(filePath, result.outputs.stdio.stdout);
|
|
140568
141745
|
} else {
|
|
140569
141746
|
if (step.runShell.overwrite == "false") {
|
|
140570
141747
|
result.description = result.description + ` Didn't save output. File already exists.`;
|
|
140571
141748
|
}
|
|
140572
|
-
const existingFile =
|
|
141749
|
+
const existingFile = import_node_fs11.default.readFileSync(filePath, "utf8");
|
|
140573
141750
|
const fractionalDiff = calculateFractionalDifference(existingFile, result.outputs.stdio.stdout);
|
|
140574
141751
|
log(config, "debug", `Fractional difference: ${fractionalDiff}`);
|
|
140575
141752
|
if (fractionalDiff > step.runShell.maxVariation) {
|
|
140576
141753
|
if (step.runShell.overwrite == "aboveVariation") {
|
|
140577
|
-
|
|
141754
|
+
import_node_fs11.default.writeFileSync(filePath, result.outputs.stdio.stdout);
|
|
140578
141755
|
result.description += ` Saved output to file.`;
|
|
140579
141756
|
}
|
|
140580
141757
|
result.status = "WARNING";
|
|
@@ -140582,7 +141759,7 @@ async function runShell({ config, step }) {
|
|
|
140582
141759
|
return result;
|
|
140583
141760
|
}
|
|
140584
141761
|
if (step.runShell.overwrite == "true") {
|
|
140585
|
-
|
|
141762
|
+
import_node_fs11.default.writeFileSync(filePath, result.outputs.stdio.stdout);
|
|
140586
141763
|
result.description += ` Saved output to file.`;
|
|
140587
141764
|
}
|
|
140588
141765
|
}
|
|
@@ -140766,8 +141943,8 @@ async function checkLink({ config, step }) {
|
|
|
140766
141943
|
// dist/core/tests/saveScreenshot.js
|
|
140767
141944
|
init_validate();
|
|
140768
141945
|
init_utils();
|
|
140769
|
-
var
|
|
140770
|
-
var
|
|
141946
|
+
var import_node_path12 = __toESM(require("node:path"), 1);
|
|
141947
|
+
var import_node_fs12 = __toESM(require("node:fs"), 1);
|
|
140771
141948
|
init_loader();
|
|
140772
141949
|
var _pngjs = null;
|
|
140773
141950
|
var _sharp = null;
|
|
@@ -140855,7 +142032,7 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
140855
142032
|
if (typeof step.screenshot.path === "undefined") {
|
|
140856
142033
|
step.screenshot.path = `${step.stepId}.png`;
|
|
140857
142034
|
if (step.screenshot.directory) {
|
|
140858
|
-
step.screenshot.path =
|
|
142035
|
+
step.screenshot.path = import_node_path12.default.resolve(step.screenshot.directory, step.screenshot.path);
|
|
140859
142036
|
}
|
|
140860
142037
|
}
|
|
140861
142038
|
step.screenshot = {
|
|
@@ -140891,17 +142068,17 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
140891
142068
|
} catch {
|
|
140892
142069
|
urlPathname = originalUrlPath;
|
|
140893
142070
|
}
|
|
140894
|
-
const rawBase =
|
|
142071
|
+
const rawBase = import_node_path12.default.basename(urlPathname.split("?")[0].split("#")[0].replace(/\\/g, "/"));
|
|
140895
142072
|
const safeBase = sanitizeFilesystemName(rawBase, `${step.stepId}.png`);
|
|
140896
|
-
dir =
|
|
140897
|
-
if (!
|
|
140898
|
-
|
|
142073
|
+
dir = import_node_path12.default.join(process.cwd(), "doc-detective-runs", getOrInitRunTimestamp(config));
|
|
142074
|
+
if (!import_node_fs12.default.existsSync(dir)) {
|
|
142075
|
+
import_node_fs12.default.mkdirSync(dir, { recursive: true });
|
|
140899
142076
|
}
|
|
140900
142077
|
const captureId = `${step.stepId || "screenshot"}_${Date.now()}`;
|
|
140901
|
-
filePath =
|
|
140902
|
-
const resolvedDir =
|
|
140903
|
-
const resolvedFile =
|
|
140904
|
-
if (!resolvedFile.startsWith(resolvedDir +
|
|
142078
|
+
filePath = import_node_path12.default.join(dir, `${captureId}_${safeBase}`);
|
|
142079
|
+
const resolvedDir = import_node_path12.default.resolve(dir);
|
|
142080
|
+
const resolvedFile = import_node_path12.default.resolve(filePath);
|
|
142081
|
+
if (!resolvedFile.startsWith(resolvedDir + import_node_path12.default.sep)) {
|
|
140905
142082
|
result.status = "FAIL";
|
|
140906
142083
|
result.description = `Refusing to write screenshot outside run folder: ${resolvedFile}`;
|
|
140907
142084
|
return result;
|
|
@@ -140910,18 +142087,18 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
140910
142087
|
log(config, "debug", `Screenshot path is a URL (${redactedUrl}); overwrite is ignored, running comparison only.`);
|
|
140911
142088
|
}
|
|
140912
142089
|
} else {
|
|
140913
|
-
dir =
|
|
140914
|
-
if (!
|
|
140915
|
-
|
|
142090
|
+
dir = import_node_path12.default.dirname(step.screenshot.path);
|
|
142091
|
+
if (!import_node_fs12.default.existsSync(dir)) {
|
|
142092
|
+
import_node_fs12.default.mkdirSync(dir, { recursive: true });
|
|
140916
142093
|
}
|
|
140917
|
-
if (
|
|
142094
|
+
if (import_node_fs12.default.existsSync(filePath)) {
|
|
140918
142095
|
if (step.screenshot.overwrite == "false") {
|
|
140919
142096
|
result.status = "SKIPPED";
|
|
140920
142097
|
result.description = `File already exists: ${filePath}`;
|
|
140921
142098
|
return result;
|
|
140922
142099
|
} else {
|
|
140923
142100
|
existFilePath = filePath;
|
|
140924
|
-
filePath =
|
|
142101
|
+
filePath = import_node_path12.default.join(dir, `${step.stepId}_${Date.now()}.png`);
|
|
140925
142102
|
}
|
|
140926
142103
|
}
|
|
140927
142104
|
}
|
|
@@ -140995,25 +142172,34 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
140995
142172
|
}, element, padding);
|
|
140996
142173
|
await driver.pause(100);
|
|
140997
142174
|
}
|
|
142175
|
+
const recordingActive = isRecordingActive(driver);
|
|
140998
142176
|
try {
|
|
140999
|
-
if (
|
|
142177
|
+
if (recordingActive) {
|
|
141000
142178
|
await driver.execute(() => {
|
|
141001
|
-
document.querySelector("dd-mouse-pointer")
|
|
142179
|
+
const pointer = document.querySelector("dd-mouse-pointer");
|
|
142180
|
+
if (pointer)
|
|
142181
|
+
pointer.style.display = "none";
|
|
141002
142182
|
});
|
|
141003
142183
|
}
|
|
141004
142184
|
await driver.saveScreenshot(filePath);
|
|
141005
|
-
if (driver?.state?.recording) {
|
|
141006
|
-
await driver.execute(() => {
|
|
141007
|
-
document.querySelector("dd-mouse-pointer").style.display = "block";
|
|
141008
|
-
});
|
|
141009
|
-
}
|
|
141010
142185
|
} catch (error) {
|
|
141011
142186
|
result.status = "FAIL";
|
|
141012
142187
|
result.description = `Couldn't save screenshot. ${error}`;
|
|
141013
|
-
if (existFilePath && filePath !== existFilePath &&
|
|
141014
|
-
|
|
142188
|
+
if (existFilePath && filePath !== existFilePath && import_node_fs12.default.existsSync(filePath)) {
|
|
142189
|
+
import_node_fs12.default.unlinkSync(filePath);
|
|
141015
142190
|
}
|
|
141016
142191
|
return result;
|
|
142192
|
+
} finally {
|
|
142193
|
+
if (recordingActive) {
|
|
142194
|
+
try {
|
|
142195
|
+
await driver.execute(() => {
|
|
142196
|
+
const pointer = document.querySelector("dd-mouse-pointer");
|
|
142197
|
+
if (pointer)
|
|
142198
|
+
pointer.style.display = "block";
|
|
142199
|
+
});
|
|
142200
|
+
} catch {
|
|
142201
|
+
}
|
|
142202
|
+
}
|
|
141017
142203
|
}
|
|
141018
142204
|
if (step.screenshot.crop) {
|
|
141019
142205
|
let padding = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
@@ -141055,7 +142241,7 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141055
142241
|
rect.width = clamped.width;
|
|
141056
142242
|
rect.height = clamped.height;
|
|
141057
142243
|
log(config, "debug", { padded_rect: rect });
|
|
141058
|
-
const croppedPath =
|
|
142244
|
+
const croppedPath = import_node_path12.default.join(dir, `cropped_${step.stepId || Date.now()}.png`);
|
|
141059
142245
|
try {
|
|
141060
142246
|
await sharp(filePath).extract({
|
|
141061
142247
|
left: rect.x,
|
|
@@ -141063,12 +142249,12 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141063
142249
|
width: rect.width,
|
|
141064
142250
|
height: rect.height
|
|
141065
142251
|
}).toFile(croppedPath);
|
|
141066
|
-
|
|
142252
|
+
import_node_fs12.default.renameSync(croppedPath, filePath);
|
|
141067
142253
|
} catch (error) {
|
|
141068
142254
|
result.status = "FAIL";
|
|
141069
142255
|
result.description = `Couldn't crop image. ${error}`;
|
|
141070
|
-
if (existFilePath && filePath !== existFilePath &&
|
|
141071
|
-
|
|
142256
|
+
if (existFilePath && filePath !== existFilePath && import_node_fs12.default.existsSync(filePath)) {
|
|
142257
|
+
import_node_fs12.default.unlinkSync(filePath);
|
|
141072
142258
|
}
|
|
141073
142259
|
return result;
|
|
141074
142260
|
}
|
|
@@ -141076,7 +142262,7 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141076
142262
|
if (existFilePath) {
|
|
141077
142263
|
if (step.screenshot.overwrite == "true" && !isUrlPath) {
|
|
141078
142264
|
result.description += ` Overwrote existing file.`;
|
|
141079
|
-
|
|
142265
|
+
import_node_fs12.default.renameSync(filePath, existFilePath);
|
|
141080
142266
|
result.outputs.screenshotPath = existFilePath;
|
|
141081
142267
|
result.outputs.changed = true;
|
|
141082
142268
|
if (step.screenshot.sourceIntegration) {
|
|
@@ -141089,21 +142275,21 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141089
142275
|
let img1;
|
|
141090
142276
|
let img2;
|
|
141091
142277
|
try {
|
|
141092
|
-
img1 = PNG.sync.read(
|
|
141093
|
-
img2 = PNG.sync.read(
|
|
142278
|
+
img1 = PNG.sync.read(import_node_fs12.default.readFileSync(existFilePath));
|
|
142279
|
+
img2 = PNG.sync.read(import_node_fs12.default.readFileSync(filePath));
|
|
141094
142280
|
} catch (error) {
|
|
141095
142281
|
result.status = "FAIL";
|
|
141096
142282
|
result.description = isUrlPath ? `Couldn't decode PNG for comparison. The URL reference (${redactedUrl}) may not be a valid PNG. ${error}` : `Couldn't decode PNG for comparison. ${error}`;
|
|
141097
|
-
if (!isUrlPath && filePath !== existFilePath &&
|
|
141098
|
-
|
|
142283
|
+
if (!isUrlPath && filePath !== existFilePath && import_node_fs12.default.existsSync(filePath)) {
|
|
142284
|
+
import_node_fs12.default.unlinkSync(filePath);
|
|
141099
142285
|
}
|
|
141100
142286
|
return result;
|
|
141101
142287
|
}
|
|
141102
142288
|
if (!aspectRatiosMatch(img1, img2)) {
|
|
141103
142289
|
result.status = "FAIL";
|
|
141104
142290
|
result.description = `Couldn't compare images. Images have different aspect ratios.`;
|
|
141105
|
-
if (!isUrlPath && filePath !== existFilePath &&
|
|
141106
|
-
|
|
142291
|
+
if (!isUrlPath && filePath !== existFilePath && import_node_fs12.default.existsSync(filePath)) {
|
|
142292
|
+
import_node_fs12.default.unlinkSync(filePath);
|
|
141107
142293
|
}
|
|
141108
142294
|
return result;
|
|
141109
142295
|
}
|
|
@@ -141130,8 +142316,8 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141130
142316
|
} catch (error) {
|
|
141131
142317
|
result.status = "FAIL";
|
|
141132
142318
|
result.description = `Couldn't load screenshot comparison dependency (pixelmatch). ${error?.message ?? error}`;
|
|
141133
|
-
if (!isUrlPath && filePath !== existFilePath &&
|
|
141134
|
-
|
|
142319
|
+
if (!isUrlPath && filePath !== existFilePath && import_node_fs12.default.existsSync(filePath)) {
|
|
142320
|
+
import_node_fs12.default.unlinkSync(filePath);
|
|
141135
142321
|
}
|
|
141136
142322
|
return result;
|
|
141137
142323
|
}
|
|
@@ -141144,7 +142330,7 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141144
142330
|
});
|
|
141145
142331
|
if (fractionalDiff > step.screenshot.maxVariation) {
|
|
141146
142332
|
if (step.screenshot.overwrite == "aboveVariation" && !isUrlPath) {
|
|
141147
|
-
|
|
142333
|
+
import_node_fs12.default.renameSync(filePath, existFilePath);
|
|
141148
142334
|
}
|
|
141149
142335
|
result.status = "WARNING";
|
|
141150
142336
|
result.description += ` The difference between the existing screenshot and new screenshot (${fractionalDiff.toFixed(2)}) is greater than the max accepted variation (${step.screenshot.maxVariation}).`;
|
|
@@ -141170,7 +142356,7 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141170
142356
|
result.outputs.sourceIntegration = step.screenshot.sourceIntegration;
|
|
141171
142357
|
}
|
|
141172
142358
|
if (step.screenshot.overwrite != "true") {
|
|
141173
|
-
|
|
142359
|
+
import_node_fs12.default.unlinkSync(filePath);
|
|
141174
142360
|
}
|
|
141175
142361
|
}
|
|
141176
142362
|
}
|
|
@@ -141189,283 +142375,8 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141189
142375
|
// dist/core/tests/startRecording.js
|
|
141190
142376
|
init_validate();
|
|
141191
142377
|
init_utils();
|
|
141192
|
-
|
|
141193
|
-
// dist/core/tests/ffmpegRecorder.js
|
|
141194
|
-
var import_node_child_process4 = require("node:child_process");
|
|
141195
|
-
var import_node_os6 = __toESM(require("node:os"), 1);
|
|
141196
|
-
var import_node_path12 = __toESM(require("node:path"), 1);
|
|
141197
|
-
var import_node_fs12 = __toESM(require("node:fs"), 1);
|
|
141198
|
-
var import_node_crypto5 = __toESM(require("node:crypto"), 1);
|
|
141199
|
-
init_loader();
|
|
141200
|
-
init_utils();
|
|
141201
|
-
var XVFB_SCREEN_SIZE = "1920x1080";
|
|
141202
|
-
function safeContextId(contextId) {
|
|
141203
|
-
const raw = String(contextId ?? "ctx");
|
|
141204
|
-
const base = sanitizeFilesystemName(raw, "ctx");
|
|
141205
|
-
if (base === raw)
|
|
141206
|
-
return base;
|
|
141207
|
-
const hash = import_node_crypto5.default.createHash("sha1").update(raw).digest("hex").slice(0, 8);
|
|
141208
|
-
return `${base}-${hash}`;
|
|
141209
|
-
}
|
|
141210
|
-
function browserCaptureTitle(contextId) {
|
|
141211
|
-
return `RECORD_ME_${safeContextId(contextId)}`;
|
|
141212
|
-
}
|
|
141213
|
-
function browserDownloadDir(contextId) {
|
|
141214
|
-
return import_node_path12.default.join(import_node_os6.default.tmpdir(), "doc-detective", "recordings", safeContextId(contextId));
|
|
141215
|
-
}
|
|
141216
|
-
function engineFields(record) {
|
|
141217
|
-
const engine = record && typeof record === "object" ? record.engine : void 0;
|
|
141218
|
-
if (typeof engine === "string")
|
|
141219
|
-
return { name: engine };
|
|
141220
|
-
if (engine && typeof engine === "object")
|
|
141221
|
-
return { name: engine.name, target: engine.target, fps: engine.fps };
|
|
141222
|
-
return {};
|
|
141223
|
-
}
|
|
141224
|
-
function resolveRecordPlan({ step, context }) {
|
|
141225
|
-
const { name, target, fps } = engineFields(step?.record);
|
|
141226
|
-
let engineName = name;
|
|
141227
|
-
if (!engineName) {
|
|
141228
|
-
const b = context?.browser;
|
|
141229
|
-
engineName = b?.name === "chrome" && b?.headless === false ? "browser" : "ffmpeg";
|
|
141230
|
-
}
|
|
141231
|
-
return {
|
|
141232
|
-
name: engineName,
|
|
141233
|
-
target: target || "display",
|
|
141234
|
-
fps: fps ?? 30
|
|
141235
|
-
};
|
|
141236
|
-
}
|
|
141237
|
-
function hasRecordStepWithoutEngine(context) {
|
|
141238
|
-
const steps = Array.isArray(context?.steps) ? context.steps : [];
|
|
141239
|
-
return steps.some((s) => {
|
|
141240
|
-
if (!s?.record)
|
|
141241
|
-
return false;
|
|
141242
|
-
return engineFields(s.record).name === void 0;
|
|
141243
|
-
});
|
|
141244
|
-
}
|
|
141245
|
-
function coerceRecordContextBrowser({ context, availableApps }) {
|
|
141246
|
-
if (context?.browser)
|
|
141247
|
-
return null;
|
|
141248
|
-
if (!hasRecordStepWithoutEngine(context))
|
|
141249
|
-
return null;
|
|
141250
|
-
const chromeAvailable = Array.isArray(availableApps) && availableApps.some((a) => a?.name === "chrome");
|
|
141251
|
-
if (!chromeAvailable)
|
|
141252
|
-
return null;
|
|
141253
|
-
return { name: "chrome", headless: false };
|
|
141254
|
-
}
|
|
141255
|
-
function buildCaptureArgs({ platform, fps, displayEnv, outputPath, screenIndex, screenSize }) {
|
|
141256
|
-
const rate = String(fps ?? 30);
|
|
141257
|
-
let input;
|
|
141258
|
-
switch (platform) {
|
|
141259
|
-
case "win32":
|
|
141260
|
-
input = ["-f", "gdigrab", "-framerate", rate, "-i", "desktop"];
|
|
141261
|
-
break;
|
|
141262
|
-
case "darwin":
|
|
141263
|
-
input = [
|
|
141264
|
-
"-f",
|
|
141265
|
-
"avfoundation",
|
|
141266
|
-
"-framerate",
|
|
141267
|
-
rate,
|
|
141268
|
-
"-i",
|
|
141269
|
-
`${screenIndex ?? "0"}:none`
|
|
141270
|
-
];
|
|
141271
|
-
break;
|
|
141272
|
-
case "linux":
|
|
141273
|
-
input = [
|
|
141274
|
-
"-f",
|
|
141275
|
-
"x11grab",
|
|
141276
|
-
"-framerate",
|
|
141277
|
-
rate,
|
|
141278
|
-
...screenSize ? ["-video_size", screenSize] : [],
|
|
141279
|
-
"-i",
|
|
141280
|
-
displayEnv || ":0.0"
|
|
141281
|
-
];
|
|
141282
|
-
break;
|
|
141283
|
-
default:
|
|
141284
|
-
throw new Error(`Screen recording isn't supported on platform '${platform}'.`);
|
|
141285
|
-
}
|
|
141286
|
-
return ["-y", ...input, "-pix_fmt", "yuv420p", outputPath];
|
|
141287
|
-
}
|
|
141288
|
-
async function resolveCropGeometry({ driver, target }) {
|
|
141289
|
-
if (target === "viewport") {
|
|
141290
|
-
const m = await driver.execute(() => {
|
|
141291
|
-
return {
|
|
141292
|
-
sx: window.screenX,
|
|
141293
|
-
sy: window.screenY,
|
|
141294
|
-
iw: window.innerWidth,
|
|
141295
|
-
ih: window.innerHeight,
|
|
141296
|
-
ow: window.outerWidth,
|
|
141297
|
-
oh: window.outerHeight,
|
|
141298
|
-
dpr: window.devicePixelRatio || 1
|
|
141299
|
-
};
|
|
141300
|
-
});
|
|
141301
|
-
const dpr = m.dpr || 1;
|
|
141302
|
-
const border = Math.max(0, (m.ow - m.iw) / 2);
|
|
141303
|
-
const topChrome = Math.max(0, m.oh - m.ih - border);
|
|
141304
|
-
return {
|
|
141305
|
-
x: Math.round((m.sx + border) * dpr),
|
|
141306
|
-
y: Math.round((m.sy + topChrome) * dpr),
|
|
141307
|
-
w: Math.round(m.iw * dpr),
|
|
141308
|
-
h: Math.round(m.ih * dpr)
|
|
141309
|
-
};
|
|
141310
|
-
}
|
|
141311
|
-
if (target === "window") {
|
|
141312
|
-
const r = await driver.getWindowRect();
|
|
141313
|
-
let dpr;
|
|
141314
|
-
try {
|
|
141315
|
-
dpr = await driver.execute(() => window.devicePixelRatio || 1) || 1;
|
|
141316
|
-
} catch {
|
|
141317
|
-
dpr = 1;
|
|
141318
|
-
}
|
|
141319
|
-
return {
|
|
141320
|
-
x: Math.round(r.x * dpr),
|
|
141321
|
-
y: Math.round(r.y * dpr),
|
|
141322
|
-
w: Math.round(r.width * dpr),
|
|
141323
|
-
h: Math.round(r.height * dpr)
|
|
141324
|
-
};
|
|
141325
|
-
}
|
|
141326
|
-
return null;
|
|
141327
|
-
}
|
|
141328
|
-
function jobIsFfmpegRecording(job) {
|
|
141329
|
-
const steps = Array.isArray(job?.context?.steps) ? job.context.steps : [];
|
|
141330
|
-
return steps.some((s) => {
|
|
141331
|
-
if (!s?.record)
|
|
141332
|
-
return false;
|
|
141333
|
-
return resolveRecordPlan({ step: s, context: job.context }).name === "ffmpeg";
|
|
141334
|
-
});
|
|
141335
|
-
}
|
|
141336
|
-
function computeEffectiveConcurrency({ requestedLimit, jobs, platform, xvfbAvailable }) {
|
|
141337
|
-
const ffmpegJobs = (jobs || []).filter(jobIsFfmpegRecording);
|
|
141338
|
-
if (ffmpegJobs.length === 0) {
|
|
141339
|
-
return { limit: requestedLimit, xvfbContexts: [], forcedSerial: false };
|
|
141340
|
-
}
|
|
141341
|
-
if (platform === "linux" && xvfbAvailable) {
|
|
141342
|
-
return {
|
|
141343
|
-
limit: requestedLimit,
|
|
141344
|
-
xvfbContexts: ffmpegJobs.map((j) => j.context),
|
|
141345
|
-
forcedSerial: false
|
|
141346
|
-
};
|
|
141347
|
-
}
|
|
141348
|
-
return { limit: 1, xvfbContexts: [], forcedSerial: requestedLimit > 1 };
|
|
141349
|
-
}
|
|
141350
|
-
async function getFfmpegPath(ctx = {}) {
|
|
141351
|
-
const mod = await loadHeavyDep("@ffmpeg-installer/ffmpeg", { ctx });
|
|
141352
|
-
const candidate = mod && (mod.path ?? mod.default?.path);
|
|
141353
|
-
if (typeof candidate !== "string" || candidate.length === 0) {
|
|
141354
|
-
throw new Error("ffmpeg binary path is missing or malformed in the installed @ffmpeg-installer/ffmpeg package. Try `doc-detective install runtime --force` to reinstall.");
|
|
141355
|
-
}
|
|
141356
|
-
return candidate;
|
|
141357
|
-
}
|
|
141358
|
-
function parseMacScreenIndex(listing) {
|
|
141359
|
-
const m = /\[(\d+)\]\s+Capture screen/i.exec(listing || "");
|
|
141360
|
-
return m ? m[1] : null;
|
|
141361
|
-
}
|
|
141362
|
-
async function detectMacScreenIndex(ffmpegPath) {
|
|
141363
|
-
return new Promise((resolve) => {
|
|
141364
|
-
let out = "";
|
|
141365
|
-
let settled = false;
|
|
141366
|
-
let proc = null;
|
|
141367
|
-
const done = (v) => {
|
|
141368
|
-
if (settled)
|
|
141369
|
-
return;
|
|
141370
|
-
settled = true;
|
|
141371
|
-
try {
|
|
141372
|
-
proc?.kill();
|
|
141373
|
-
} catch {
|
|
141374
|
-
}
|
|
141375
|
-
resolve(v);
|
|
141376
|
-
};
|
|
141377
|
-
try {
|
|
141378
|
-
proc = (0, import_node_child_process4.spawn)(ffmpegPath, ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { stdio: ["ignore", "ignore", "pipe"] });
|
|
141379
|
-
proc.stderr?.on("data", (d) => {
|
|
141380
|
-
out += d.toString();
|
|
141381
|
-
});
|
|
141382
|
-
proc.on("error", () => done(null));
|
|
141383
|
-
proc.on("close", () => done(parseMacScreenIndex(out)));
|
|
141384
|
-
setTimeout(() => done(null), 5e3);
|
|
141385
|
-
} catch {
|
|
141386
|
-
done(null);
|
|
141387
|
-
}
|
|
141388
|
-
});
|
|
141389
|
-
}
|
|
141390
|
-
async function detectX11ScreenSize(display) {
|
|
141391
|
-
return new Promise((resolve) => {
|
|
141392
|
-
let out = "";
|
|
141393
|
-
let settled = false;
|
|
141394
|
-
let proc = null;
|
|
141395
|
-
const done = (v) => {
|
|
141396
|
-
if (settled)
|
|
141397
|
-
return;
|
|
141398
|
-
settled = true;
|
|
141399
|
-
try {
|
|
141400
|
-
proc?.kill();
|
|
141401
|
-
} catch {
|
|
141402
|
-
}
|
|
141403
|
-
resolve(v);
|
|
141404
|
-
};
|
|
141405
|
-
try {
|
|
141406
|
-
const env = display ? { ...process.env, DISPLAY: display } : process.env;
|
|
141407
|
-
proc = (0, import_node_child_process4.spawn)("xdpyinfo", [], { env, stdio: ["ignore", "pipe", "ignore"] });
|
|
141408
|
-
proc.stdout?.on("data", (d) => {
|
|
141409
|
-
out += d.toString();
|
|
141410
|
-
});
|
|
141411
|
-
proc.on("error", () => done(null));
|
|
141412
|
-
proc.on("close", () => {
|
|
141413
|
-
const m = /dimensions:\s+(\d+x\d+)\s+pixels/i.exec(out);
|
|
141414
|
-
done(m ? m[1] : null);
|
|
141415
|
-
});
|
|
141416
|
-
setTimeout(() => done(null), 5e3);
|
|
141417
|
-
} catch {
|
|
141418
|
-
done(null);
|
|
141419
|
-
}
|
|
141420
|
-
});
|
|
141421
|
-
}
|
|
141422
|
-
async function checkSystemBinary(name) {
|
|
141423
|
-
return new Promise((resolve) => {
|
|
141424
|
-
try {
|
|
141425
|
-
const proc = (0, import_node_child_process4.spawn)(name, ["-help"], { stdio: "ignore" });
|
|
141426
|
-
proc.on("error", () => resolve(false));
|
|
141427
|
-
proc.on("close", () => resolve(true));
|
|
141428
|
-
} catch {
|
|
141429
|
-
resolve(false);
|
|
141430
|
-
}
|
|
141431
|
-
});
|
|
141432
|
-
}
|
|
141433
|
-
function xvfbDisplay(index) {
|
|
141434
|
-
return `:${99 + index}`;
|
|
141435
|
-
}
|
|
141436
|
-
async function startXvfb(display, opts = {}) {
|
|
141437
|
-
const num = display.replace(/^:/, "").split(".")[0];
|
|
141438
|
-
const [defW, defH] = XVFB_SCREEN_SIZE.split("x").map(Number);
|
|
141439
|
-
const w = opts.width ?? defW;
|
|
141440
|
-
const h = opts.height ?? defH;
|
|
141441
|
-
const startMs = Date.now();
|
|
141442
|
-
const proc = (0, import_node_child_process4.spawn)("Xvfb", [display, "-screen", "0", `${w}x${h}x24`, "-nolisten", "tcp"], { stdio: "ignore" });
|
|
141443
|
-
let spawnErr = null;
|
|
141444
|
-
proc.on("error", (e) => {
|
|
141445
|
-
spawnErr = e;
|
|
141446
|
-
});
|
|
141447
|
-
const lock = `/tmp/.X${num}-lock`;
|
|
141448
|
-
for (let i = 0; i < 50; i++) {
|
|
141449
|
-
if (spawnErr)
|
|
141450
|
-
throw spawnErr;
|
|
141451
|
-
if (proc.exitCode !== null)
|
|
141452
|
-
throw new Error(`Xvfb exited early on ${display} (code ${proc.exitCode})`);
|
|
141453
|
-
try {
|
|
141454
|
-
if (import_node_fs12.default.statSync(lock).mtimeMs >= startMs)
|
|
141455
|
-
return proc;
|
|
141456
|
-
} catch {
|
|
141457
|
-
}
|
|
141458
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
141459
|
-
}
|
|
141460
|
-
try {
|
|
141461
|
-
proc.kill();
|
|
141462
|
-
} catch {
|
|
141463
|
-
}
|
|
141464
|
-
throw new Error(`Xvfb did not become ready on ${display} within 5s.`);
|
|
141465
|
-
}
|
|
141466
|
-
|
|
141467
|
-
// dist/core/tests/startRecording.js
|
|
141468
142378
|
var import_node_child_process5 = require("node:child_process");
|
|
142379
|
+
var import_node_crypto6 = require("node:crypto");
|
|
141469
142380
|
var import_node_path13 = __toESM(require("node:path"), 1);
|
|
141470
142381
|
var import_node_fs13 = __toESM(require("node:fs"), 1);
|
|
141471
142382
|
var import_node_os7 = __toESM(require("node:os"), 1);
|
|
@@ -141513,7 +142424,22 @@ async function startRecording({ config, context, step, driver }) {
|
|
|
141513
142424
|
result.description = `File already exists: ${filePath}`;
|
|
141514
142425
|
return result;
|
|
141515
142426
|
}
|
|
141516
|
-
const
|
|
142427
|
+
const normalizeActiveTarget = (p) => {
|
|
142428
|
+
const resolved = import_node_path13.default.resolve(p);
|
|
142429
|
+
return process.platform === "win32" || process.platform === "darwin" ? resolved.toLowerCase() : resolved;
|
|
142430
|
+
};
|
|
142431
|
+
const normalizedTarget = normalizeActiveTarget(filePath);
|
|
142432
|
+
if (Array.isArray(driver?.state?.recordings) && driver.state.recordings.some((r) => typeof r?.targetPath === "string" && normalizeActiveTarget(r.targetPath) === normalizedTarget)) {
|
|
142433
|
+
result.status = "SKIPPED";
|
|
142434
|
+
result.description = `Recording target is already in use by an active recording: ${filePath}`;
|
|
142435
|
+
return result;
|
|
142436
|
+
}
|
|
142437
|
+
let plan = resolveRecordPlan({ step, context });
|
|
142438
|
+
const hasActiveBrowserRecording = Array.isArray(driver?.state?.recordings) && driver.state.recordings.some((r) => r?.type === "MediaRecorder");
|
|
142439
|
+
if (plan.name === "browser" && hasActiveBrowserRecording) {
|
|
142440
|
+
log(config, "warning", "A browser-engine recording is already active in this context; recording this one with the ffmpeg engine (viewport target) instead, since only one browser-engine recording can run at a time.");
|
|
142441
|
+
plan = { name: "ffmpeg", target: "viewport", fps: plan.fps };
|
|
142442
|
+
}
|
|
141517
142443
|
if (plan.name === "browser") {
|
|
141518
142444
|
if (context.browser?.headless) {
|
|
141519
142445
|
result.status = "SKIPPED";
|
|
@@ -141663,7 +142589,7 @@ async function startRecording({ config, context, step, driver }) {
|
|
|
141663
142589
|
const tempDir = import_node_path13.default.join(import_node_os7.default.tmpdir(), "doc-detective", "recordings");
|
|
141664
142590
|
if (!import_node_fs13.default.existsSync(tempDir))
|
|
141665
142591
|
import_node_fs13.default.mkdirSync(tempDir, { recursive: true });
|
|
141666
|
-
const tempPath = import_node_path13.default.join(tempDir, `${safeContextId(context.contextId)}-${baseName}.mkv`);
|
|
142592
|
+
const tempPath = import_node_path13.default.join(tempDir, `${safeContextId(context.contextId)}-${baseName}-${(0, import_node_crypto6.randomUUID)().slice(0, 8)}.mkv`);
|
|
141667
142593
|
let ffmpegPath;
|
|
141668
142594
|
try {
|
|
141669
142595
|
ffmpegPath = await getFfmpegPath({ cacheDir: config?.cacheDir });
|
|
@@ -141735,12 +142661,32 @@ async function stopRecording({ config, step, driver }) {
|
|
|
141735
142661
|
result.description = `Invalid step definition: ${isValidStep.errors}`;
|
|
141736
142662
|
return result;
|
|
141737
142663
|
}
|
|
141738
|
-
|
|
142664
|
+
if (step.stopRecord === false) {
|
|
142665
|
+
result.status = "SKIPPED";
|
|
142666
|
+
result.description = "Recording stop is disabled (stopRecord: false).";
|
|
142667
|
+
return result;
|
|
142668
|
+
}
|
|
142669
|
+
const recordings = Array.isArray(driver?.state?.recordings) ? driver.state.recordings : [];
|
|
142670
|
+
const recording = selectRecordingToStop(recordings, step.stopRecord, {
|
|
142671
|
+
includeSynthetic: step?.__stopAny === true
|
|
142672
|
+
});
|
|
141739
142673
|
if (!recording) {
|
|
141740
142674
|
result.status = "SKIPPED";
|
|
141741
|
-
|
|
142675
|
+
const target = stopRecordTargetName(step.stopRecord);
|
|
142676
|
+
if (target !== void 0) {
|
|
142677
|
+
result.description = `No active recording named '${target}'.`;
|
|
142678
|
+
} else if (recordings.length > 0) {
|
|
142679
|
+
result.description = `No user-stoppable recording is active; an automatic (autoRecord) recording is still running and stops at the end of the context.`;
|
|
142680
|
+
} else {
|
|
142681
|
+
result.description = `Recording isn't started.`;
|
|
142682
|
+
}
|
|
141742
142683
|
return result;
|
|
141743
142684
|
}
|
|
142685
|
+
const dropHandle = () => {
|
|
142686
|
+
const idx = recordings.indexOf(recording);
|
|
142687
|
+
if (idx !== -1)
|
|
142688
|
+
recordings.splice(idx, 1);
|
|
142689
|
+
};
|
|
141744
142690
|
try {
|
|
141745
142691
|
if (recording.type === "MediaRecorder") {
|
|
141746
142692
|
await driver.switchToWindow(recording.tab);
|
|
@@ -141756,7 +142702,7 @@ async function stopRecording({ config, step, driver }) {
|
|
|
141756
142702
|
if (remainingHandles2.length > 0) {
|
|
141757
142703
|
await driver.switchToWindow(remainingHandles2[0]);
|
|
141758
142704
|
}
|
|
141759
|
-
|
|
142705
|
+
dropHandle();
|
|
141760
142706
|
return result;
|
|
141761
142707
|
}
|
|
141762
142708
|
await driver.execute(() => {
|
|
@@ -141766,7 +142712,7 @@ async function stopRecording({ config, step, driver }) {
|
|
|
141766
142712
|
if (!downloaded) {
|
|
141767
142713
|
result.status = "FAIL";
|
|
141768
142714
|
result.description = "Recording download timed out.";
|
|
141769
|
-
|
|
142715
|
+
dropHandle();
|
|
141770
142716
|
return result;
|
|
141771
142717
|
}
|
|
141772
142718
|
const allHandles = await driver.getWindowHandles();
|
|
@@ -141781,7 +142727,7 @@ async function stopRecording({ config, step, driver }) {
|
|
|
141781
142727
|
targetPath: recording.targetPath,
|
|
141782
142728
|
deleteSource: true
|
|
141783
142729
|
});
|
|
141784
|
-
|
|
142730
|
+
dropHandle();
|
|
141785
142731
|
} else if (recording.type === "ffmpeg") {
|
|
141786
142732
|
const proc = recording.process;
|
|
141787
142733
|
try {
|
|
@@ -141820,12 +142766,12 @@ async function stopRecording({ config, step, driver }) {
|
|
|
141820
142766
|
deleteSource: true,
|
|
141821
142767
|
crop: recording.crop
|
|
141822
142768
|
});
|
|
141823
|
-
|
|
142769
|
+
dropHandle();
|
|
141824
142770
|
}
|
|
141825
142771
|
} catch (error) {
|
|
141826
142772
|
result.status = "FAIL";
|
|
141827
142773
|
result.description = `Couldn't stop recording. ${error}`;
|
|
141828
|
-
|
|
142774
|
+
dropHandle();
|
|
141829
142775
|
return result;
|
|
141830
142776
|
}
|
|
141831
142777
|
return result;
|
|
@@ -141883,7 +142829,7 @@ async function waitForStableFile(filePath, maxSeconds) {
|
|
|
141883
142829
|
}
|
|
141884
142830
|
if (size > 0 && size === lastSize) {
|
|
141885
142831
|
stableReads++;
|
|
141886
|
-
if (stableReads >=
|
|
142832
|
+
if (stableReads >= 2)
|
|
141887
142833
|
return true;
|
|
141888
142834
|
} else {
|
|
141889
142835
|
stableReads = 0;
|
|
@@ -143009,7 +143955,7 @@ async function dragAndDropElement({ config, step, driver }) {
|
|
|
143009
143955
|
// dist/core/tests.js
|
|
143010
143956
|
var import_node_path19 = __toESM(require("node:path"), 1);
|
|
143011
143957
|
var import_node_child_process7 = require("node:child_process");
|
|
143012
|
-
var
|
|
143958
|
+
var import_node_crypto7 = require("node:crypto");
|
|
143013
143959
|
init_appium();
|
|
143014
143960
|
|
|
143015
143961
|
// dist/core/expressions.js
|
|
@@ -143744,6 +144690,7 @@ async function runSpecs({ resolvedTests }) {
|
|
|
143744
144690
|
let limit = resolveConcurrentRunners(config);
|
|
143745
144691
|
log(config, "info", "Running test specs.");
|
|
143746
144692
|
const jobs = [];
|
|
144693
|
+
let autoRecordInjected = false;
|
|
143747
144694
|
for (const spec of specs) {
|
|
143748
144695
|
log(config, "debug", `SPEC: ${spec.specId}`);
|
|
143749
144696
|
metaValues.specs[spec.specId] ??= { tests: {} };
|
|
@@ -143765,6 +144712,17 @@ async function runSpecs({ resolvedTests }) {
|
|
|
143765
144712
|
contexts: new Array(test.contexts.length)
|
|
143766
144713
|
};
|
|
143767
144714
|
specReport.tests.push(testReport);
|
|
144715
|
+
let recordingNameConflict = null;
|
|
144716
|
+
for (const c of test.contexts) {
|
|
144717
|
+
const conflict = detectRecordingNameConflict(c?.steps);
|
|
144718
|
+
if (conflict) {
|
|
144719
|
+
recordingNameConflict = conflict;
|
|
144720
|
+
break;
|
|
144721
|
+
}
|
|
144722
|
+
}
|
|
144723
|
+
if (recordingNameConflict) {
|
|
144724
|
+
log(config, "warning", `Skipping test '${test.testId}': recording name '${recordingNameConflict}' is reused while a recording with that name is still active. Names must be unique among recordings that overlap in time.`);
|
|
144725
|
+
}
|
|
143768
144726
|
const usedContextIds = new Set(test.contexts.map((c) => c.contextId).filter(Boolean));
|
|
143769
144727
|
test.contexts.forEach((context, slot) => {
|
|
143770
144728
|
if (!context.contextId) {
|
|
@@ -143777,6 +144735,25 @@ async function runSpecs({ resolvedTests }) {
|
|
|
143777
144735
|
usedContextIds.add(id);
|
|
143778
144736
|
context.contextId = id;
|
|
143779
144737
|
}
|
|
144738
|
+
if (recordingNameConflict) {
|
|
144739
|
+
testReport.contexts[slot] = {
|
|
144740
|
+
contextId: context.contextId,
|
|
144741
|
+
platform: context.platform,
|
|
144742
|
+
browser: context.browser,
|
|
144743
|
+
result: "SKIPPED",
|
|
144744
|
+
resultDescription: `Skipped \u2014 recording name '${recordingNameConflict}' is reused while still active; names must be unique among overlapping recordings.`,
|
|
144745
|
+
steps: []
|
|
144746
|
+
};
|
|
144747
|
+
return;
|
|
144748
|
+
}
|
|
144749
|
+
if (resolveAutoRecord({ config, spec, test })) {
|
|
144750
|
+
const autoStep = buildAutoRecordStep({ config, spec, test, context });
|
|
144751
|
+
if (autoStep) {
|
|
144752
|
+
const authored = Array.isArray(context.steps) ? context.steps.filter((s) => !s?.__autoRecord) : [];
|
|
144753
|
+
context.steps = [autoStep, ...authored];
|
|
144754
|
+
autoRecordInjected = true;
|
|
144755
|
+
}
|
|
144756
|
+
}
|
|
143780
144757
|
const coercedBrowser = coerceRecordContextBrowser({
|
|
143781
144758
|
context,
|
|
143782
144759
|
availableApps: runnerDetails.availableApps
|
|
@@ -143792,16 +144769,25 @@ async function runSpecs({ resolvedTests }) {
|
|
|
143792
144769
|
if (anyFfmpegRecording && process.platform === "linux") {
|
|
143793
144770
|
xvfbAvailable = await checkSystemBinary("Xvfb");
|
|
143794
144771
|
}
|
|
144772
|
+
const hasExplicitFfmpegRecording = jobs.some((job) => jobIsFfmpegRecording({
|
|
144773
|
+
context: {
|
|
144774
|
+
...job.context,
|
|
144775
|
+
steps: Array.isArray(job.context?.steps) ? job.context.steps.filter((s) => !s?.__autoRecord) : []
|
|
144776
|
+
}
|
|
144777
|
+
}));
|
|
143795
144778
|
const concurrency = computeEffectiveConcurrency({
|
|
143796
144779
|
requestedLimit: limit,
|
|
143797
144780
|
jobs,
|
|
143798
144781
|
platform: process.platform,
|
|
143799
|
-
xvfbAvailable
|
|
144782
|
+
xvfbAvailable,
|
|
144783
|
+
allowOverlappingCaptures: autoRecordInjected && !hasExplicitFfmpegRecording
|
|
143800
144784
|
});
|
|
143801
144785
|
limit = concurrency.limit;
|
|
143802
144786
|
if (concurrency.forcedSerial) {
|
|
143803
144787
|
log(config, "warning", 'Recording with the ffmpeg engine needs exclusive use of the display, so this run is executing serially (concurrentRunners=1). To record concurrently, use the Chrome browser engine (record: { engine: "browser" }) or, on Linux, install Xvfb.');
|
|
143804
144788
|
report.recordingForcedSerial = true;
|
|
144789
|
+
} else if (concurrency.overlappingCaptures && limit > 1) {
|
|
144790
|
+
log(config, "warning", "autoRecord is running ffmpeg recordings concurrently on a shared display, so the captured videos will overlap (each context records the whole screen). For isolated concurrent recordings, run on Linux with Xvfb installed.");
|
|
143805
144791
|
}
|
|
143806
144792
|
const driverJobCount = jobs.filter((job) => isDriverRequired({ test: job.context })).length;
|
|
143807
144793
|
let appiumServers = [];
|
|
@@ -144040,6 +145026,24 @@ async function warmUpContexts({ jobs, config, runnerDetails, appiumPool, install
|
|
|
144040
145026
|
function resolveAutoScreenshot({ config, spec, test }) {
|
|
144041
145027
|
return Boolean(test?.autoScreenshot ?? spec?.autoScreenshot ?? config?.autoScreenshot);
|
|
144042
145028
|
}
|
|
145029
|
+
function resolveAutoRecord({ config, spec, test }) {
|
|
145030
|
+
return Boolean(test?.autoRecord ?? spec?.autoRecord ?? config?.autoRecord);
|
|
145031
|
+
}
|
|
145032
|
+
function buildAutoRecordStep({ config, spec, test, context }) {
|
|
145033
|
+
if (!isDriverRequired({ test: context }))
|
|
145034
|
+
return null;
|
|
145035
|
+
const runDir = getRunOutputDir(config, { create: false });
|
|
145036
|
+
const fileName = `${capPathSegment(sanitizeFilesystemName(String(context.contextId ?? ""), "context"))}.mp4`;
|
|
145037
|
+
const recordPath = import_node_path19.default.join(runDir, "recordings", capPathSegment(sanitizeFilesystemName(String(spec.specId ?? ""), "spec")), capPathSegment(sanitizeFilesystemName(String(test.testId ?? ""), "test")), fileName);
|
|
145038
|
+
return {
|
|
145039
|
+
record: { path: recordPath, overwrite: "true", engine: "ffmpeg" },
|
|
145040
|
+
description: "Automatic full-context recording",
|
|
145041
|
+
stepId: `${sanitizeFilesystemName(String(test.testId ?? ""), "test")}~autorecord`,
|
|
145042
|
+
// Internal marker — the runStep record dispatch flags the started handle as
|
|
145043
|
+
// synthetic so it survives untargeted stopRecord and is swept by cleanup.
|
|
145044
|
+
__autoRecord: true
|
|
145045
|
+
};
|
|
145046
|
+
}
|
|
144043
145047
|
function capPathSegment(segment, max = 64) {
|
|
144044
145048
|
return segment.length <= max ? segment : segment.slice(segment.length - max);
|
|
144045
145049
|
}
|
|
@@ -144232,7 +145236,7 @@ ${JSON.stringify(context, null, 2)}`);
|
|
|
144232
145236
|
const usedStepIds = new Set(context.steps.map((s) => s.stepId).filter(Boolean));
|
|
144233
145237
|
for (const [stepIndex, step] of context.steps.entries()) {
|
|
144234
145238
|
if (!step.stepId) {
|
|
144235
|
-
const baseId = sanitizeFilesystemName(`${test.testId}~s${contentHash(step)}`, `step-${(0,
|
|
145239
|
+
const baseId = sanitizeFilesystemName(`${test.testId}~s${contentHash(step)}`, `step-${(0, import_node_crypto7.randomUUID)()}`);
|
|
144236
145240
|
let stepId = baseId;
|
|
144237
145241
|
let suffix = 2;
|
|
144238
145242
|
while (usedStepIds.has(stepId)) {
|
|
@@ -144302,32 +145306,13 @@ ${JSON.stringify(stepResult, null, 2)}`);
|
|
|
144302
145306
|
stepExecutionFailed = true;
|
|
144303
145307
|
}
|
|
144304
145308
|
}
|
|
144305
|
-
|
|
144306
|
-
const stopRecordStep = {
|
|
144307
|
-
stopRecord: true,
|
|
144308
|
-
description: "Stopping recording",
|
|
144309
|
-
stepId: (0, import_node_crypto6.randomUUID)()
|
|
144310
|
-
};
|
|
144311
|
-
const stepResult = await runStep({
|
|
144312
|
-
config,
|
|
144313
|
-
context,
|
|
144314
|
-
step: stopRecordStep,
|
|
144315
|
-
driver,
|
|
144316
|
-
options: {
|
|
144317
|
-
openApiDefinitions: context.openApi || []
|
|
144318
|
-
}
|
|
144319
|
-
});
|
|
144320
|
-
stepResult.result = stepResult.status;
|
|
144321
|
-
stepResult.resultDescription = stepResult.description;
|
|
144322
|
-
delete stepResult.status;
|
|
144323
|
-
delete stepResult.description;
|
|
144324
|
-
const stepReport = {
|
|
144325
|
-
...stopRecordStep,
|
|
144326
|
-
...stepResult
|
|
144327
|
-
};
|
|
144328
|
-
contextReport.steps.push(stepReport);
|
|
144329
|
-
}
|
|
145309
|
+
await stopAllRecordings({ config, context, driver, contextReport });
|
|
144330
145310
|
} finally {
|
|
145311
|
+
try {
|
|
145312
|
+
await stopAllRecordings({ config, context, driver, contextReport });
|
|
145313
|
+
} catch (error) {
|
|
145314
|
+
clog("error", `Failed to stop recordings during cleanup: ${error?.message ?? error}`);
|
|
145315
|
+
}
|
|
144331
145316
|
if (driver) {
|
|
144332
145317
|
try {
|
|
144333
145318
|
await driver.deleteSession();
|
|
@@ -144342,6 +145327,42 @@ ${JSON.stringify(stepResult, null, 2)}`);
|
|
|
144342
145327
|
contextReport.result = rollUpResults(contextReport.steps);
|
|
144343
145328
|
return contextReport;
|
|
144344
145329
|
}
|
|
145330
|
+
async function stopAllRecordings({ config, context, driver, contextReport }) {
|
|
145331
|
+
if (!Array.isArray(driver?.state?.recordings))
|
|
145332
|
+
return;
|
|
145333
|
+
let guard = driver.state.recordings.length + 1;
|
|
145334
|
+
while (driver.state.recordings.length > 0 && guard-- > 0) {
|
|
145335
|
+
const stopRecordStep = {
|
|
145336
|
+
stopRecord: true,
|
|
145337
|
+
__stopAny: true,
|
|
145338
|
+
description: "Stopping recording",
|
|
145339
|
+
stepId: (0, import_node_crypto7.randomUUID)()
|
|
145340
|
+
};
|
|
145341
|
+
try {
|
|
145342
|
+
const stepResult = await runStep({
|
|
145343
|
+
config,
|
|
145344
|
+
context,
|
|
145345
|
+
step: stopRecordStep,
|
|
145346
|
+
driver,
|
|
145347
|
+
options: { openApiDefinitions: context.openApi || [] }
|
|
145348
|
+
});
|
|
145349
|
+
stepResult.result = stepResult.status;
|
|
145350
|
+
stepResult.resultDescription = stepResult.description;
|
|
145351
|
+
delete stepResult.status;
|
|
145352
|
+
delete stepResult.description;
|
|
145353
|
+
delete stopRecordStep.__stopAny;
|
|
145354
|
+
contextReport.steps.push({ ...stopRecordStep, ...stepResult });
|
|
145355
|
+
} catch (error) {
|
|
145356
|
+
delete stopRecordStep.__stopAny;
|
|
145357
|
+
driver.state.recordings.pop();
|
|
145358
|
+
contextReport.steps.push({
|
|
145359
|
+
...stopRecordStep,
|
|
145360
|
+
result: "FAIL",
|
|
145361
|
+
resultDescription: `Couldn't stop recording. ${error?.message ?? error}`
|
|
145362
|
+
});
|
|
145363
|
+
}
|
|
145364
|
+
}
|
|
145365
|
+
}
|
|
144345
145366
|
async function runStep({ config = {}, context = {}, step, driver, metaValues = {}, options = {} }) {
|
|
144346
145367
|
let actionResult;
|
|
144347
145368
|
step = replaceEnvs(step);
|
|
@@ -144392,7 +145413,16 @@ async function runStep({ config = {}, context = {}, step, driver, metaValues = {
|
|
|
144392
145413
|
step,
|
|
144393
145414
|
driver
|
|
144394
145415
|
});
|
|
144395
|
-
|
|
145416
|
+
if (actionResult.recording) {
|
|
145417
|
+
if (!Array.isArray(driver.state.recordings))
|
|
145418
|
+
driver.state.recordings = [];
|
|
145419
|
+
const handle = actionResult.recording;
|
|
145420
|
+
handle.id = handle.id ?? (0, import_node_crypto7.randomUUID)();
|
|
145421
|
+
handle.name = handle.name ?? recordStepName(step.record);
|
|
145422
|
+
if (step.__autoRecord)
|
|
145423
|
+
handle.synthetic = true;
|
|
145424
|
+
driver.state.recordings.push(handle);
|
|
145425
|
+
}
|
|
144396
145426
|
} else if (typeof step.runCode !== "undefined") {
|
|
144397
145427
|
actionResult = await runCode({ config, step });
|
|
144398
145428
|
} else if (typeof step.runShell !== "undefined") {
|
|
@@ -144417,7 +145447,7 @@ async function runStep({ config = {}, context = {}, step, driver, metaValues = {
|
|
|
144417
145447
|
description: `Unknown step action: ${JSON.stringify(step)}`
|
|
144418
145448
|
};
|
|
144419
145449
|
}
|
|
144420
|
-
if (driver
|
|
145450
|
+
if (isRecordingActive(driver)) {
|
|
144421
145451
|
const currentUrl = await driver.getUrl();
|
|
144422
145452
|
if (currentUrl !== driver.state.url) {
|
|
144423
145453
|
driver.state.url = currentUrl;
|
|
@@ -144503,7 +145533,7 @@ async function driverStart(capabilities, port, maxAttempts = 4, ctx = {}) {
|
|
|
144503
145533
|
waitforTimeout: 12e4
|
|
144504
145534
|
// 2 minutes
|
|
144505
145535
|
});
|
|
144506
|
-
driver.state = { url: "", x: null, y: null,
|
|
145536
|
+
driver.state = { url: "", x: null, y: null, recordings: [] };
|
|
144507
145537
|
return driver;
|
|
144508
145538
|
} catch (err) {
|
|
144509
145539
|
lastError = err;
|