@elench/testkit 0.1.28 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/runner/index.mjs +11 -3
- package/lib/runner/results.mjs +28 -10
- package/lib/runner/results.test.mjs +121 -0
- package/package.json +1 -1
package/lib/runner/index.mjs
CHANGED
|
@@ -642,7 +642,7 @@ async function runPlaywrightBatch(targetConfig, batch) {
|
|
|
642
642
|
const startedAt = Date.now();
|
|
643
643
|
const result = await execa(
|
|
644
644
|
"npx",
|
|
645
|
-
["playwright", "test", "--config", playwrightConfigPath, "--reporter=json"
|
|
645
|
+
["playwright", "test", "--config", playwrightConfigPath, "--reporter=json"],
|
|
646
646
|
{
|
|
647
647
|
cwd,
|
|
648
648
|
env: buildPlaywrightEnv(targetConfig, local.baseUrl),
|
|
@@ -655,7 +655,8 @@ async function runPlaywrightBatch(targetConfig, batch) {
|
|
|
655
655
|
}
|
|
656
656
|
|
|
657
657
|
const parsed = parsePlaywrightJsonResults(result.stdout, cwd);
|
|
658
|
-
const
|
|
658
|
+
const finishedAt = Date.now();
|
|
659
|
+
const batchDurationMs = finishedAt - startedAt;
|
|
659
660
|
const genericError =
|
|
660
661
|
result.exitCode === 0
|
|
661
662
|
? parsed.errors[0] || null
|
|
@@ -677,6 +678,8 @@ async function runPlaywrightBatch(targetConfig, batch) {
|
|
|
677
678
|
fileResult.durationMs > 0
|
|
678
679
|
? fileResult.durationMs
|
|
679
680
|
: Math.round(batchDurationMs / Math.max(1, batch.tasks.length)),
|
|
681
|
+
startedAt,
|
|
682
|
+
finishedAt,
|
|
680
683
|
};
|
|
681
684
|
}
|
|
682
685
|
|
|
@@ -685,6 +688,8 @@ async function runPlaywrightBatch(targetConfig, batch) {
|
|
|
685
688
|
failed: result.exitCode !== 0,
|
|
686
689
|
error: result.exitCode !== 0 ? genericError : null,
|
|
687
690
|
durationMs: Math.round(batchDurationMs / Math.max(1, batch.tasks.length)),
|
|
691
|
+
startedAt,
|
|
692
|
+
finishedAt,
|
|
688
693
|
};
|
|
689
694
|
});
|
|
690
695
|
}
|
|
@@ -1087,12 +1092,15 @@ async function runDefaultRuntimeTask(targetConfig, task, args) {
|
|
|
1087
1092
|
|
|
1088
1093
|
const summary = readDefaultRuntimeSummary(summaryFile);
|
|
1089
1094
|
const runtimeError = determineDefaultRuntimeFailure(result, summary);
|
|
1095
|
+
const finishedAt = Date.now();
|
|
1090
1096
|
|
|
1091
1097
|
return {
|
|
1092
1098
|
task,
|
|
1093
1099
|
failed: runtimeError !== null,
|
|
1094
1100
|
error: runtimeError,
|
|
1095
|
-
durationMs:
|
|
1101
|
+
durationMs: finishedAt - startedAt,
|
|
1102
|
+
startedAt,
|
|
1103
|
+
finishedAt,
|
|
1096
1104
|
};
|
|
1097
1105
|
}
|
|
1098
1106
|
|
package/lib/runner/results.mjs
CHANGED
|
@@ -17,6 +17,7 @@ export function buildServiceTrackers(servicePlans, startedAt) {
|
|
|
17
17
|
startedAt,
|
|
18
18
|
firstTaskAt: null,
|
|
19
19
|
lastTaskAt: null,
|
|
20
|
+
totalTaskDurationMs: 0,
|
|
20
21
|
});
|
|
21
22
|
continue;
|
|
22
23
|
}
|
|
@@ -72,6 +73,7 @@ export function buildServiceTrackers(servicePlans, startedAt) {
|
|
|
72
73
|
startedAt,
|
|
73
74
|
firstTaskAt: null,
|
|
74
75
|
lastTaskAt: null,
|
|
76
|
+
totalTaskDurationMs: 0,
|
|
75
77
|
});
|
|
76
78
|
}
|
|
77
79
|
|
|
@@ -82,26 +84,38 @@ export function recordTaskOutcome(trackers, task, outcome, finishedAt = Date.now
|
|
|
82
84
|
const tracker = trackers.get(task.serviceName);
|
|
83
85
|
if (!tracker || tracker.skipped) return;
|
|
84
86
|
|
|
85
|
-
if (!tracker.firstTaskAt) tracker.firstTaskAt = finishedAt;
|
|
86
|
-
tracker.lastTaskAt = finishedAt;
|
|
87
|
-
|
|
88
87
|
const suite = tracker.suitesByKey.get(task.suiteKey);
|
|
89
88
|
if (!suite) return;
|
|
90
89
|
|
|
90
|
+
const outcomeDurationMs = Number(outcome.durationMs ?? 0);
|
|
91
|
+
const outcomeFinishedAt = Number(outcome.finishedAt ?? finishedAt);
|
|
92
|
+
const outcomeStartedAt = Number(
|
|
93
|
+
outcome.startedAt ?? Math.max(0, outcomeFinishedAt - outcomeDurationMs)
|
|
94
|
+
);
|
|
95
|
+
tracker.firstTaskAt =
|
|
96
|
+
tracker.firstTaskAt === null
|
|
97
|
+
? outcomeStartedAt
|
|
98
|
+
: Math.min(tracker.firstTaskAt, outcomeStartedAt);
|
|
99
|
+
tracker.lastTaskAt =
|
|
100
|
+
tracker.lastTaskAt === null
|
|
101
|
+
? outcomeFinishedAt
|
|
102
|
+
: Math.max(tracker.lastTaskAt, outcomeFinishedAt);
|
|
103
|
+
tracker.totalTaskDurationMs += outcomeDurationMs;
|
|
104
|
+
|
|
91
105
|
suite.completedFileCount += 1;
|
|
92
|
-
suite.durationMs +=
|
|
106
|
+
suite.durationMs += outcomeDurationMs;
|
|
93
107
|
const normalizedPath = normalizePathSeparators(task.file);
|
|
94
108
|
const existingFileResult = suite.fileResultsByPath.get(normalizedPath);
|
|
95
109
|
if (existingFileResult) {
|
|
96
110
|
existingFileResult.failed = outcome.failed;
|
|
97
|
-
existingFileResult.durationMs =
|
|
111
|
+
existingFileResult.durationMs = outcomeDurationMs;
|
|
98
112
|
existingFileResult.error = outcome.error;
|
|
99
113
|
existingFileResult.status = outcome.failed ? "failed" : "passed";
|
|
100
114
|
} else {
|
|
101
115
|
const fileResult = {
|
|
102
116
|
path: normalizedPath,
|
|
103
117
|
failed: outcome.failed,
|
|
104
|
-
durationMs:
|
|
118
|
+
durationMs: outcomeDurationMs,
|
|
105
119
|
error: outcome.error,
|
|
106
120
|
status: outcome.failed ? "failed" : "passed",
|
|
107
121
|
};
|
|
@@ -144,6 +158,7 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
|
|
|
144
158
|
completedSuiteCount: 0,
|
|
145
159
|
failedSuiteCount: 0,
|
|
146
160
|
durationMs: 0,
|
|
161
|
+
totalTaskDurationMs: 0,
|
|
147
162
|
suites: [],
|
|
148
163
|
errors: [],
|
|
149
164
|
};
|
|
@@ -169,11 +184,12 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
|
|
|
169
184
|
0
|
|
170
185
|
);
|
|
171
186
|
const notRunFileCount = totalFileCount - completedFileCount;
|
|
172
|
-
const
|
|
187
|
+
const totalTaskDurationMs =
|
|
188
|
+
tracker.totalTaskDurationMs || suites.reduce((sum, suite) => sum + suite.durationMs, 0);
|
|
173
189
|
const durationMs =
|
|
174
|
-
tracker.firstTaskAt && tracker.lastTaskAt
|
|
175
|
-
? Math.max(tracker.lastTaskAt - tracker.firstTaskAt
|
|
176
|
-
: Math.max(finishedAt - startedAt
|
|
190
|
+
tracker.firstTaskAt !== null && tracker.lastTaskAt !== null
|
|
191
|
+
? Math.max(0, tracker.lastTaskAt - tracker.firstTaskAt)
|
|
192
|
+
: Math.max(0, finishedAt - startedAt);
|
|
177
193
|
|
|
178
194
|
return {
|
|
179
195
|
name: tracker.name,
|
|
@@ -192,6 +208,7 @@ export function finalizeServiceResult(tracker, startedAt, finishedAt) {
|
|
|
192
208
|
failedFileCount,
|
|
193
209
|
notRunFileCount,
|
|
194
210
|
durationMs,
|
|
211
|
+
totalTaskDurationMs,
|
|
195
212
|
suites: suites.map((suite) => ({
|
|
196
213
|
name: suite.name,
|
|
197
214
|
type: suite.type,
|
|
@@ -385,6 +402,7 @@ export function buildRunArtifact({
|
|
|
385
402
|
failedFileCount: result.failedFileCount,
|
|
386
403
|
notRunFileCount: result.notRunFileCount,
|
|
387
404
|
durationMs: result.durationMs,
|
|
405
|
+
totalTaskDurationMs: result.totalTaskDurationMs,
|
|
388
406
|
dbBackend: result.dbBackend,
|
|
389
407
|
suites: result.suites,
|
|
390
408
|
errors: result.errors,
|
|
@@ -81,6 +81,123 @@ describe("runner-results", () => {
|
|
|
81
81
|
]);
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
+
it("reports service duration as wall-clock time, not accumulated file time", () => {
|
|
85
|
+
const trackers = buildServiceTrackers(
|
|
86
|
+
[
|
|
87
|
+
{
|
|
88
|
+
skipped: false,
|
|
89
|
+
config: {
|
|
90
|
+
name: "frontend",
|
|
91
|
+
testkit: {
|
|
92
|
+
database: {
|
|
93
|
+
selectedBackend: null,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
suites: [
|
|
98
|
+
{
|
|
99
|
+
name: "browser",
|
|
100
|
+
type: "e2e",
|
|
101
|
+
framework: "playwright",
|
|
102
|
+
files: ["tests/a.pw.testkit.ts", "tests/b.pw.testkit.ts"],
|
|
103
|
+
orderIndex: 0,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
1000
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
recordTaskOutcome(
|
|
112
|
+
trackers,
|
|
113
|
+
{
|
|
114
|
+
serviceName: "frontend",
|
|
115
|
+
suiteKey: "e2e:browser",
|
|
116
|
+
file: "tests/a.pw.testkit.ts",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
failed: false,
|
|
120
|
+
durationMs: 30_000,
|
|
121
|
+
startedAt: 1000,
|
|
122
|
+
finishedAt: 8000,
|
|
123
|
+
error: null,
|
|
124
|
+
},
|
|
125
|
+
8000
|
|
126
|
+
);
|
|
127
|
+
recordTaskOutcome(
|
|
128
|
+
trackers,
|
|
129
|
+
{
|
|
130
|
+
serviceName: "frontend",
|
|
131
|
+
suiteKey: "e2e:browser",
|
|
132
|
+
file: "tests/b.pw.testkit.ts",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
failed: false,
|
|
136
|
+
durationMs: 45_000,
|
|
137
|
+
startedAt: 3000,
|
|
138
|
+
finishedAt: 9000,
|
|
139
|
+
error: null,
|
|
140
|
+
},
|
|
141
|
+
9000
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const result = finalizeServiceResult(trackers.get("frontend"), 1000, 10_000);
|
|
145
|
+
|
|
146
|
+
expect(result.durationMs).toBe(8000);
|
|
147
|
+
expect(result.totalTaskDurationMs).toBe(75_000);
|
|
148
|
+
expect(result.suites[0].durationMs).toBe(75_000);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("handles epoch-zero task timestamps when calculating wall-clock duration", () => {
|
|
152
|
+
const trackers = buildServiceTrackers(
|
|
153
|
+
[
|
|
154
|
+
{
|
|
155
|
+
skipped: false,
|
|
156
|
+
config: {
|
|
157
|
+
name: "frontend",
|
|
158
|
+
testkit: {
|
|
159
|
+
database: {
|
|
160
|
+
selectedBackend: null,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
suites: [
|
|
165
|
+
{
|
|
166
|
+
name: "browser",
|
|
167
|
+
type: "e2e",
|
|
168
|
+
framework: "playwright",
|
|
169
|
+
files: ["tests/a.pw.testkit.ts"],
|
|
170
|
+
orderIndex: 0,
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
0
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
recordTaskOutcome(
|
|
179
|
+
trackers,
|
|
180
|
+
{
|
|
181
|
+
serviceName: "frontend",
|
|
182
|
+
suiteKey: "e2e:browser",
|
|
183
|
+
file: "tests/a.pw.testkit.ts",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
failed: false,
|
|
187
|
+
durationMs: 3_000,
|
|
188
|
+
startedAt: 0,
|
|
189
|
+
finishedAt: 8_000,
|
|
190
|
+
error: null,
|
|
191
|
+
},
|
|
192
|
+
8_000
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const result = finalizeServiceResult(trackers.get("frontend"), 0, 10_000);
|
|
196
|
+
|
|
197
|
+
expect(result.durationMs).toBe(8_000);
|
|
198
|
+
expect(result.totalTaskDurationMs).toBe(3_000);
|
|
199
|
+
});
|
|
200
|
+
|
|
84
201
|
it("builds run artifacts and formatting helpers", () => {
|
|
85
202
|
const results = [
|
|
86
203
|
{
|
|
@@ -96,6 +213,7 @@ describe("runner-results", () => {
|
|
|
96
213
|
failedFileCount: 0,
|
|
97
214
|
notRunFileCount: 0,
|
|
98
215
|
durationMs: 1200,
|
|
216
|
+
totalTaskDurationMs: 2400,
|
|
99
217
|
dbBackend: "local",
|
|
100
218
|
suites: [],
|
|
101
219
|
errors: [],
|
|
@@ -113,6 +231,7 @@ describe("runner-results", () => {
|
|
|
113
231
|
failedFileCount: 0,
|
|
114
232
|
notRunFileCount: 0,
|
|
115
233
|
durationMs: 0,
|
|
234
|
+
totalTaskDurationMs: 0,
|
|
116
235
|
dbBackend: null,
|
|
117
236
|
suites: [],
|
|
118
237
|
errors: [],
|
|
@@ -154,6 +273,8 @@ describe("runner-results", () => {
|
|
|
154
273
|
failed: 0,
|
|
155
274
|
notRun: 0,
|
|
156
275
|
});
|
|
276
|
+
expect(artifact.services[0].durationMs).toBe(1200);
|
|
277
|
+
expect(artifact.services[0].totalTaskDurationMs).toBe(2400);
|
|
157
278
|
expect(summarizeDbBackend(results)).toBe("local");
|
|
158
279
|
expect(formatDuration(65_000)).toBe("1m 05s");
|
|
159
280
|
expect(
|