@elench/testkit 0.1.32 → 0.1.34
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/artifacts.mjs +43 -0
- package/lib/runner/default-runtime-errors.mjs +53 -0
- package/lib/runner/default-runtime-errors.test.mjs +49 -0
- package/lib/runner/default-runtime-runner.mjs +119 -0
- package/lib/runner/formatting.mjs +129 -0
- package/lib/runner/formatting.test.mjs +100 -0
- package/lib/runner/index.mjs +2 -1575
- package/lib/runner/maintenance.mjs +72 -0
- package/lib/runner/orchestrator.mjs +254 -0
- package/lib/runner/playwright-config.mjs +61 -0
- package/lib/runner/playwright-config.test.mjs +58 -0
- package/lib/runner/playwright-runner.mjs +85 -0
- package/lib/runner/processes.mjs +106 -0
- package/lib/runner/readiness.mjs +117 -0
- package/lib/runner/reporting.mjs +180 -0
- package/lib/runner/reporting.test.mjs +193 -0
- package/lib/runner/results.mjs +36 -266
- package/lib/runner/results.test.mjs +4 -204
- package/lib/runner/runtime-contexts.mjs +133 -0
- package/lib/runner/selection.mjs +33 -0
- package/lib/runner/selection.test.mjs +25 -0
- package/lib/runner/services.mjs +73 -0
- package/lib/runner/state-io.mjs +25 -0
- package/lib/runner/worker-loop.mjs +95 -0
- package/package.json +1 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import net from "net";
|
|
2
|
+
import {
|
|
3
|
+
cleanupStaleRuns,
|
|
4
|
+
findPortOwner,
|
|
5
|
+
formatRunSummary,
|
|
6
|
+
isPidRunning,
|
|
7
|
+
listRunManifests,
|
|
8
|
+
} from "./lifecycle.mjs";
|
|
9
|
+
import { socketFromUrl } from "./template.mjs";
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_READY_TIMEOUT_MS = 120_000;
|
|
12
|
+
|
|
13
|
+
export async function waitForReady({ name, url, timeoutMs, process, signal, sleep }) {
|
|
14
|
+
const start = Date.now();
|
|
15
|
+
|
|
16
|
+
while (Date.now() - start < timeoutMs) {
|
|
17
|
+
if (signal?.aborted) {
|
|
18
|
+
throw signal.reason || new Error(`Service "${name}" startup aborted`);
|
|
19
|
+
}
|
|
20
|
+
if (process.exitCode !== null) {
|
|
21
|
+
throw new Error(`Service "${name}" exited before becoming ready`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(url);
|
|
26
|
+
if (response.ok) return;
|
|
27
|
+
} catch {
|
|
28
|
+
// Service still warming up.
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await sleep(1_000);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
throw new Error(`Timed out waiting for "${name}" to become ready at ${url}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function assertLocalServicePortsAvailable(config, isPortInUse) {
|
|
38
|
+
const endpoints = [config.testkit.local.baseUrl, config.testkit.local.readyUrl];
|
|
39
|
+
const seen = new Set();
|
|
40
|
+
|
|
41
|
+
for (const endpoint of endpoints) {
|
|
42
|
+
const socket = socketFromUrl(endpoint);
|
|
43
|
+
if (!socket) continue;
|
|
44
|
+
|
|
45
|
+
const key = `${socket.host}:${socket.port}`;
|
|
46
|
+
if (seen.has(key)) continue;
|
|
47
|
+
seen.add(key);
|
|
48
|
+
|
|
49
|
+
if (await isPortInUse(socket)) {
|
|
50
|
+
await cleanupStaleRuns(config.productDir);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (await isPortInUse(socket)) {
|
|
54
|
+
const owner = findPortOwner(config.productDir, socket);
|
|
55
|
+
const ownerDetail = owner
|
|
56
|
+
? owner.active
|
|
57
|
+
? ` Active testkit run ${formatRunSummary(owner.manifest)} owns ${key} via ${owner.service.workerLabel}:${owner.service.serviceName}.`
|
|
58
|
+
: ` Stale testkit run ${formatRunSummary(owner.manifest)} owns ${key}.`
|
|
59
|
+
: "";
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Cannot start "${config.workerLabel}:${config.name}" because ${key} is already in use. ` +
|
|
62
|
+
`Stop the existing process and rerun testkit.${ownerDetail}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function isPortInUse({ host, port }) {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const socket = new net.Socket();
|
|
71
|
+
let settled = false;
|
|
72
|
+
|
|
73
|
+
const finish = (value, error = null) => {
|
|
74
|
+
if (settled) return;
|
|
75
|
+
settled = true;
|
|
76
|
+
socket.destroy();
|
|
77
|
+
if (error) {
|
|
78
|
+
reject(error);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
resolve(value);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
socket.setTimeout(1_000);
|
|
85
|
+
socket.once("connect", () => finish(true));
|
|
86
|
+
socket.once("timeout", () => finish(false));
|
|
87
|
+
socket.once("error", (error) => {
|
|
88
|
+
if (["ECONNREFUSED", "EHOSTUNREACH", "ENOTFOUND"].includes(error.code)) {
|
|
89
|
+
finish(false);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
finish(false, error);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
socket.connect(port, host);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function printRunStatus(productDir) {
|
|
100
|
+
const manifests = listRunManifests(productDir);
|
|
101
|
+
if (manifests.length === 0) return;
|
|
102
|
+
|
|
103
|
+
console.log(" runs/");
|
|
104
|
+
for (const manifest of manifests) {
|
|
105
|
+
const state = isPidRunning(manifest.pid) ? "active" : "stale";
|
|
106
|
+
const ports = [
|
|
107
|
+
...new Set(
|
|
108
|
+
(manifest.services || []).flatMap((service) =>
|
|
109
|
+
(service.ports || []).map((socket) => `${socket.host}:${socket.port}`)
|
|
110
|
+
)
|
|
111
|
+
),
|
|
112
|
+
];
|
|
113
|
+
console.log(
|
|
114
|
+
` ${manifest.runId}: ${state} pid=${manifest.pid}${ports.length > 0 ? ` ports=${ports.join(",")}` : ""}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
|
|
3
|
+
export function buildStatusArtifact({
|
|
4
|
+
productDir,
|
|
5
|
+
results,
|
|
6
|
+
suiteType,
|
|
7
|
+
suiteNames,
|
|
8
|
+
fileNames,
|
|
9
|
+
framework,
|
|
10
|
+
shard,
|
|
11
|
+
serviceFilter,
|
|
12
|
+
metadata,
|
|
13
|
+
}) {
|
|
14
|
+
const executedResults = results.filter((result) => !result.skipped);
|
|
15
|
+
const tests = [];
|
|
16
|
+
|
|
17
|
+
for (const result of executedResults) {
|
|
18
|
+
for (const suite of result.suites) {
|
|
19
|
+
for (const file of suite.files) {
|
|
20
|
+
tests.push({
|
|
21
|
+
service: result.name,
|
|
22
|
+
type: suite.type,
|
|
23
|
+
path: file.path,
|
|
24
|
+
status: file.status,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
tests.sort(
|
|
31
|
+
(left, right) =>
|
|
32
|
+
left.service.localeCompare(right.service) ||
|
|
33
|
+
left.type.localeCompare(right.type) ||
|
|
34
|
+
left.path.localeCompare(right.path)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const summary = {
|
|
38
|
+
services: {
|
|
39
|
+
total: executedResults.length,
|
|
40
|
+
passed: executedResults.filter((result) => !result.failed).length,
|
|
41
|
+
failed: executedResults.filter((result) => result.failed).length,
|
|
42
|
+
},
|
|
43
|
+
tests: {
|
|
44
|
+
total: tests.length,
|
|
45
|
+
passed: tests.filter((test) => test.status === "passed").length,
|
|
46
|
+
failed: tests.filter((test) => test.status === "failed").length,
|
|
47
|
+
notRun: tests.filter((test) => test.status === "not_run").length,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const scope = {
|
|
52
|
+
suiteType,
|
|
53
|
+
suiteNames: [...(suiteNames || [])].sort(),
|
|
54
|
+
fileNames: [...(fileNames || [])].sort(),
|
|
55
|
+
framework: formatFrameworkForArtifact(framework || "all"),
|
|
56
|
+
shard: shard || null,
|
|
57
|
+
serviceFilter: serviceFilter || null,
|
|
58
|
+
};
|
|
59
|
+
scope.isFullRun =
|
|
60
|
+
scope.suiteNames.length === 0 &&
|
|
61
|
+
scope.fileNames.length === 0 &&
|
|
62
|
+
scope.framework === "all" &&
|
|
63
|
+
scope.shard === null &&
|
|
64
|
+
scope.serviceFilter === null;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
schemaVersion: 1,
|
|
68
|
+
source: "testkit",
|
|
69
|
+
notice: "Generated file. Do not edit manually.",
|
|
70
|
+
product: {
|
|
71
|
+
name: path.basename(productDir),
|
|
72
|
+
},
|
|
73
|
+
git: {
|
|
74
|
+
branch: metadata.git?.branch || null,
|
|
75
|
+
commitSha: metadata.git?.commitSha || null,
|
|
76
|
+
},
|
|
77
|
+
testkitVersion: metadata.testkitVersion,
|
|
78
|
+
scope,
|
|
79
|
+
summary,
|
|
80
|
+
tests,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function buildRunArtifact({
|
|
85
|
+
productDir,
|
|
86
|
+
results,
|
|
87
|
+
startedAt,
|
|
88
|
+
finishedAt,
|
|
89
|
+
requestedJobs,
|
|
90
|
+
workerCount,
|
|
91
|
+
suiteType,
|
|
92
|
+
suiteNames,
|
|
93
|
+
fileNames,
|
|
94
|
+
framework,
|
|
95
|
+
shard,
|
|
96
|
+
serviceFilter,
|
|
97
|
+
metadata,
|
|
98
|
+
summarizeDbBackend,
|
|
99
|
+
}) {
|
|
100
|
+
const executed = results.filter((result) => !result.skipped);
|
|
101
|
+
const failedServices = executed.filter((result) => result.failed);
|
|
102
|
+
const totalSuites = executed.reduce((sum, result) => sum + result.suiteCount, 0);
|
|
103
|
+
const completedSuites = executed.reduce((sum, result) => sum + result.completedSuiteCount, 0);
|
|
104
|
+
const failedSuites = executed.reduce((sum, result) => sum + result.failedSuiteCount, 0);
|
|
105
|
+
const totalFiles = executed.reduce((sum, result) => sum + (result.totalFileCount || 0), 0);
|
|
106
|
+
const passedFiles = executed.reduce((sum, result) => sum + (result.passedFileCount || 0), 0);
|
|
107
|
+
const failedFiles = executed.reduce((sum, result) => sum + (result.failedFileCount || 0), 0);
|
|
108
|
+
const notRunFiles = executed.reduce((sum, result) => sum + (result.notRunFileCount || 0), 0);
|
|
109
|
+
const dbBackend = summarizeDbBackend(results);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
schemaVersion: 1,
|
|
113
|
+
source: "testkit",
|
|
114
|
+
generatedAt: new Date(finishedAt).toISOString(),
|
|
115
|
+
product: {
|
|
116
|
+
name: path.basename(productDir),
|
|
117
|
+
directory: productDir,
|
|
118
|
+
},
|
|
119
|
+
git: metadata.git,
|
|
120
|
+
host: metadata.host,
|
|
121
|
+
run: {
|
|
122
|
+
status: failedServices.length > 0 ? "failed" : "passed",
|
|
123
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
124
|
+
finishedAt: new Date(finishedAt).toISOString(),
|
|
125
|
+
durationMs: finishedAt - startedAt,
|
|
126
|
+
requestedJobs,
|
|
127
|
+
workerCount,
|
|
128
|
+
dbBackend,
|
|
129
|
+
suiteType,
|
|
130
|
+
suiteNames,
|
|
131
|
+
fileNames,
|
|
132
|
+
framework: formatFrameworkForArtifact(framework),
|
|
133
|
+
shard,
|
|
134
|
+
serviceFilter,
|
|
135
|
+
testkitVersion: metadata.testkitVersion,
|
|
136
|
+
},
|
|
137
|
+
summary: {
|
|
138
|
+
services: {
|
|
139
|
+
total: executed.length,
|
|
140
|
+
passed: executed.length - failedServices.length,
|
|
141
|
+
failed: failedServices.length,
|
|
142
|
+
},
|
|
143
|
+
suites: {
|
|
144
|
+
total: totalSuites,
|
|
145
|
+
completed: completedSuites,
|
|
146
|
+
passed: completedSuites - failedSuites,
|
|
147
|
+
failed: failedSuites,
|
|
148
|
+
},
|
|
149
|
+
files: {
|
|
150
|
+
total: totalFiles,
|
|
151
|
+
passed: passedFiles,
|
|
152
|
+
failed: failedFiles,
|
|
153
|
+
notRun: notRunFiles,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
services: results.map((result) => ({
|
|
157
|
+
name: result.name,
|
|
158
|
+
failed: result.failed,
|
|
159
|
+
skipped: result.skipped,
|
|
160
|
+
suiteCount: result.suiteCount,
|
|
161
|
+
completedSuiteCount: result.completedSuiteCount,
|
|
162
|
+
failedSuiteCount: result.failedSuiteCount,
|
|
163
|
+
totalFileCount: result.totalFileCount,
|
|
164
|
+
completedFileCount: result.completedFileCount,
|
|
165
|
+
passedFileCount: result.passedFileCount,
|
|
166
|
+
failedFileCount: result.failedFileCount,
|
|
167
|
+
notRunFileCount: result.notRunFileCount,
|
|
168
|
+
durationMs: result.durationMs,
|
|
169
|
+
totalTaskDurationMs: result.totalTaskDurationMs,
|
|
170
|
+
dbBackend: result.dbBackend,
|
|
171
|
+
suites: result.suites,
|
|
172
|
+
errors: result.errors,
|
|
173
|
+
})),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function formatFrameworkForArtifact(framework) {
|
|
178
|
+
if (framework === "k6") return "default";
|
|
179
|
+
return framework;
|
|
180
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildRunArtifact, buildStatusArtifact } from "./reporting.mjs";
|
|
3
|
+
|
|
4
|
+
describe("runner reporting", () => {
|
|
5
|
+
it("builds run artifacts", () => {
|
|
6
|
+
const results = [
|
|
7
|
+
{
|
|
8
|
+
name: "api",
|
|
9
|
+
failed: false,
|
|
10
|
+
skipped: false,
|
|
11
|
+
suiteCount: 1,
|
|
12
|
+
completedSuiteCount: 1,
|
|
13
|
+
failedSuiteCount: 0,
|
|
14
|
+
totalFileCount: 3,
|
|
15
|
+
completedFileCount: 3,
|
|
16
|
+
passedFileCount: 3,
|
|
17
|
+
failedFileCount: 0,
|
|
18
|
+
notRunFileCount: 0,
|
|
19
|
+
durationMs: 1200,
|
|
20
|
+
totalTaskDurationMs: 2400,
|
|
21
|
+
dbBackend: "local",
|
|
22
|
+
suites: [],
|
|
23
|
+
errors: [],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "frontend",
|
|
27
|
+
failed: false,
|
|
28
|
+
skipped: true,
|
|
29
|
+
suiteCount: 0,
|
|
30
|
+
completedSuiteCount: 0,
|
|
31
|
+
failedSuiteCount: 0,
|
|
32
|
+
totalFileCount: 0,
|
|
33
|
+
completedFileCount: 0,
|
|
34
|
+
passedFileCount: 0,
|
|
35
|
+
failedFileCount: 0,
|
|
36
|
+
notRunFileCount: 0,
|
|
37
|
+
durationMs: 0,
|
|
38
|
+
totalTaskDurationMs: 0,
|
|
39
|
+
dbBackend: null,
|
|
40
|
+
suites: [],
|
|
41
|
+
errors: [],
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const artifact = buildRunArtifact({
|
|
46
|
+
productDir: "/tmp/my-product",
|
|
47
|
+
results,
|
|
48
|
+
startedAt: 1000,
|
|
49
|
+
finishedAt: 4000,
|
|
50
|
+
requestedJobs: 2,
|
|
51
|
+
workerCount: 1,
|
|
52
|
+
suiteType: "all",
|
|
53
|
+
suiteNames: [],
|
|
54
|
+
fileNames: [],
|
|
55
|
+
framework: "all",
|
|
56
|
+
shard: null,
|
|
57
|
+
serviceFilter: null,
|
|
58
|
+
metadata: {
|
|
59
|
+
git: {
|
|
60
|
+
branch: "main",
|
|
61
|
+
commitSha: "abc",
|
|
62
|
+
repoRoot: "/tmp",
|
|
63
|
+
},
|
|
64
|
+
host: {
|
|
65
|
+
hostname: "local",
|
|
66
|
+
username: "dev",
|
|
67
|
+
},
|
|
68
|
+
testkitVersion: "0.1.17",
|
|
69
|
+
},
|
|
70
|
+
summarizeDbBackend: () => "local",
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(artifact.product.name).toBe("my-product");
|
|
74
|
+
expect(artifact.summary.services.total).toBe(1);
|
|
75
|
+
expect(artifact.summary.files).toEqual({
|
|
76
|
+
total: 3,
|
|
77
|
+
passed: 3,
|
|
78
|
+
failed: 0,
|
|
79
|
+
notRun: 0,
|
|
80
|
+
});
|
|
81
|
+
expect(artifact.services[0].durationMs).toBe(1200);
|
|
82
|
+
expect(artifact.services[0].totalTaskDurationMs).toBe(2400);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("builds deterministic status artifacts", () => {
|
|
86
|
+
const status = buildStatusArtifact({
|
|
87
|
+
productDir: "/tmp/my-product",
|
|
88
|
+
results: [
|
|
89
|
+
{
|
|
90
|
+
name: "api",
|
|
91
|
+
failed: true,
|
|
92
|
+
skipped: false,
|
|
93
|
+
suites: [
|
|
94
|
+
{
|
|
95
|
+
name: "health",
|
|
96
|
+
type: "integration",
|
|
97
|
+
framework: "k6",
|
|
98
|
+
files: [
|
|
99
|
+
{ path: "tests/api/integration/a.int.testkit.ts", status: "passed" },
|
|
100
|
+
{ path: "tests/api/integration/b.int.testkit.ts", status: "failed" },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
suiteType: "int",
|
|
107
|
+
suiteNames: ["health"],
|
|
108
|
+
fileNames: ["tests/api/integration/b.int.testkit.ts"],
|
|
109
|
+
framework: "default",
|
|
110
|
+
shard: null,
|
|
111
|
+
serviceFilter: "api",
|
|
112
|
+
metadata: {
|
|
113
|
+
git: {
|
|
114
|
+
branch: "main",
|
|
115
|
+
commitSha: "abc123",
|
|
116
|
+
},
|
|
117
|
+
testkitVersion: "0.1.20",
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(status).toEqual({
|
|
122
|
+
schemaVersion: 1,
|
|
123
|
+
source: "testkit",
|
|
124
|
+
notice: "Generated file. Do not edit manually.",
|
|
125
|
+
product: {
|
|
126
|
+
name: "my-product",
|
|
127
|
+
},
|
|
128
|
+
git: {
|
|
129
|
+
branch: "main",
|
|
130
|
+
commitSha: "abc123",
|
|
131
|
+
},
|
|
132
|
+
testkitVersion: "0.1.20",
|
|
133
|
+
scope: {
|
|
134
|
+
suiteType: "int",
|
|
135
|
+
suiteNames: ["health"],
|
|
136
|
+
fileNames: ["tests/api/integration/b.int.testkit.ts"],
|
|
137
|
+
framework: "default",
|
|
138
|
+
shard: null,
|
|
139
|
+
serviceFilter: "api",
|
|
140
|
+
isFullRun: false,
|
|
141
|
+
},
|
|
142
|
+
summary: {
|
|
143
|
+
services: {
|
|
144
|
+
total: 1,
|
|
145
|
+
passed: 0,
|
|
146
|
+
failed: 1,
|
|
147
|
+
},
|
|
148
|
+
tests: {
|
|
149
|
+
total: 2,
|
|
150
|
+
passed: 1,
|
|
151
|
+
failed: 1,
|
|
152
|
+
notRun: 0,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
tests: [
|
|
156
|
+
{
|
|
157
|
+
service: "api",
|
|
158
|
+
type: "integration",
|
|
159
|
+
path: "tests/api/integration/a.int.testkit.ts",
|
|
160
|
+
status: "passed",
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
service: "api",
|
|
164
|
+
type: "integration",
|
|
165
|
+
path: "tests/api/integration/b.int.testkit.ts",
|
|
166
|
+
status: "failed",
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("marks unfiltered status artifacts as full runs", () => {
|
|
173
|
+
const status = buildStatusArtifact({
|
|
174
|
+
productDir: "/tmp/my-product",
|
|
175
|
+
results: [],
|
|
176
|
+
suiteType: "all",
|
|
177
|
+
suiteNames: [],
|
|
178
|
+
fileNames: [],
|
|
179
|
+
framework: "all",
|
|
180
|
+
shard: null,
|
|
181
|
+
serviceFilter: null,
|
|
182
|
+
metadata: {
|
|
183
|
+
git: {
|
|
184
|
+
branch: "main",
|
|
185
|
+
commitSha: "abc123",
|
|
186
|
+
},
|
|
187
|
+
testkitVersion: "0.1.20",
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(status.scope.isFullRun).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
});
|