@empiricalrun/playwright-utils 0.58.6 → 0.59.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 +1 @@
1
- {"version":3,"file":"test-case-sync.d.ts","sourceRoot":"","sources":["../../src/reporter/test-case-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,oBAAoB,EAErB,MAAM,6CAA6C,CAAC;AACrD,OAAO,KAAK,EACV,UAAU,EAEV,KAAK,EAEN,MAAM,2BAA2B,CAAC;AAUnC,YAAY,EACV,uBAAuB,EACvB,6BAA6B,EAC7B,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,6CAA6C,CAAC;AA+JrD,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,KAAK,GACf,oBAAoB,CAuBtB"}
1
+ {"version":3,"file":"test-case-sync.d.ts","sourceRoot":"","sources":["../../src/reporter/test-case-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,oBAAoB,EAErB,MAAM,6CAA6C,CAAC;AACrD,OAAO,KAAK,EACV,UAAU,EAEV,KAAK,EAEN,MAAM,2BAA2B,CAAC;AAUnC,YAAY,EACV,uBAAuB,EACvB,6BAA6B,EAC7B,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,6CAA6C,CAAC;AAwRrD,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,KAAK,GACf,oBAAoB,CAyBtB"}
@@ -41,7 +41,7 @@ function stableGroupId(input) {
41
41
  input.column,
42
42
  ]))
43
43
  .digest("hex");
44
- return `playwright_serial:${hash}`;
44
+ return `${input.kind}:${hash}`;
45
45
  }
