doc-detective 4.12.0 → 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 +198 -36
- package/dist/core/tests.js.map +1 -1
- package/dist/core/utils.d.ts +5 -2
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/core/utils.js +87 -20
- 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 +1617 -568
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +45 -9
- 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
|
},
|
|
@@ -134369,33 +135177,50 @@ function getOrInitRunTimestamp(config) {
|
|
|
134369
135177
|
}
|
|
134370
135178
|
return config.__runTimestamp;
|
|
134371
135179
|
}
|
|
134372
|
-
function getRunOutputDir(config) {
|
|
134373
|
-
if (config?.__runOutputDir)
|
|
135180
|
+
function getRunOutputDir(config, { create = true } = {}) {
|
|
135181
|
+
if (config?.__runOutputDir) {
|
|
135182
|
+
if (create)
|
|
135183
|
+
import_node_fs.default.mkdirSync(config.__runOutputDir, { recursive: true });
|
|
134374
135184
|
return config.__runOutputDir;
|
|
135185
|
+
}
|
|
134375
135186
|
let base = String(config?.output || ".");
|
|
134376
135187
|
const reportFileExtensions = [".json", ".html", ".htm"];
|
|
134377
135188
|
if (reportFileExtensions.some((ext) => base.toLowerCase().endsWith(ext))) {
|
|
134378
135189
|
base = import_node_path.default.dirname(base);
|
|
134379
135190
|
}
|
|
134380
135191
|
const runsRoot = import_node_path.default.resolve(base, ".doc-detective");
|
|
134381
|
-
import_node_fs.default.mkdirSync(runsRoot, { recursive: true });
|
|
134382
135192
|
const runId = getOrInitRunTimestamp(config);
|
|
134383
135193
|
let dir = import_node_path.default.join(runsRoot, `run-${runId}`);
|
|
134384
|
-
|
|
134385
|
-
|
|
134386
|
-
|
|
134387
|
-
|
|
134388
|
-
|
|
134389
|
-
|
|
134390
|
-
|
|
134391
|
-
|
|
134392
|
-
|
|
135194
|
+
if (create) {
|
|
135195
|
+
import_node_fs.default.mkdirSync(runsRoot, { recursive: true });
|
|
135196
|
+
let suffix = 2;
|
|
135197
|
+
for (; ; ) {
|
|
135198
|
+
try {
|
|
135199
|
+
import_node_fs.default.mkdirSync(dir);
|
|
135200
|
+
break;
|
|
135201
|
+
} catch (error) {
|
|
135202
|
+
if (error?.code !== "EEXIST")
|
|
135203
|
+
throw error;
|
|
135204
|
+
dir = import_node_path.default.join(runsRoot, `run-${runId}-${suffix++}`);
|
|
135205
|
+
}
|
|
134393
135206
|
}
|
|
134394
135207
|
}
|
|
134395
135208
|
if (config)
|
|
134396
135209
|
config.__runOutputDir = dir;
|
|
134397
135210
|
return dir;
|
|
134398
135211
|
}
|
|
135212
|
+
function runArchivesArtifacts(config = {}, specs = []) {
|
|
135213
|
+
const list = Array.isArray(specs) ? specs : [];
|
|
135214
|
+
if (list.length > 0) {
|
|
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)
|
|
135217
|
+
return true;
|
|
135218
|
+
} else if (Boolean(config?.autoScreenshot) || Boolean(config?.autoRecord)) {
|
|
135219
|
+
return true;
|
|
135220
|
+
}
|
|
135221
|
+
const active = Array.isArray(config?.reporters) && config.reporters.length > 0 ? config.reporters : ["terminal", "json", "runFolder"];
|
|
135222
|
+
return active.some((reporter) => typeof reporter === "string" && (reporter.toLowerCase() === "runfolder" || reporter === "runFolderReporter"));
|
|
135223
|
+
}
|
|
134399
135224
|
async function spawnCommand(cmd, args = [], options = {}) {
|
|
134400
135225
|
const spawnOptions = {
|
|
134401
135226
|
shell: true
|
|
@@ -139757,6 +140582,375 @@ async function findElementByCriteria({ selector, elementText, elementId, element
|
|
|
139757
140582
|
// dist/core/tests/typeKeys.js
|
|
139758
140583
|
init_validate();
|
|
139759
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
|
|
139760
140954
|
var _specialKeyMap = null;
|
|
139761
140955
|
async function getSpecialKeyMap(ctx = {}) {
|
|
139762
140956
|
if (_specialKeyMap)
|
|
@@ -139883,7 +141077,7 @@ async function typeKeys({ config, step, driver }) {
|
|
|
139883
141077
|
return result;
|
|
139884
141078
|
}
|
|
139885
141079
|
}
|
|
139886
|
-
if (driver
|
|
141080
|
+
if (isRecordingActive(driver)) {
|
|
139887
141081
|
let keys = [];
|
|
139888
141082
|
step.type.keys.forEach((key) => {
|
|
139889
141083
|
if (key.startsWith("$") && key.endsWith("$")) {
|
|
@@ -139913,7 +141107,7 @@ async function typeKeys({ config, step, driver }) {
|
|
|
139913
141107
|
});
|
|
139914
141108
|
}
|
|
139915
141109
|
try {
|
|
139916
|
-
if (driver
|
|
141110
|
+
if (isRecordingActive(driver)) {
|
|
139917
141111
|
for (let i = 0; i < step.type.keys.length; i++) {
|
|
139918
141112
|
await driver.keys(step.type.keys[i]);
|
|
139919
141113
|
await new Promise((resolve) => setTimeout(resolve, step.type.inputDelay));
|
|
@@ -140077,7 +141271,7 @@ async function findElement({ config, step, driver, click }) {
|
|
|
140077
141271
|
result.description += " Typed keys.";
|
|
140078
141272
|
}
|
|
140079
141273
|
}
|
|
140080
|
-
if (driver
|
|
141274
|
+
if (isRecordingActive(driver)) {
|
|
140081
141275
|
await wait({ config, step: { wait: 2e3 }, driver });
|
|
140082
141276
|
}
|
|
140083
141277
|
return result;
|
|
@@ -140466,8 +141660,8 @@ async function waitForDOMStable(driver, idleTime, timeout) {
|
|
|
140466
141660
|
// dist/core/tests/runShell.js
|
|
140467
141661
|
init_validate();
|
|
140468
141662
|
init_utils();
|
|
140469
|
-
var
|
|
140470
|
-
var
|
|
141663
|
+
var import_node_fs11 = __toESM(require("node:fs"), 1);
|
|
141664
|
+
var import_node_path11 = __toESM(require("node:path"), 1);
|
|
140471
141665
|
async function runShell({ config, step }) {
|
|
140472
141666
|
const result = {
|
|
140473
141667
|
status: "PASS",
|
|
@@ -140540,24 +141734,24 @@ async function runShell({ config, step }) {
|
|
|
140540
141734
|
}
|
|
140541
141735
|
}
|
|
140542
141736
|
if (step.runShell.path) {
|
|
140543
|
-
const dir =
|
|
140544
|
-
if (!
|
|
140545
|
-
|
|
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 });
|
|
140546
141740
|
}
|
|
140547
141741
|
let filePath = step.runShell.path;
|
|
140548
141742
|
log(config, "debug", `Saving stdio to file: ${filePath}`);
|
|
140549
|
-
if (!
|
|
140550
|
-
|
|
141743
|
+
if (!import_node_fs11.default.existsSync(filePath)) {
|
|
141744
|
+
import_node_fs11.default.writeFileSync(filePath, result.outputs.stdio.stdout);
|
|
140551
141745
|
} else {
|
|
140552
141746
|
if (step.runShell.overwrite == "false") {
|
|
140553
141747
|
result.description = result.description + ` Didn't save output. File already exists.`;
|
|
140554
141748
|
}
|
|
140555
|
-
const existingFile =
|
|
141749
|
+
const existingFile = import_node_fs11.default.readFileSync(filePath, "utf8");
|
|
140556
141750
|
const fractionalDiff = calculateFractionalDifference(existingFile, result.outputs.stdio.stdout);
|
|
140557
141751
|
log(config, "debug", `Fractional difference: ${fractionalDiff}`);
|
|
140558
141752
|
if (fractionalDiff > step.runShell.maxVariation) {
|
|
140559
141753
|
if (step.runShell.overwrite == "aboveVariation") {
|
|
140560
|
-
|
|
141754
|
+
import_node_fs11.default.writeFileSync(filePath, result.outputs.stdio.stdout);
|
|
140561
141755
|
result.description += ` Saved output to file.`;
|
|
140562
141756
|
}
|
|
140563
141757
|
result.status = "WARNING";
|
|
@@ -140565,7 +141759,7 @@ async function runShell({ config, step }) {
|
|
|
140565
141759
|
return result;
|
|
140566
141760
|
}
|
|
140567
141761
|
if (step.runShell.overwrite == "true") {
|
|
140568
|
-
|
|
141762
|
+
import_node_fs11.default.writeFileSync(filePath, result.outputs.stdio.stdout);
|
|
140569
141763
|
result.description += ` Saved output to file.`;
|
|
140570
141764
|
}
|
|
140571
141765
|
}
|
|
@@ -140749,8 +141943,8 @@ async function checkLink({ config, step }) {
|
|
|
140749
141943
|
// dist/core/tests/saveScreenshot.js
|
|
140750
141944
|
init_validate();
|
|
140751
141945
|
init_utils();
|
|
140752
|
-
var
|
|
140753
|
-
var
|
|
141946
|
+
var import_node_path12 = __toESM(require("node:path"), 1);
|
|
141947
|
+
var import_node_fs12 = __toESM(require("node:fs"), 1);
|
|
140754
141948
|
init_loader();
|
|
140755
141949
|
var _pngjs = null;
|
|
140756
141950
|
var _sharp = null;
|
|
@@ -140838,7 +142032,7 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
140838
142032
|
if (typeof step.screenshot.path === "undefined") {
|
|
140839
142033
|
step.screenshot.path = `${step.stepId}.png`;
|
|
140840
142034
|
if (step.screenshot.directory) {
|
|
140841
|
-
step.screenshot.path =
|
|
142035
|
+
step.screenshot.path = import_node_path12.default.resolve(step.screenshot.directory, step.screenshot.path);
|
|
140842
142036
|
}
|
|
140843
142037
|
}
|
|
140844
142038
|
step.screenshot = {
|
|
@@ -140874,17 +142068,17 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
140874
142068
|
} catch {
|
|
140875
142069
|
urlPathname = originalUrlPath;
|
|
140876
142070
|
}
|
|
140877
|
-
const rawBase =
|
|
142071
|
+
const rawBase = import_node_path12.default.basename(urlPathname.split("?")[0].split("#")[0].replace(/\\/g, "/"));
|
|
140878
142072
|
const safeBase = sanitizeFilesystemName(rawBase, `${step.stepId}.png`);
|
|
140879
|
-
dir =
|
|
140880
|
-
if (!
|
|
140881
|
-
|
|
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 });
|
|
140882
142076
|
}
|
|
140883
142077
|
const captureId = `${step.stepId || "screenshot"}_${Date.now()}`;
|
|
140884
|
-
filePath =
|
|
140885
|
-
const resolvedDir =
|
|
140886
|
-
const resolvedFile =
|
|
140887
|
-
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)) {
|
|
140888
142082
|
result.status = "FAIL";
|
|
140889
142083
|
result.description = `Refusing to write screenshot outside run folder: ${resolvedFile}`;
|
|
140890
142084
|
return result;
|
|
@@ -140893,18 +142087,18 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
140893
142087
|
log(config, "debug", `Screenshot path is a URL (${redactedUrl}); overwrite is ignored, running comparison only.`);
|
|
140894
142088
|
}
|
|
140895
142089
|
} else {
|
|
140896
|
-
dir =
|
|
140897
|
-
if (!
|
|
140898
|
-
|
|
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 });
|
|
140899
142093
|
}
|
|
140900
|
-
if (
|
|
142094
|
+
if (import_node_fs12.default.existsSync(filePath)) {
|
|
140901
142095
|
if (step.screenshot.overwrite == "false") {
|
|
140902
142096
|
result.status = "SKIPPED";
|
|
140903
142097
|
result.description = `File already exists: ${filePath}`;
|
|
140904
142098
|
return result;
|
|
140905
142099
|
} else {
|
|
140906
142100
|
existFilePath = filePath;
|
|
140907
|
-
filePath =
|
|
142101
|
+
filePath = import_node_path12.default.join(dir, `${step.stepId}_${Date.now()}.png`);
|
|
140908
142102
|
}
|
|
140909
142103
|
}
|
|
140910
142104
|
}
|
|
@@ -140978,25 +142172,34 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
140978
142172
|
}, element, padding);
|
|
140979
142173
|
await driver.pause(100);
|
|
140980
142174
|
}
|
|
142175
|
+
const recordingActive = isRecordingActive(driver);
|
|
140981
142176
|
try {
|
|
140982
|
-
if (
|
|
142177
|
+
if (recordingActive) {
|
|
140983
142178
|
await driver.execute(() => {
|
|
140984
|
-
document.querySelector("dd-mouse-pointer")
|
|
142179
|
+
const pointer = document.querySelector("dd-mouse-pointer");
|
|
142180
|
+
if (pointer)
|
|
142181
|
+
pointer.style.display = "none";
|
|
140985
142182
|
});
|
|
140986
142183
|
}
|
|
140987
142184
|
await driver.saveScreenshot(filePath);
|
|
140988
|
-
if (driver?.state?.recording) {
|
|
140989
|
-
await driver.execute(() => {
|
|
140990
|
-
document.querySelector("dd-mouse-pointer").style.display = "block";
|
|
140991
|
-
});
|
|
140992
|
-
}
|
|
140993
142185
|
} catch (error) {
|
|
140994
142186
|
result.status = "FAIL";
|
|
140995
142187
|
result.description = `Couldn't save screenshot. ${error}`;
|
|
140996
|
-
if (existFilePath && filePath !== existFilePath &&
|
|
140997
|
-
|
|
142188
|
+
if (existFilePath && filePath !== existFilePath && import_node_fs12.default.existsSync(filePath)) {
|
|
142189
|
+
import_node_fs12.default.unlinkSync(filePath);
|
|
140998
142190
|
}
|
|
140999
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
|
+
}
|
|
141000
142203
|
}
|
|
141001
142204
|
if (step.screenshot.crop) {
|
|
141002
142205
|
let padding = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
@@ -141038,7 +142241,7 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141038
142241
|
rect.width = clamped.width;
|
|
141039
142242
|
rect.height = clamped.height;
|
|
141040
142243
|
log(config, "debug", { padded_rect: rect });
|
|
141041
|
-
const croppedPath =
|
|
142244
|
+
const croppedPath = import_node_path12.default.join(dir, `cropped_${step.stepId || Date.now()}.png`);
|
|
141042
142245
|
try {
|
|
141043
142246
|
await sharp(filePath).extract({
|
|
141044
142247
|
left: rect.x,
|
|
@@ -141046,12 +142249,12 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141046
142249
|
width: rect.width,
|
|
141047
142250
|
height: rect.height
|
|
141048
142251
|
}).toFile(croppedPath);
|
|
141049
|
-
|
|
142252
|
+
import_node_fs12.default.renameSync(croppedPath, filePath);
|
|
141050
142253
|
} catch (error) {
|
|
141051
142254
|
result.status = "FAIL";
|
|
141052
142255
|
result.description = `Couldn't crop image. ${error}`;
|
|
141053
|
-
if (existFilePath && filePath !== existFilePath &&
|
|
141054
|
-
|
|
142256
|
+
if (existFilePath && filePath !== existFilePath && import_node_fs12.default.existsSync(filePath)) {
|
|
142257
|
+
import_node_fs12.default.unlinkSync(filePath);
|
|
141055
142258
|
}
|
|
141056
142259
|
return result;
|
|
141057
142260
|
}
|
|
@@ -141059,7 +142262,7 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141059
142262
|
if (existFilePath) {
|
|
141060
142263
|
if (step.screenshot.overwrite == "true" && !isUrlPath) {
|
|
141061
142264
|
result.description += ` Overwrote existing file.`;
|
|
141062
|
-
|
|
142265
|
+
import_node_fs12.default.renameSync(filePath, existFilePath);
|
|
141063
142266
|
result.outputs.screenshotPath = existFilePath;
|
|
141064
142267
|
result.outputs.changed = true;
|
|
141065
142268
|
if (step.screenshot.sourceIntegration) {
|
|
@@ -141072,21 +142275,21 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141072
142275
|
let img1;
|
|
141073
142276
|
let img2;
|
|
141074
142277
|
try {
|
|
141075
|
-
img1 = PNG.sync.read(
|
|
141076
|
-
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));
|
|
141077
142280
|
} catch (error) {
|
|
141078
142281
|
result.status = "FAIL";
|
|
141079
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}`;
|
|
141080
|
-
if (!isUrlPath && filePath !== existFilePath &&
|
|
141081
|
-
|
|
142283
|
+
if (!isUrlPath && filePath !== existFilePath && import_node_fs12.default.existsSync(filePath)) {
|
|
142284
|
+
import_node_fs12.default.unlinkSync(filePath);
|
|
141082
142285
|
}
|
|
141083
142286
|
return result;
|
|
141084
142287
|
}
|
|
141085
142288
|
if (!aspectRatiosMatch(img1, img2)) {
|
|
141086
142289
|
result.status = "FAIL";
|
|
141087
142290
|
result.description = `Couldn't compare images. Images have different aspect ratios.`;
|
|
141088
|
-
if (!isUrlPath && filePath !== existFilePath &&
|
|
141089
|
-
|
|
142291
|
+
if (!isUrlPath && filePath !== existFilePath && import_node_fs12.default.existsSync(filePath)) {
|
|
142292
|
+
import_node_fs12.default.unlinkSync(filePath);
|
|
141090
142293
|
}
|
|
141091
142294
|
return result;
|
|
141092
142295
|
}
|
|
@@ -141113,8 +142316,8 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141113
142316
|
} catch (error) {
|
|
141114
142317
|
result.status = "FAIL";
|
|
141115
142318
|
result.description = `Couldn't load screenshot comparison dependency (pixelmatch). ${error?.message ?? error}`;
|
|
141116
|
-
if (!isUrlPath && filePath !== existFilePath &&
|
|
141117
|
-
|
|
142319
|
+
if (!isUrlPath && filePath !== existFilePath && import_node_fs12.default.existsSync(filePath)) {
|
|
142320
|
+
import_node_fs12.default.unlinkSync(filePath);
|
|
141118
142321
|
}
|
|
141119
142322
|
return result;
|
|
141120
142323
|
}
|
|
@@ -141127,7 +142330,7 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141127
142330
|
});
|
|
141128
142331
|
if (fractionalDiff > step.screenshot.maxVariation) {
|
|
141129
142332
|
if (step.screenshot.overwrite == "aboveVariation" && !isUrlPath) {
|
|
141130
|
-
|
|
142333
|
+
import_node_fs12.default.renameSync(filePath, existFilePath);
|
|
141131
142334
|
}
|
|
141132
142335
|
result.status = "WARNING";
|
|
141133
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}).`;
|
|
@@ -141153,7 +142356,7 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141153
142356
|
result.outputs.sourceIntegration = step.screenshot.sourceIntegration;
|
|
141154
142357
|
}
|
|
141155
142358
|
if (step.screenshot.overwrite != "true") {
|
|
141156
|
-
|
|
142359
|
+
import_node_fs12.default.unlinkSync(filePath);
|
|
141157
142360
|
}
|
|
141158
142361
|
}
|
|
141159
142362
|
}
|
|
@@ -141172,283 +142375,8 @@ async function saveScreenshot({ config, step, driver }) {
|
|
|
141172
142375
|
// dist/core/tests/startRecording.js
|
|
141173
142376
|
init_validate();
|
|
141174
142377
|
init_utils();
|
|
141175
|
-
|
|
141176
|
-
// dist/core/tests/ffmpegRecorder.js
|
|
141177
|
-
var import_node_child_process4 = require("node:child_process");
|
|
141178
|
-
var import_node_os6 = __toESM(require("node:os"), 1);
|
|
141179
|
-
var import_node_path12 = __toESM(require("node:path"), 1);
|
|
141180
|
-
var import_node_fs12 = __toESM(require("node:fs"), 1);
|
|
141181
|
-
var import_node_crypto5 = __toESM(require("node:crypto"), 1);
|
|
141182
|
-
init_loader();
|
|
141183
|
-
init_utils();
|
|
141184
|
-
var XVFB_SCREEN_SIZE = "1920x1080";
|
|
141185
|
-
function safeContextId(contextId) {
|
|
141186
|
-
const raw = String(contextId ?? "ctx");
|
|
141187
|
-
const base = sanitizeFilesystemName(raw, "ctx");
|
|
141188
|
-
if (base === raw)
|
|
141189
|
-
return base;
|
|
141190
|
-
const hash = import_node_crypto5.default.createHash("sha1").update(raw).digest("hex").slice(0, 8);
|
|
141191
|
-
return `${base}-${hash}`;
|
|
141192
|
-
}
|
|
141193
|
-
function browserCaptureTitle(contextId) {
|
|
141194
|
-
return `RECORD_ME_${safeContextId(contextId)}`;
|
|
141195
|
-
}
|
|
141196
|
-
function browserDownloadDir(contextId) {
|
|
141197
|
-
return import_node_path12.default.join(import_node_os6.default.tmpdir(), "doc-detective", "recordings", safeContextId(contextId));
|
|
141198
|
-
}
|
|
141199
|
-
function engineFields(record) {
|
|
141200
|
-
const engine = record && typeof record === "object" ? record.engine : void 0;
|
|
141201
|
-
if (typeof engine === "string")
|
|
141202
|
-
return { name: engine };
|
|
141203
|
-
if (engine && typeof engine === "object")
|
|
141204
|
-
return { name: engine.name, target: engine.target, fps: engine.fps };
|
|
141205
|
-
return {};
|
|
141206
|
-
}
|
|
141207
|
-
function resolveRecordPlan({ step, context }) {
|
|
141208
|
-
const { name, target, fps } = engineFields(step?.record);
|
|
141209
|
-
let engineName = name;
|
|
141210
|
-
if (!engineName) {
|
|
141211
|
-
const b = context?.browser;
|
|
141212
|
-
engineName = b?.name === "chrome" && b?.headless === false ? "browser" : "ffmpeg";
|
|
141213
|
-
}
|
|
141214
|
-
return {
|
|
141215
|
-
name: engineName,
|
|
141216
|
-
target: target || "display",
|
|
141217
|
-
fps: fps ?? 30
|
|
141218
|
-
};
|
|
141219
|
-
}
|
|
141220
|
-
function hasRecordStepWithoutEngine(context) {
|
|
141221
|
-
const steps = Array.isArray(context?.steps) ? context.steps : [];
|
|
141222
|
-
return steps.some((s) => {
|
|
141223
|
-
if (!s?.record)
|
|
141224
|
-
return false;
|
|
141225
|
-
return engineFields(s.record).name === void 0;
|
|
141226
|
-
});
|
|
141227
|
-
}
|
|
141228
|
-
function coerceRecordContextBrowser({ context, availableApps }) {
|
|
141229
|
-
if (context?.browser)
|
|
141230
|
-
return null;
|
|
141231
|
-
if (!hasRecordStepWithoutEngine(context))
|
|
141232
|
-
return null;
|
|
141233
|
-
const chromeAvailable = Array.isArray(availableApps) && availableApps.some((a) => a?.name === "chrome");
|
|
141234
|
-
if (!chromeAvailable)
|
|
141235
|
-
return null;
|
|
141236
|
-
return { name: "chrome", headless: false };
|
|
141237
|
-
}
|
|
141238
|
-
function buildCaptureArgs({ platform, fps, displayEnv, outputPath, screenIndex, screenSize }) {
|
|
141239
|
-
const rate = String(fps ?? 30);
|
|
141240
|
-
let input;
|
|
141241
|
-
switch (platform) {
|
|
141242
|
-
case "win32":
|
|
141243
|
-
input = ["-f", "gdigrab", "-framerate", rate, "-i", "desktop"];
|
|
141244
|
-
break;
|
|
141245
|
-
case "darwin":
|
|
141246
|
-
input = [
|
|
141247
|
-
"-f",
|
|
141248
|
-
"avfoundation",
|
|
141249
|
-
"-framerate",
|
|
141250
|
-
rate,
|
|
141251
|
-
"-i",
|
|
141252
|
-
`${screenIndex ?? "0"}:none`
|
|
141253
|
-
];
|
|
141254
|
-
break;
|
|
141255
|
-
case "linux":
|
|
141256
|
-
input = [
|
|
141257
|
-
"-f",
|
|
141258
|
-
"x11grab",
|
|
141259
|
-
"-framerate",
|
|
141260
|
-
rate,
|
|
141261
|
-
...screenSize ? ["-video_size", screenSize] : [],
|
|
141262
|
-
"-i",
|
|
141263
|
-
displayEnv || ":0.0"
|
|
141264
|
-
];
|
|
141265
|
-
break;
|
|
141266
|
-
default:
|
|
141267
|
-
throw new Error(`Screen recording isn't supported on platform '${platform}'.`);
|
|
141268
|
-
}
|
|
141269
|
-
return ["-y", ...input, "-pix_fmt", "yuv420p", outputPath];
|
|
141270
|
-
}
|
|
141271
|
-
async function resolveCropGeometry({ driver, target }) {
|
|
141272
|
-
if (target === "viewport") {
|
|
141273
|
-
const m = await driver.execute(() => {
|
|
141274
|
-
return {
|
|
141275
|
-
sx: window.screenX,
|
|
141276
|
-
sy: window.screenY,
|
|
141277
|
-
iw: window.innerWidth,
|
|
141278
|
-
ih: window.innerHeight,
|
|
141279
|
-
ow: window.outerWidth,
|
|
141280
|
-
oh: window.outerHeight,
|
|
141281
|
-
dpr: window.devicePixelRatio || 1
|
|
141282
|
-
};
|
|
141283
|
-
});
|
|
141284
|
-
const dpr = m.dpr || 1;
|
|
141285
|
-
const border = Math.max(0, (m.ow - m.iw) / 2);
|
|
141286
|
-
const topChrome = Math.max(0, m.oh - m.ih - border);
|
|
141287
|
-
return {
|
|
141288
|
-
x: Math.round((m.sx + border) * dpr),
|
|
141289
|
-
y: Math.round((m.sy + topChrome) * dpr),
|
|
141290
|
-
w: Math.round(m.iw * dpr),
|
|
141291
|
-
h: Math.round(m.ih * dpr)
|
|
141292
|
-
};
|
|
141293
|
-
}
|
|
141294
|
-
if (target === "window") {
|
|
141295
|
-
const r = await driver.getWindowRect();
|
|
141296
|
-
let dpr;
|
|
141297
|
-
try {
|
|
141298
|
-
dpr = await driver.execute(() => window.devicePixelRatio || 1) || 1;
|
|
141299
|
-
} catch {
|
|
141300
|
-
dpr = 1;
|
|
141301
|
-
}
|
|
141302
|
-
return {
|
|
141303
|
-
x: Math.round(r.x * dpr),
|
|
141304
|
-
y: Math.round(r.y * dpr),
|
|
141305
|
-
w: Math.round(r.width * dpr),
|
|
141306
|
-
h: Math.round(r.height * dpr)
|
|
141307
|
-
};
|
|
141308
|
-
}
|
|
141309
|
-
return null;
|
|
141310
|
-
}
|
|
141311
|
-
function jobIsFfmpegRecording(job) {
|
|
141312
|
-
const steps = Array.isArray(job?.context?.steps) ? job.context.steps : [];
|
|
141313
|
-
return steps.some((s) => {
|
|
141314
|
-
if (!s?.record)
|
|
141315
|
-
return false;
|
|
141316
|
-
return resolveRecordPlan({ step: s, context: job.context }).name === "ffmpeg";
|
|
141317
|
-
});
|
|
141318
|
-
}
|
|
141319
|
-
function computeEffectiveConcurrency({ requestedLimit, jobs, platform, xvfbAvailable }) {
|
|
141320
|
-
const ffmpegJobs = (jobs || []).filter(jobIsFfmpegRecording);
|
|
141321
|
-
if (ffmpegJobs.length === 0) {
|
|
141322
|
-
return { limit: requestedLimit, xvfbContexts: [], forcedSerial: false };
|
|
141323
|
-
}
|
|
141324
|
-
if (platform === "linux" && xvfbAvailable) {
|
|
141325
|
-
return {
|
|
141326
|
-
limit: requestedLimit,
|
|
141327
|
-
xvfbContexts: ffmpegJobs.map((j) => j.context),
|
|
141328
|
-
forcedSerial: false
|
|
141329
|
-
};
|
|
141330
|
-
}
|
|
141331
|
-
return { limit: 1, xvfbContexts: [], forcedSerial: requestedLimit > 1 };
|
|
141332
|
-
}
|
|
141333
|
-
async function getFfmpegPath(ctx = {}) {
|
|
141334
|
-
const mod = await loadHeavyDep("@ffmpeg-installer/ffmpeg", { ctx });
|
|
141335
|
-
const candidate = mod && (mod.path ?? mod.default?.path);
|
|
141336
|
-
if (typeof candidate !== "string" || candidate.length === 0) {
|
|
141337
|
-
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.");
|
|
141338
|
-
}
|
|
141339
|
-
return candidate;
|
|
141340
|
-
}
|
|
141341
|
-
function parseMacScreenIndex(listing) {
|
|
141342
|
-
const m = /\[(\d+)\]\s+Capture screen/i.exec(listing || "");
|
|
141343
|
-
return m ? m[1] : null;
|
|
141344
|
-
}
|
|
141345
|
-
async function detectMacScreenIndex(ffmpegPath) {
|
|
141346
|
-
return new Promise((resolve) => {
|
|
141347
|
-
let out = "";
|
|
141348
|
-
let settled = false;
|
|
141349
|
-
let proc = null;
|
|
141350
|
-
const done = (v) => {
|
|
141351
|
-
if (settled)
|
|
141352
|
-
return;
|
|
141353
|
-
settled = true;
|
|
141354
|
-
try {
|
|
141355
|
-
proc?.kill();
|
|
141356
|
-
} catch {
|
|
141357
|
-
}
|
|
141358
|
-
resolve(v);
|
|
141359
|
-
};
|
|
141360
|
-
try {
|
|
141361
|
-
proc = (0, import_node_child_process4.spawn)(ffmpegPath, ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { stdio: ["ignore", "ignore", "pipe"] });
|
|
141362
|
-
proc.stderr?.on("data", (d) => {
|
|
141363
|
-
out += d.toString();
|
|
141364
|
-
});
|
|
141365
|
-
proc.on("error", () => done(null));
|
|
141366
|
-
proc.on("close", () => done(parseMacScreenIndex(out)));
|
|
141367
|
-
setTimeout(() => done(null), 5e3);
|
|
141368
|
-
} catch {
|
|
141369
|
-
done(null);
|
|
141370
|
-
}
|
|
141371
|
-
});
|
|
141372
|
-
}
|
|
141373
|
-
async function detectX11ScreenSize(display) {
|
|
141374
|
-
return new Promise((resolve) => {
|
|
141375
|
-
let out = "";
|
|
141376
|
-
let settled = false;
|
|
141377
|
-
let proc = null;
|
|
141378
|
-
const done = (v) => {
|
|
141379
|
-
if (settled)
|
|
141380
|
-
return;
|
|
141381
|
-
settled = true;
|
|
141382
|
-
try {
|
|
141383
|
-
proc?.kill();
|
|
141384
|
-
} catch {
|
|
141385
|
-
}
|
|
141386
|
-
resolve(v);
|
|
141387
|
-
};
|
|
141388
|
-
try {
|
|
141389
|
-
const env = display ? { ...process.env, DISPLAY: display } : process.env;
|
|
141390
|
-
proc = (0, import_node_child_process4.spawn)("xdpyinfo", [], { env, stdio: ["ignore", "pipe", "ignore"] });
|
|
141391
|
-
proc.stdout?.on("data", (d) => {
|
|
141392
|
-
out += d.toString();
|
|
141393
|
-
});
|
|
141394
|
-
proc.on("error", () => done(null));
|
|
141395
|
-
proc.on("close", () => {
|
|
141396
|
-
const m = /dimensions:\s+(\d+x\d+)\s+pixels/i.exec(out);
|
|
141397
|
-
done(m ? m[1] : null);
|
|
141398
|
-
});
|
|
141399
|
-
setTimeout(() => done(null), 5e3);
|
|
141400
|
-
} catch {
|
|
141401
|
-
done(null);
|
|
141402
|
-
}
|
|
141403
|
-
});
|
|
141404
|
-
}
|
|
141405
|
-
async function checkSystemBinary(name) {
|
|
141406
|
-
return new Promise((resolve) => {
|
|
141407
|
-
try {
|
|
141408
|
-
const proc = (0, import_node_child_process4.spawn)(name, ["-help"], { stdio: "ignore" });
|
|
141409
|
-
proc.on("error", () => resolve(false));
|
|
141410
|
-
proc.on("close", () => resolve(true));
|
|
141411
|
-
} catch {
|
|
141412
|
-
resolve(false);
|
|
141413
|
-
}
|
|
141414
|
-
});
|
|
141415
|
-
}
|
|
141416
|
-
function xvfbDisplay(index) {
|
|
141417
|
-
return `:${99 + index}`;
|
|
141418
|
-
}
|
|
141419
|
-
async function startXvfb(display, opts = {}) {
|
|
141420
|
-
const num = display.replace(/^:/, "").split(".")[0];
|
|
141421
|
-
const [defW, defH] = XVFB_SCREEN_SIZE.split("x").map(Number);
|
|
141422
|
-
const w = opts.width ?? defW;
|
|
141423
|
-
const h = opts.height ?? defH;
|
|
141424
|
-
const startMs = Date.now();
|
|
141425
|
-
const proc = (0, import_node_child_process4.spawn)("Xvfb", [display, "-screen", "0", `${w}x${h}x24`, "-nolisten", "tcp"], { stdio: "ignore" });
|
|
141426
|
-
let spawnErr = null;
|
|
141427
|
-
proc.on("error", (e) => {
|
|
141428
|
-
spawnErr = e;
|
|
141429
|
-
});
|
|
141430
|
-
const lock = `/tmp/.X${num}-lock`;
|
|
141431
|
-
for (let i = 0; i < 50; i++) {
|
|
141432
|
-
if (spawnErr)
|
|
141433
|
-
throw spawnErr;
|
|
141434
|
-
if (proc.exitCode !== null)
|
|
141435
|
-
throw new Error(`Xvfb exited early on ${display} (code ${proc.exitCode})`);
|
|
141436
|
-
try {
|
|
141437
|
-
if (import_node_fs12.default.statSync(lock).mtimeMs >= startMs)
|
|
141438
|
-
return proc;
|
|
141439
|
-
} catch {
|
|
141440
|
-
}
|
|
141441
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
141442
|
-
}
|
|
141443
|
-
try {
|
|
141444
|
-
proc.kill();
|
|
141445
|
-
} catch {
|
|
141446
|
-
}
|
|
141447
|
-
throw new Error(`Xvfb did not become ready on ${display} within 5s.`);
|
|
141448
|
-
}
|
|
141449
|
-
|
|
141450
|
-
// dist/core/tests/startRecording.js
|
|
141451
142378
|
var import_node_child_process5 = require("node:child_process");
|
|
142379
|
+
var import_node_crypto6 = require("node:crypto");
|
|
141452
142380
|
var import_node_path13 = __toESM(require("node:path"), 1);
|
|
141453
142381
|
var import_node_fs13 = __toESM(require("node:fs"), 1);
|
|
141454
142382
|
var import_node_os7 = __toESM(require("node:os"), 1);
|
|
@@ -141496,7 +142424,22 @@ async function startRecording({ config, context, step, driver }) {
|
|
|
141496
142424
|
result.description = `File already exists: ${filePath}`;
|
|
141497
142425
|
return result;
|
|
141498
142426
|
}
|
|
141499
|
-
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
|
+
}
|
|
141500
142443
|
if (plan.name === "browser") {
|
|
141501
142444
|
if (context.browser?.headless) {
|
|
141502
142445
|
result.status = "SKIPPED";
|
|
@@ -141646,7 +142589,7 @@ async function startRecording({ config, context, step, driver }) {
|
|
|
141646
142589
|
const tempDir = import_node_path13.default.join(import_node_os7.default.tmpdir(), "doc-detective", "recordings");
|
|
141647
142590
|
if (!import_node_fs13.default.existsSync(tempDir))
|
|
141648
142591
|
import_node_fs13.default.mkdirSync(tempDir, { recursive: true });
|
|
141649
|
-
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`);
|
|
141650
142593
|
let ffmpegPath;
|
|
141651
142594
|
try {
|
|
141652
142595
|
ffmpegPath = await getFfmpegPath({ cacheDir: config?.cacheDir });
|
|
@@ -141718,12 +142661,32 @@ async function stopRecording({ config, step, driver }) {
|
|
|
141718
142661
|
result.description = `Invalid step definition: ${isValidStep.errors}`;
|
|
141719
142662
|
return result;
|
|
141720
142663
|
}
|
|
141721
|
-
|
|
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
|
+
});
|
|
141722
142673
|
if (!recording) {
|
|
141723
142674
|
result.status = "SKIPPED";
|
|
141724
|
-
|
|
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
|
+
}
|
|
141725
142683
|
return result;
|
|
141726
142684
|
}
|
|
142685
|
+
const dropHandle = () => {
|
|
142686
|
+
const idx = recordings.indexOf(recording);
|
|
142687
|
+
if (idx !== -1)
|
|
142688
|
+
recordings.splice(idx, 1);
|
|
142689
|
+
};
|
|
141727
142690
|
try {
|
|
141728
142691
|
if (recording.type === "MediaRecorder") {
|
|
141729
142692
|
await driver.switchToWindow(recording.tab);
|
|
@@ -141739,7 +142702,7 @@ async function stopRecording({ config, step, driver }) {
|
|
|
141739
142702
|
if (remainingHandles2.length > 0) {
|
|
141740
142703
|
await driver.switchToWindow(remainingHandles2[0]);
|
|
141741
142704
|
}
|
|
141742
|
-
|
|
142705
|
+
dropHandle();
|
|
141743
142706
|
return result;
|
|
141744
142707
|
}
|
|
141745
142708
|
await driver.execute(() => {
|
|
@@ -141749,7 +142712,7 @@ async function stopRecording({ config, step, driver }) {
|
|
|
141749
142712
|
if (!downloaded) {
|
|
141750
142713
|
result.status = "FAIL";
|
|
141751
142714
|
result.description = "Recording download timed out.";
|
|
141752
|
-
|
|
142715
|
+
dropHandle();
|
|
141753
142716
|
return result;
|
|
141754
142717
|
}
|
|
141755
142718
|
const allHandles = await driver.getWindowHandles();
|
|
@@ -141764,7 +142727,7 @@ async function stopRecording({ config, step, driver }) {
|
|
|
141764
142727
|
targetPath: recording.targetPath,
|
|
141765
142728
|
deleteSource: true
|
|
141766
142729
|
});
|
|
141767
|
-
|
|
142730
|
+
dropHandle();
|
|
141768
142731
|
} else if (recording.type === "ffmpeg") {
|
|
141769
142732
|
const proc = recording.process;
|
|
141770
142733
|
try {
|
|
@@ -141803,12 +142766,12 @@ async function stopRecording({ config, step, driver }) {
|
|
|
141803
142766
|
deleteSource: true,
|
|
141804
142767
|
crop: recording.crop
|
|
141805
142768
|
});
|
|
141806
|
-
|
|
142769
|
+
dropHandle();
|
|
141807
142770
|
}
|
|
141808
142771
|
} catch (error) {
|
|
141809
142772
|
result.status = "FAIL";
|
|
141810
142773
|
result.description = `Couldn't stop recording. ${error}`;
|
|
141811
|
-
|
|
142774
|
+
dropHandle();
|
|
141812
142775
|
return result;
|
|
141813
142776
|
}
|
|
141814
142777
|
return result;
|
|
@@ -141866,7 +142829,7 @@ async function waitForStableFile(filePath, maxSeconds) {
|
|
|
141866
142829
|
}
|
|
141867
142830
|
if (size > 0 && size === lastSize) {
|
|
141868
142831
|
stableReads++;
|
|
141869
|
-
if (stableReads >=
|
|
142832
|
+
if (stableReads >= 2)
|
|
141870
142833
|
return true;
|
|
141871
142834
|
} else {
|
|
141872
142835
|
stableReads = 0;
|
|
@@ -142992,7 +143955,7 @@ async function dragAndDropElement({ config, step, driver }) {
|
|
|
142992
143955
|
// dist/core/tests.js
|
|
142993
143956
|
var import_node_path19 = __toESM(require("node:path"), 1);
|
|
142994
143957
|
var import_node_child_process7 = require("node:child_process");
|
|
142995
|
-
var
|
|
143958
|
+
var import_node_crypto7 = require("node:crypto");
|
|
142996
143959
|
init_appium();
|
|
142997
143960
|
|
|
142998
143961
|
// dist/core/expressions.js
|
|
@@ -143689,7 +144652,9 @@ async function runSpecs({ resolvedTests }) {
|
|
|
143689
144652
|
const metaValues = { specs: {} };
|
|
143690
144653
|
const installAttempts = /* @__PURE__ */ new Map();
|
|
143691
144654
|
const warmUpResults = /* @__PURE__ */ new Map();
|
|
143692
|
-
const runDir = getRunOutputDir(config
|
|
144655
|
+
const runDir = getRunOutputDir(config, {
|
|
144656
|
+
create: runArchivesArtifacts(config, specs)
|
|
144657
|
+
});
|
|
143693
144658
|
const runId = import_node_path19.default.basename(runDir).replace(/^run-/, "");
|
|
143694
144659
|
const report = {
|
|
143695
144660
|
runId,
|
|
@@ -143725,6 +144690,7 @@ async function runSpecs({ resolvedTests }) {
|
|
|
143725
144690
|
let limit = resolveConcurrentRunners(config);
|
|
143726
144691
|
log(config, "info", "Running test specs.");
|
|
143727
144692
|
const jobs = [];
|
|
144693
|
+
let autoRecordInjected = false;
|
|
143728
144694
|
for (const spec of specs) {
|
|
143729
144695
|
log(config, "debug", `SPEC: ${spec.specId}`);
|
|
143730
144696
|
metaValues.specs[spec.specId] ??= { tests: {} };
|
|
@@ -143746,6 +144712,17 @@ async function runSpecs({ resolvedTests }) {
|
|
|
143746
144712
|
contexts: new Array(test.contexts.length)
|
|
143747
144713
|
};
|
|
143748
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
|
+
}
|
|
143749
144726
|
const usedContextIds = new Set(test.contexts.map((c) => c.contextId).filter(Boolean));
|
|
143750
144727
|
test.contexts.forEach((context, slot) => {
|
|
143751
144728
|
if (!context.contextId) {
|
|
@@ -143758,6 +144735,25 @@ async function runSpecs({ resolvedTests }) {
|
|
|
143758
144735
|
usedContextIds.add(id);
|
|
143759
144736
|
context.contextId = id;
|
|
143760
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
|
+
}
|
|
143761
144757
|
const coercedBrowser = coerceRecordContextBrowser({
|
|
143762
144758
|
context,
|
|
143763
144759
|
availableApps: runnerDetails.availableApps
|
|
@@ -143773,16 +144769,25 @@ async function runSpecs({ resolvedTests }) {
|
|
|
143773
144769
|
if (anyFfmpegRecording && process.platform === "linux") {
|
|
143774
144770
|
xvfbAvailable = await checkSystemBinary("Xvfb");
|
|
143775
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
|
+
}));
|
|
143776
144778
|
const concurrency = computeEffectiveConcurrency({
|
|
143777
144779
|
requestedLimit: limit,
|
|
143778
144780
|
jobs,
|
|
143779
144781
|
platform: process.platform,
|
|
143780
|
-
xvfbAvailable
|
|
144782
|
+
xvfbAvailable,
|
|
144783
|
+
allowOverlappingCaptures: autoRecordInjected && !hasExplicitFfmpegRecording
|
|
143781
144784
|
});
|
|
143782
144785
|
limit = concurrency.limit;
|
|
143783
144786
|
if (concurrency.forcedSerial) {
|
|
143784
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.');
|
|
143785
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.");
|
|
143786
144791
|
}
|
|
143787
144792
|
const driverJobCount = jobs.filter((job) => isDriverRequired({ test: job.context })).length;
|
|
143788
144793
|
let appiumServers = [];
|
|
@@ -144021,6 +145026,24 @@ async function warmUpContexts({ jobs, config, runnerDetails, appiumPool, install
|
|
|
144021
145026
|
function resolveAutoScreenshot({ config, spec, test }) {
|
|
144022
145027
|
return Boolean(test?.autoScreenshot ?? spec?.autoScreenshot ?? config?.autoScreenshot);
|
|
144023
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
|
+
}
|
|
144024
145047
|
function capPathSegment(segment, max = 64) {
|
|
144025
145048
|
return segment.length <= max ? segment : segment.slice(segment.length - max);
|
|
144026
145049
|
}
|
|
@@ -144213,7 +145236,7 @@ ${JSON.stringify(context, null, 2)}`);
|
|
|
144213
145236
|
const usedStepIds = new Set(context.steps.map((s) => s.stepId).filter(Boolean));
|
|
144214
145237
|
for (const [stepIndex, step] of context.steps.entries()) {
|
|
144215
145238
|
if (!step.stepId) {
|
|
144216
|
-
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)()}`);
|
|
144217
145240
|
let stepId = baseId;
|
|
144218
145241
|
let suffix = 2;
|
|
144219
145242
|
while (usedStepIds.has(stepId)) {
|
|
@@ -144283,32 +145306,13 @@ ${JSON.stringify(stepResult, null, 2)}`);
|
|
|
144283
145306
|
stepExecutionFailed = true;
|
|
144284
145307
|
}
|
|
144285
145308
|
}
|
|
144286
|
-
|
|
144287
|
-
const stopRecordStep = {
|
|
144288
|
-
stopRecord: true,
|
|
144289
|
-
description: "Stopping recording",
|
|
144290
|
-
stepId: (0, import_node_crypto6.randomUUID)()
|
|
144291
|
-
};
|
|
144292
|
-
const stepResult = await runStep({
|
|
144293
|
-
config,
|
|
144294
|
-
context,
|
|
144295
|
-
step: stopRecordStep,
|
|
144296
|
-
driver,
|
|
144297
|
-
options: {
|
|
144298
|
-
openApiDefinitions: context.openApi || []
|
|
144299
|
-
}
|
|
144300
|
-
});
|
|
144301
|
-
stepResult.result = stepResult.status;
|
|
144302
|
-
stepResult.resultDescription = stepResult.description;
|
|
144303
|
-
delete stepResult.status;
|
|
144304
|
-
delete stepResult.description;
|
|
144305
|
-
const stepReport = {
|
|
144306
|
-
...stopRecordStep,
|
|
144307
|
-
...stepResult
|
|
144308
|
-
};
|
|
144309
|
-
contextReport.steps.push(stepReport);
|
|
144310
|
-
}
|
|
145309
|
+
await stopAllRecordings({ config, context, driver, contextReport });
|
|
144311
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
|
+
}
|
|
144312
145316
|
if (driver) {
|
|
144313
145317
|
try {
|
|
144314
145318
|
await driver.deleteSession();
|
|
@@ -144323,6 +145327,42 @@ ${JSON.stringify(stepResult, null, 2)}`);
|
|
|
144323
145327
|
contextReport.result = rollUpResults(contextReport.steps);
|
|
144324
145328
|
return contextReport;
|
|
144325
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
|
+
}
|
|
144326
145366
|
async function runStep({ config = {}, context = {}, step, driver, metaValues = {}, options = {} }) {
|
|
144327
145367
|
let actionResult;
|
|
144328
145368
|
step = replaceEnvs(step);
|
|
@@ -144373,7 +145413,16 @@ async function runStep({ config = {}, context = {}, step, driver, metaValues = {
|
|
|
144373
145413
|
step,
|
|
144374
145414
|
driver
|
|
144375
145415
|
});
|
|
144376
|
-
|
|
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
|
+
}
|
|
144377
145426
|
} else if (typeof step.runCode !== "undefined") {
|
|
144378
145427
|
actionResult = await runCode({ config, step });
|
|
144379
145428
|
} else if (typeof step.runShell !== "undefined") {
|
|
@@ -144398,7 +145447,7 @@ async function runStep({ config = {}, context = {}, step, driver, metaValues = {
|
|
|
144398
145447
|
description: `Unknown step action: ${JSON.stringify(step)}`
|
|
144399
145448
|
};
|
|
144400
145449
|
}
|
|
144401
|
-
if (driver
|
|
145450
|
+
if (isRecordingActive(driver)) {
|
|
144402
145451
|
const currentUrl = await driver.getUrl();
|
|
144403
145452
|
if (currentUrl !== driver.state.url) {
|
|
144404
145453
|
driver.state.url = currentUrl;
|
|
@@ -144484,7 +145533,7 @@ async function driverStart(capabilities, port, maxAttempts = 4, ctx = {}) {
|
|
|
144484
145533
|
waitforTimeout: 12e4
|
|
144485
145534
|
// 2 minutes
|
|
144486
145535
|
});
|
|
144487
|
-
driver.state = { url: "", x: null, y: null,
|
|
145536
|
+
driver.state = { url: "", x: null, y: null, recordings: [] };
|
|
144488
145537
|
return driver;
|
|
144489
145538
|
} catch (err) {
|
|
144490
145539
|
lastError = err;
|