@jsenv/test 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import os, { cpus, release, totalmem, availableParallelism, freemem } from "node:os";
2
2
  import process$1, { cpuUsage, memoryUsage } from "node:process";
3
- import { readdir, chmod, stat, lstat, chmodSync, statSync, lstatSync, promises, readFile as readFile$1, writeFileSync as writeFileSync$1, mkdirSync, unlink, openSync, closeSync, rmdir, unlinkSync, readdirSync, rmdirSync, readFileSync, existsSync } from "node:fs";
3
+ import { readdir, chmod, stat, lstat, chmodSync, statSync, lstatSync, promises, readFile as readFile$1, readFileSync, writeFileSync as writeFileSync$1, mkdirSync, unlink, openSync, closeSync, rmdir, unlinkSync, readdirSync, rmdirSync, existsSync } from "node:fs";
4
4
  import { takeCoverage } from "node:v8";
5
5
  import stripAnsi from "strip-ansi";
6
6
  import { URL_META, createException } from "./js/exception.js";
@@ -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
 
@@ -2486,6 +2490,9 @@ const readFile = async (value, { as = "buffer" } = {}) => {
2486
2490
  const writeFileSync = (destination, content = "") => {
2487
2491
  const destinationUrl = assertAndNormalizeFileUrl(destination);
2488
2492
  const destinationUrlObject = new URL(destinationUrl);
2493
+ if (content && content instanceof URL) {
2494
+ content = readFileSync(content);
2495
+ }
2489
2496
  try {
2490
2497
  writeFileSync$1(destinationUrlObject, content);
2491
2498
  } catch (error) {
@@ -4295,7 +4302,7 @@ const stringifyUrlSite = ({ url, line, column }) => {
4295
4302
  };
4296
4303
 
4297
4304
  const replaceUrls$1 = (source, replace) => {
4298
- return source.replace(/(?:https?|ftp|file):\/\/\S+/gm, (match) => {
4305
+ return source.replace(/(?:https?|ftp|file):\/\/\S+/g, (match) => {
4299
4306
  let replacement = "";
4300
4307
  const lastChar = match[match.length - 1];
4301
4308
 
@@ -4562,6 +4569,57 @@ const normalizeRuntimeError = (runtimeError) => {
4562
4569
  return errorProxy;
4563
4570
  };
4564
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
+
4565
4623
  const formatErrorForTerminal = (
4566
4624
  error,
4567
4625
  { rootDirectoryUrl, mainFileRelativeUrl, mockFluctuatingValues, tryColors },
@@ -4694,57 +4752,6 @@ const formatErrorForTerminal = (
4694
4752
  return text;
4695
4753
  };
4696
4754
 
4697
- // `Error: yo
4698
- // at Object.execute (http://127.0.0.1:57300/build/src/__test__/file-throw.js:9:13)
4699
- // at doExec (http://127.0.0.1:3000/src/__test__/file-throw.js:452:38)
4700
- // at postOrderExec (http://127.0.0.1:3000/src/__test__/file-throw.js:448:16)
4701
- // at http://127.0.0.1:3000/src/__test__/file-throw.js:399:18`.replace(/(?:https?|ftp|file):\/\/(.*+)$/gm, (...args) => {
4702
- // debugger
4703
- // })
4704
- const replaceUrls = (source, replace) => {
4705
- return source.replace(/(?:https?|ftp|file):\/\/\S+/gm, (match) => {
4706
- let replacement = "";
4707
- const lastChar = match[match.length - 1];
4708
-
4709
- // hotfix because our url regex sucks a bit
4710
- const endsWithSeparationChar = lastChar === ")" || lastChar === ":";
4711
- if (endsWithSeparationChar) {
4712
- match = match.slice(0, -1);
4713
- }
4714
-
4715
- const lineAndColumnPattern = /:([0-9]+):([0-9]+)$/;
4716
- const lineAndColumMatch = match.match(lineAndColumnPattern);
4717
- if (lineAndColumMatch) {
4718
- const lineAndColumnString = lineAndColumMatch[0];
4719
- const lineString = lineAndColumMatch[1];
4720
- const columnString = lineAndColumMatch[2];
4721
- replacement = replace({
4722
- url: match.slice(0, -lineAndColumnString.length),
4723
- line: lineString ? parseInt(lineString) : null,
4724
- column: columnString ? parseInt(columnString) : null,
4725
- });
4726
- } else {
4727
- const linePattern = /:([0-9]+)$/;
4728
- const lineMatch = match.match(linePattern);
4729
- if (lineMatch) {
4730
- const lineString = lineMatch[0];
4731
- replacement = replace({
4732
- url: match.slice(0, -lineString.length),
4733
- line: lineString ? parseInt(lineString) : null,
4734
- });
4735
- } else {
4736
- replacement = replace({
4737
- url: match,
4738
- });
4739
- }
4740
- }
4741
- if (endsWithSeparationChar) {
4742
- return `${replacement}${lastChar}`;
4743
- }
4744
- return replacement;
4745
- });
4746
- };
4747
-
4748
4755
  /*
4749
4756
  * label
4750
4757
  * ┌───────────────────────┴────────────────────────────────┐
@@ -4819,9 +4826,15 @@ const reporterList = ({
4819
4826
  Object.keys(testPlanResult.results).length === 0
4820
4827
  ) {
4821
4828
  return {
4822
- afterEachInOrder: (execution) => {
4823
- const log = renderExecutionLog(execution, logOptions);
4824
- 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
+ }
4825
4838
  },
4826
4839
  afterAll: async () => {
4827
4840
  await write(renderOutro(testPlanResult, logOptions));
@@ -4889,12 +4902,18 @@ const reporterList = ({
4889
4902
  console.warn(warning.message);
4890
4903
  });
4891
4904
  },
4892
- afterEachInOrder: (execution) => {
4905
+ afterEachInOrder: (execution, testPlanResult, testPlanHelpers) => {
4893
4906
  oneExecutionWritten = true;
4894
4907
  dynamicLog.clearDuringFunctionCall(
4895
4908
  () => {
4896
- const log = renderExecutionLog(execution, logOptions);
4897
- write(log);
4909
+ const log = renderExecutionLog(
4910
+ execution,
4911
+ logOptions,
4912
+ testPlanHelpers,
4913
+ );
4914
+ if (log) {
4915
+ write(log);
4916
+ }
4898
4917
  },
4899
4918
  // regenerate the dynamic log to put the leading "\n"
4900
4919
  // because of oneExecutionWritten becoming true
@@ -4951,11 +4970,11 @@ const renderIntro = (testPlanResult, logOptions) => {
4951
4970
 
4952
4971
  let title;
4953
4972
  if (planified === 0) {
4954
- title = `no file to execute`;
4973
+ title = `nothing to execute`;
4955
4974
  } else if (planified === 1) {
4956
- title = `1 execution planified`;
4975
+ title = `1 execution ready`;
4957
4976
  } else {
4958
- title = `${planified} executions planified`;
4977
+ title = `${planified} executions ready`;
4959
4978
  }
4960
4979
  const lines = [];
4961
4980
  lines.push(`directory: ${directory}`);
@@ -4977,6 +4996,11 @@ const renderIntro = (testPlanResult, logOptions) => {
4977
4996
  testPlanLog += "}";
4978
4997
  lines.push(`testPlan: ${testPlanLog}`);
4979
4998
  }
4999
+ if (testPlanResult.fragment) {
5000
+ lines.push(
5001
+ `fragment: ${testPlanResult.fragment}, executing only ${testPlanResult.fragmentStart}:${testPlanResult.fragmentEnd}`,
5002
+ );
5003
+ }
4980
5004
  if (logOptions.platformInfo) {
4981
5005
  {
4982
5006
  let osLine = `os: `;
@@ -5013,7 +5037,36 @@ const renderIntro = (testPlanResult, logOptions) => {
5013
5037
  })}\n`;
5014
5038
  };
5015
5039
 
5016
- 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
+
5017
5070
  let log = "";
5018
5071
  // label
5019
5072
  {
@@ -5094,15 +5147,38 @@ const descriptionFormatters = {
5094
5147
  executing: ({ fileRelativeUrl }) => {
5095
5148
  return ANSI.color(`${fileRelativeUrl}`, COLOR_EXECUTING);
5096
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
+ },
5097
5166
  aborted: ({ index, countersInOrder, fileRelativeUrl }) => {
5098
5167
  const total = countersInOrder.planified;
5099
5168
  const number = fillLeft(index + 1, total, "0");
5100
-
5101
5169
  return ANSI.color(
5102
5170
  `${UNICODE.FAILURE_RAW} ${number}/${total} ${fileRelativeUrl}`,
5103
5171
  COLOR_ABORTED,
5104
5172
  );
5105
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
+ },
5106
5182
  timedout: ({ index, countersInOrder, fileRelativeUrl, params }) => {
5107
5183
  const total = countersInOrder.planified;
5108
5184
  const number = fillLeft(index + 1, total, "0");
@@ -5130,23 +5206,15 @@ const descriptionFormatters = {
5130
5206
  COLOR_COMPLETED,
5131
5207
  );
5132
5208
  },
5133
- cancelled: ({ index, countersInOrder, fileRelativeUrl }) => {
5134
- const total = countersInOrder.planified;
5135
- const number = fillLeft(index + 1, total, "0");
5136
-
5137
- return ANSI.color(
5138
- `${UNICODE.FAILURE_RAW} ${number}/${total} ${fileRelativeUrl}`,
5139
- COLOR_CANCELLED,
5140
- );
5141
- },
5142
5209
  };
5143
5210
 
5144
5211
  const COLOR_EXECUTING = ANSI.BLUE;
5212
+ const COLOR_SKIPPED = ANSI.GREY;
5145
5213
  const COLOR_ABORTED = ANSI.MAGENTA;
5214
+ const COLOR_CANCELLED = ANSI.GREY;
5146
5215
  const COLOR_TIMEOUT = ANSI.MAGENTA;
5147
5216
  const COLOR_FAILED = ANSI.RED;
5148
5217
  const COLOR_COMPLETED = ANSI.GREEN;
5149
- const COLOR_CANCELLED = ANSI.GREY;
5150
5218
  const fillLeft = (value, biggestValue, char = " ") => {
5151
5219
  const width = String(value).length;
5152
5220
  const biggestWidth = String(biggestValue).length;
@@ -5424,9 +5492,15 @@ const renderStatusRepartition = (counters, { showProgression } = {}) => {
5424
5492
  if (counters.planified === 0) {
5425
5493
  return ``;
5426
5494
  }
5495
+ if (counters.skipped === counters.planified) {
5496
+ return `all ${ANSI.color(`skipped`, COLOR_SKIPPED)}`;
5497
+ }
5427
5498
  if (counters.aborted === counters.planified) {
5428
5499
  return `all ${ANSI.color(`aborted`, COLOR_ABORTED)}`;
5429
5500
  }
5501
+ if (counters.cancelled === counters.planified) {
5502
+ return `all ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`;
5503
+ }
5430
5504
  if (counters.timedout === counters.planified) {
5431
5505
  return `all ${ANSI.color(`timed out`, COLOR_TIMEOUT)}`;
5432
5506
  }
@@ -5436,9 +5510,6 @@ const renderStatusRepartition = (counters, { showProgression } = {}) => {
5436
5510
  if (counters.completed === counters.planified) {
5437
5511
  return `all ${ANSI.color(`completed`, COLOR_COMPLETED)}`;
5438
5512
  }
5439
- if (counters.cancelled === counters.planified) {
5440
- return `all ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`;
5441
- }
5442
5513
  const parts = [];
5443
5514
  if (counters.timedout) {
5444
5515
  parts.push(
@@ -5461,6 +5532,9 @@ const renderStatusRepartition = (counters, { showProgression } = {}) => {
5461
5532
  `${counters.cancelled} ${ANSI.color(`cancelled`, COLOR_CANCELLED)}`,
5462
5533
  );
5463
5534
  }
5535
+ if (counters.skipped) {
5536
+ parts.push(`${counters.skipped} ${ANSI.color(`skipped`, COLOR_SKIPPED)}`);
5537
+ }
5464
5538
  if (showProgression) {
5465
5539
  if (counters.executing) {
5466
5540
  parts.push(`${counters.executing} executing`);
@@ -5543,6 +5617,39 @@ const renderDetails = (data) => {
5543
5617
  return string;
5544
5618
  };
5545
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
+
5546
5653
  /*
5547
5654
  *
5548
5655
  */
@@ -5628,6 +5735,11 @@ const executeTestPlan = async ({
5628
5735
  handleSIGTERM = true,
5629
5736
  updateProcessExitCode = true,
5630
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,
5631
5743
  defaultMsAllocatedPerExecution = 30_000,
5632
5744
  failFast = false,
5633
5745
  // keepRunning: false to ensure runtime is stopped once executed
@@ -5721,6 +5833,9 @@ const executeTestPlan = async ({
5721
5833
  rootDirectoryUrl: String(rootDirectoryUrl),
5722
5834
  patterns: Object.keys(testPlan),
5723
5835
  groups: {},
5836
+ fragment,
5837
+ fragmentStart: undefined,
5838
+ fragmentEnd: undefined,
5724
5839
  counters: {
5725
5840
  planified: 0,
5726
5841
  remaining: 0,
@@ -5728,6 +5843,7 @@ const executeTestPlan = async ({
5728
5843
  executing: 0,
5729
5844
  executed: 0,
5730
5845
 
5846
+ skipped: 0,
5731
5847
  aborted: 0,
5732
5848
  cancelled: 0,
5733
5849
  timedout: 0,
@@ -5757,6 +5873,36 @@ const executeTestPlan = async ({
5757
5873
  };
5758
5874
  const beforeEachCallbackSet = new Set();
5759
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
+ };
5760
5906
  const afterEachCallbackSet = new Set();
5761
5907
  const afterEachInOrderCallbackSet = new Set();
5762
5908
  const afterAllCallbackSet = new Set();
@@ -5889,6 +6035,16 @@ const executeTestPlan = async ({
5889
6035
  );
5890
6036
  }
5891
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
+ }
5892
6048
  // testPlan
5893
6049
  {
5894
6050
  if (typeof testPlan !== "object") {
@@ -6093,9 +6249,21 @@ To fix this warning:
6093
6249
  }
6094
6250
  }
6095
6251
 
6096
- 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
+ };
6097
6265
 
6098
- // collect files to execute + fill executionPlanifiedSet
6266
+ // collect files to execute + fill executionPlanifiedArray
6099
6267
  {
6100
6268
  const fileResultArray = await collectFiles({
6101
6269
  signal,
@@ -6103,6 +6271,11 @@ To fix this warning:
6103
6271
  associations: { testPlan },
6104
6272
  predicate: ({ testPlan }) => testPlan,
6105
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
+
6106
6279
  let index = 0;
6107
6280
  let lastExecution;
6108
6281
  const fileExecutionCountMap = new Map();
@@ -6125,10 +6298,6 @@ To fix this warning:
6125
6298
  ),
6126
6299
  );
6127
6300
  }
6128
- if (stepConfig.runtime?.disabled) {
6129
- continue;
6130
- }
6131
-
6132
6301
  const {
6133
6302
  runtime,
6134
6303
  runtimeParams,
@@ -6185,6 +6354,8 @@ To fix this warning:
6185
6354
  runtimeName,
6186
6355
  runtimeVersion,
6187
6356
  params,
6357
+ skipped: false,
6358
+ skipReason: "",
6188
6359
 
6189
6360
  // will be set by run()
6190
6361
  status: "planified",
@@ -6195,7 +6366,7 @@ To fix this warning:
6195
6366
  }
6196
6367
 
6197
6368
  lastExecution = execution;
6198
- executionPlanifiedSet.add(execution);
6369
+ executionPlanifiedArray.push(execution);
6199
6370
  const existingResults = results[relativeUrl];
6200
6371
  if (existingResults) {
6201
6372
  existingResults[groupName] = execution.result;
@@ -6215,9 +6386,48 @@ To fix this warning:
6215
6386
  runtimeVersion,
6216
6387
  };
6217
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
+ }
6218
6407
  index++;
6219
6408
  }
6220
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
+ }
6221
6431
  fileResultArray.length = 0;
6222
6432
  fileExecutionCountMap.clear();
6223
6433
  if (lastExecution) {
@@ -6228,11 +6438,11 @@ To fix this warning:
6228
6438
  counters.planified =
6229
6439
  counters.remaining =
6230
6440
  counters.waiting =
6231
- executionPlanifiedSet.size;
6441
+ executionPlanifiedArray.length;
6232
6442
  countersInOrder.planified =
6233
6443
  countersInOrder.remaining =
6234
6444
  countersInOrder.waiting =
6235
- executionPlanifiedSet.size;
6445
+ executionPlanifiedArray.length;
6236
6446
  if (githubCheck) {
6237
6447
  const githubCheckRun = await startGithubCheckRun({
6238
6448
  logLevel: githubCheck.logLevel,
@@ -6242,7 +6452,7 @@ To fix this warning:
6242
6452
  commitSha: githubCheck.commitSha,
6243
6453
  checkName: githubCheck.name,
6244
6454
  checkTitle: githubCheck.title,
6245
- checkSummary: `${executionPlanifiedSet.size} files will be executed`,
6455
+ checkSummary: `${executionPlanifiedArray.length} files will be executed`,
6246
6456
  });
6247
6457
  const annotations = [];
6248
6458
  reporters.push({
@@ -6322,80 +6532,66 @@ To fix this warning:
6322
6532
 
6323
6533
  const callWhenPreviousExecutionAreDone = createCallOrderer();
6324
6534
 
6325
- const executionRemainingSet = new Set(executionPlanifiedSet);
6535
+ const executionRemainingSet = new Set(executionPlanifiedArray);
6326
6536
  const executionExecutingSet = new Set();
6327
6537
  const usedTagSet = new Set();
6328
6538
  const start = async (execution) => {
6329
- if (execution.params.uses) {
6330
- for (const tagThatWillBeUsed of execution.params.uses) {
6331
- usedTagSet.add(tagThatWillBeUsed);
6332
- }
6333
- }
6334
6539
  execution.fileExecutionCount = Object.keys(
6335
6540
  testPlanResult.results[execution.fileRelativeUrl],
6336
6541
  ).length;
6337
6542
  mutateCountersBeforeExecutionStarts(counters, execution);
6338
6543
  mutateCountersBeforeExecutionStarts(countersInOrder, execution);
6339
-
6340
- execution.status = "executing";
6341
6544
  executionRemainingSet.delete(execution);
6342
6545
  executionExecutingSet.add(execution);
6343
- for (const beforeEachCallback of beforeEachCallbackSet) {
6344
- const returnValue = beforeEachCallback(execution, testPlanResult);
6345
- if (typeof returnValue === "function") {
6346
- const callback = (...args) => {
6347
- afterEachCallbackSet.delete(callback);
6348
- return returnValue(...args);
6349
- };
6350
- 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
+ }
6351
6555
  }
6352
- }
6353
- for (const beforeEachInOrderCallback of beforeEachInOrderCallbackSet) {
6354
- const returnValue = beforeEachInOrderCallback(
6355
- execution,
6356
- testPlanResult,
6357
- );
6358
- if (typeof returnValue === "function") {
6359
- const callback = (...args) => {
6360
- afterEachInOrderCallbackSet.delete(callback);
6361
- return returnValue(...args);
6362
- };
6363
- 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
+ }
6364
6572
  }
6365
- }
6366
- const executionResult = await run({
6367
- ...execution.params,
6368
- signal: operation.signal,
6369
- logger,
6370
- keepRunning,
6371
- mirrorConsole: false, // might be executed in parallel: log would be a mess to read
6372
- coverageEnabled: Boolean(coverage),
6373
- coverageTempDirectoryUrl: coverage?.tempDirectoryUrl,
6374
- });
6375
- Object.assign(execution.result, executionResult);
6376
- execution.status = "executed";
6377
- executionExecutingSet.delete(execution);
6378
- mutateCountersAfterExecutionEnds(counters, execution);
6379
- if (execution.result.status !== "completed") {
6380
- testPlanResult.failed = true;
6381
- if (updateProcessExitCode) {
6382
- process.exitCode = 1;
6573
+ if (execution.result.status !== "completed") {
6574
+ testPlanResult.failed = true;
6575
+ if (updateProcessExitCode) {
6576
+ process.exitCode = 1;
6577
+ }
6383
6578
  }
6384
6579
  }
6580
+ mutateCountersAfterExecutionEnds(counters, execution);
6581
+ executionExecutingSet.delete(execution);
6385
6582
  for (const afterEachCallback of afterEachCallbackSet) {
6386
- afterEachCallback(execution, testPlanResult);
6583
+ afterEachCallback(execution, testPlanResult, testPlanHelpers);
6387
6584
  }
6388
6585
  callWhenPreviousExecutionAreDone(execution.index, () => {
6389
6586
  mutateCountersAfterExecutionEnds(countersInOrder, execution);
6390
6587
  for (const afterEachInOrderCallback of afterEachInOrderCallbackSet) {
6391
- afterEachInOrderCallback(execution, testPlanResult);
6588
+ afterEachInOrderCallback(
6589
+ execution,
6590
+ testPlanResult,
6591
+ testPlanHelpers,
6592
+ );
6392
6593
  }
6393
6594
  });
6394
- if (execution.params.uses) {
6395
- for (const tagNoLongerInUse of execution.params.uses) {
6396
- usedTagSet.delete(tagNoLongerInUse);
6397
- }
6398
- }
6399
6595
  if (testPlanResult.failed && failFast && counters.remaining) {
6400
6596
  logger.info(`"failFast" enabled -> cancel remaining executions`);
6401
6597
  failFastAbortController.abort();
@@ -6609,7 +6805,9 @@ const mutateCountersAfterExecutionEnds = (counters, execution) => {
6609
6805
  counters.executing--;
6610
6806
  counters.executed++;
6611
6807
  counters.remaining--;
6612
- if (execution.result.status === "aborted") {
6808
+ if (execution.result.status === "skipped") {
6809
+ counters.skipped++;
6810
+ } else if (execution.result.status === "aborted") {
6613
6811
  counters.aborted++;
6614
6812
  } else if (execution.result.status === "timedout") {
6615
6813
  counters.timedout++;
@@ -7479,7 +7677,7 @@ const createFirefoxRuntime = ({
7479
7677
  if (disableOnWindowsBecauseFlaky === undefined) {
7480
7678
  // https://github.com/microsoft/playwright/issues/1396
7481
7679
  console.warn(
7482
- `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).
7483
7681
  To disable this warning, use disableOnWindowsBecauseFlaky: true
7484
7682
  To ignore potential flakyness, use disableOnWindowsBecauseFlaky: false`,
7485
7683
  );
@@ -7488,6 +7686,7 @@ To ignore potential flakyness, use disableOnWindowsBecauseFlaky: false`,
7488
7686
  if (disableOnWindowsBecauseFlaky) {
7489
7687
  return {
7490
7688
  disabled: true,
7689
+ disabledReason: "flaky on windows",
7491
7690
  };
7492
7691
  }
7493
7692
  }
@@ -7874,11 +8073,8 @@ const nodeChildProcess = ({
7874
8073
  env = {
7875
8074
  ...env,
7876
8075
  JSENV: true,
7877
- FORCE_COLOR:
7878
- supportsColor.stdout ||
7879
- // GitHub workflow does support ANSI but "supports-color" returns false
7880
- // because stream.isTTY returns false, see https://github.com/actions/runner/issues/241
7881
- process.env.GITHUB_WORKFLOW,
8076
+ ...(ANSI.supported ? { FORCE_COLOR: "1" } : {}),
8077
+ ...(UNICODE.supported ? { FORCE_UNICODE: "1" } : {}),
7882
8078
  };
7883
8079
 
7884
8080
  return {
@@ -8353,11 +8549,8 @@ const nodeWorkerThread = ({
8353
8549
  env = {
8354
8550
  ...env,
8355
8551
  JSENV: true,
8356
- FORCE_COLOR:
8357
- supportsColor.stdout ||
8358
- // GitHub workflow does support ANSI but "supports-color" returns false
8359
- // because stream.isTTY returns false, see https://github.com/actions/runner/issues/241
8360
- process.env.GITHUB_WORKFLOW,
8552
+ ...(ANSI.supported ? { FORCE_COLOR: "1" } : {}),
8553
+ ...(UNICODE.supported ? { FORCE_UNICODE: "1" } : {}),
8361
8554
  };
8362
8555
 
8363
8556
  return {
@@ -8966,22 +9159,26 @@ const reportAsJunitXml = async (
8966
9159
  });
8967
9160
  testSuite.appendChild(testCase);
8968
9161
 
8969
- 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") {
8970
9169
  testCase.attributes.skipped = 1;
8971
9170
  const skipped = testCase.createNode("skipped", {
8972
9171
  message: "Execution was aborted",
8973
9172
  });
8974
9173
  testSuite.appendChild(skipped);
8975
- }
8976
- if (executionResult.status === "timedout") {
9174
+ } else if (executionResult.status === "timedout") {
8977
9175
  testCase.attributes.failures = 1;
8978
9176
  const failure = testCase.createNode("failure", {
8979
9177
  message: `Execution timeout after ${executionResult.params.allocatedMs}ms"`,
8980
9178
  type: "timeout",
8981
9179
  });
8982
9180
  testSuite.appendChild(failure);
8983
- }
8984
- if (executionResult.status === "failed") {
9181
+ } else if (executionResult.status === "failed") {
8985
9182
  const [error] = executionResult.errors;
8986
9183
  if (
8987
9184
  error &&
@@ -9035,7 +9232,7 @@ const reportAsJunitXml = async (
9035
9232
  propertiesNode.appendChild(propertyNode);
9036
9233
  }
9037
9234
 
9038
- const { consoleCalls } = executionResult;
9235
+ const { consoleCalls = [] } = executionResult;
9039
9236
  let stdout = "";
9040
9237
  let stderr = "";
9041
9238
  for (const consoleCall of consoleCalls) {
@@ -9068,6 +9265,123 @@ ${testSuite.renderAsString()}`;
9068
9265
  writeFileSync(new URL(fileUrl), junitXmlFileContent);
9069
9266
  };
9070
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
+
9071
9385
  /*
9072
9386
  * Export a function capable to execute a file on a runtime (browser or node) and return how it goes.
9073
9387
  *
@@ -9262,4 +9576,4 @@ const inlineRuntime = (fn) => {
9262
9576
  };
9263
9577
  };
9264
9578
 
9265
- 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 };