46
46
  function collectDirectTestCases(rootDir, suite, state) {
47
47
  return suite.tests.map((test) => ({
@@ -53,11 +53,7 @@ function collectDirectTestCases(rootDir, suite, state) {
53
53
  tags: normalizeTags(test.tags),
54
54
  }));
55
55
  }
56
- function collectOutermostSerialGroup(rootDir, suite, state) {
57
- if (state.serialAncestor || getSuiteParallelMode(suite) !== "serial") {
58
- return null;
59
- }
60
- const tests = suite.allTests();
56
+ function buildDependencyGroup(rootDir, suite, state, kind, tests) {
61
57
  if (tests.length < 2) {
62
58
  return null;
63
59
  }
@@ -71,13 +67,14 @@ function collectOutermostSerialGroup(rootDir, suite, state) {
71
67
  const location = suite.location ?? firstTest.location;
72
68
  return {
73
69
  id: stableGroupId({
70
+ kind,
74
71
  projectName,
75
72
  filePath,
76
73
  titlePath,
77
74
  line: location.line,
78
75
  column: location.column,
79
76
  }),
80
- kind: "playwright_serial",
77
+ kind,
81
78
  file_path: filePath,
82
79
  title_path: titlePath,
83
80
  playwright_project: projectName,
@@ -87,14 +84,81 @@ function collectOutermostSerialGroup(rootDir, suite, state) {
87
84
  })),
88
85
  };
89
86
  }
87
+ function collectOutermostSerialGroup(rootDir, suite, state) {
88
+ if (state.serialAncestor || getSuiteParallelMode(suite) !== "serial") {
89
+ return null;
90
+ }
91
+ return buildDependencyGroup(rootDir, suite, state, "playwright_serial", suite.allTests());
92
+ }
93
+ /**
94
+ * Tests under this suite that Playwright would place in its sequential
95
+ * ("general") group: everything except subtrees that opt back into
96
+ * parallelism. Order is declaration order, which is the order the group
97
+ * executes in.
98
+ */
99
+ function sequentialTests(suite) {
100
+ const tests = [...suite.tests];
101
+ for (const child of suite.suites) {
102
+ if (getSuiteParallelMode(child) !== "parallel") {
103
+ tests.push(...sequentialTests(child));
104
+ }
105
+ }
106
+ return tests;
107
+ }
108
+ /**
109
+ * The `playwright_default` counterpart of the serial collector: the outermost
110
+ * scope whose tests Playwright schedules as one ordered worker group without
111
+ * serial semantics.
112
+ *
113
+ * Two shapes qualify (mirroring `createTestGroups` in Playwright's
114
+ * `runner/testGroups.ts`):
115
+ *
116
+ * - a `test.describe.configure({ mode: "default" })` suite — grouped whole,
117
+ * including any nested parallel subtree, because Playwright keys the group
118
+ * on the outermost sequential suite;
119
+ * - a file with no explicit mode in a non-fullyParallel project — Playwright's
120
+ * per-file "general" group, which excludes subtrees that opt back into
121
+ * `parallel`.
122
+ */
123
+ function collectOutermostDefaultGroup(rootDir, suite, state) {
124
+ if (state.sequentialAncestor) {
125
+ return null;
126
+ }
127
+ const mode = getSuiteParallelMode(suite);
128
+ if (mode === "default") {
129
+ return buildDependencyGroup(rootDir, suite, state, "playwright_default", suite.allTests());
130
+ }
131
+ if (suite.type === "file" && !state.insideParallel && mode === "none") {
132
+ return buildDependencyGroup(rootDir, suite, state, "playwright_default", sequentialTests(suite));
133
+ }
134
+ return null;
135
+ }
90
136
  function walkSuite(rootDir, suite, state, testCases, dependencyGroups) {
91
137
  const isDescribe = suite.type === "describe";
92
138
  const suites = isDescribe ? [...state.suites, suite.title] : state.suites;
93
139
  const nextState = { ...state, suites };
140
+ const mode = getSuiteParallelMode(suite);
141
+ // A serial group is emitted even inside a default scope: the outer default
142
+ // group wins for scheduling, but serial membership carries extra semantics
143
+ // (failure cascade, re-expansion) its consumers still need.
94
144
  const serialGroup = collectOutermostSerialGroup(rootDir, suite, nextState);
95
145
  if (serialGroup) {
96
146
  dependencyGroups.push(serialGroup);
97
147
  nextState.serialAncestor = suite;
148
+ if (!nextState.sequentialAncestor) {
149
+ nextState.sequentialAncestor = suite;
150
+ }
151
+ }
152
+ const defaultGroup = collectOutermostDefaultGroup(rootDir, suite, nextState);
153
+ if (defaultGroup) {
154
+ dependencyGroups.push(defaultGroup);
155
+ nextState.sequentialAncestor = suite;
156
+ }
157
+ // fullyParallel (project suite) or an explicit `mode: "parallel"` — every
158
+ // descendant is independently schedulable unless a sequential suite below
159
+ // says otherwise.
160
+ if (mode === "parallel") {
161
+ nextState.insideParallel = true;
98
162
  }
99
163
  testCases.push(...collectDirectTestCases(rootDir, suite, nextState));
100
164
  for (const child of suite.suites) {
@@ -112,6 +176,15 @@ function walkSuite(rootDir, suite, state, testCases, dependencyGroups) {
112
176
  : "";
113
177
  childState.suites = [];
114
178
  childState.serialAncestor = null;
179
+ childState.sequentialAncestor = null;
180
+ }
181
+ else if (childState.sequentialAncestor &&
182
+ getSuiteParallelMode(childState.sequentialAncestor) === "none" &&
183
+ getSuiteParallelMode(child) === "parallel") {
184
+ // The scope was a plain file's "general" group, which excludes subtrees
185
+ // that opt back into parallel — so below this child, sequential suites
186
+ // start their own groups again.
187
+ childState.sequentialAncestor = null;
115
188
  }
116
189
  walkSuite(rootDir, child, childState, testCases, dependencyGroups);
117
190
  }
@@ -125,6 +198,8 @@ function collectTestCaseSyncManifest(config, rootSuite) {
125
198
  filePath: "",
126
199
  suites: [],
127
200
  serialAncestor: null,
201
+ sequentialAncestor: null,
202
+ insideParallel: false,
128
203
  }, testCases, dependencyGroups);
129
204
  return {
130
205
  version: 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/playwright-utils",
3
- "version": "0.58.6",
3
+ "version": "0.59.0",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -41,7 +41,7 @@
41
41
  "openapi-typescript": "7.6.1",
42
42
  "playwright-core": "1.62.1",
43
43
  "serve-handler": "^6.1.6",
44
- "@empiricalrun/shared-types": "0.15.4"
44
+ "@empiricalrun/shared-types": "0.16.0"
45
45
  },
46
46
  "dependencies": {
47
47
  "@babel/code-frame": "^7.24.7",