@jsenv/test 3.2.1 → 3.3.2

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.
@@ -720,10 +720,10 @@ function createSupportsColor(stream, options = {}) {
720
720
  return translateLevel(level);
721
721
  }
722
722
 
723
- const supportsColor = {
723
+ ({
724
724
  stdout: createSupportsColor({isTTY: tty.isatty(1)}),
725
725
  stderr: createSupportsColor({isTTY: tty.isatty(2)}),
726
- };
726
+ });
727
727
 
728
728
  // https://github.com/Marak/colors.js/blob/master/lib/styles.js
729
729
  // https://stackoverflow.com/a/75985833/2634179
@@ -763,6 +763,7 @@ const processSupportsBasicColor = createSupportsColor(process.stdout).hasBasic;
763
763
 
764
764
  const ANSI = createAnsi({
765
765
  supported:
766
+ process.env.FORCE_COLOR === "1" ||
766
767
  processSupportsBasicColor ||
767
768
  // GitHub workflow does support ANSI but "supports-color" returns false
768
769
  // because stream.isTTY returns false, see https://github.com/actions/runner/issues/241
@@ -810,6 +811,9 @@ const createUnicode = ({ supported, ANSI }) => {
810
811
  get CIRCLE_CROSS_RAW() {
811
812
  return UNICODE.supported ? `ⓧ` : `(×)`;
812
813
  },
814
+ get CIRCLE_DOTTED_RAW() {
815
+ return UNICODE.supported ? `◌` : `*`;
816
+ },
813
817
  get COMMAND() {
814
818
  return ANSI.color(UNICODE.COMMAND_RAW, ANSI.GREY); // ANSI_MAGENTA)
815
819
  },
@@ -839,7 +843,7 @@ const createUnicode = ({ supported, ANSI }) => {
839
843
  };
840
844
 
841
845
  const UNICODE = createUnicode({
842
- supported: isUnicodeSupported() || process.env.FORCE_UNICODE === "1",
846
+ supported: process.env.FORCE_UNICODE === "1" || isUnicodeSupported(),
843
847
  ANSI,
844
848
  });
845
849
 
@@ -4565,6 +4569,57 @@ const normalizeRuntimeError = (runtimeError) => {
4565
4569
  return errorProxy;
4566
4570
  };
4567
4571
 
4572
+ // `Error: yo
4573
+ // at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
4574
+ // at doExec (http://127.0.0.1:3000/src/__test__/file-throw.js:452:38)
4575
+ // at postOrderExec (http://127.0.0.1:3000/src/__test__/file-throw.js:448:16)
4576
+ // at http://127.0.0.1:3000/src/__test__/file-throw.js:399:18`.replace(/(?:https?|ftp|file):\/\/(.*+)$/gm, (...args) => {
4577
+ // debugger
4578
+ // })
4579
+ const replaceUrls = (source, replace) => {
4580
+ return source.replace(/(?:https?|ftp|file):\/\/\S+/g, (match) => {
4581
+ let replacement = "";
4582
+ const lastChar = match[match.length - 1];
4583
+
4584
+ // hotfix because our url regex sucks a bit
4585
+ const endsWithSeparationChar = lastChar === ")" || lastChar === ":";
4586
+ if (endsWithSeparationChar) {
4587
+ match = match.slice(0, -1);
4588
+ }
4589
+
4590
+ const lineAndColumnPattern = /:([0-9]+):([0-9]+)$/;
4591
+ const lineAndColumMatch = match.match(lineAndColumnPattern);
4592
+ if (lineAndColumMatch) {
4593
+ const lineAndColumnString = lineAndColumMatch[0];
4594
+ const lineString = lineAndColumMatch[1];
4595
+ const columnString = lineAndColumMatch[2];
4596
+ replacement = replace({
4597
+ url: match.slice(0, -lineAndColumnString.length),
4598
+ line: lineString ? parseInt(lineString) : null,
4599
+ column: columnString ? parseInt(columnString) : null,
4600
+ });
4601
+ } else {
4602
+ const linePattern = /:([0-9]+)$/;
4603
+ const lineMatch = match.match(linePattern);
4604
+ if (lineMatch) {
4605
+ const lineString = lineMatch[0];
4606
+ replacement = replace({
4607
+ url: match.slice(0, -lineString.length),
4608
+ line: lineString ? parseInt(lineString) : null,
4609
+ });
4610
+ } else {
4611
+ replacement = replace({
4612
+ url: match,
4613
+ });
4614
+ }
4615
+ }
4616
+ if (endsWithSeparationChar) {
4617
+ return `${replacement}${lastChar}`;
4618
+ }
4619
+ return replacement;
4620
+ });
4621
+ };
4622
+
4568
4623
  const formatErrorForTerminal = (
4569
4624
  error,
4570
4625
  { rootDirectoryUrl, mainFileRelativeUrl, mockFluctuatingValues, tryColors },
@@ -4697,57 +4752,6 @@ const formatErrorForTerminal = (
4697
4752
  return text;
4698
4753
  };
4699
4754
 
4700
- // `Error: yo
4701
- // at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
4702
- // at doExec (http://127.0.0.1:3000/src/__test__/file-throw.js:452:38)
4703
- // at postOrderExec (http://127.0.0.1:3000/src/__test__/file-throw.js:448:16)
4704
- // at http://127.0.0.1:3000/src/__test__/file-throw.js:399:18`.replace(/(?:https?|ftp|file):\/\/(.*+)$/gm, (...args) => {
4705
- // debugger
4706
- // })
4707
- const replaceUrls = (source, replace) => {
4708
- return source.replace(/(?:https?|ftp|file):\/\/\S+/g, (match) => {
4709
- let replacement = "";
4710
- const lastChar = match[match.length - 1];
4711
-
4712
- // hotfix because our url regex sucks a bit
4713
- const endsWithSeparationChar = lastChar === ")" || lastChar === ":";
4714
- if (endsWithSeparationChar) {
4715
- match = match.slice(0, -1);
4716
- }
4717
-
4718
- const lineAndColumnPattern = /:([0-9]+):([0-9]+)$/;
4719
- const lineAndColumMatch = match.match(lineAndColumnPattern);
4720
- if (lineAndColumMatch) {
4721
- const lineAndColumnString = lineAndColumMatch[0];
4722
- const lineString = lineAndColumMatch[1];
4723
- const columnString = lineAndColumMatch[2];
4724
- replacement = replace({
4725
- url: match.slice(0, -lineAndColumnString.length),
4726
- line: lineString ? parseInt(lineString) : null,
4727
- column: columnString ? parseInt(columnString) : null,
4728
- });
4729
- } else {
4730
- const linePattern = /:([0-9]+)$/;
4731
- const lineMatch = match.match(linePattern);
4732
- if (lineMatch) {
4733
- const lineString = lineMatch[0];
4734
- replacement = replace({
4735
- url: match.slice(0, -lineString.length),
4736
- line: lineString ? parseInt(lineString) : null,
4737
- });
4738
- } else {
4739
- replacement = replace({
4740
- url: match,
4741
- });
4742
- }
4743
- }
4744
- if (endsWithSeparationChar) {
4745
- return `${replacement}${lastChar}`;
4746
- }
4747
- return replacement;
4748
- });
4749
- };
4750
-
4751
4755
  /*
4752
4756
  * label
4753
4757
  * ┌───────────────────────┴────────────────────────────────┐
@@ -4822,9 +4826,15 @@ const reporterList = ({
4822
4826
  Object.keys(testPlanResult.results).length === 0
4823
4827
  ) {
4824
4828
  return {
4825
- afterEachInOrder: (execution) => {
4826
- const log = renderExecutionLog(execution, logOptions);
4827
- write(log);
4829
+ afterEachInOrder: (execution, testPlanResult, testPlanHelpers) => {
4830
+ const log = renderExecutionLog(
4831
+ execution,
4832
+ logOptions,
4833
+ testPlanHelpers,
4834
+ );
4835
+ if (log) {
4836
+ write(log);
4837
+ }
4828
4838
  },
4829
4839
  afterAll: async () => {
4830
4840
  await write(renderOutro(testPlanResult, logOptions));
@@ -4892,12 +4902,18 @@ const reporterList = ({
4892
4902
  console.warn(warning.message);
4893
4903
  });
4894
4904
  },
4895
- afterEachInOrder: (execution) => {
4905
+ afterEachInOrder: (execution, testPlanResult, testPlanHelpers) => {
4896
4906
  oneExecutionWritten = true;
4897
4907
  dynamicLog.clearDuringFunctionCall(
4898
4908
  () => {
4899
- const log = renderExecutionLog(execution, logOptions);
4900
- write(log);
4909
+ const log = renderExecutionLog(
4910
+ execution,
4911
+ logOptions,
4912
+ testPlanHelpers,
4913
+ );
4914
+ if (log) {
4915
+ write(log);
4916
+ }
4901
4917
  },
4902
4918
  // regenerate the dynamic log to put the leading "\n"
4903
4919
  // because of oneExecutionWritten becoming true
@@ -4954,11 +4970,11 @@ const renderIntro = (testPlanResult, logOptions) => {
4954
4970
 
4955
4971
  let title;
4956
4972
  if (planified === 0) {
4957
- title = `no file to execute`;
4973
+ title = `nothing to execute`;
4958
4974
  } else if (planified === 1) {
4959
- title = `1 execution planified`;
4975
+ title = `1 execution ready`;
4960
4976
  } else {
4961
- title = `${planified} executions planified`;
4977
+ title = `${planified} executions ready`;
4962
4978
  }
4963
4979
  const lines = [];
4964
4980
  lines.push(`directory: ${directory}`);
@@ -4980,6 +4996,11 @@ const renderIntro = (testPlanResult, logOptions) => {
4980
4996
  testPlanLog += "}";
4981
4997
  lines.push(`testPlan: ${testPlanLog}`);
4982
4998
  }
4999
+ if (testPlanResult.fragment) {
5000
+ lines.push(
5001
+ `fragment: ${testPlanResult.fragment}, executing only ${testPlanResult.fragmentStart}:${testPlanResult.fragmentEnd}`,
5002
+ );
5003
+ }
4983
5004
  if (logOptions.platformInfo) {
4984
5005
  {
4985
5006
  let osLine = `os: `;
@@ -5016,7 +5037,36 @@ const renderIntro = (testPlanResult, logOptions) => {
5016
5037
  })}\n`;
5017
5038
  };
5018
5039
 
5019
- const renderExecutionLog = (execution, logOptions) => {
5040
+ const renderExecutionLog = (
5041
+ execution,
5042
+ logOptions,
5043
+ { getPreviousExecution, getNextExecution },
5044
+ ) => {
5045
+ if (execution.skipped) {
5046
+ const skipReason = execution.skipReason;
5047
+ const prev = getPreviousExecution(execution);
5048
+ if (prev && prev.skipped && prev.skipReason === skipReason) {
5049
+ return "";
5050
+ }
5051
+ let nextExecutionSkippedWithSameReason = 0;
5052
+ let next = getNextExecution(execution);
5053
+ while (next) {
5054
+ if (!next.skipped || next.skipReason !== skipReason) {
5055
+ break;
5056
+ }
5057
+ nextExecutionSkippedWithSameReason++;
5058
+ next = getNextExecution(next);
5059
+ }
5060
+ if (nextExecutionSkippedWithSameReason) {
5061
+ const skippedGroupLog = descriptionFormatters.skippedGroup({
5062
+ from: execution.index,
5063
+ to: execution.index + nextExecutionSkippedWithSameReason,
5064
+ skipReason,
5065
+ });
5066
+ return `${skippedGroupLog}\n`;
5067
+ }
5068
+ }
5069
+
5020
5070
  let log = "";
5021
5071
  // label
5022
5072
  {
@@ -5097,15 +5147,38 @@ const descriptionFormatters = {
5097
5147
  executing: ({ fileRelativeUrl }) => {
5098
5148
  return ANSI.color(`${fileRelativeUrl}`, COLOR_EXECUTING);
5099
5149
  },
5150
+ skippedGroup: ({ from, to, skipReason }) => {
5151
+ let description = `${UNICODE.CIRCLE_DOTTED_RAW} ${from + 1}:${to + 1} skipped`;
5152
+ if (skipReason) {
5153
+ description += ` (${skipReason})`;
5154
+ }
5155
+ return ANSI.color(description, COLOR_SKIPPED);
5156
+ },
5157
+ skipped: ({ index, countersInOrder, fileRelativeUrl, skipReason }) => {
5158
+ const total = countersInOrder.planified;
5159
+ const number = fillLeft(index + 1, total, "0");
5160
+ let description = `${UNICODE.CIRCLE_DOTTED_RAW} ${number}/${total} skipped ${fileRelativeUrl}`;
5161
+ if (skipReason) {
5162
+ description += ` (${skipReason})`;
5163
+ }
5164
+ return ANSI.color(description, COLOR_SKIPPED);
5165
+ },
5100
5166
  aborted: ({ index, countersInOrder, fileRelativeUrl }) => {
5101
5167
  const total = countersInOrder.planified;
5102
5168
  const number = fillLeft(index + 1, total, "0");
5103
-
5104
5169
  return ANSI.color(
5105
5170
  `${UNICODE.FAILURE_RAW} ${number}/${total} ${fileRelativeUrl}`,
5106
5171
  COLOR_ABORTED,
5107
5172
  );
5108
5173
  },
5174
+ cancelled: ({ index, countersInOrder, fileRelativeUrl }) => {
5175
+ const total = countersInOrder.planified;
5176
+ const number = fillLeft(index + 1, total, "0");
5177
+ return ANSI.color(
5178
+ `${UNICODE.FAILURE_RAW} ${number}/${total} ${fileRelativeUrl}`,
5179
+ COLOR_CANCELLED,
5180
+ );
5181
+ },
5109
5182
  timedout: ({ index, countersInOrder, fileRelativeUrl, params }) => {
5110
5183
  const total = countersInOrder.planified;
5111
5184
  const number = fillLeft(index + 1, total, "0");
@@ -5133,23 +5206,15 @@ const descriptionFormatters = {
5133
5206
  COLOR_COMPLETED,
5134
5207
  );
5135
5208
  },
5136
- cancelled: ({ index, countersInOrder, fileRelativeUrl }) => {
5137
- const total = countersInOrder.planified;
5138
- const number = fillLeft(index + 1, total, "0");
5139
-
5140
- return ANSI.color(
5141
- `${UNICODE.FAILURE_RAW} ${number}/${total} ${fileRelativeUrl}`,
5142
- COLOR_CANCELLED,
5143
- );
5144
- },
5145
5209
  };
5146
5210
 
5147
5211
  const COLOR_EXECUTING = ANSI.BLUE;
5212
+ const COLOR_SKIPPED = ANSI.GREY;
5148
5213
  const COLOR_ABORTED = ANSI.MAGENTA;
5214
+ const COLOR_CANCELLED = ANSI.GREY;
5149
5215
  const COLOR_TIMEOUT = ANSI.MAGENTA;
5150
5216
  const COLOR_FAILED = ANSI.RED;
5151
5217
  const COLOR_COMPLETED = ANSI.GREEN;
5152
- const COLOR_CANCELLED = ANSI.GREY;
5153
5218
  const fillLeft = (value, biggestValue, char = " ") => {
5154
5219
  const width = String(value).length;
5155
5220
  const biggestWidth = String(biggestValue).length;
@@ -5427,9 +5492,15 @@ const renderStatusRepartition = (counters, { showProgression } = {}) => {
5427
5492
  if (counters.planified === 0) {
5428
5493
  return ``;
5429
5494
  }
5495
+ if (counters.skipped === counters.planified) {
5496
+ return `all ${ANSI.color(`skipped`, COLOR_SKIPPED)}`;
5497
+ }
5430
5498
  if (counters.aborted === counters.planified) {
5431
5499
  return `all ${ANSI.color(`aborted`, COLOR_ABORTED)}`;
5432
5500
  }
5501
+ if (counters.cancelled === counters.planified) {
5502
+ return `all ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`;
5503
+ }
5433
5504
  if (counters.timedout === counters.planified) {
5434
5505
  return `all ${ANSI.color(`timed out`, COLOR_TIMEOUT)}`;
5435
5506
  }
@@ -5439,10 +5510,15 @@ const renderStatusRepartition = (counters, { showProgression } = {}) => {
5439
5510
  if (counters.completed === counters.planified) {
5440
5511
  return `all ${ANSI.color(`completed`, COLOR_COMPLETED)}`;
5441
5512
  }
5442
- if (counters.cancelled === counters.planified) {
5443
- return `all ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`;
5444
- }
5445
5513
  const parts = [];
5514
+ if (showProgression) {
5515
+ if (counters.executing) {
5516
+ parts.push(`${counters.executing} executing`);
5517
+ }
5518
+ if (counters.waiting) {
5519
+ parts.push(`${counters.waiting} waiting`);
5520
+ }
5521
+ }
5446
5522
  if (counters.timedout) {
5447
5523
  parts.push(
5448
5524
  `${counters.timedout} ${ANSI.color(`timed out`, COLOR_TIMEOUT)}`,
@@ -5464,13 +5540,8 @@ const renderStatusRepartition = (counters, { showProgression } = {}) => {
5464
5540
  `${counters.cancelled} ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`,
5465
5541
  );
5466
5542
  }
5467
- if (showProgression) {
5468
- if (counters.executing) {
5469
- parts.push(`${counters.executing} executing`);
5470
- }
5471
- if (counters.waiting) {
5472
- parts.push(`${counters.waiting} waiting`);
5473
- }
5543
+ if (counters.skipped) {
5544
+ parts.push(`${counters.skipped} ${ANSI.color(`skipped`, COLOR_SKIPPED)}`);
5474
5545
  }
5475
5546
  return `${parts.join(", ")}`;
5476
5547
  };
@@ -5546,6 +5617,39 @@ const renderDetails = (data) => {
5546
5617
  return string;
5547
5618
  };
5548
5619
 
5620
+ const createIsInsideFragment = (fragment, total) => {
5621
+ let [dividend, divisor] = fragment.split("/");
5622
+ dividend = parseInt(dividend);
5623
+ divisor = parseInt(divisor);
5624
+ const groupSize = Math.ceil(total / divisor);
5625
+ let from;
5626
+ let to;
5627
+ if (groupSize === 0) {
5628
+ if (dividend === 1) {
5629
+ from = 0;
5630
+ to = 0;
5631
+ } else {
5632
+ from = Infinity;
5633
+ to = -1;
5634
+ }
5635
+ } else if (dividend === 1) {
5636
+ from = 0;
5637
+ to = groupSize;
5638
+ } else {
5639
+ from = (dividend - 1) * groupSize;
5640
+ if (dividend === divisor) {
5641
+ to = total;
5642
+ } else {
5643
+ to = from + groupSize;
5644
+ }
5645
+ }
5646
+ return (index) => {
5647
+ if (index < from) return false;
5648
+ if (index >= to) return false;
5649
+ return true;
5650
+ };
5651
+ };
5652
+
5549
5653
  /*
5550
5654
  *
5551
5655
  */
@@ -5631,6 +5735,11 @@ const executeTestPlan = async ({
5631
5735
  handleSIGTERM = true,
5632
5736
  updateProcessExitCode = true,
5633
5737
  parallel = parallelDefault,
5738
+ // https://github.com/avajs/ava/blob/main/docs/recipes/splitting-tests-ci.md
5739
+ // https://playwright.dev/docs/test-sharding
5740
+ fragment,
5741
+ fragmentByRuntime,
5742
+ fragmentBy,
5634
5743
  defaultMsAllocatedPerExecution = 30_000,
5635
5744
  failFast = false,
5636
5745
  // keepRunning: false to ensure runtime is stopped once executed
@@ -5724,6 +5833,9 @@ const executeTestPlan = async ({
5724
5833
  rootDirectoryUrl: String(rootDirectoryUrl),
5725
5834
  patterns: Object.keys(testPlan),
5726
5835
  groups: {},
5836
+ fragment,
5837
+ fragmentStart: undefined,
5838
+ fragmentEnd: undefined,
5727
5839
  counters: {
5728
5840
  planified: 0,
5729
5841
  remaining: 0,
@@ -5731,6 +5843,7 @@ const executeTestPlan = async ({
5731
5843
  executing: 0,
5732
5844
  executed: 0,
5733
5845
 
5846
+ skipped: 0,
5734
5847
  aborted: 0,
5735
5848
  cancelled: 0,
5736
5849
  timedout: 0,
@@ -5760,6 +5873,36 @@ const executeTestPlan = async ({
5760
5873
  };
5761
5874
  const beforeEachCallbackSet = new Set();
5762
5875
  const beforeEachInOrderCallbackSet = new Set();
5876
+ const triggerBeforeEach = (execution, testPlanResult, testPlanHelpers) => {
5877
+ for (const beforeEachCallback of beforeEachCallbackSet) {
5878
+ const returnValue = beforeEachCallback(
5879
+ execution,
5880
+ testPlanResult,
5881
+ testPlanHelpers,
5882
+ );
5883
+ if (typeof returnValue === "function") {
5884
+ const callback = (...args) => {
5885
+ afterEachCallbackSet.delete(callback);
5886
+ return returnValue(...args);
5887
+ };
5888
+ afterEachCallbackSet.add(callback);
5889
+ }
5890
+ }
5891
+ for (const beforeEachInOrderCallback of beforeEachInOrderCallbackSet) {
5892
+ const returnValue = beforeEachInOrderCallback(
5893
+ execution,
5894
+ testPlanResult,
5895
+ testPlanHelpers,
5896
+ );
5897
+ if (typeof returnValue === "function") {
5898
+ const callback = (...args) => {
5899
+ afterEachInOrderCallbackSet.delete(callback);
5900
+ return returnValue(...args);
5901
+ };
5902
+ afterEachInOrderCallbackSet.add(callback);
5903
+ }
5904
+ }
5905
+ };
5763
5906
  const afterEachCallbackSet = new Set();
5764
5907
  const afterEachInOrderCallbackSet = new Set();
5765
5908
  const afterAllCallbackSet = new Set();
@@ -5892,6 +6035,16 @@ const executeTestPlan = async ({
5892
6035
  );
5893
6036
  }
5894
6037
  }
6038
+ // fragment/fragmentByRuntime
6039
+ {
6040
+ if (fragment) {
6041
+ if (!/[0-9]+\/[0-9]+/.test(fragment)) {
6042
+ throw new TypeError(
6043
+ `fragment must look like number/number, received ${fragment}`,
6044
+ );
6045
+ }
6046
+ }
6047
+ }
5895
6048
  // testPlan
5896
6049
  {
5897
6050
  if (typeof testPlan !== "object") {
@@ -6096,9 +6249,21 @@ To fix this warning:
6096
6249
  }
6097
6250
  }
6098
6251
 
6099
- const executionPlanifiedSet = new Set();
6252
+ const executionPlanifiedArray = [];
6253
+ const getPreviousExecution = (execution) => {
6254
+ const index = execution.index;
6255
+ return executionPlanifiedArray[index - 1] || null;
6256
+ };
6257
+ const getNextExecution = (execution) => {
6258
+ const index = execution.index;
6259
+ return executionPlanifiedArray[index + 1] || null;
6260
+ };
6261
+ const testPlanHelpers = {
6262
+ getPreviousExecution,
6263
+ getNextExecution,
6264
+ };
6100
6265
 
6101
- // collect files to execute + fill executionPlanifiedSet
6266
+ // collect files to execute + fill executionPlanifiedArray
6102
6267
  {
6103
6268
  const fileResultArray = await collectFiles({
6104
6269
  signal,
@@ -6106,6 +6271,11 @@ To fix this warning:
6106
6271
  associations: { testPlan },
6107
6272
  predicate: ({ testPlan }) => testPlan,
6108
6273
  });
6274
+
6275
+ // idéalement dans les logs on aurait un truc comme ceci:
6276
+ // --- 100 execution found ---
6277
+ // Executing only from 33 to 66 according to fragment: "2/3"
6278
+
6109
6279
  let index = 0;
6110
6280
  let lastExecution;
6111
6281
  const fileExecutionCountMap = new Map();
@@ -6128,10 +6298,6 @@ To fix this warning:
6128
6298
  ),
6129
6299
  );
6130
6300
  }
6131
- if (stepConfig.runtime?.disabled) {
6132
- continue;
6133
- }
6134
-
6135
6301
  const {
6136
6302
  runtime,
6137
6303
  runtimeParams,
@@ -6188,6 +6354,8 @@ To fix this warning:
6188
6354
  runtimeName,
6189
6355
  runtimeVersion,
6190
6356
  params,
6357
+ skipped: false,
6358
+ skipReason: "",
6191
6359
 
6192
6360
  // will be set by run()
6193
6361
  status: "planified",
@@ -6198,7 +6366,7 @@ To fix this warning:
6198
6366
  }
6199
6367
 
6200
6368
  lastExecution = execution;
6201
- executionPlanifiedSet.add(execution);
6369
+ executionPlanifiedArray.push(execution);
6202
6370
  const existingResults = results[relativeUrl];
6203
6371
  if (existingResults) {
6204
6372
  existingResults[groupName] = execution.result;
@@ -6218,9 +6386,48 @@ To fix this warning:
6218
6386
  runtimeVersion,
6219
6387
  };
6220
6388
  }
6389
+
6390
+ if (runtime?.disabled) {
6391
+ execution.skipped = true;
6392
+ execution.skipReason =
6393
+ runtime.disabledReason || "reason not specified";
6394
+ } else if (fragmentByRuntime && runtime.name !== fragmentByRuntime) {
6395
+ execution.skipped = true;
6396
+ execution.skipReason = "runtime disabled";
6397
+ } else if (fragmentBy) {
6398
+ const fragmentByResult = fragmentBy(execution);
6399
+ if (fragmentByResult) {
6400
+ execution.skipped = true;
6401
+ execution.skipReason =
6402
+ typeof fragmentByResult === "string"
6403
+ ? fragmentByResult
6404
+ : `reason not specified`;
6405
+ }
6406
+ }
6221
6407
  index++;
6222
6408
  }
6223
6409
  }
6410
+
6411
+ if (fragment) {
6412
+ const total = executionPlanifiedArray.length;
6413
+ const isInsideFragment = createIsInsideFragment(fragment, total);
6414
+ for (const execution of executionPlanifiedArray) {
6415
+ if (isInsideFragment(execution.index)) {
6416
+ const executionNumber = execution.index + 1;
6417
+ if (testPlanResult.fragmentStart === undefined) {
6418
+ testPlanResult.fragmentStart = executionNumber;
6419
+ }
6420
+ if (
6421
+ testPlanResult.fragmentEnd === undefined ||
6422
+ executionNumber > testPlanResult.fragmentEnd
6423
+ ) {
6424
+ testPlanResult.fragmentEnd = executionNumber;
6425
+ }
6426
+ } else {
6427
+ execution.skipped = true;
6428
+ }
6429
+ }
6430
+ }
6224
6431
  fileResultArray.length = 0;
6225
6432
  fileExecutionCountMap.clear();
6226
6433
  if (lastExecution) {
@@ -6231,11 +6438,11 @@ To fix this warning:
6231
6438
  counters.planified =
6232
6439
  counters.remaining =
6233
6440
  counters.waiting =
6234
- executionPlanifiedSet.size;
6441
+ executionPlanifiedArray.length;
6235
6442
  countersInOrder.planified =
6236
6443
  countersInOrder.remaining =
6237
6444
  countersInOrder.waiting =
6238
- executionPlanifiedSet.size;
6445
+ executionPlanifiedArray.length;
6239
6446
  if (githubCheck) {
6240
6447
  const githubCheckRun = await startGithubCheckRun({
6241
6448
  logLevel: githubCheck.logLevel,
@@ -6245,7 +6452,7 @@ To fix this warning:
6245
6452
  commitSha: githubCheck.commitSha,
6246
6453
  checkName: githubCheck.name,
6247
6454
  checkTitle: githubCheck.title,
6248
- checkSummary: `${executionPlanifiedSet.size} files will be executed`,
6455
+ checkSummary: `${executionPlanifiedArray.length} files will be executed`,
6249
6456
  });
6250
6457
  const annotations = [];
6251
6458
  reporters.push({
@@ -6325,80 +6532,66 @@ To fix this warning:
6325
6532
 
6326
6533
  const callWhenPreviousExecutionAreDone = createCallOrderer();
6327
6534
 
6328
- const executionRemainingSet = new Set(executionPlanifiedSet);
6535
+ const executionRemainingSet = new Set(executionPlanifiedArray);
6329
6536
  const executionExecutingSet = new Set();
6330
6537
  const usedTagSet = new Set();
6331
6538
  const start = async (execution) => {
6332
- if (execution.params.uses) {
6333
- for (const tagThatWillBeUsed of execution.params.uses) {
6334
- usedTagSet.add(tagThatWillBeUsed);
6335
- }
6336
- }
6337
6539
  execution.fileExecutionCount = Object.keys(
6338
6540
  testPlanResult.results[execution.fileRelativeUrl],
6339
6541
  ).length;
6340
6542
  mutateCountersBeforeExecutionStarts(counters, execution);
6341
6543
  mutateCountersBeforeExecutionStarts(countersInOrder, execution);
6342
-
6343
- execution.status = "executing";
6344
6544
  executionRemainingSet.delete(execution);
6345
6545
  executionExecutingSet.add(execution);
6346
- for (const beforeEachCallback of beforeEachCallbackSet) {
6347
- const returnValue = beforeEachCallback(execution, testPlanResult);
6348
- if (typeof returnValue === "function") {
6349
- const callback = (...args) => {
6350
- afterEachCallbackSet.delete(callback);
6351
- return returnValue(...args);
6352
- };
6353
- afterEachCallbackSet.add(callback);
6546
+ triggerBeforeEach(execution, testPlanResult, testPlanHelpers);
6547
+ if (execution.skipped) {
6548
+ execution.result.status = "skipped";
6549
+ execution.result.value = execution.skipReason;
6550
+ } else {
6551
+ if (execution.params.uses) {
6552
+ for (const tagThatWillBeUsed of execution.params.uses) {
6553
+ usedTagSet.add(tagThatWillBeUsed);
6554
+ }
6354
6555
  }
6355
- }
6356
- for (const beforeEachInOrderCallback of beforeEachInOrderCallbackSet) {
6357
- const returnValue = beforeEachInOrderCallback(
6358
- execution,
6359
- testPlanResult,
6360
- );
6361
- if (typeof returnValue === "function") {
6362
- const callback = (...args) => {
6363
- afterEachInOrderCallbackSet.delete(callback);
6364
- return returnValue(...args);
6365
- };
6366
- afterEachInOrderCallbackSet.add(callback);
6556
+ execution.status = "executing";
6557
+ const executionResult = await run({
6558
+ ...execution.params,
6559
+ signal: operation.signal,
6560
+ logger,
6561
+ keepRunning,
6562
+ mirrorConsole: false, // might be executed in parallel: log would be a mess to read
6563
+ coverageEnabled: Boolean(coverage),
6564
+ coverageTempDirectoryUrl: coverage?.tempDirectoryUrl,
6565
+ });
6566
+ Object.assign(execution.result, executionResult);
6567
+ execution.status = "executed";
6568
+ if (execution.params.uses) {
6569
+ for (const tagNoLongerInUse of execution.params.uses) {
6570
+ usedTagSet.delete(tagNoLongerInUse);
6571
+ }
6367
6572
  }
6368
- }
6369
- const executionResult = await run({
6370
- ...execution.params,
6371
- signal: operation.signal,
6372
- logger,
6373
- keepRunning,
6374
- mirrorConsole: false, // might be executed in parallel: log would be a mess to read
6375
- coverageEnabled: Boolean(coverage),
6376
- coverageTempDirectoryUrl: coverage?.tempDirectoryUrl,
6377
- });
6378
- Object.assign(execution.result, executionResult);
6379
- execution.status = "executed";
6380
- executionExecutingSet.delete(execution);
6381
- mutateCountersAfterExecutionEnds(counters, execution);
6382
- if (execution.result.status !== "completed") {
6383
- testPlanResult.failed = true;
6384
- if (updateProcessExitCode) {
6385
- process.exitCode = 1;
6573
+ if (execution.result.status !== "completed") {
6574
+ testPlanResult.failed = true;
6575
+ if (updateProcessExitCode) {
6576
+ process.exitCode = 1;
6577
+ }
6386
6578
  }
6387
6579
  }
6580
+ mutateCountersAfterExecutionEnds(counters, execution);
6581
+ executionExecutingSet.delete(execution);
6388
6582
  for (const afterEachCallback of afterEachCallbackSet) {
6389
- afterEachCallback(execution, testPlanResult);
6583
+ afterEachCallback(execution, testPlanResult, testPlanHelpers);
6390
6584
  }
6391
6585
  callWhenPreviousExecutionAreDone(execution.index, () => {
6392
6586
  mutateCountersAfterExecutionEnds(countersInOrder, execution);
6393
6587
  for (const afterEachInOrderCallback of afterEachInOrderCallbackSet) {
6394
- afterEachInOrderCallback(execution, testPlanResult);
6588
+ afterEachInOrderCallback(
6589
+ execution,
6590
+ testPlanResult,
6591
+ testPlanHelpers,
6592
+ );
6395
6593
  }
6396
6594
  });
6397
- if (execution.params.uses) {
6398
- for (const tagNoLongerInUse of execution.params.uses) {
6399
- usedTagSet.delete(tagNoLongerInUse);
6400
- }
6401
- }
6402
6595
  if (testPlanResult.failed && failFast && counters.remaining) {
6403
6596
  logger.info(`"failFast" enabled -> cancel remaining executions`);
6404
6597
  failFastAbortController.abort();
@@ -6612,7 +6805,9 @@ const mutateCountersAfterExecutionEnds = (counters, execution) => {
6612
6805
  counters.executing--;
6613
6806
  counters.executed++;
6614
6807
  counters.remaining--;
6615
- if (execution.result.status === "aborted") {
6808
+ if (execution.result.status === "skipped") {
6809
+ counters.skipped++;
6810
+ } else if (execution.result.status === "aborted") {
6616
6811
  counters.aborted++;
6617
6812
  } else if (execution.result.status === "timedout") {
6618
6813
  counters.timedout++;
@@ -7482,7 +7677,7 @@ const createFirefoxRuntime = ({
7482
7677
  if (disableOnWindowsBecauseFlaky === undefined) {
7483
7678
  // https://github.com/microsoft/playwright/issues/1396
7484
7679
  console.warn(
7485
- `Windows + firefox detected: executions on firefox will be ignored (firefox is flaky on windows).
7680
+ `Windows + firefox detected: executions on firefox will be ignored (firefox is flaky on windows).
7486
7681
  To disable this warning, use disableOnWindowsBecauseFlaky: true
7487
7682
  To ignore potential flakyness, use disableOnWindowsBecauseFlaky: false`,
7488
7683
  );
@@ -7491,6 +7686,7 @@ To ignore potential flakyness, use disableOnWindowsBecauseFlaky: false`,
7491
7686
  if (disableOnWindowsBecauseFlaky) {
7492
7687
  return {
7493
7688
  disabled: true,
7689
+ disabledReason: "flaky on windows",
7494
7690
  };
7495
7691
  }
7496
7692
  }
@@ -7877,11 +8073,8 @@ const nodeChildProcess = ({
7877
8073
  env = {
7878
8074
  ...env,
7879
8075
  JSENV: true,
7880
- FORCE_COLOR:
7881
- supportsColor.stdout ||
7882
- // GitHub workflow does support ANSI but "supports-color" returns false
7883
- // because stream.isTTY returns false, see https://github.com/actions/runner/issues/241
7884
- process.env.GITHUB_WORKFLOW,
8076
+ ...(ANSI.supported ? { FORCE_COLOR: "1" } : {}),
8077
+ ...(UNICODE.supported ? { FORCE_UNICODE: "1" } : {}),
7885
8078
  };
7886
8079
 
7887
8080
  return {
@@ -8356,11 +8549,8 @@ const nodeWorkerThread = ({
8356
8549
  env = {
8357
8550
  ...env,
8358
8551
  JSENV: true,
8359
- FORCE_COLOR:
8360
- supportsColor.stdout ||
8361
- // GitHub workflow does support ANSI but "supports-color" returns false
8362
- // because stream.isTTY returns false, see https://github.com/actions/runner/issues/241
8363
- process.env.GITHUB_WORKFLOW,
8552
+ ...(ANSI.supported ? { FORCE_COLOR: "1" } : {}),
8553
+ ...(UNICODE.supported ? { FORCE_UNICODE: "1" } : {}),
8364
8554
  };
8365
8555
 
8366
8556
  return {
@@ -8969,22 +9159,26 @@ const reportAsJunitXml = async (
8969
9159
  });
8970
9160
  testSuite.appendChild(testCase);
8971
9161
 
8972
- if (executionResult.status === "aborted") {
9162
+ if (executionResult.status === "skipped") {
9163
+ testCase.attributes.skipped = 1;
9164
+ const skipped = testCase.createNode("skipped", {
9165
+ message: "Execution was skipped",
9166
+ });
9167
+ testSuite.appendChild(skipped);
9168
+ } else if (executionResult.status === "aborted") {
8973
9169
  testCase.attributes.skipped = 1;
8974
9170
  const skipped = testCase.createNode("skipped", {
8975
9171
  message: "Execution was aborted",
8976
9172
  });
8977
9173
  testSuite.appendChild(skipped);
8978
- }
8979
- if (executionResult.status === "timedout") {
9174
+ } else if (executionResult.status === "timedout") {
8980
9175
  testCase.attributes.failures = 1;
8981
9176
  const failure = testCase.createNode("failure", {
8982
9177
  message: `Execution timeout after ${executionResult.params.allocatedMs}ms"`,
8983
9178
  type: "timeout",
8984
9179
  });
8985
9180
  testSuite.appendChild(failure);
8986
- }
8987
- if (executionResult.status === "failed") {
9181
+ } else if (executionResult.status === "failed") {
8988
9182
  const [error] = executionResult.errors;
8989
9183
  if (
8990
9184
  error &&
@@ -9038,7 +9232,7 @@ const reportAsJunitXml = async (
9038
9232
  propertiesNode.appendChild(propertyNode);
9039
9233
  }
9040
9234
 
9041
- const { consoleCalls } = executionResult;
9235
+ const { consoleCalls = [] } = executionResult;
9042
9236
  let stdout = "";
9043
9237
  let stderr = "";
9044
9238
  for (const consoleCall of consoleCalls) {
@@ -9071,6 +9265,123 @@ ${testSuite.renderAsString()}`;
9071
9265
  writeFileSync(new URL(fileUrl), junitXmlFileContent);
9072
9266
  };
9073
9267
 
9268
+ const reportAsJson = (
9269
+ testPlanResult,
9270
+ fileUrl,
9271
+ { mockFluctuatingValues } = {},
9272
+ ) => {
9273
+ fileUrl = assertAndNormalizeFileUrl(fileUrl);
9274
+
9275
+ if (!mockFluctuatingValues) {
9276
+ writeFileSync(fileUrl, JSON.stringify(testPlanResult, null, " "));
9277
+ return;
9278
+ }
9279
+ const testPlanResultCopy = deepCopy(testPlanResult, {});
9280
+ testPlanResultCopy.os.name = "<mock>";
9281
+ testPlanResultCopy.os.version = "<mock>";
9282
+ testPlanResultCopy.os.availableCpu = "<mock>";
9283
+ testPlanResultCopy.os.availableMemory = "<mock>";
9284
+ testPlanResultCopy.process.version = "<mock>";
9285
+ testPlanResultCopy.memoryUsage = "<mock>";
9286
+ testPlanResultCopy.cpuUsage = "<mock>";
9287
+ testPlanResultCopy.rootDirectoryUrl = "/mock/";
9288
+ testPlanResultCopy.timings = "<mock>";
9289
+ for (const group of Object.keys(testPlanResultCopy.groups)) {
9290
+ testPlanResultCopy.groups[group].runtimeVersion = "<mock>";
9291
+ }
9292
+
9293
+ for (const relativeUrl of Object.keys(testPlanResultCopy.results)) {
9294
+ const fileResults = testPlanResultCopy.results[relativeUrl];
9295
+ for (const groupName of Object.keys(fileResults)) {
9296
+ const executionResult = fileResults[groupName];
9297
+ const { consoleCalls } = executionResult;
9298
+ if (consoleCalls) {
9299
+ for (const consoleCall of consoleCalls) {
9300
+ consoleCall.text = mockFluctuatingValuesInMessage(consoleCall.text, {
9301
+ rootDirectoryUrl: testPlanResult.rootDirectoryUrl,
9302
+ });
9303
+ consoleCall.text = consoleCall.text.replace(/\r\n/g, "\n");
9304
+ }
9305
+ }
9306
+ const { errors } = executionResult;
9307
+ if (errors) {
9308
+ for (const error of errors) {
9309
+ if (error.message) {
9310
+ error.message = mockFluctuatingValuesInMessage(error.message, {
9311
+ rootDirectoryUrl: testPlanResult.rootDirectoryUrl,
9312
+ });
9313
+ }
9314
+ if (error.stack) {
9315
+ error.stack = mockFluctuatingValuesInMessage(error.stack, {
9316
+ rootDirectoryUrl: testPlanResult.rootDirectoryUrl,
9317
+ });
9318
+ }
9319
+ }
9320
+ }
9321
+ if (executionResult.timings) {
9322
+ executionResult.timings = "<mock>";
9323
+ }
9324
+ if (Object.hasOwn(executionResult, "memoryUsage")) {
9325
+ executionResult.memoryUsage = "<mock>";
9326
+ }
9327
+ if (executionResult.performance) {
9328
+ executionResult.performance = "<mock>";
9329
+ }
9330
+ if (executionResult.coverageFileUrl) {
9331
+ executionResult.coverageFileUrl = "<mock>";
9332
+ }
9333
+ }
9334
+ }
9335
+ writeFileSync(fileUrl, JSON.stringify(testPlanResultCopy, null, " "));
9336
+ };
9337
+
9338
+ const mockFluctuatingValuesInMessage = (message, { rootDirectoryUrl }) => {
9339
+ return replaceUrls(message, ({ url, line, column }) => {
9340
+ let urlAsPath = String(url).startsWith("file:")
9341
+ ? urlToFileSystemPath(url)
9342
+ : url;
9343
+ const rootDirectoryPath = urlToFileSystemPath(rootDirectoryUrl);
9344
+ urlAsPath = urlAsPath.replace(rootDirectoryPath, "/mock/");
9345
+ if (process.platform === "win32") {
9346
+ urlAsPath = urlAsPath.replace(/\\/g, "/");
9347
+ }
9348
+ if (typeof line === "number" && typeof column === "number") {
9349
+ return `${urlAsPath}:${line}:${column}`;
9350
+ }
9351
+ if (typeof line === "number") {
9352
+ return `${urlAsPath}:${line}`;
9353
+ }
9354
+ return urlAsPath;
9355
+ });
9356
+ };
9357
+
9358
+ const deepCopy = (from, into) => {
9359
+ const copyValue = (value) => {
9360
+ if (value === null) {
9361
+ return null;
9362
+ }
9363
+ if (typeof value === "object") {
9364
+ if (Array.isArray(value)) {
9365
+ return deepCopy(value, []);
9366
+ }
9367
+ return deepCopy(value, {});
9368
+ }
9369
+ return value;
9370
+ };
9371
+
9372
+ if (Array.isArray(from)) {
9373
+ let i = 0;
9374
+ while (i < from.length) {
9375
+ into[i] = copyValue(from[i]);
9376
+ i++;
9377
+ }
9378
+ }
9379
+ for (const key of Object.keys(from)) {
9380
+ into[key] = copyValue(from[key]);
9381
+ }
9382
+ return into;
9383
+ };
9384
+
9074
9385
  /*
9075
9386
  * Export a function capable to execute a file on a runtime (browser or node) and return how it goes.
9076
9387
  *
@@ -9265,4 +9576,4 @@ const inlineRuntime = (fn) => {
9265
9576
  };
9266
9577
  };
9267
9578
 
9268
- export { chromium, chromiumIsolatedTab, execute, executeTestPlan, firefox, firefoxIsolatedTab, inlineRuntime, nodeChildProcess, nodeWorkerThread, reportAsJunitXml, reportCoverageAsHtml, reportCoverageAsJson, reportCoverageInConsole, reporterList, webkit, webkitIsolatedTab };
9579
+ export { chromium, chromiumIsolatedTab, execute, executeTestPlan, firefox, firefoxIsolatedTab, inlineRuntime, nodeChildProcess, nodeWorkerThread, reportAsJson, reportAsJunitXml, reportCoverageAsHtml, reportCoverageAsJson, reportCoverageInConsole, reporterList, webkit, webkitIsolatedTab };