doc-detective 4.28.0 → 4.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/cli.js +69 -39
  2. package/dist/cli.js.map +1 -1
  3. package/dist/common/src/detectTests.d.ts +9 -0
  4. package/dist/common/src/detectTests.d.ts.map +1 -1
  5. package/dist/common/src/detectTests.js +22 -9
  6. package/dist/common/src/detectTests.js.map +1 -1
  7. package/dist/common/src/schemas/schemas.json +141 -57
  8. package/dist/common/src/types/generated/report_v3.d.ts +34 -0
  9. package/dist/common/src/types/generated/report_v3.d.ts.map +1 -1
  10. package/dist/common/src/validate.d.ts.map +1 -1
  11. package/dist/common/src/validate.js +95 -12
  12. package/dist/common/src/validate.js.map +1 -1
  13. package/dist/core/config.d.ts +66 -1
  14. package/dist/core/config.d.ts.map +1 -1
  15. package/dist/core/config.js +121 -68
  16. package/dist/core/config.js.map +1 -1
  17. package/dist/core/detectTests.d.ts.map +1 -1
  18. package/dist/core/detectTests.js +142 -36
  19. package/dist/core/detectTests.js.map +1 -1
  20. package/dist/core/expressions.d.ts.map +1 -1
  21. package/dist/core/expressions.js +24 -1
  22. package/dist/core/expressions.js.map +1 -1
  23. package/dist/core/index.d.ts +2 -1
  24. package/dist/core/index.d.ts.map +1 -1
  25. package/dist/core/index.js +55 -16
  26. package/dist/core/index.js.map +1 -1
  27. package/dist/core/openapi.d.ts.map +1 -1
  28. package/dist/core/openapi.js +22 -5
  29. package/dist/core/openapi.js.map +1 -1
  30. package/dist/core/resolveTests.d.ts.map +1 -1
  31. package/dist/core/resolveTests.js +56 -14
  32. package/dist/core/resolveTests.js.map +1 -1
  33. package/dist/core/telem.d.ts +2 -1
  34. package/dist/core/telem.d.ts.map +1 -1
  35. package/dist/core/telem.js +26 -2
  36. package/dist/core/telem.js.map +1 -1
  37. package/dist/core/tests/androidEmulator.d.ts.map +1 -1
  38. package/dist/core/tests/androidEmulator.js +13 -0
  39. package/dist/core/tests/androidEmulator.js.map +1 -1
  40. package/dist/core/tests/appSurface.d.ts +23 -0
  41. package/dist/core/tests/appSurface.d.ts.map +1 -1
  42. package/dist/core/tests/appSurface.js +3 -1
  43. package/dist/core/tests/appSurface.js.map +1 -1
  44. package/dist/core/tests/findStrategies.d.ts.map +1 -1
  45. package/dist/core/tests/findStrategies.js +83 -10
  46. package/dist/core/tests/findStrategies.js.map +1 -1
  47. package/dist/core/tests/iosSimulator.d.ts.map +1 -1
  48. package/dist/core/tests/iosSimulator.js +13 -0
  49. package/dist/core/tests/iosSimulator.js.map +1 -1
  50. package/dist/core/tests/mobileBrowser.d.ts +1 -0
  51. package/dist/core/tests/mobileBrowser.d.ts.map +1 -1
  52. package/dist/core/tests/mobileBrowser.js +8 -0
  53. package/dist/core/tests/mobileBrowser.js.map +1 -1
  54. package/dist/core/tests.d.ts +83 -2
  55. package/dist/core/tests.d.ts.map +1 -1
  56. package/dist/core/tests.js +572 -70
  57. package/dist/core/tests.js.map +1 -1
  58. package/dist/core/utils.d.ts +3 -2
  59. package/dist/core/utils.d.ts.map +1 -1
  60. package/dist/core/utils.js +46 -29
  61. package/dist/core/utils.js.map +1 -1
  62. package/dist/core/warmPhase.d.ts +161 -0
  63. package/dist/core/warmPhase.d.ts.map +1 -0
  64. package/dist/core/warmPhase.js +341 -0
  65. package/dist/core/warmPhase.js.map +1 -0
  66. package/dist/index.cjs +1073 -246
  67. package/dist/utils.d.ts +2 -1
  68. package/dist/utils.d.ts.map +1 -1
  69. package/dist/utils.js +41 -8
  70. package/dist/utils.js.map +1 -1
  71. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -9324,7 +9324,7 @@ var init_schemas = __esm({
9324
9324
  },
9325
9325
  elementText: {
9326
9326
  type: "string",
9327
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
9327
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
9328
9328
  },
9329
9329
  selector: {
9330
9330
  type: "string",
@@ -13470,7 +13470,7 @@ var init_schemas = __esm({
13470
13470
  },
13471
13471
  elementText: {
13472
13472
  type: "string",
13473
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
13473
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
13474
13474
  },
13475
13475
  selector: {
13476
13476
  type: "string",
@@ -18701,7 +18701,7 @@ var init_schemas = __esm({
18701
18701
  },
18702
18702
  elementText: {
18703
18703
  type: "string",
18704
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
18704
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
18705
18705
  },
18706
18706
  elementId: {
18707
18707
  type: "string",
@@ -19233,7 +19233,7 @@ var init_schemas = __esm({
19233
19233
  },
19234
19234
  elementText: {
19235
19235
  type: "string",
19236
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
19236
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
19237
19237
  },
19238
19238
  elementId: {
19239
19239
  type: "string",
@@ -54963,7 +54963,7 @@ var init_schemas = __esm({
54963
54963
  },
54964
54964
  elementText: {
54965
54965
  type: "string",
54966
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
54966
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
54967
54967
  },
54968
54968
  selector: {
54969
54969
  type: "string",
@@ -59109,7 +59109,7 @@ var init_schemas = __esm({
59109
59109
  },
59110
59110
  elementText: {
59111
59111
  type: "string",
59112
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
59112
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
59113
59113
  },
59114
59114
  selector: {
59115
59115
  type: "string",
@@ -64340,7 +64340,7 @@ var init_schemas = __esm({
64340
64340
  },
64341
64341
  elementText: {
64342
64342
  type: "string",
64343
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
64343
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
64344
64344
  },
64345
64345
  elementId: {
64346
64346
  type: "string",
@@ -64872,7 +64872,7 @@ var init_schemas = __esm({
64872
64872
  },
64873
64873
  elementText: {
64874
64874
  type: "string",
64875
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
64875
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
64876
64876
  },
64877
64877
  elementId: {
64878
64878
  type: "string",
@@ -95536,7 +95536,7 @@ var init_schemas = __esm({
95536
95536
  },
95537
95537
  elementText: {
95538
95538
  type: "string",
95539
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
95539
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
95540
95540
  },
95541
95541
  selector: {
95542
95542
  type: "string",
@@ -99682,7 +99682,7 @@ var init_schemas = __esm({
99682
99682
  },
99683
99683
  elementText: {
99684
99684
  type: "string",
99685
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
99685
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
99686
99686
  },
99687
99687
  selector: {
99688
99688
  type: "string",
@@ -103964,7 +103964,7 @@ var init_schemas = __esm({
103964
103964
  },
103965
103965
  elementText: {
103966
103966
  type: "string",
103967
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
103967
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
103968
103968
  },
103969
103969
  elementId: {
103970
103970
  type: "string",
@@ -104496,7 +104496,7 @@ var init_schemas = __esm({
104496
104496
  },
104497
104497
  elementText: {
104498
104498
  type: "string",
104499
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
104499
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
104500
104500
  },
104501
104501
  elementId: {
104502
104502
  type: "string",
@@ -113889,7 +113889,7 @@ var init_schemas = __esm({
113889
113889
  },
113890
113890
  elementText: {
113891
113891
  type: "string",
113892
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
113892
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
113893
113893
  },
113894
113894
  selector: {
113895
113895
  type: "string",
@@ -118035,7 +118035,7 @@ var init_schemas = __esm({
118035
118035
  },
118036
118036
  elementText: {
118037
118037
  type: "string",
118038
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
118038
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
118039
118039
  },
118040
118040
  selector: {
118041
118041
  type: "string",
@@ -123266,7 +123266,7 @@ var init_schemas = __esm({
123266
123266
  },
123267
123267
  elementText: {
123268
123268
  type: "string",
123269
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
123269
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
123270
123270
  },
123271
123271
  elementId: {
123272
123272
  type: "string",
@@ -123798,7 +123798,7 @@ var init_schemas = __esm({
123798
123798
  },
123799
123799
  elementText: {
123800
123800
  type: "string",
123801
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
123801
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
123802
123802
  },
123803
123803
  elementId: {
123804
123804
  type: "string",
@@ -159528,7 +159528,7 @@ var init_schemas = __esm({
159528
159528
  },
159529
159529
  elementText: {
159530
159530
  type: "string",
159531
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
159531
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
159532
159532
  },
159533
159533
  selector: {
159534
159534
  type: "string",
@@ -163674,7 +163674,7 @@ var init_schemas = __esm({
163674
163674
  },
163675
163675
  elementText: {
163676
163676
  type: "string",
163677
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
163677
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
163678
163678
  },
163679
163679
  selector: {
163680
163680
  type: "string",
@@ -168905,7 +168905,7 @@ var init_schemas = __esm({
168905
168905
  },
168906
168906
  elementText: {
168907
168907
  type: "string",
168908
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
168908
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
168909
168909
  },
168910
168910
  elementId: {
168911
168911
  type: "string",
@@ -169437,7 +169437,7 @@ var init_schemas = __esm({
169437
169437
  },
169438
169438
  elementText: {
169439
169439
  type: "string",
169440
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
169440
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
169441
169441
  },
169442
169442
  elementId: {
169443
169443
  type: "string",
@@ -206906,7 +206906,7 @@ var init_schemas = __esm({
206906
206906
  },
206907
206907
  elementText: {
206908
206908
  type: "string",
206909
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
206909
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
206910
206910
  },
206911
206911
  selector: {
206912
206912
  type: "string",
@@ -211052,7 +211052,7 @@ var init_schemas = __esm({
211052
211052
  },
211053
211053
  elementText: {
211054
211054
  type: "string",
211055
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
211055
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
211056
211056
  },
211057
211057
  selector: {
211058
211058
  type: "string",
@@ -216283,7 +216283,7 @@ var init_schemas = __esm({
216283
216283
  },
216284
216284
  elementText: {
216285
216285
  type: "string",
216286
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
216286
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
216287
216287
  },
216288
216288
  elementId: {
216289
216289
  type: "string",
@@ -216815,7 +216815,7 @@ var init_schemas = __esm({
216815
216815
  },
216816
216816
  elementText: {
216817
216817
  type: "string",
216818
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
216818
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
216819
216819
  },
216820
216820
  elementId: {
216821
216821
  type: "string",
@@ -251807,7 +251807,7 @@ var init_schemas = __esm({
251807
251807
  },
251808
251808
  elementText: {
251809
251809
  type: "string",
251810
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
251810
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
251811
251811
  },
251812
251812
  selector: {
251813
251813
  type: "string",
@@ -255953,7 +255953,7 @@ var init_schemas = __esm({
255953
255953
  },
255954
255954
  elementText: {
255955
255955
  type: "string",
255956
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
255956
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
255957
255957
  },
255958
255958
  selector: {
255959
255959
  type: "string",
@@ -261184,7 +261184,7 @@ var init_schemas = __esm({
261184
261184
  },
261185
261185
  elementText: {
261186
261186
  type: "string",
261187
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
261187
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
261188
261188
  },
261189
261189
  elementId: {
261190
261190
  type: "string",
@@ -261716,7 +261716,7 @@ var init_schemas = __esm({
261716
261716
  },
261717
261717
  elementText: {
261718
261718
  type: "string",
261719
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
261719
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
261720
261720
  },
261721
261721
  elementId: {
261722
261722
  type: "string",
@@ -299669,7 +299669,7 @@ var init_schemas = __esm({
299669
299669
  },
299670
299670
  elementText: {
299671
299671
  type: "string",
299672
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
299672
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
299673
299673
  },
299674
299674
  selector: {
299675
299675
  type: "string",
@@ -303815,7 +303815,7 @@ var init_schemas = __esm({
303815
303815
  },
303816
303816
  elementText: {
303817
303817
  type: "string",
303818
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
303818
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
303819
303819
  },
303820
303820
  selector: {
303821
303821
  type: "string",
@@ -309046,7 +309046,7 @@ var init_schemas = __esm({
309046
309046
  },
309047
309047
  elementText: {
309048
309048
  type: "string",
309049
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
309049
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
309050
309050
  },
309051
309051
  elementId: {
309052
309052
  type: "string",
@@ -309578,7 +309578,7 @@ var init_schemas = __esm({
309578
309578
  },
309579
309579
  elementText: {
309580
309580
  type: "string",
309581
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
309581
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
309582
309582
  },
309583
309583
  elementId: {
309584
309584
  type: "string",
@@ -344570,7 +344570,7 @@ var init_schemas = __esm({
344570
344570
  },
344571
344571
  elementText: {
344572
344572
  type: "string",
344573
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
344573
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
344574
344574
  },
344575
344575
  selector: {
344576
344576
  type: "string",
@@ -348716,7 +348716,7 @@ var init_schemas = __esm({
348716
348716
  },
348717
348717
  elementText: {
348718
348718
  type: "string",
348719
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
348719
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
348720
348720
  },
348721
348721
  selector: {
348722
348722
  type: "string",
@@ -353947,7 +353947,7 @@ var init_schemas = __esm({
353947
353947
  },
353948
353948
  elementText: {
353949
353949
  type: "string",
353950
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
353950
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
353951
353951
  },
353952
353952
  elementId: {
353953
353953
  type: "string",
@@ -354479,7 +354479,7 @@ var init_schemas = __esm({
354479
354479
  },
354480
354480
  elementText: {
354481
354481
  type: "string",
354482
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
354482
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
354483
354483
  },
354484
354484
  elementId: {
354485
354485
  type: "string",
@@ -383844,6 +383844,71 @@ var init_schemas = __esm({
383844
383844
  }
383845
383845
  ]
383846
383846
  }
383847
+ },
383848
+ warm: {
383849
+ type: "object",
383850
+ description: "Results of the run's inline warm phase \u2014 the always-on, best-effort provisioning pass (dependency installs, device boots, session probes) performed between test resolution and test execution. A failed task never gates the run; the per-context paths retry or skip with their normal semantics. Absent when the run ended before the phase (for example, when no specs matched). System-populated.",
383851
+ readOnly: true,
383852
+ additionalProperties: false,
383853
+ required: [
383854
+ "durationMs",
383855
+ "tasks"
383856
+ ],
383857
+ properties: {
383858
+ durationMs: {
383859
+ type: "number",
383860
+ description: "Wall-clock duration of the warm phase in milliseconds. Tasks overlap, so this is less than the sum of the per-task durations."
383861
+ },
383862
+ tasks: {
383863
+ type: "array",
383864
+ description: "One entry per executed warm task.",
383865
+ items: {
383866
+ type: "object",
383867
+ additionalProperties: false,
383868
+ required: [
383869
+ "name",
383870
+ "kind",
383871
+ "outcome",
383872
+ "durationMs"
383873
+ ],
383874
+ properties: {
383875
+ name: {
383876
+ type: "string",
383877
+ description: "Task identity, e.g. `browser-install:chrome` or `device-boot:android:<default>:<latest>`."
383878
+ },
383879
+ kind: {
383880
+ type: "string",
383881
+ enum: [
383882
+ "driver-install",
383883
+ "browser-install",
383884
+ "device-boot",
383885
+ "wda-check",
383886
+ "session-probe",
383887
+ "chromedriver-prefetch"
383888
+ ],
383889
+ description: "What the task pre-pays: a native app driver install, a browser install, a simulator/emulator boot, the managed WebDriverAgent availability check, the concurrent-run driver session probe, or the android mobile-web chromedriver download."
383890
+ },
383891
+ outcome: {
383892
+ type: "string",
383893
+ enum: [
383894
+ "warmed",
383895
+ "skipped",
383896
+ "failed"
383897
+ ],
383898
+ description: "`warmed` \u2014 the work was performed (for device boots, the boot was initiated); `skipped` \u2014 nothing to do or the environment isn't ready (the consuming context handles it as usual); `failed` \u2014 the attempt failed (logged as a warning; never fails the run)."
383899
+ },
383900
+ durationMs: {
383901
+ type: "number",
383902
+ description: "Wall-clock duration of this task in milliseconds."
383903
+ },
383904
+ note: {
383905
+ type: "string",
383906
+ description: "Human-readable detail about the outcome."
383907
+ }
383908
+ }
383909
+ }
383910
+ }
383911
+ }
383847
383912
  }
383848
383913
  },
383849
383914
  required: [
@@ -383865,7 +383930,26 @@ var init_schemas = __esm({
383865
383930
  }
383866
383931
  ]
383867
383932
  }
383868
- ]
383933
+ ],
383934
+ warm: {
383935
+ durationMs: 4211,
383936
+ tasks: [
383937
+ {
383938
+ name: "browser-install:chrome",
383939
+ kind: "browser-install",
383940
+ outcome: "skipped",
383941
+ durationMs: 2,
383942
+ note: "'chrome' is already available"
383943
+ },
383944
+ {
383945
+ name: "session-probe",
383946
+ kind: "session-probe",
383947
+ outcome: "warmed",
383948
+ durationMs: 4205,
383949
+ note: "1 combination ok"
383950
+ }
383951
+ ]
383952
+ }
383869
383953
  },
383870
383954
  {
383871
383955
  specs: [
@@ -395672,7 +395756,7 @@ var init_schemas = __esm({
395672
395756
  },
395673
395757
  elementText: {
395674
395758
  type: "string",
395675
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
395759
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
395676
395760
  },
395677
395761
  selector: {
395678
395762
  type: "string",
@@ -399818,7 +399902,7 @@ var init_schemas = __esm({
399818
399902
  },
399819
399903
  elementText: {
399820
399904
  type: "string",
399821
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
399905
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
399822
399906
  },
399823
399907
  selector: {
399824
399908
  type: "string",
@@ -405049,7 +405133,7 @@ var init_schemas = __esm({
405049
405133
  },
405050
405134
  elementText: {
405051
405135
  type: "string",
405052
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
405136
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
405053
405137
  },
405054
405138
  elementId: {
405055
405139
  type: "string",
@@ -405581,7 +405665,7 @@ var init_schemas = __esm({
405581
405665
  },
405582
405666
  elementText: {
405583
405667
  type: "string",
405584
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
405668
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
405585
405669
  },
405586
405670
  elementId: {
405587
405671
  type: "string",
@@ -440573,7 +440657,7 @@ var init_schemas = __esm({
440573
440657
  },
440574
440658
  elementText: {
440575
440659
  type: "string",
440576
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
440660
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
440577
440661
  },
440578
440662
  selector: {
440579
440663
  type: "string",
@@ -444719,7 +444803,7 @@ var init_schemas = __esm({
444719
444803
  },
444720
444804
  elementText: {
444721
444805
  type: "string",
444722
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
444806
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
444723
444807
  },
444724
444808
  selector: {
444725
444809
  type: "string",
@@ -449950,7 +450034,7 @@ var init_schemas = __esm({
449950
450034
  },
449951
450035
  elementText: {
449952
450036
  type: "string",
449953
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
450037
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
449954
450038
  },
449955
450039
  elementId: {
449956
450040
  type: "string",
@@ -450482,7 +450566,7 @@ var init_schemas = __esm({
450482
450566
  },
450483
450567
  elementText: {
450484
450568
  type: "string",
450485
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
450569
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
450486
450570
  },
450487
450571
  elementId: {
450488
450572
  type: "string",
@@ -487166,7 +487250,7 @@ var init_schemas = __esm({
487166
487250
  },
487167
487251
  elementText: {
487168
487252
  type: "string",
487169
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
487253
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
487170
487254
  },
487171
487255
  selector: {
487172
487256
  type: "string",
@@ -491312,7 +491396,7 @@ var init_schemas = __esm({
491312
491396
  },
491313
491397
  elementText: {
491314
491398
  type: "string",
491315
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
491399
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
491316
491400
  },
491317
491401
  selector: {
491318
491402
  type: "string",
@@ -496543,7 +496627,7 @@ var init_schemas = __esm({
496543
496627
  },
496544
496628
  elementText: {
496545
496629
  type: "string",
496546
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
496630
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
496547
496631
  },
496548
496632
  elementId: {
496549
496633
  type: "string",
@@ -497075,7 +497159,7 @@ var init_schemas = __esm({
497075
497159
  },
497076
497160
  elementText: {
497077
497161
  type: "string",
497078
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
497162
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
497079
497163
  },
497080
497164
  elementId: {
497081
497165
  type: "string",
@@ -535412,7 +535496,7 @@ var init_schemas = __esm({
535412
535496
  },
535413
535497
  elementText: {
535414
535498
  type: "string",
535415
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
535499
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
535416
535500
  },
535417
535501
  selector: {
535418
535502
  type: "string",
@@ -539558,7 +539642,7 @@ var init_schemas = __esm({
539558
539642
  },
539559
539643
  elementText: {
539560
539644
  type: "string",
539561
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
539645
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
539562
539646
  },
539563
539647
  selector: {
539564
539648
  type: "string",
@@ -544789,7 +544873,7 @@ var init_schemas = __esm({
544789
544873
  },
544790
544874
  elementText: {
544791
544875
  type: "string",
544792
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
544876
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
544793
544877
  },
544794
544878
  elementId: {
544795
544879
  type: "string",
@@ -545321,7 +545405,7 @@ var init_schemas = __esm({
545321
545405
  },
545322
545406
  elementText: {
545323
545407
  type: "string",
545324
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
545408
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
545325
545409
  },
545326
545410
  elementId: {
545327
545411
  type: "string",
@@ -580313,7 +580397,7 @@ var init_schemas = __esm({
580313
580397
  },
580314
580398
  elementText: {
580315
580399
  type: "string",
580316
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
580400
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
580317
580401
  },
580318
580402
  selector: {
580319
580403
  type: "string",
@@ -584459,7 +584543,7 @@ var init_schemas = __esm({
584459
584543
  },
584460
584544
  elementText: {
584461
584545
  type: "string",
584462
- description: "Display text of the element to find. If combined with other element finding fields, the element must match all specified criteria."
584546
+ description: "Display text of the element to find. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed to single spaces), so text a framework splits across several nodes still matches and a driver's surrounding whitespace doesn't cause a miss. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text. If combined with other element finding fields, the element must match all specified criteria."
584463
584547
  },
584464
584548
  selector: {
584465
584549
  type: "string",
@@ -589690,7 +589774,7 @@ var init_schemas = __esm({
589690
589774
  },
589691
589775
  elementText: {
589692
589776
  type: "string",
589693
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
589777
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
589694
589778
  },
589695
589779
  elementId: {
589696
589780
  type: "string",
@@ -590222,7 +590306,7 @@ var init_schemas = __esm({
590222
590306
  },
590223
590307
  elementText: {
590224
590308
  type: "string",
590225
- description: "Text content the element must contain. Supports exact match or regex pattern using /pattern/ syntax."
590309
+ description: "Display text of the element to wait for. Matched against the element's full visible text with whitespace normalized (leading/trailing trimmed, internal runs collapsed), so framework-fragmented text still matches. Wrap the value in slashes (`/pattern/`) to match a substring by regular expression instead of the whole normalized text."
590226
590310
  },
590227
590311
  elementId: {
590228
590312
  type: "string",
@@ -631784,6 +631868,9 @@ function getRandomUUID() {
631784
631868
  return v.toString(16);
631785
631869
  });
631786
631870
  }
631871
+ function cloneForValidation(object) {
631872
+ return JSON.parse(JSON.stringify(object));
631873
+ }
631787
631874
  function escapeRegExp(string) {
631788
631875
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
631789
631876
  }
@@ -631807,29 +631894,37 @@ function validate({ schemaKey, object, addDefaults = true }) {
631807
631894
  result.object = object;
631808
631895
  return result;
631809
631896
  }
631810
- validationObject = JSON.parse(JSON.stringify(object));
631897
+ validationObject = cloneForValidation(object);
631811
631898
  result.valid = check(validationObject);
631812
631899
  result.errors = "";
631813
631900
  if (check.errors) {
631901
+ const targetErrors = check.errors;
631814
631902
  const compatibleSchemasList = compatibleSchemas[schemaKey];
631815
631903
  if (!compatibleSchemasList) {
631816
- result.errors = check.errors.map((error) => `${error.instancePath} ${error.message} (${JSON.stringify(error.params)})`).join(", ");
631904
+ result.errors = targetErrors.map((error) => `${error.instancePath} ${error.message} (${JSON.stringify(error.params)})`).join(", ");
631817
631905
  result.object = object;
631818
631906
  result.valid = false;
631819
631907
  return result;
631820
631908
  }
631821
- const matchedSchemaKey = compatibleSchemasList.find((key) => {
631822
- validationObject = JSON.parse(JSON.stringify(object));
631823
- const check2 = ajv.getSchema(key);
631824
- if (check2 && check2(validationObject))
631825
- return key;
631909
+ let matchedSchemaKey = compatibleSchemasList.find((key) => {
631910
+ const probe = ajvCheck.getSchema(key);
631911
+ return probe(object);
631826
631912
  });
631827
631913
  if (!matchedSchemaKey) {
631828
- result.errors = check.errors.map((error) => `${error.instancePath} ${error.message} (${JSON.stringify(error.params)})`).join(", ");
631914
+ matchedSchemaKey = compatibleSchemasList.find((key) => {
631915
+ const mutatingCheck = ajv.getSchema(key);
631916
+ return mutatingCheck(cloneForValidation(object));
631917
+ });
631918
+ }
631919
+ if (!matchedSchemaKey) {
631920
+ result.errors = targetErrors.map((error) => `${error.instancePath} ${error.message} (${JSON.stringify(error.params)})`).join(", ");
631829
631921
  result.object = object;
631830
631922
  result.valid = false;
631831
631923
  return result;
631832
631924
  } else {
631925
+ validationObject = cloneForValidation(object);
631926
+ const matchedCheck = ajv.getSchema(matchedSchemaKey);
631927
+ matchedCheck(validationObject);
631833
631928
  const transformedObject = transformToSchemaKey({
631834
631929
  currentSchema: matchedSchemaKey,
631835
631930
  targetSchema: schemaKey,
@@ -632186,7 +632281,7 @@ Errors: ${result.errors}`);
632186
632281
  }
632187
632282
  return null;
632188
632283
  }
632189
- var import_ajv, import_ajv_formats, import_ajv_keywords, import_ajv_errors, import_dynamicDefaults, ajv, compatibleSchemas;
632284
+ var import_ajv, import_ajv_formats, import_ajv_keywords, import_ajv_errors, import_dynamicDefaults, ajv, ajvCheck, compatibleSchemas;
632190
632285
  var init_validate = __esm({
632191
632286
  "dist/common/src/validate.js"() {
632192
632287
  "use strict";
@@ -632203,12 +632298,23 @@ var init_validate = __esm({
632203
632298
  allowUnionTypes: true,
632204
632299
  coerceTypes: true
632205
632300
  });
632301
+ ajvCheck = new import_ajv.default({
632302
+ strictSchema: false,
632303
+ useDefaults: false,
632304
+ allErrors: true,
632305
+ allowUnionTypes: true,
632306
+ coerceTypes: false
632307
+ });
632206
632308
  import_dynamicDefaults.default.DEFAULTS.uuid = (_args) => getRandomUUID;
632207
632309
  (0, import_ajv_formats.default)(ajv);
632208
632310
  (0, import_ajv_keywords.default)(ajv);
632209
632311
  (0, import_ajv_errors.default)(ajv);
632312
+ (0, import_ajv_formats.default)(ajvCheck);
632313
+ (0, import_ajv_keywords.default)(ajvCheck);
632314
+ (0, import_ajv_errors.default)(ajvCheck);
632210
632315
  for (const [key, value] of Object.entries(schemas)) {
632211
632316
  ajv.addSchema(value, key);
632317
+ ajvCheck.addSchema(value, key);
632212
632318
  }
632213
632319
  compatibleSchemas = {
632214
632320
  config_v3: ["config_v2"],
@@ -633638,8 +633744,8 @@ function waitForStdio(bg, expected, { deadline }) {
633638
633744
  return Promise.reject(new Error(`waitUntil.stdio: invalid regular expression ${expected}: ${error.message}`));
633639
633745
  }
633640
633746
  }
633641
- const matchesText = (text) => regex ? regex.test(text) : text.includes(expected);
633642
- const matched = () => matchesText(bg.getStdout()) || matchesText(bg.getStderr());
633747
+ const matchesText2 = (text) => regex ? regex.test(text) : text.includes(expected);
633748
+ const matched = () => matchesText2(bg.getStdout()) || matchesText2(bg.getStderr());
633643
633749
  return new Promise((resolve, reject) => {
633644
633750
  if (matched())
633645
633751
  return resolve();
@@ -633977,21 +634083,24 @@ async function loadEnvs(envsFile) {
633977
634083
  return { status: "FAIL", description: "Invalid file." };
633978
634084
  }
633979
634085
  }
634086
+ function logLevelEnabled(config, level) {
634087
+ const currentLevel = config.logLevel || "info";
634088
+ if (currentLevel === "error" && level === "error")
634089
+ return true;
634090
+ if (currentLevel === "warning" && (level === "error" || level === "warning"))
634091
+ return true;
634092
+ if (currentLevel === "info" && (level === "error" || level === "warning" || level === "info"))
634093
+ return true;
634094
+ if (currentLevel === "debug" && (level === "error" || level === "warning" || level === "info" || level === "debug"))
634095
+ return true;
634096
+ return false;
634097
+ }
633980
634098
  async function log(config, level, message) {
633981
634099
  if (message === void 0) {
633982
634100
  message = config;
633983
634101
  config = {};
633984
634102
  }
633985
- let logLevelMatch = false;
633986
- if (config.logLevel === "error" && level === "error") {
633987
- logLevelMatch = true;
633988
- } else if (config.logLevel === "warning" && (level === "error" || level === "warning")) {
633989
- logLevelMatch = true;
633990
- } else if (config.logLevel === "info" && (level === "error" || level === "warning" || level === "info")) {
633991
- logLevelMatch = true;
633992
- } else if (config.logLevel === "debug" && (level === "error" || level === "warning" || level === "info" || level === "debug")) {
633993
- logLevelMatch = true;
633994
- }
634103
+ const logLevelMatch = logLevelEnabled(config, level);
633995
634104
  if (logLevelMatch) {
633996
634105
  if (typeof message === "string") {
633997
634106
  let logMessage = `(${level.toUpperCase()}) ${message}`;
@@ -634076,8 +634185,7 @@ function replaceEnvs(stringOrObject) {
634076
634185
  stringOrObject[key] = replaceEnvs(stringOrObject[key]);
634077
634186
  });
634078
634187
  } else if (typeof stringOrObject === "string") {
634079
- const variableRegex = new RegExp(/\$[a-zA-Z0-9_]+(?![a-zA-Z0-9_$])/, "g");
634080
- const matches = stringOrObject.match(variableRegex);
634188
+ const matches = stringOrObject.match(ENV_VAR_REGEX);
634081
634189
  if (!matches)
634082
634190
  return stringOrObject;
634083
634191
  matches.forEach((match) => {
@@ -634321,7 +634429,7 @@ function llevenshteinDistance(s, t) {
634321
634429
  }
634322
634430
  return arr[t.length][s.length];
634323
634431
  }
634324
- var import_node_fs7, import_node_os2, import_node_path5, import_node_crypto2, import_promises, import_node_net, import_axios, import_node_child_process4, BACKGROUND_BUFFER_LIMIT, PTY_PACKAGE, READY_POLL_INTERVAL_MS, TRANSIENT_SESSION_ERROR, SESSION_TIMEOUT_ABORT, TRANSIENT_PROCESS_INIT_EXIT_CODES, SETTLE_CEILING_MAX_MS, PRESERVED_TEMP_ENTRIES, FETCH_BINARY_DEFAULTS, posixBashProbe;
634432
+ var import_node_fs7, import_node_os2, import_node_path5, import_node_crypto2, import_promises, import_node_net, import_axios, import_node_child_process4, BACKGROUND_BUFFER_LIMIT, PTY_PACKAGE, READY_POLL_INTERVAL_MS, TRANSIENT_SESSION_ERROR, SESSION_TIMEOUT_ABORT, TRANSIENT_PROCESS_INIT_EXIT_CODES, SETTLE_CEILING_MAX_MS, PRESERVED_TEMP_ENTRIES, FETCH_BINARY_DEFAULTS, ENV_VAR_REGEX, posixBashProbe;
634325
634433
  var init_utils = __esm({
634326
634434
  "dist/core/utils.js"() {
634327
634435
  "use strict";
@@ -634363,6 +634471,7 @@ var init_utils = __esm({
634363
634471
  maxBodyLength: 50 * 1024 * 1024,
634364
634472
  maxRedirects: 5
634365
634473
  };
634474
+ ENV_VAR_REGEX = /\$[a-zA-Z0-9_]+(?![a-zA-Z0-9_$])/g;
634366
634475
  }
634367
634476
  });
634368
634477
 
@@ -635014,12 +635123,19 @@ var init_files = __esm({
635014
635123
  });
635015
635124
 
635016
635125
  // dist/core/openapi.js
635126
+ function getGenerator() {
635127
+ if (!jsfPromise) {
635128
+ jsfPromise = import("json-schema-faker").then(({ createGenerator }) => createGenerator({ optionalsProbability: 0 }));
635129
+ }
635130
+ return jsfPromise;
635131
+ }
635017
635132
  async function loadDescription(descriptionPath = "") {
635018
635133
  if (!descriptionPath) {
635019
635134
  throw new Error("Description is required.");
635020
635135
  }
635021
635136
  const definition = await readFile({ fileURLOrPath: descriptionPath });
635022
- const dereferencedDefinition = await import_json_schema_ref_parser.default.dereference(definition);
635137
+ const parser = (await import("@apidevtools/json-schema-ref-parser")).default;
635138
+ const dereferencedDefinition = await parser.dereference(definition);
635023
635139
  return dereferencedDefinition;
635024
635140
  }
635025
635141
  async function getOperation(definition = {}, operationId = "", responseCode = "", exampleKey = "", server = "") {
@@ -635143,7 +635259,8 @@ async function getExample(definition = {}, exampleKey = "", generateFromSchema =
635143
635259
  }
635144
635260
  if (generateFromSchema && definition.type) {
635145
635261
  try {
635146
- example = await jsf.generate(definition);
635262
+ const generator = await getGenerator();
635263
+ example = await generator.generate(definition);
635147
635264
  if (example)
635148
635265
  return example;
635149
635266
  } catch (error) {
@@ -635218,15 +635335,12 @@ function checkForExamples(definition = {}, exampleKey = "") {
635218
635335
  return true;
635219
635336
  return false;
635220
635337
  }
635221
- var import_json_schema_faker, import_json_schema_ref_parser, jsf;
635338
+ var jsfPromise;
635222
635339
  var init_openapi = __esm({
635223
635340
  "dist/core/openapi.js"() {
635224
635341
  "use strict";
635225
635342
  init_utils();
635226
- import_json_schema_faker = require("json-schema-faker");
635227
635343
  init_files();
635228
- import_json_schema_ref_parser = __toESM(require("@apidevtools/json-schema-ref-parser"), 1);
635229
- jsf = (0, import_json_schema_faker.createGenerator)({ optionalsProbability: 0 });
635230
635344
  }
635231
635345
  });
635232
635346
 
@@ -635234,9 +635348,11 @@ var init_openapi = __esm({
635234
635348
  var config_exports = {};
635235
635349
  __export(config_exports, {
635236
635350
  clearAppCache: () => clearAppCache,
635351
+ detectInstalledBrowserDrivers: () => detectInstalledBrowserDrivers,
635237
635352
  getAvailableApps: () => getAvailableApps,
635238
635353
  getBrowserDiagnostics: () => getBrowserDiagnostics,
635239
635354
  getEnvironment: () => getEnvironment,
635355
+ patchAppCache: () => patchAppCache,
635240
635356
  resolveConcurrentRunners: () => resolveConcurrentRunners,
635241
635357
  setConfig: () => setConfig,
635242
635358
  verifyAppDrivers: () => verifyAppDrivers
@@ -635447,13 +635563,62 @@ function clearAppCache(config) {
635447
635563
  }
635448
635564
  cachedAppsByDir.delete(cacheKeyFor(config));
635449
635565
  }
635566
+ async function patchAppCache(config, installed, deps = {}) {
635567
+ const key = cacheKeyFor(config);
635568
+ try {
635569
+ const verify = deps.verify ?? verifyDriverBinary;
635570
+ const drivers = (deps.detectDrivers ?? detectInstalledBrowserDrivers)(config);
635571
+ const descriptors = [];
635572
+ for (const b of installed ?? []) {
635573
+ if (b.name === "chrome" && b.path && b.driverPath && drivers.chromium) {
635574
+ descriptors.push({
635575
+ app: {
635576
+ name: "chrome",
635577
+ version: b.version,
635578
+ path: b.path,
635579
+ driver: b.driverPath
635580
+ },
635581
+ driverName: "chromedriver",
635582
+ driverPath: b.driverPath
635583
+ });
635584
+ } else if (b.name === "firefox" && b.path && drivers.gecko) {
635585
+ descriptors.push({
635586
+ app: { name: "firefox", version: b.version, path: b.path },
635587
+ driverName: b.driverPath ? "geckodriver" : void 0,
635588
+ driverPath: b.driverPath
635589
+ });
635590
+ }
635591
+ }
635592
+ if (descriptors.length === 0) {
635593
+ cachedAppsByDir.delete(key);
635594
+ return;
635595
+ }
635596
+ const verifiedApps = await verifyAppDrivers(descriptors, {
635597
+ verify: (driverName, driverPath) => verify(driverName, driverPath),
635598
+ logger: (msg, level) => log(config, level ?? "warning", msg)
635599
+ });
635600
+ const existing = cachedAppsByDir.get(key) ?? [];
635601
+ const existingNames = new Set(existing.map((a) => a?.name));
635602
+ const additions = verifiedApps.filter((a) => !existingNames.has(a?.name));
635603
+ cachedAppsByDir.set(key, [...existing, ...additions]);
635604
+ } catch {
635605
+ cachedAppsByDir.delete(key);
635606
+ }
635607
+ }
635608
+ function detectInstalledBrowserDrivers(config, isInstalled = (name) => Boolean(resolveHeavyDepPath(name, { cacheDir: config?.cacheDir }))) {
635609
+ return {
635610
+ chromium: isInstalled("appium-chromium-driver"),
635611
+ gecko: isInstalled("appium-geckodriver"),
635612
+ safari: isInstalled("appium-safari-driver")
635613
+ };
635614
+ }
635450
635615
  async function probeBrowserEnvironment({ config, browsersDir }) {
635451
635616
  setAppiumHome({ cacheDir: config?.cacheDir });
635452
635617
  const cwd = process.cwd();
635453
635618
  process.chdir(import_node_path9.default.join(__dirname4, "../.."));
635454
635619
  let installedBrowsers = [];
635455
635620
  let browserDetectionFailed = false;
635456
- let appiumDriverOutput = "";
635621
+ let installedDrivers = { chromium: false, gecko: false, safari: false };
635457
635622
  try {
635458
635623
  const browsersInstalled = resolveHeavyDepPath("@puppeteer/browsers", {
635459
635624
  cacheDir: config?.cacheDir
@@ -635473,47 +635638,11 @@ async function probeBrowserEnvironment({ config, browsersDir }) {
635473
635638
  installedBrowsers = [];
635474
635639
  }
635475
635640
  }
635476
- const appiumEntry = resolveHeavyDepPath("appium", {
635477
- cacheDir: config?.cacheDir
635478
- });
635479
- const installedAppiumDrivers = await new Promise((resolve) => {
635480
- if (!appiumEntry) {
635481
- resolve({
635482
- stdout: "",
635483
- stderr: "appium is not installed; driver list unavailable",
635484
- exitCode: 1
635485
- });
635486
- return;
635487
- }
635488
- const child = (0, import_node_child_process6.spawn)(process.execPath, [appiumEntry, "driver", "list"], { env: process.env });
635489
- let stdout = "";
635490
- let stderr = "";
635491
- child.stdout?.on("data", (c) => {
635492
- stdout += typeof c === "string" ? c : c.toString("utf8");
635493
- });
635494
- child.stderr?.on("data", (c) => {
635495
- stderr += typeof c === "string" ? c : c.toString("utf8");
635496
- });
635497
- child.on("error", (err) => {
635498
- resolve({
635499
- stdout: stdout.replace(/\n$/, ""),
635500
- stderr: (stderr + String(err)).replace(/\n$/, ""),
635501
- exitCode: 1
635502
- });
635503
- });
635504
- child.on("close", (code) => {
635505
- resolve({
635506
- stdout: stdout.replace(/\n$/, ""),
635507
- stderr: stderr.replace(/\n$/, ""),
635508
- exitCode: code ?? 1
635509
- });
635510
- });
635511
- });
635512
- appiumDriverOutput = installedAppiumDrivers.stdout + "\n" + installedAppiumDrivers.stderr;
635641
+ installedDrivers = detectInstalledBrowserDrivers(config);
635513
635642
  } finally {
635514
635643
  process.chdir(cwd);
635515
635644
  }
635516
- return { installedBrowsers, appiumDriverOutput, browserDetectionFailed };
635645
+ return { installedBrowsers, installedDrivers, browserDetectionFailed };
635517
635646
  }
635518
635647
  async function getAvailableApps({ config }) {
635519
635648
  const browsersDir = getBrowsersDir({ cacheDir: config?.cacheDir });
@@ -635521,12 +635650,12 @@ async function getAvailableApps({ config }) {
635521
635650
  const hit = cachedAppsByDir.get(key);
635522
635651
  if (hit)
635523
635652
  return hit;
635524
- const { installedBrowsers, appiumDriverOutput, browserDetectionFailed } = await probeBrowserEnvironment({ config, browsersDir });
635653
+ const { installedBrowsers, installedDrivers, browserDetectionFailed } = await probeBrowserEnvironment({ config, browsersDir });
635525
635654
  const descriptors = [];
635526
635655
  const chrome = installedBrowsers.find((browser) => browser.browser === "chrome");
635527
635656
  const chromeVersion = chrome?.buildId;
635528
635657
  const chromedriver = installedBrowsers.find((browser) => browser.browser === "chromedriver");
635529
- const appiumChromium = appiumDriverOutput.match(/\n.*chromium.*installed \(npm\).*\n/);
635658
+ const appiumChromium = installedDrivers.chromium;
635530
635659
  if (chrome && chromedriver && appiumChromium) {
635531
635660
  descriptors.push({
635532
635661
  app: {
@@ -635540,7 +635669,7 @@ async function getAvailableApps({ config }) {
635540
635669
  });
635541
635670
  }
635542
635671
  const firefox = installedBrowsers.find((browser) => browser.browser === "firefox");
635543
- const appiumFirefox = appiumDriverOutput.match(/\n.*gecko.*installed \(npm\).*\n/);
635672
+ const appiumFirefox = installedDrivers.gecko;
635544
635673
  if (firefox && appiumFirefox) {
635545
635674
  const geckodriverPath = resolveGeckodriverBinaryPath(config, browsersDir);
635546
635675
  descriptors.push({
@@ -635556,7 +635685,7 @@ async function getAvailableApps({ config }) {
635556
635685
  const currentPlatform = config?.environment?.platform ?? getEnvironment().platform;
635557
635686
  if (currentPlatform === "mac") {
635558
635687
  const safariVersion = await spawnCommand("defaults read /Applications/Safari.app/Contents/Info.plist CFBundleShortVersionString");
635559
- const appiumSafari = appiumDriverOutput.match(/\n.*safari.*installed \(npm\).*\n/);
635688
+ const appiumSafari = installedDrivers.safari;
635560
635689
  if (safariVersion.exitCode === 0 && appiumSafari) {
635561
635690
  descriptors.push({
635562
635691
  app: { name: "safari", version: safariVersion.stdout.trim(), path: "" },
@@ -636496,17 +636625,24 @@ function detectFileTypeFromContent(content) {
636496
636625
  }
636497
636626
 
636498
636627
  // dist/common/src/detectTests.js
636628
+ var compiledRegExpCache = /* @__PURE__ */ new Map();
636499
636629
  function safeRegExp(pattern, flags) {
636500
636630
  if (typeof pattern !== "string" || pattern.length === 0)
636501
636631
  return null;
636502
636632
  if (pattern.length > 1500)
636503
636633
  return null;
636504
- const sanitized = Array.from(pattern, (c) => String.fromCharCode(c.charCodeAt(0))).join("");
636634
+ const cacheKey = pattern + " " + flags;
636635
+ const cached = compiledRegExpCache.get(cacheKey);
636636
+ if (cached !== void 0)
636637
+ return cached;
636638
+ let compiled;
636505
636639
  try {
636506
- return new RegExp(sanitized, flags);
636640
+ compiled = new RegExp(pattern, flags);
636507
636641
  } catch {
636508
- return null;
636642
+ compiled = null;
636509
636643
  }
636644
+ compiledRegExpCache.set(cacheKey, compiled);
636645
+ return compiled;
636510
636646
  }
636511
636647
  var HASH_EXCLUDED_KEYS = /* @__PURE__ */ new Set([
636512
636648
  "location",
@@ -638201,6 +638337,7 @@ async function loadHerettoContent(herettoConfig, log3, config, deps) {
638201
638337
  }
638202
638338
 
638203
638339
  // dist/core/detectTests.js
638340
+ var DETECTION_PARSE_CONCURRENCY = 8;
638204
638341
  async function detectTests2({ config }) {
638205
638342
  const files = await qualifyFiles({ config });
638206
638343
  log(config, "debug", `FILES:`);
@@ -638219,16 +638356,33 @@ function generateSpecId(filePath) {
638219
638356
  const normalizedPath = relativePath.split(import_node_path11.default.sep).join("/").replace(/^\.\//, "").replace(/[^a-zA-Z0-9._\-\/]/g, "_");
638220
638357
  return normalizedPath;
638221
638358
  }
638222
- async function isValidSourceFile({ config, files, source }) {
638223
- log(config, "debug", `validation: ${source}`);
638224
- let allowedExtensions = ["json", "yaml", "yml"];
638225
- config.fileTypes.forEach((fileType) => {
638226
- allowedExtensions = allowedExtensions.concat(fileType.extensions);
638359
+ function buildAllowedExtensions(config) {
638360
+ const allowed = /* @__PURE__ */ new Set(["json", "yaml", "yml"]);
638361
+ (config.fileTypes || []).forEach((fileType) => {
638362
+ fileType.extensions.forEach((ext) => allowed.add(ext));
638227
638363
  });
638364
+ return allowed;
638365
+ }
638366
+ async function isValidSourceFile({ config, files, source, allowedExtensions }) {
638367
+ log(config, "debug", `validation: ${source}`);
638368
+ const allowed = allowedExtensions;
638228
638369
  if (files.indexOf(source) >= 0)
638229
638370
  return false;
638230
638371
  if (import_node_path11.default.extname(source) === ".json" || import_node_path11.default.extname(source) === ".yaml" || import_node_path11.default.extname(source) === ".yml") {
638231
- const content = await readFile({ fileURLOrPath: source });
638372
+ const ext = import_node_path11.default.extname(source).slice(1).toLowerCase();
638373
+ let rawContent;
638374
+ try {
638375
+ rawContent = await import_node_fs13.default.promises.readFile(source, "utf8");
638376
+ } catch {
638377
+ log(config, "debug", `${source} couldn't be read. Skipping.`);
638378
+ return false;
638379
+ }
638380
+ let content;
638381
+ try {
638382
+ content = ext === "json" ? JSON.parse(rawContent) : import_yaml3.default.parse(rawContent);
638383
+ } catch {
638384
+ content = rawContent;
638385
+ }
638232
638386
  if (typeof content !== "object") {
638233
638387
  log(config, "debug", `${source} isn't a valid test specification. Skipping.`);
638234
638388
  return false;
@@ -638269,9 +638423,10 @@ async function isValidSourceFile({ config, files, source }) {
638269
638423
  }
638270
638424
  }
638271
638425
  }
638426
+ config._parsedFileCache?.set(source, { rawContent, content });
638272
638427
  }
638273
638428
  const extension = import_node_path11.default.extname(source).substring(1);
638274
- if (!allowedExtensions.includes(extension)) {
638429
+ if (!allowed.has(extension)) {
638275
638430
  log(config, "debug", `${source} extension isn't specified in a \`config.fileTypes\` object. Skipping.`);
638276
638431
  return false;
638277
638432
  }
@@ -638311,6 +638466,8 @@ async function qualifyFiles({ config }) {
638311
638466
  let sequence = [];
638312
638467
  const phaseByFile = /* @__PURE__ */ new Map();
638313
638468
  config._phaseByFile = phaseByFile;
638469
+ const parsedFileCache = /* @__PURE__ */ new Map();
638470
+ config._parsedFileCache = parsedFileCache;
638314
638471
  const toEntries = (value, phase) => (value == null ? [] : [].concat(value)).map((source) => ({
638315
638472
  source,
638316
638473
  phase
@@ -638323,6 +638480,7 @@ async function qualifyFiles({ config }) {
638323
638480
  return [];
638324
638481
  }
638325
638482
  const ignoredDitaMaps = [];
638483
+ const allowedExtensions = buildAllowedExtensions(config);
638326
638484
  if (!config._herettoPathMapping) {
638327
638485
  config._herettoPathMapping = {};
638328
638486
  }
@@ -638390,8 +638548,9 @@ async function qualifyFiles({ config }) {
638390
638548
  let isFile = false;
638391
638549
  let isDir = false;
638392
638550
  try {
638393
- isFile = import_node_fs13.default.statSync(source).isFile();
638394
- isDir = import_node_fs13.default.statSync(source).isDirectory();
638551
+ const stat = import_node_fs13.default.statSync(source);
638552
+ isFile = stat.isFile();
638553
+ isDir = stat.isDirectory();
638395
638554
  } catch {
638396
638555
  log(config, "warning", `Cannot access path: ${source}. Skipping.`);
638397
638556
  continue;
@@ -638406,7 +638565,7 @@ async function qualifyFiles({ config }) {
638406
638565
  continue;
638407
638566
  }
638408
638567
  const resolved = import_node_path11.default.resolve(source);
638409
- if (isFile && await isValidSourceFile({ config, files, source: resolved })) {
638568
+ if (isFile && await isValidSourceFile({ config, files, allowedExtensions, source: resolved })) {
638410
638569
  files.push(resolved);
638411
638570
  if (!phaseByFile.has(resolved))
638412
638571
  phaseByFile.set(resolved, phase);
@@ -638419,9 +638578,10 @@ async function qualifyFiles({ config }) {
638419
638578
  const content = import_node_path11.default.resolve(dir + "/" + object);
638420
638579
  if (content.includes("node_modules"))
638421
638580
  continue;
638422
- const isFile2 = import_node_fs13.default.statSync(content).isFile();
638423
- const isDir2 = import_node_fs13.default.statSync(content).isDirectory();
638424
- if (isFile2 && await isValidSourceFile({ config, files, source: content })) {
638581
+ const stat = import_node_fs13.default.statSync(content);
638582
+ const isFile2 = stat.isFile();
638583
+ const isDir2 = stat.isDirectory();
638584
+ if (isFile2 && await isValidSourceFile({ config, files, allowedExtensions, source: content })) {
638425
638585
  const resolved2 = import_node_path11.default.resolve(content);
638426
638586
  files.push(resolved2);
638427
638587
  if (!phaseByFile.has(resolved2))
@@ -638436,27 +638596,34 @@ async function qualifyFiles({ config }) {
638436
638596
  return files;
638437
638597
  }
638438
638598
  async function parseTests({ config, files }) {
638439
- let specs = [];
638440
- for (const file of files) {
638599
+ const parseOneFile = async (file) => {
638441
638600
  log(config, "debug", `file: ${file}`);
638442
638601
  const extension = import_node_path11.default.extname(file).slice(1);
638443
638602
  let content = "";
638444
638603
  let rawContent;
638445
638604
  if (extension === "json" || extension === "yaml" || extension === "yml") {
638446
- try {
638447
- rawContent = await import_node_fs13.default.promises.readFile(file, "utf8");
638448
- if (extension === "json") {
638449
- content = JSON.parse(rawContent);
638450
- } else {
638451
- content = import_yaml3.default.parse(rawContent);
638605
+ const cached = config._parsedFileCache?.get(file);
638606
+ if (cached) {
638607
+ rawContent = cached.rawContent;
638608
+ content = cached.content;
638609
+ } else {
638610
+ try {
638611
+ rawContent = await import_node_fs13.default.promises.readFile(file, "utf8");
638612
+ if (extension === "json") {
638613
+ content = JSON.parse(rawContent);
638614
+ } else {
638615
+ content = import_yaml3.default.parse(rawContent);
638616
+ }
638617
+ } catch (err) {
638618
+ console.warn(`Failed to read/parse ${file}: ${err.message}`);
638619
+ content = await readFile({ fileURLOrPath: file });
638452
638620
  }
638453
- } catch (err) {
638454
- console.warn(`Failed to read/parse ${file}: ${err.message}`);
638455
- content = await readFile({ fileURLOrPath: file });
638456
638621
  }
638457
638622
  } else {
638458
638623
  content = await readFile({ fileURLOrPath: file });
638459
638624
  }
638625
+ if (content === null)
638626
+ return null;
638460
638627
  if (typeof content === "object") {
638461
638628
  if (!content.specId)
638462
638629
  content.specId = generateSpecId(file);
@@ -638520,7 +638687,10 @@ async function parseTests({ config, files }) {
638520
638687
  content = await resolvePaths({
638521
638688
  config,
638522
638689
  object: content,
638523
- filePath: file
638690
+ filePath: file,
638691
+ // Detection only ever resolves specs; passing the known objectType skips
638692
+ // resolvePaths' config_v3-then-spec_v3 discovery probe (item 3.1).
638693
+ objectType: "spec"
638524
638694
  });
638525
638695
  for (let t = 0; t < content.tests.length; t++) {
638526
638696
  const test = content.tests[t];
@@ -638585,13 +638755,16 @@ async function parseTests({ config, files }) {
638585
638755
  if (!validation.valid) {
638586
638756
  log(config, "warning", validation);
638587
638757
  log(config, "warning", `After applying setup and cleanup steps, ${file} isn't a valid test specification. Skipping.`);
638588
- continue;
638758
+ return null;
638589
638759
  }
638590
638760
  content = validation.object;
638591
638761
  content = await resolvePaths({
638592
638762
  config,
638593
638763
  object: content,
638594
- filePath: file
638764
+ filePath: file,
638765
+ // Detection only ever resolves specs; passing the known objectType skips
638766
+ // resolvePaths' config_v3-then-spec_v3 discovery probe (item 3.1).
638767
+ objectType: "spec"
638595
638768
  });
638596
638769
  content._phase = specPhase;
638597
638770
  for (const [testIdx, testMap] of stepLocations) {
@@ -638624,7 +638797,7 @@ async function parseTests({ config, files }) {
638624
638797
  }
638625
638798
  }
638626
638799
  }
638627
- specs.push(content);
638800
+ return content;
638628
638801
  } else {
638629
638802
  let id = generateSpecId(file);
638630
638803
  let spec = {
@@ -638652,7 +638825,7 @@ async function parseTests({ config, files }) {
638652
638825
  });
638653
638826
  if (!validation2.valid) {
638654
638827
  log(config, "warning", `Failed to convert ${file} to a runShell step: ${validation2.errors}. Skipping.`);
638655
- continue;
638828
+ return null;
638656
638829
  }
638657
638830
  spec.tests.push(test);
638658
638831
  const specValidation = validate({
@@ -638666,12 +638839,14 @@ async function parseTests({ config, files }) {
638666
638839
  spec = await resolvePaths({
638667
638840
  config,
638668
638841
  object: spec,
638669
- filePath: file
638842
+ filePath: file,
638843
+ // Detection only resolves specs — skip the discovery probe (3.1).
638844
+ objectType: "spec"
638670
638845
  });
638671
638846
  spec._phase = config._phaseByFile?.get(import_node_path11.default.resolve(file)) ?? "main";
638672
- specs.push(spec);
638847
+ return spec;
638673
638848
  }
638674
- continue;
638849
+ return null;
638675
638850
  }
638676
638851
  const tests = await detectTests({
638677
638852
  config,
@@ -638693,14 +638868,21 @@ async function parseTests({ config, files }) {
638693
638868
  spec = await resolvePaths({
638694
638869
  config,
638695
638870
  object: spec,
638696
- filePath: file
638871
+ filePath: file,
638872
+ // Detection only resolves specs — skip the discovery probe (3.1).
638873
+ objectType: "spec"
638697
638874
  });
638698
638875
  spec._phase = config._phaseByFile?.get(import_node_path11.default.resolve(file)) ?? "main";
638699
- specs.push(spec);
638876
+ return spec;
638700
638877
  }
638701
638878
  }
638702
- }
638703
- return specs;
638879
+ return null;
638880
+ };
638881
+ const slots = new Array(files.length).fill(null);
638882
+ await runConcurrent(files.map((_, i) => i), DETECTION_PARSE_CONCURRENCY, async (i) => {
638883
+ slots[i] = await parseOneFile(files[i]);
638884
+ });
638885
+ return slots.filter((spec) => spec != null);
638704
638886
  }
638705
638887
 
638706
638888
  // dist/core/resolveTests.js
@@ -638785,21 +638967,34 @@ function resolveContexts({ contexts, test, config }) {
638785
638967
  if (resolvedContexts.length === 0) {
638786
638968
  resolvedContexts.push({});
638787
638969
  }
638788
- log(config, "debug", `Resolved contexts for test ${test.testId}:
638970
+ if (logLevelEnabled(config, "debug"))
638971
+ log(config, "debug", `Resolved contexts for test ${test.testId}:
638789
638972
  ${JSON.stringify(resolvedContexts, null, 2)}`);
638790
638973
  return resolvedContexts;
638791
638974
  }
638792
638975
  async function fetchOpenApiDocuments({ config, documentArray }) {
638793
- log(config, "debug", `Fetching OpenAPI documents:
638976
+ if (logLevelEnabled(config, "debug"))
638977
+ log(config, "debug", `Fetching OpenAPI documents:
638794
638978
  ${JSON.stringify(documentArray, null, 2)}`);
638795
638979
  const openApiDocuments = [];
638796
638980
  if (config?.integrations?.openApi?.length > 0)
638797
638981
  openApiDocuments.push(...config.integrations.openApi);
638982
+ const descriptionCache = config._openApiDescriptionCache || (config._openApiDescriptionCache = /* @__PURE__ */ new Map());
638798
638983
  if (documentArray?.length > 0) {
638799
638984
  for (const definition of documentArray) {
638800
638985
  try {
638801
- const openApiDefinition = await loadDescription(definition.descriptionPath);
638802
- definition.definition = openApiDefinition;
638986
+ if (!definition.definition) {
638987
+ const key = definition.descriptionPath;
638988
+ let pending = descriptionCache.get(key);
638989
+ if (!pending) {
638990
+ pending = loadDescription(key).catch((err) => {
638991
+ descriptionCache.delete(key);
638992
+ throw err;
638993
+ });
638994
+ descriptionCache.set(key, pending);
638995
+ }
638996
+ definition.definition = await pending;
638997
+ }
638803
638998
  } catch (error) {
638804
638999
  log(config, "error", `Failed to load OpenAPI definition from ${definition.descriptionPath}: ${error.message}`);
638805
639000
  continue;
@@ -638811,7 +639006,8 @@ ${JSON.stringify(documentArray, null, 2)}`);
638811
639006
  openApiDocuments.push(definition);
638812
639007
  }
638813
639008
  }
638814
- log(config, "debug", `Fetched OpenAPI documents:
639009
+ if (logLevelEnabled(config, "debug"))
639010
+ log(config, "debug", `Fetched OpenAPI documents:
638815
639011
  ${JSON.stringify(openApiDocuments, null, 2)}`);
638816
639012
  return openApiDocuments;
638817
639013
  }
@@ -638831,7 +639027,8 @@ async function resolveContext({ config, test, context, usedContextIds, openApi }
638831
639027
  context.contextId = context.contextId ? uniqueId(context.contextId, usedContextIds) : deriveContextId({ context, usedIds: usedContextIds });
638832
639028
  const contextId = context.contextId;
638833
639029
  usedContextIds.add(contextId);
638834
- log(config, "debug", `RESOLVING CONTEXT ID ${contextId}:
639030
+ if (logLevelEnabled(config, "debug"))
639031
+ log(config, "debug", `RESOLVING CONTEXT ID ${contextId}:
638835
639032
  ${JSON.stringify(context, null, 2)}`);
638836
639033
  const resolvedContext = {
638837
639034
  ...context,
@@ -638844,13 +639041,15 @@ ${JSON.stringify(context, null, 2)}`);
638844
639041
  steps: [...test.steps],
638845
639042
  contextId
638846
639043
  };
638847
- log(config, "debug", `RESOLVED CONTEXT ${contextId}:
639044
+ if (logLevelEnabled(config, "debug"))
639045
+ log(config, "debug", `RESOLVED CONTEXT ${contextId}:
638848
639046
  ${JSON.stringify(resolvedContext, null, 2)}`);
638849
639047
  return resolvedContext;
638850
639048
  }
638851
639049
  async function resolveTest({ config, spec, test }) {
638852
639050
  const testId = test.testId || `${spec.specId}~${contentHash(test)}`;
638853
- log(config, "debug", `RESOLVING TEST ID ${testId}:
639051
+ if (logLevelEnabled(config, "debug"))
639052
+ log(config, "debug", `RESOLVING TEST ID ${testId}:
638854
639053
  ${JSON.stringify(test, null, 2)}`);
638855
639054
  const resolvedTest = {
638856
639055
  ...test,
@@ -638880,13 +639079,15 @@ ${JSON.stringify(test, null, 2)}`);
638880
639079
  });
638881
639080
  resolvedTest.contexts.push(resolvedContext);
638882
639081
  }
638883
- log(config, "debug", `RESOLVED TEST ${testId}:
639082
+ if (logLevelEnabled(config, "debug"))
639083
+ log(config, "debug", `RESOLVED TEST ${testId}:
638884
639084
  ${JSON.stringify(resolvedTest, null, 2)}`);
638885
639085
  return resolvedTest;
638886
639086
  }
638887
639087
  async function resolveSpec({ config, spec }) {
638888
639088
  const specId = spec.specId || (spec.contentPath ? generateSpecId(spec.contentPath) : import_node_crypto5.default.randomUUID());
638889
- log(config, "debug", `RESOLVING SPEC ID ${specId}:
639089
+ if (logLevelEnabled(config, "debug"))
639090
+ log(config, "debug", `RESOLVING SPEC ID ${specId}:
638890
639091
  ${JSON.stringify(spec, null, 2)}`);
638891
639092
  const resolvedSpec = {
638892
639093
  ...spec,
@@ -638906,24 +639107,28 @@ ${JSON.stringify(spec, null, 2)}`);
638906
639107
  });
638907
639108
  resolvedSpec.tests.push(resolvedTest);
638908
639109
  }
638909
- log(config, "debug", `RESOLVED SPEC ${specId}:
639110
+ if (logLevelEnabled(config, "debug"))
639111
+ log(config, "debug", `RESOLVED SPEC ${specId}:
638910
639112
  ${JSON.stringify(resolvedSpec, null, 2)}`);
638911
639113
  return resolvedSpec;
638912
639114
  }
638913
639115
  async function resolveTests({ config, detectedTests }) {
638914
- log(config, "debug", `RESOLVING DETECTED TEST SPECS:
639116
+ if (logLevelEnabled(config, "debug"))
639117
+ log(config, "debug", `RESOLVING DETECTED TEST SPECS:
638915
639118
  ${JSON.stringify(detectedTests, null, 2)}`);
638916
639119
  const resolvedTests = {
638917
639120
  resolvedTestsId: import_node_crypto5.default.randomUUID(),
638918
639121
  config,
638919
639122
  specs: []
638920
639123
  };
639124
+ config._openApiDescriptionCache = /* @__PURE__ */ new Map();
638921
639125
  log(config, "info", "Resolving test specs.");
638922
639126
  for (const spec of detectedTests) {
638923
639127
  const resolvedSpec = await resolveSpec({ config, spec });
638924
639128
  resolvedTests.specs.push(resolvedSpec);
638925
639129
  }
638926
- log(config, "debug", `RESOLVED TEST SPECS:
639130
+ if (logLevelEnabled(config, "debug"))
639131
+ log(config, "debug", `RESOLVED TEST SPECS:
638927
639132
  ${JSON.stringify(resolvedTests, null, 2)}`);
638928
639133
  return resolvedTests;
638929
639134
  }
@@ -641084,6 +641289,13 @@ async function teardownDeviceRegistry(registry, kill4) {
641084
641289
  for (const entry of registry.values()) {
641085
641290
  if (!entry.bootedByUs)
641086
641291
  continue;
641292
+ if (entry.ready) {
641293
+ try {
641294
+ await entry.ready;
641295
+ } catch {
641296
+ continue;
641297
+ }
641298
+ }
641087
641299
  try {
641088
641300
  await kill4(entry);
641089
641301
  } catch {
@@ -642579,7 +642791,7 @@ async function setElementOutputs({ element }) {
642579
642791
  }
642580
642792
  async function findElementByRegex({ pattern, timeout, driver }) {
642581
642793
  await driver.pause(timeout);
642582
- const elements = await driver.$$("//*[normalize-space(text())]");
642794
+ const elements = await driver.$$("//*[text()[normalize-space()]]");
642583
642795
  for (const element of elements) {
642584
642796
  const text = await element.getText();
642585
642797
  if (text.match(pattern)) {
@@ -642666,7 +642878,8 @@ async function findElementByShorthand({ string, timeout = 5e3, driver }) {
642666
642878
  await el.waitForExist({ timeout });
642667
642879
  return el;
642668
642880
  }).catch(() => null);
642669
- const textPromise = driver.$(`//*[normalize-space(text())="${string}"]`).then(async (el) => {
642881
+ const textLiteral = xpathLiteral2(normalizeText(string));
642882
+ const textPromise = driver.$(`//*[normalize-space(.)=${textLiteral} and not(.//*[normalize-space(.)=${textLiteral}])]`).then(async (el) => {
642670
642883
  await el.waitForExist({ timeout });
642671
642884
  return el;
642672
642885
  }).catch(() => null);
@@ -642714,6 +642927,24 @@ async function findElementByShorthand({ string, timeout = 5e3, driver }) {
642714
642927
  function isRegexPattern(str) {
642715
642928
  return typeof str === "string" && str.startsWith("/") && str.endsWith("/");
642716
642929
  }
642930
+ function normalizeText(value) {
642931
+ return String(value ?? "").replace(/\s+/g, " ").trim();
642932
+ }
642933
+ function xpathLiteral2(value) {
642934
+ if (!value.includes('"'))
642935
+ return `"${value}"`;
642936
+ if (!value.includes("'"))
642937
+ return `'${value}'`;
642938
+ const parts = value.split('"').map((p) => `"${p}"`);
642939
+ return `concat(${parts.join(`, '"', `)})`;
642940
+ }
642941
+ function matchesText(value, pattern) {
642942
+ if (isRegexPattern(pattern)) {
642943
+ const regex = new RegExp(pattern.slice(1, -1));
642944
+ return regex.test(String(value ?? ""));
642945
+ }
642946
+ return normalizeText(value) === normalizeText(pattern);
642947
+ }
642717
642948
  function matchesPattern(value, pattern) {
642718
642949
  if (isRegexPattern(pattern)) {
642719
642950
  const regex = new RegExp(pattern.slice(1, -1));
@@ -642815,8 +643046,11 @@ async function findElementByCriteria({ selector, elementText, elementId, element
642815
643046
  }
642816
643047
  }
642817
643048
  }
642818
- if (elementText) {
642819
- xpathConditions.push(`normalize-space(text())`);
643049
+ if (elementText && !isRegexPattern(elementText)) {
643050
+ const lit = xpathLiteral2(normalizeText(elementText));
643051
+ xpathConditions.push(`(normalize-space(.)=${lit} and not(.//*[normalize-space(.)=${lit}]))`);
643052
+ } else if (elementText) {
643053
+ xpathConditions.push(`text()[normalize-space()]`);
642820
643054
  }
642821
643055
  let xpath;
642822
643056
  if (xpathConditions.length > 0) {
@@ -642893,7 +643127,9 @@ async function findElementByCriteria({ selector, elementText, elementId, element
642893
643127
  }
642894
643128
  elementCriteriaUsed.push(checkType.type);
642895
643129
  } else {
642896
- if (!actualValue || !matchesPattern(actualValue, checkType.value)) {
643130
+ const isTextCriterion = checkType.type === "elementText" || checkType.type === "elementAria";
643131
+ const matched = isTextCriterion ? matchesText(actualValue, checkType.value) : matchesPattern(actualValue, checkType.value);
643132
+ if (!actualValue || !matched) {
642897
643133
  allChecksPassed = false;
642898
643134
  break;
642899
643135
  }
@@ -643777,6 +644013,7 @@ function containsOperators(expression, allowOperators = false) {
643777
644013
  }
643778
644014
  return false;
643779
644015
  }
644016
+ var compiledEvaluatorCache = /* @__PURE__ */ new Map();
643780
644017
  async function evaluateExpression(expression, context) {
643781
644018
  try {
643782
644019
  expression = preprocessExpression(expression);
@@ -643850,7 +644087,13 @@ async function evaluateExpression(expression, context) {
643850
644087
  }
643851
644088
  })
643852
644089
  };
643853
- const evaluator = new Function(...Object.keys(evalContext), `return ${expression};`);
644090
+ const argNames = Object.keys(evalContext);
644091
+ const cacheKey = `${argNames.join(",")}\0${expression}`;
644092
+ let evaluator = compiledEvaluatorCache.get(cacheKey);
644093
+ if (!evaluator) {
644094
+ evaluator = new Function(...argNames, `return ${expression};`);
644095
+ compiledEvaluatorCache.set(cacheKey, evaluator);
644096
+ }
643854
644097
  return evaluator(...Object.values(evalContext));
643855
644098
  } catch (error) {
643856
644099
  log(`Error evaluating expression '${expression}': ${error.message}`, "error");
@@ -648672,6 +648915,10 @@ function mobileBrowserConfigError(browser) {
648672
648915
  }
648673
648916
  return null;
648674
648917
  }
648918
+ var CHROMEDRIVER_AUTODOWNLOAD_ARGS = [
648919
+ "--allow-insecure",
648920
+ "uiautomator2:chromedriver_autodownload"
648921
+ ];
648675
648922
  function mobileBrowserGate({ platform, browser, hasBrowserStep, hasAppStep }) {
648676
648923
  if (!hasBrowserStep)
648677
648924
  return { action: "proceed", browserName: null };
@@ -648732,6 +648979,216 @@ function buildMobileBrowserCapabilities({ platform, udid, cacheDir, timeout, loc
648732
648979
  return capabilities;
648733
648980
  }
648734
648981
 
648982
+ // dist/core/warmPhase.js
648983
+ init_utils();
648984
+ var WARM_POOL_LIMIT = 4;
648985
+ var RUNTIME_INSTALL_RESOURCE = "runtime-install";
648986
+ function deviceIdentity(platform, desc) {
648987
+ if (desc?.name)
648988
+ return `${platform}:name:${desc.name}`;
648989
+ return `${platform}:default:${desc?.deviceType ?? "<any>"}:${desc?.osVersion ?? "<latest>"}`;
648990
+ }
648991
+ function deviceResourceTag(platform, desc) {
648992
+ return `warm-device:${deviceIdentity(platform, desc)}`;
648993
+ }
648994
+ function wrapInitiationEffects(deps, keys, signal) {
648995
+ const wrapped = { ...deps };
648996
+ for (const key of keys) {
648997
+ const original = deps[key];
648998
+ if (typeof original !== "function")
648999
+ continue;
649000
+ wrapped[key] = (...args) => {
649001
+ const result = original(...args);
649002
+ signal();
649003
+ return result;
649004
+ };
649005
+ }
649006
+ return wrapped;
649007
+ }
649008
+ function planWarmTasks({ sizingJobs, runnerDetails, limit, hasAppiumPool, deps }) {
649009
+ const hostPlatform = runnerDetails?.environment?.platform;
649010
+ const tasks = [];
649011
+ const seen = /* @__PURE__ */ new Set();
649012
+ const addTask = (task) => {
649013
+ if (seen.has(task.name))
649014
+ return;
649015
+ seen.add(task.name);
649016
+ tasks.push(task);
649017
+ };
649018
+ let probeEligible = false;
649019
+ let defaultBrowser;
649020
+ const getDefaultBrowser2 = () => defaultBrowser ??= deps.getDefaultBrowser({ runnerDetails });
649021
+ const bootPlannedFor = /* @__PURE__ */ new Set();
649022
+ for (const job of sizingJobs ?? []) {
649023
+ const context = job?.context;
649024
+ if (!context)
649025
+ continue;
649026
+ if (deps.contextRequirementsSkipMessage({ context }))
649027
+ continue;
649028
+ const effPlatform = context.platform || hostPlatform;
649029
+ const mobileTarget = deps.isMobileTargetPlatform(effPlatform);
649030
+ if (mobileTarget) {
649031
+ if (mobileTarget === "ios" && hostPlatform !== "mac")
649032
+ continue;
649033
+ const hasAppStep = deps.isAppDriverRequired({ test: context });
649034
+ const gate = deps.mobileBrowserGate({
649035
+ platform: mobileTarget,
649036
+ browser: context.browser,
649037
+ hasBrowserStep: deps.isBrowserRequired({ test: context }),
649038
+ hasAppStep
649039
+ });
649040
+ if (gate.action !== "proceed")
649041
+ continue;
649042
+ const isMobileWeb = typeof gate.browserName === "string";
649043
+ if (!isMobileWeb && !hasAppStep)
649044
+ continue;
649045
+ const driverPackage = deps.appDriverPlatforms[mobileTarget]?.driverPackage;
649046
+ if (driverPackage) {
649047
+ addTask({
649048
+ name: `driver-install:${driverPackage}`,
649049
+ kind: "driver-install",
649050
+ exclusiveResources: [RUNTIME_INSTALL_RESOURCE],
649051
+ payload: { driverPackage, platform: mobileTarget }
649052
+ });
649053
+ }
649054
+ for (const stepDevice of deps.collectDeviceDescriptors(context)) {
649055
+ const desc = deps.normalizeDeviceDescriptor({
649056
+ contextDevice: context.device,
649057
+ stepDevice,
649058
+ platform: mobileTarget
649059
+ });
649060
+ const identity = deviceIdentity(mobileTarget, desc);
649061
+ const tag = deviceResourceTag(mobileTarget, desc);
649062
+ if (!bootPlannedFor.has(mobileTarget)) {
649063
+ bootPlannedFor.add(mobileTarget);
649064
+ addTask({
649065
+ name: `device-boot:${identity}`,
649066
+ kind: "device-boot",
649067
+ exclusiveResources: [tag],
649068
+ payload: { platform: mobileTarget, desc }
649069
+ });
649070
+ }
649071
+ if (mobileTarget === "android" && isMobileWeb) {
649072
+ addTask({
649073
+ name: `chromedriver-prefetch:${identity}`,
649074
+ kind: "chromedriver-prefetch",
649075
+ exclusiveResources: [tag],
649076
+ payload: { platform: mobileTarget, desc }
649077
+ });
649078
+ }
649079
+ }
649080
+ if (mobileTarget === "ios") {
649081
+ addTask({
649082
+ name: "wda-check",
649083
+ kind: "wda-check",
649084
+ // Read-only (plus the last-used stamp) — contends with nothing.
649085
+ exclusiveResources: [],
649086
+ payload: {}
649087
+ });
649088
+ }
649089
+ continue;
649090
+ }
649091
+ if (deps.isBrowserRequired({ test: context })) {
649092
+ const effBrowser = context.browser ?? getDefaultBrowser2();
649093
+ const browserName = effBrowser?.name;
649094
+ if (browserName && effPlatform === hostPlatform) {
649095
+ probeEligible = true;
649096
+ if (deps.requiredBrowserAssets(browserName).length > 0) {
649097
+ addTask({
649098
+ name: `browser-install:${browserName.toLowerCase()}`,
649099
+ kind: "browser-install",
649100
+ exclusiveResources: [RUNTIME_INSTALL_RESOURCE],
649101
+ payload: { browserName }
649102
+ });
649103
+ }
649104
+ }
649105
+ }
649106
+ if ((effPlatform === "windows" || effPlatform === "mac") && effPlatform === hostPlatform && deps.isAppDriverRequired({ test: context })) {
649107
+ const driverPackage = deps.appDriverPlatforms[effPlatform]?.driverPackage;
649108
+ if (driverPackage) {
649109
+ addTask({
649110
+ name: `driver-install:${driverPackage}`,
649111
+ kind: "driver-install",
649112
+ exclusiveResources: [RUNTIME_INSTALL_RESOURCE],
649113
+ payload: { driverPackage, platform: effPlatform }
649114
+ });
649115
+ }
649116
+ }
649117
+ }
649118
+ if (limit > 1 && hasAppiumPool && probeEligible) {
649119
+ addTask({
649120
+ name: "session-probe",
649121
+ kind: "session-probe",
649122
+ // Its install half mutates the shared caches, exactly like the
649123
+ // dedicated install tasks (usually a memo hit by the time it runs).
649124
+ exclusiveResources: [RUNTIME_INSTALL_RESOURCE],
649125
+ payload: {}
649126
+ });
649127
+ }
649128
+ return tasks;
649129
+ }
649130
+ async function executeWarmTasks({ tasks, registry, runTask, log: log3, now = Date.now }) {
649131
+ const results = [];
649132
+ const start = now();
649133
+ await runResourceAware(tasks, WARM_POOL_LIMIT, registry, async (task) => {
649134
+ const taskStart = now();
649135
+ try {
649136
+ const { outcome, note } = await runTask(task);
649137
+ results.push({
649138
+ name: task.name,
649139
+ kind: task.kind,
649140
+ outcome,
649141
+ durationMs: now() - taskStart,
649142
+ ...note ? { note } : {}
649143
+ });
649144
+ if (outcome === "failed") {
649145
+ log3("warning", `Warm task '${task.name}' failed (continuing)${note ? `: ${note}` : "."}`);
649146
+ } else {
649147
+ log3("debug", `Warm task '${task.name}': ${outcome}${note ? ` (${note})` : ""}.`);
649148
+ }
649149
+ } catch (error) {
649150
+ const note = error?.message ?? String(error);
649151
+ results.push({
649152
+ name: task.name,
649153
+ kind: task.kind,
649154
+ outcome: "failed",
649155
+ durationMs: now() - taskStart,
649156
+ note
649157
+ });
649158
+ log3("warning", `Warm task '${task.name}' failed (continuing): ${note}`);
649159
+ }
649160
+ });
649161
+ return { durationMs: now() - start, tasks: results };
649162
+ }
649163
+ function raceBootInitiation({ startAcquire, onError }) {
649164
+ let initiatedResolve;
649165
+ const initiated = new Promise((resolve) => {
649166
+ initiatedResolve = resolve;
649167
+ });
649168
+ const acquired = (async () => {
649169
+ try {
649170
+ const result = await startAcquire(() => initiatedResolve());
649171
+ if (result && typeof result === "object" && "skip" in result) {
649172
+ return { outcome: "skipped", note: result.skip };
649173
+ }
649174
+ return { outcome: "warmed", note: "device ready" };
649175
+ } catch (error) {
649176
+ onError(error);
649177
+ return {
649178
+ outcome: "failed",
649179
+ note: error?.message ?? String(error)
649180
+ };
649181
+ }
649182
+ })();
649183
+ return Promise.race([
649184
+ initiated.then(() => ({
649185
+ outcome: "warmed",
649186
+ note: "boot initiated"
649187
+ })),
649188
+ acquired
649189
+ ]);
649190
+ }
649191
+
648735
649192
  // dist/core/tests.js
648736
649193
  init_cacheDir();
648737
649194
 
@@ -649001,6 +649458,13 @@ async function teardownSimulatorRegistry(registry, shutdown) {
649001
649458
  for (const entry of registry.values()) {
649002
649459
  if (!entry.bootedByUs)
649003
649460
  continue;
649461
+ if (entry.ready) {
649462
+ try {
649463
+ await entry.ready;
649464
+ } catch {
649465
+ continue;
649466
+ }
649467
+ }
649004
649468
  try {
649005
649469
  await shutdown(entry);
649006
649470
  } catch {
@@ -649537,6 +650001,20 @@ function combinationKey(context) {
649537
650001
  function warmUpDecision(prev) {
649538
650002
  return prev === "failed" ? "skip" : "attempt";
649539
650003
  }
650004
+ function buildWarmPlanDeps() {
650005
+ return {
650006
+ isBrowserRequired,
650007
+ isAppDriverRequired,
650008
+ isMobileTargetPlatform,
650009
+ getDefaultBrowser,
650010
+ requiredBrowserAssets,
650011
+ collectDeviceDescriptors,
650012
+ normalizeDeviceDescriptor,
650013
+ mobileBrowserGate,
650014
+ contextRequirementsSkipMessage,
650015
+ appDriverPlatforms: APP_DRIVER_PLATFORMS
650016
+ };
650017
+ }
649540
650018
  function getDriverCapabilities({ runnerDetails, name, options }) {
649541
650019
  let capabilities = {};
649542
650020
  let args = [];
@@ -650146,7 +650624,12 @@ async function runSpecs({ resolvedTests }) {
650146
650624
  skipped: 0
650147
650625
  }
650148
650626
  },
650149
- specs: []
650627
+ specs: [],
650628
+ // Inline warm phase results (docs/design/warm-phase.md). Present on the
650629
+ // skeleton so a run that plans nothing (or whose planning fails —
650630
+ // best-effort) still reports the structural empty block; the phase
650631
+ // overwrites it with real task results below.
650632
+ warm: { durationMs: 0, tasks: [] }
650150
650633
  };
650151
650634
  let limit = resolveConcurrentRunners(config);
650152
650635
  log(config, "info", "Running test specs.");
@@ -650301,6 +650784,7 @@ async function runSpecs({ resolvedTests }) {
650301
650784
  const serverCount = Math.min(limit, browserPoolJobCount);
650302
650785
  log(config, "debug", `Starting ${serverCount} Appium server(s).`);
650303
650786
  try {
650787
+ const readinessWaits = [];
650304
650788
  for (let i = 0; i < serverCount; i++) {
650305
650789
  let display;
650306
650790
  if (useXvfbDisplays) {
@@ -650308,7 +650792,16 @@ async function runSpecs({ resolvedTests }) {
650308
650792
  xvfbProcesses.push(await startXvfb(display));
650309
650793
  log(config, "debug", `Started Xvfb on ${display} for recording.`);
650310
650794
  }
650311
- appiumServers.push(await startAppiumServer(appiumEntry, config, display));
650795
+ const server = await spawnAppiumServer(appiumEntry, config, display);
650796
+ appiumServers.push(server);
650797
+ const wait2 = appiumIsReady(server.port);
650798
+ wait2.catch(() => {
650799
+ });
650800
+ readinessWaits.push(wait2);
650801
+ }
650802
+ await Promise.all(readinessWaits);
650803
+ for (const server of appiumServers) {
650804
+ log(config, "debug", `Appium is ready on port ${server.port}.`);
650312
650805
  }
650313
650806
  } catch (error) {
650314
650807
  await Promise.all(appiumServers.map((server) => {
@@ -650331,6 +650824,7 @@ async function runSpecs({ resolvedTests }) {
650331
650824
  const processRegistry = /* @__PURE__ */ new Map();
650332
650825
  const deviceRegistry = createDeviceRegistry();
650333
650826
  const simulatorRegistry = createSimulatorRegistry();
650827
+ const resourceRegistry = createResourceRegistry();
650334
650828
  const killAllRegistered = async () => {
650335
650829
  const entries = [...processRegistry.entries()];
650336
650830
  processRegistry.clear();
@@ -650367,15 +650861,35 @@ async function runSpecs({ resolvedTests }) {
650367
650861
  process.on("SIGINT", onSignal);
650368
650862
  process.on("SIGTERM", onSignal);
650369
650863
  try {
650370
- if (limit > 1 && appiumPool) {
650371
- await warmUpContexts({
650372
- jobs: sizingJobs,
650373
- config,
650864
+ try {
650865
+ const warmTasks = planWarmTasks({
650866
+ sizingJobs,
650374
650867
  runnerDetails,
650375
- appiumPool,
650376
- installAttempts,
650377
- warmUpResults
650868
+ limit,
650869
+ hasAppiumPool: !!appiumPool,
650870
+ deps: buildWarmPlanDeps()
650378
650871
  });
650872
+ if (warmTasks.length > 0) {
650873
+ log(config, "debug", `Warm phase: ${warmTasks.length} task(s).`);
650874
+ report.warm = await executeWarmTasks({
650875
+ tasks: warmTasks,
650876
+ registry: resourceRegistry,
650877
+ runTask: buildWarmTaskRunner({
650878
+ config,
650879
+ runnerDetails,
650880
+ sizingJobs,
650881
+ appiumPool,
650882
+ installAttempts,
650883
+ warmUpResults,
650884
+ deviceRegistry,
650885
+ simulatorRegistry,
650886
+ resourceRegistry
650887
+ }),
650888
+ log: (level, message) => log(config, level, message)
650889
+ });
650890
+ }
650891
+ } catch (error) {
650892
+ log(config, "warning", `Warm phase skipped (planning failed; the run proceeds with on-demand provisioning): ${error?.message ?? error}`);
650379
650893
  }
650380
650894
  const runJob = async (job) => {
650381
650895
  try {
@@ -650427,7 +650941,6 @@ async function runSpecs({ resolvedTests }) {
650427
650941
  const phase = PHASES.includes(entry.spec?._phase) ? entry.spec._phase : "main";
650428
650942
  routedByPhase[phase].push(entry);
650429
650943
  }
650430
- const resourceRegistry = createResourceRegistry();
650431
650944
  for (const phase of PHASES) {
650432
650945
  if (limit > 1) {
650433
650946
  await runResourceAware(jobsByPhase[phase], limit, resourceRegistry, runJob);
@@ -650741,27 +651254,17 @@ async function warmUpContexts({ jobs, config, runnerDetails, appiumPool, install
650741
651254
  platform
650742
651255
  });
650743
651256
  if (!supported && context.platform === platform && Array.isArray(context?.steps) && requiredBrowserAssets(context.browser?.name).length > 0) {
650744
- const firstAttempt = !installAttempts.has((context.browser?.name ?? "<none>").toLowerCase());
650745
- const outcome = await ensureContextBrowserInstalled({
651257
+ await warmBrowserInstall({
650746
651258
  browserName: context.browser?.name,
650747
651259
  config,
650748
- installAttempts,
650749
- deps: {
650750
- ensureBrowser: (asset, options) => ensureBrowserInstalled(asset, options),
650751
- log
650752
- },
650753
- // Repair a present-but-broken driver, not just install-if-missing.
650754
- repair: true
651260
+ runnerDetails,
651261
+ installAttempts
651262
+ });
651263
+ supported = isSupportedContext({
651264
+ context,
651265
+ apps: runnerDetails.availableApps,
651266
+ platform
650755
651267
  });
650756
- if (firstAttempt && (outcome === "installed" || outcome === "failed")) {
650757
- clearAppCache(config);
650758
- runnerDetails.availableApps = await getAvailableApps({ config });
650759
- supported = isSupportedContext({
650760
- context,
650761
- apps: runnerDetails.availableApps,
650762
- platform
650763
- });
650764
- }
650765
651268
  }
650766
651269
  if (!supported)
650767
651270
  continue;
@@ -650803,6 +651306,292 @@ async function warmUpContexts({ jobs, config, runnerDetails, appiumPool, install
650803
651306
  }
650804
651307
  }
650805
651308
  }
651309
+ async function warmBrowserInstall({ browserName, config, runnerDetails, installAttempts, deps = {} }) {
651310
+ const appName = normalizeBrowserName(browserName);
651311
+ if (runnerDetails.availableApps?.find((app) => app.name === appName)) {
651312
+ return {
651313
+ outcome: "skipped",
651314
+ note: `'${browserName}' is already available`
651315
+ };
651316
+ }
651317
+ const firstAttempt = !installAttempts.has((browserName ?? "<none>").toLowerCase());
651318
+ const outcome = await ensureContextBrowserInstalled({
651319
+ browserName,
651320
+ config,
651321
+ installAttempts,
651322
+ deps: {
651323
+ ensureBrowser: deps.ensureBrowser ?? ((asset, options) => ensureBrowserInstalled(asset, options)),
651324
+ log
651325
+ },
651326
+ // Repair a present-but-broken driver, not just install-if-missing.
651327
+ repair: true
651328
+ });
651329
+ if (firstAttempt && (outcome === "installed" || outcome === "failed")) {
651330
+ (deps.clearAppCache ?? clearAppCache)(config);
651331
+ runnerDetails.availableApps = await (deps.getAvailableApps ?? getAvailableApps)({ config });
651332
+ }
651333
+ if (outcome === "installed")
651334
+ return { outcome: "warmed" };
651335
+ if (outcome === "failed") {
651336
+ return { outcome: "failed", note: `couldn't install '${browserName}'` };
651337
+ }
651338
+ return {
651339
+ outcome: "skipped",
651340
+ note: `'${browserName}' has no installable assets`
651341
+ };
651342
+ }
651343
+ async function resolveAndroidWarmEnv(config) {
651344
+ try {
651345
+ const abi = hostAbi();
651346
+ const sdk = detectAndroidSdk({ cacheDir: config?.cacheDir });
651347
+ if (!sdk?.emulator)
651348
+ return null;
651349
+ const deviceDeps = buildAcquireDeviceDeps(sdk, abi, (m) => log(config, "debug", m));
651350
+ const running = await deviceDeps.listRunning();
651351
+ const capable = running.length > 0 || await checkEmulatorAcceleration(sdk.emulator);
651352
+ if (!capable)
651353
+ return null;
651354
+ return { sdkRoot: sdk.sdkRoot, deviceDeps };
651355
+ } catch {
651356
+ return null;
651357
+ }
651358
+ }
651359
+ async function probeIosToolchainWarm() {
651360
+ if (process.platform !== "darwin")
651361
+ return probeIosToolchain();
651362
+ const runAsync = (command, args, timeout) => new Promise((resolve) => {
651363
+ let stdout = "";
651364
+ let stderr = "";
651365
+ try {
651366
+ const child = (0, import_node_child_process15.spawn)(command, args, { windowsHide: true, timeout });
651367
+ child.stdout?.on("data", (d) => stdout += String(d));
651368
+ child.stderr?.on("data", (d) => stderr += String(d));
651369
+ child.on("error", () => resolve({ status: null, stdout, stderr }));
651370
+ child.on("close", (status) => resolve({ status, stdout, stderr }));
651371
+ } catch {
651372
+ resolve({ status: null, stdout, stderr });
651373
+ }
651374
+ });
651375
+ const [xcodeSelect, simctl] = await Promise.all([
651376
+ runAsync("xcode-select", ["-p"], 15e3),
651377
+ // Same generous ceiling as the sync probe's xcrun spawn: the first cold
651378
+ // simctl call launches CoreSimulatorService.
651379
+ runAsync("xcrun", ["simctl", "list", "devices", "available"], 12e4)
651380
+ ]);
651381
+ return probeIosToolchain({
651382
+ run: (command) => command === "xcrun" ? simctl : xcodeSelect
651383
+ });
651384
+ }
651385
+ function buildWarmTaskRunner({ config, runnerDetails, sizingJobs, appiumPool, installAttempts, warmUpResults, deviceRegistry, simulatorRegistry, resourceRegistry }) {
651386
+ let androidEnv;
651387
+ const getAndroidEnv = () => androidEnv ??= resolveAndroidWarmEnv(config);
651388
+ let iosToolchain;
651389
+ const getIosToolchain = () => iosToolchain ??= probeIosToolchainWarm();
651390
+ const acquireLease = async (names) => {
651391
+ while (!resourceRegistry.tryAcquire(names)) {
651392
+ await resourceRegistry.waitForFree();
651393
+ }
651394
+ let released = false;
651395
+ return () => {
651396
+ if (released)
651397
+ return;
651398
+ released = true;
651399
+ resourceRegistry.release(names);
651400
+ };
651401
+ };
651402
+ const withRuntimeInstallLock = async (fn) => {
651403
+ const release = await acquireLease([RUNTIME_INSTALL_RESOURCE]);
651404
+ try {
651405
+ return await fn();
651406
+ } finally {
651407
+ release();
651408
+ }
651409
+ };
651410
+ let androidPreflight;
651411
+ const getAndroidPreflight = () => androidPreflight ??= withRuntimeInstallLock(() => appSurfacePreflight({ config, platform: "android" }));
651412
+ return async (task) => {
651413
+ switch (task.kind) {
651414
+ case "browser-install":
651415
+ return warmBrowserInstall({
651416
+ browserName: task.payload.browserName,
651417
+ config,
651418
+ runnerDetails,
651419
+ installAttempts
651420
+ });
651421
+ case "driver-install": {
651422
+ if (task.payload.platform === "android") {
651423
+ const env = await getAndroidEnv();
651424
+ if (!env) {
651425
+ return {
651426
+ outcome: "skipped",
651427
+ note: "Android toolchain not ready; the driver install stays with the consuming context"
651428
+ };
651429
+ }
651430
+ }
651431
+ if (task.payload.platform === "ios") {
651432
+ const toolchain = await getIosToolchain();
651433
+ if (!toolchain.ok) {
651434
+ return { outcome: "skipped", note: toolchain.reason };
651435
+ }
651436
+ }
651437
+ const { ensureRuntimeInstalled: ensureRuntimeInstalled2 } = await Promise.resolve().then(() => (init_loader(), loader_exports));
651438
+ await ensureRuntimeInstalled2([task.payload.driverPackage], {
651439
+ ctx: { cacheDir: config?.cacheDir },
651440
+ deps: { logger: (m) => log(config, "debug", m) }
651441
+ });
651442
+ return { outcome: "warmed" };
651443
+ }
651444
+ case "device-boot": {
651445
+ const desc = task.payload.desc;
651446
+ const onError = (error) => log(config, "warning", `Warm boot of '${task.name}' failed (a consuming context will retry): ${error?.message ?? String(error)}`);
651447
+ if (task.payload.platform === "android") {
651448
+ const env = await getAndroidEnv();
651449
+ if (!env) {
651450
+ return {
651451
+ outcome: "skipped",
651452
+ note: "Android toolchain not ready; device setup stays with the consuming context"
651453
+ };
651454
+ }
651455
+ const releaseEmulatorLease = await acquireLease([
651456
+ "android-emulator"
651457
+ ]);
651458
+ return raceBootInitiation({
651459
+ onError,
651460
+ startAcquire: (signalInitiated) => {
651461
+ const acquiring = acquireDevice({
651462
+ desc,
651463
+ registry: deviceRegistry,
651464
+ sdkRoot: env.sdkRoot,
651465
+ deps: wrapInitiationEffects(env.deviceDeps, ["createAvd", "boot"], signalInitiated)
651466
+ });
651467
+ acquiring.then(() => releaseEmulatorLease(), () => releaseEmulatorLease());
651468
+ return acquiring;
651469
+ }
651470
+ });
651471
+ }
651472
+ const toolchain = await getIosToolchain();
651473
+ if (!toolchain.ok) {
651474
+ return { outcome: "skipped", note: toolchain.reason };
651475
+ }
651476
+ const simDeps = buildAcquireSimulatorDeps((m) => log(config, "debug", m));
651477
+ return raceBootInitiation({
651478
+ onError,
651479
+ startAcquire: (signalInitiated) => acquireSimulator({
651480
+ desc,
651481
+ registry: simulatorRegistry,
651482
+ deps: wrapInitiationEffects(simDeps, ["create", "boot"], signalInitiated)
651483
+ })
651484
+ });
651485
+ }
651486
+ case "wda-check": {
651487
+ const hit = locateManagedWda({ ctx: { cacheDir: config?.cacheDir } });
651488
+ if (hit) {
651489
+ return {
651490
+ outcome: "warmed",
651491
+ note: `prebuilt WebDriverAgent available (${hit.key})`
651492
+ };
651493
+ }
651494
+ return {
651495
+ outcome: "skipped",
651496
+ note: "no prebuilt WebDriverAgent for the current toolchain \u2014 `doc-detective install ios` prebuilds it"
651497
+ };
651498
+ }
651499
+ case "session-probe": {
651500
+ if (!appiumPool) {
651501
+ return { outcome: "skipped", note: "no browser Appium pool" };
651502
+ }
651503
+ await warmUpContexts({
651504
+ jobs: sizingJobs,
651505
+ config,
651506
+ runnerDetails,
651507
+ appiumPool,
651508
+ installAttempts,
651509
+ warmUpResults
651510
+ });
651511
+ const ok = [...warmUpResults.values()].filter((v) => v === "ok").length;
651512
+ const failed = warmUpResults.size - ok;
651513
+ return {
651514
+ outcome: "warmed",
651515
+ note: `${ok} combination${ok === 1 ? "" : "s"} ok${failed ? `, ${failed} failed` : ""}`
651516
+ };
651517
+ }
651518
+ case "chromedriver-prefetch":
651519
+ return prefetchMobileChromedriver({
651520
+ config,
651521
+ desc: task.payload.desc,
651522
+ deviceRegistry,
651523
+ getAndroidEnv,
651524
+ deps: {
651525
+ // The cache-mutating half (driver install + Appium co-homing)
651526
+ // runs once per run under the manual runtime-install lease, so
651527
+ // the prefetch task itself only holds its device tag while it
651528
+ // awaits readiness and runs the throwaway session.
651529
+ appSurfacePreflight: () => getAndroidPreflight(),
651530
+ acquireEmulatorLease: () => acquireLease(["android-emulator"])
651531
+ }
651532
+ });
651533
+ }
651534
+ };
651535
+ }
651536
+ async function prefetchMobileChromedriver({ config, desc, deviceRegistry, getAndroidEnv, deps = {} }) {
651537
+ const preflight = deps.appSurfacePreflight ?? appSurfacePreflight;
651538
+ const acquire = deps.acquireDevice ?? acquireDevice;
651539
+ const startServer = deps.startAppiumServer ?? startAppiumServer;
651540
+ const startDriver = deps.driverStart ?? driverStart;
651541
+ const kill4 = deps.killTree ?? killTree;
651542
+ const env = await getAndroidEnv();
651543
+ if (!env) {
651544
+ return {
651545
+ outcome: "skipped",
651546
+ note: "Android toolchain not ready; the first mobile-web session downloads chromedriver as needed"
651547
+ };
651548
+ }
651549
+ const pre = await preflight({ config, platform: "android" });
651550
+ if (!pre.ok)
651551
+ return { outcome: "skipped", note: pre.reason };
651552
+ const releaseLease = deps.acquireEmulatorLease ? await deps.acquireEmulatorLease() : void 0;
651553
+ let acquired;
651554
+ try {
651555
+ acquired = await acquire({
651556
+ desc,
651557
+ registry: deviceRegistry,
651558
+ sdkRoot: env.sdkRoot,
651559
+ deps: env.deviceDeps
651560
+ });
651561
+ } finally {
651562
+ releaseLease?.();
651563
+ }
651564
+ if ("skip" in acquired)
651565
+ return { outcome: "skipped", note: acquired.skip };
651566
+ let server;
651567
+ let driver;
651568
+ try {
651569
+ server = await startServer(pre.appiumEntry, config, void 0, {
651570
+ APPIUM_HOME: pre.appiumHome,
651571
+ ANDROID_HOME: env.sdkRoot,
651572
+ ANDROID_SDK_ROOT: env.sdkRoot
651573
+ }, CHROMEDRIVER_AUTODOWNLOAD_ARGS);
651574
+ driver = await startDriver(buildMobileBrowserCapabilities({
651575
+ platform: "android",
651576
+ udid: acquired.entry.udid,
651577
+ cacheDir: getCacheDir({ cacheDir: config?.cacheDir })
651578
+ }), server.port, 2, { cacheDir: config?.cacheDir });
651579
+ return {
651580
+ outcome: "warmed",
651581
+ note: `chromedriver ready for device '${acquired.entry.name}'`
651582
+ };
651583
+ } finally {
651584
+ if (driver) {
651585
+ try {
651586
+ await driver.deleteSession();
651587
+ } catch {
651588
+ }
651589
+ }
651590
+ if (server) {
651591
+ await kill4(server.process?.pid);
651592
+ }
651593
+ }
651594
+ }
650806
651595
  function specIsRouted(spec) {
650807
651596
  if (!spec || !Array.isArray(spec.tests))
650808
651597
  return false;
@@ -651150,7 +651939,8 @@ async function runContext({ config, spec, test, context, runnerDetails, appiumPo
651150
651939
  contextReport.resultDescription = errorMessage;
651151
651940
  return contextReport;
651152
651941
  }
651153
- clog("debug", `CONTEXT:
651942
+ if (logLevelEnabled(config, "debug"))
651943
+ clog("debug", `CONTEXT:
651154
651944
  ${JSON.stringify(context, null, 2)}`);
651155
651945
  let driver;
651156
651946
  let appiumPort;
@@ -651169,7 +651959,7 @@ ${JSON.stringify(context, null, 2)}`);
651169
651959
  env.ANDROID_HOME = appSession.androidSdkRoot;
651170
651960
  env.ANDROID_SDK_ROOT = appSession.androidSdkRoot;
651171
651961
  }
651172
- const extraArgs = mobileTarget === "android" ? ["--allow-insecure", "uiautomator2:chromedriver_autodownload"] : [];
651962
+ const extraArgs = mobileTarget === "android" ? CHROMEDRIVER_AUTODOWNLOAD_ARGS : [];
651173
651963
  let acquired;
651174
651964
  try {
651175
651965
  const server = await startAppiumServer(appSession.appiumEntry, config, void 0, env, extraArgs);
@@ -651462,7 +652252,8 @@ ${JSON.stringify(context, null, 2)}`);
651462
652252
  stepExecutionFailed = true;
651463
652253
  break;
651464
652254
  }
651465
- clog("debug", `STEP:
652255
+ if (logLevelEnabled(config, "debug"))
652256
+ clog("debug", `STEP:
651466
652257
  ${JSON.stringify(step, null, 2)}`);
651467
652258
  if (step.unsafe && runnerDetails.allowUnsafeSteps === false) {
651468
652259
  clog("warning", `Skipping unsafe step: ${step.description} in test ${test.testId} context ${context.contextId}`);
@@ -651528,7 +652319,8 @@ ${JSON.stringify(step, null, 2)}`);
651528
652319
  processRegistry,
651529
652320
  appSession
651530
652321
  });
651531
- clog("debug", `RESULT: ${r.status}
652322
+ if (logLevelEnabled(config, "debug"))
652323
+ clog("debug", `RESULT: ${r.status}
651532
652324
  ${JSON.stringify(r, null, 2)}`);
651533
652325
  r.result = r.status;
651534
652326
  r.resultDescription = r.description;
@@ -651925,7 +652717,7 @@ async function runStep({ config = {}, context = {}, step, driver, metaValues = {
651925
652717
  }
651926
652718
  return actionResult;
651927
652719
  }
651928
- async function startAppiumServer(appiumEntry, config, display, extraEnv, extraArgs) {
652720
+ async function spawnAppiumServer(appiumEntry, config, display, extraEnv, extraArgs) {
651929
652721
  const port = await findFreePort();
651930
652722
  log(config, "debug", `Starting Appium on port ${port}`);
651931
652723
  const env = display || extraEnv ? {
@@ -651945,31 +652737,41 @@ async function startAppiumServer(appiumEntry, config, display, extraEnv, extraAr
651945
652737
  });
651946
652738
  proc.stderr.on("data", () => {
651947
652739
  });
652740
+ return { port, process: proc, display };
652741
+ }
652742
+ async function startAppiumServer(appiumEntry, config, display, extraEnv, extraArgs) {
652743
+ const server = await spawnAppiumServer(appiumEntry, config, display, extraEnv, extraArgs);
651948
652744
  try {
651949
- await appiumIsReady(port);
652745
+ await appiumIsReady(server.port);
651950
652746
  } catch (error) {
651951
- await killTree(proc?.pid);
652747
+ await killTree(server.process?.pid);
651952
652748
  throw error;
651953
652749
  }
651954
- log(config, "debug", `Appium is ready on port ${port}.`);
651955
- return { port, process: proc, display };
652750
+ log(config, "debug", `Appium is ready on port ${server.port}.`);
652751
+ return server;
651956
652752
  }
651957
- async function appiumIsReady(port, timeoutMs = 12e4) {
651958
- let isReady = false;
652753
+ var STATUS_PROBE_TIMEOUT_MS = 1e4;
652754
+ async function appiumIsReady(port, timeoutMs = 12e4, deps = {}) {
652755
+ const probe = deps.probe ?? (async (p) => {
652756
+ try {
652757
+ const resp = await import_axios6.default.get(`http://127.0.0.1:${p}/status`, {
652758
+ timeout: STATUS_PROBE_TIMEOUT_MS
652759
+ });
652760
+ return resp.status === 200;
652761
+ } catch {
652762
+ return false;
652763
+ }
652764
+ });
652765
+ const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
651959
652766
  const start = Date.now();
651960
- while (!isReady) {
652767
+ while (true) {
652768
+ if (await probe(port))
652769
+ return true;
651961
652770
  if (Date.now() - start > timeoutMs) {
651962
652771
  throw new Error(`Appium server on port ${port} failed to start within ${timeoutMs / 1e3} seconds`);
651963
652772
  }
651964
- await new Promise((resolve) => setTimeout(resolve, 1e3));
651965
- try {
651966
- let resp = await import_axios6.default.get(`http://127.0.0.1:${port}/status`);
651967
- if (resp.status === 200)
651968
- isReady = true;
651969
- } catch {
651970
- }
652773
+ await sleep4(250);
651971
652774
  }
651972
- return isReady;
651973
652775
  }
651974
652776
  async function driverStart(capabilities, port, maxAttempts = 4, ctx = {}) {
651975
652777
  const wdio = await loadHeavyDep("webdriverio", { ctx });
@@ -652159,6 +652961,8 @@ function telemetryNotice(config) {
652159
652961
  log(config, "info", "Doc Detective collects basic anonymous telemetry to understand product issues and usage. To disable telemetry, set 'telemetry.send' to 'false' in your .doc-detective.json config file.");
652160
652962
  }
652161
652963
  }
652964
+ var TELEMETRY_FLUSH_TIMEOUT_MS = 2e3;
652965
+ var pendingTelemetryFlush = null;
652162
652966
  function sendTelemetry(config, command, results) {
652163
652967
  if (config?.telemetry?.send === false)
652164
652968
  return;
@@ -652199,7 +653003,7 @@ function sendTelemetry(config, command, results) {
652199
653003
  const event = { distinctId, event: command, properties: telemetryData };
652200
653004
  const client = new import_posthog_node.PostHog("phc_rjV0MH3nsAd45zFISLgaKAdAXbgDeXt2mOBV2EBHomB", { host: "https://app.posthog.com" });
652201
653005
  client.capture(event);
652202
- client.shutdown();
653006
+ pendingTelemetryFlush = Promise.resolve(client.shutdown(TELEMETRY_FLUSH_TIMEOUT_MS)).then(() => void 0).catch(() => void 0);
652203
653007
  }
652204
653008
 
652205
653009
  // dist/core/index.js
@@ -652243,6 +653047,9 @@ async function runTests(config, options = {}) {
652243
653047
  return null;
652244
653048
  }
652245
653049
  }
653050
+ if (typeof options.updateJoin === "function") {
653051
+ await options.updateJoin();
653052
+ }
652246
653053
  if (config.dryRun) {
652247
653054
  console.log(JSON.stringify(resolvedTests, null, 2));
652248
653055
  cleanTemp();
@@ -652274,23 +653081,43 @@ async function runTests(config, options = {}) {
652274
653081
  }
652275
653082
  if (needs.browsers.size > 0) {
652276
653083
  try {
652277
- const { getAvailableApps: getAvailableApps2, clearAppCache: clearAppCache2 } = await Promise.resolve().then(() => (init_config(), config_exports));
653084
+ const { getAvailableApps: getAvailableApps2, patchAppCache: patchAppCache2 } = await Promise.resolve().then(() => (init_config(), config_exports));
652278
653085
  const { ensureBrowserInstalled: ensureBrowserInstalled2, requiredBrowserAssets: requiredBrowserAssets2 } = await Promise.resolve().then(() => (init_browsers(), browsers_exports));
652279
653086
  const available = await getAvailableApps2({ config });
652280
653087
  const availableNames = new Set(available.map((a) => a.name));
652281
- let installedAnything = false;
653088
+ const installedDescriptors = [];
652282
653089
  for (const browser of needs.browsers) {
652283
653090
  if (availableNames.has(browser))
652284
653091
  continue;
652285
653092
  const assets = requiredBrowserAssets2(browser);
653093
+ const assetResults = {};
652286
653094
  for (const asset of assets) {
652287
- await ensureBrowserInstalled2(asset, { ctx, deps: { logger: preflightLogger } });
653095
+ assetResults[asset] = await ensureBrowserInstalled2(asset, {
653096
+ ctx,
653097
+ deps: { logger: preflightLogger }
653098
+ });
653099
+ }
653100
+ if (assets.length === 0)
653101
+ continue;
653102
+ if (browser === "chrome") {
653103
+ installedDescriptors.push({
653104
+ name: "chrome",
653105
+ version: assetResults.chrome?.version,
653106
+ path: assetResults.chrome?.path,
653107
+ driverPath: assetResults.chromedriver?.path
653108
+ });
653109
+ } else if (browser === "firefox") {
653110
+ installedDescriptors.push({
653111
+ name: "firefox",
653112
+ version: assetResults.firefox?.version,
653113
+ path: assetResults.firefox?.path,
653114
+ driverPath: assetResults.geckodriver?.path
653115
+ });
652288
653116
  }
652289
- if (assets.length > 0)
652290
- installedAnything = true;
652291
653117
  }
652292
- if (installedAnything)
652293
- clearAppCache2(config);
653118
+ if (installedDescriptors.length > 0) {
653119
+ await patchAppCache2(config, installedDescriptors);
653120
+ }
652294
653121
  } catch (browserErr) {
652295
653122
  log(config, "debug", `Browser pre-flight check skipped: ${browserErr?.message ?? browserErr}`);
652296
653123
  }
@@ -652317,8 +653144,8 @@ async function runTests(config, options = {}) {
652317
653144
  } else {
652318
653145
  results = await runSpecs({ resolvedTests });
652319
653146
  }
652320
- log(config, "info", "RESULTS:");
652321
- log(config, "info", results);
653147
+ log(config, "debug", "RESULTS:");
653148
+ log(config, "debug", results);
652322
653149
  log(config, "info", "Cleaning up and finishing post-processing.");
652323
653150
  cleanTemp();
652324
653151
  sendTelemetry(config, "runTests", results);