@bigbinary/neeto-playwright-reporter 3.0.0-beta.2 → 3.0.0-beta.4
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/bin/extractReporterConfig.mjs +95 -0
- package/bin/neetoplaydash.mjs +109 -13
- package/index.cjs.js +1 -1
- package/index.cjs.js.map +1 -1
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { pathToFileURL } from "url";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
|
|
7
|
+
const NEETO_REPORTER_NAME = "@bigbinary/neeto-playwright-reporter";
|
|
8
|
+
|
|
9
|
+
const findPlaywrightConfig = cwd => {
|
|
10
|
+
const configNames = [
|
|
11
|
+
"playwright.config.ts",
|
|
12
|
+
"playwright.config.js",
|
|
13
|
+
"playwright.config.mjs",
|
|
14
|
+
"playwright.config.mts",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
for (const name of configNames) {
|
|
18
|
+
const configPath = path.resolve(cwd, name);
|
|
19
|
+
if (fs.existsSync(configPath)) {
|
|
20
|
+
return configPath;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return null;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const extractFromReporterArray = reporters => {
|
|
28
|
+
if (!reporters) return null;
|
|
29
|
+
|
|
30
|
+
if (typeof reporters === "string") {
|
|
31
|
+
return reporters === NEETO_REPORTER_NAME ? {} : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (Array.isArray(reporters)) {
|
|
35
|
+
for (const reporter of reporters) {
|
|
36
|
+
if (typeof reporter === "string" && reporter === NEETO_REPORTER_NAME) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (Array.isArray(reporter)) {
|
|
41
|
+
const [name, options] = reporter;
|
|
42
|
+
if (name === NEETO_REPORTER_NAME) {
|
|
43
|
+
return options || {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const extractNeetoReporterOptions = config => {
|
|
53
|
+
const reporters = config.reporter || config.default?.reporter;
|
|
54
|
+
return extractFromReporterArray(reporters);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const main = async () => {
|
|
58
|
+
try {
|
|
59
|
+
const cwd = process.cwd();
|
|
60
|
+
const configArg = process.argv[2];
|
|
61
|
+
|
|
62
|
+
let configPath;
|
|
63
|
+
if (configArg) {
|
|
64
|
+
configPath = path.isAbsolute(configArg)
|
|
65
|
+
? configArg
|
|
66
|
+
: path.resolve(cwd, configArg);
|
|
67
|
+
} else {
|
|
68
|
+
configPath = findPlaywrightConfig(cwd);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!configPath || !fs.existsSync(configPath)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const configUrl = pathToFileURL(configPath).href;
|
|
76
|
+
const configModule = await import(configUrl);
|
|
77
|
+
const config = configModule.default || configModule;
|
|
78
|
+
|
|
79
|
+
console.error("\n[neetoplaydash] Resolved Playwright config:");
|
|
80
|
+
console.error(JSON.stringify(config, null, 2));
|
|
81
|
+
|
|
82
|
+
const options = extractNeetoReporterOptions(config);
|
|
83
|
+
|
|
84
|
+
console.error("\n[neetoplaydash] Extracted reporter options:");
|
|
85
|
+
console.error(JSON.stringify(options, null, 2));
|
|
86
|
+
|
|
87
|
+
if (options) {
|
|
88
|
+
console.log(JSON.stringify({ success: true, options }));
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error("[neetoplaydash] Config extraction error:", error.message);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
main();
|
package/bin/neetoplaydash.mjs
CHANGED
|
@@ -1,18 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { spawn } from "child_process";
|
|
3
|
+
import { spawn, execSync } from "child_process";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
4
6
|
import * as R from "ramda";
|
|
5
7
|
import smartOrchestrationApi from "./smartOrchestration.js";
|
|
6
8
|
|
|
7
|
-
const
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
8
11
|
|
|
9
|
-
const
|
|
12
|
+
const buildFullTitle = (parentTitles, specTitle) =>
|
|
13
|
+
[...parentTitles, specTitle].join(" ");
|
|
14
|
+
|
|
15
|
+
const collectSpecs = (suite, parentTitles = []) =>
|
|
10
16
|
R.concat(
|
|
11
|
-
R.map(
|
|
12
|
-
|
|
17
|
+
R.map(
|
|
18
|
+
spec => ({
|
|
19
|
+
id: spec.id,
|
|
20
|
+
title: buildFullTitle(parentTitles, spec.title),
|
|
21
|
+
}),
|
|
22
|
+
suite.specs || []
|
|
23
|
+
),
|
|
24
|
+
R.chain(
|
|
25
|
+
childSuite =>
|
|
26
|
+
collectSpecs(childSuite, [...parentTitles, childSuite.title]),
|
|
27
|
+
suite.suites || []
|
|
28
|
+
)
|
|
13
29
|
);
|
|
14
30
|
|
|
15
|
-
const extractTestTitlesAndIds =
|
|
31
|
+
const extractTestTitlesAndIds = json =>
|
|
32
|
+
R.chain(
|
|
33
|
+
project => R.chain(file => collectSpecs(file, []), project.suites || []),
|
|
34
|
+
json.suites || []
|
|
35
|
+
);
|
|
16
36
|
|
|
17
37
|
const args = process.argv.slice(2);
|
|
18
38
|
|
|
@@ -57,9 +77,65 @@ const neetoOptions = {};
|
|
|
57
77
|
remainingArgs = filteredArgs;
|
|
58
78
|
});
|
|
59
79
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
const
|
|
80
|
+
const playwrightArgs = remainingArgs;
|
|
81
|
+
|
|
82
|
+
const extractConfigPath = args => {
|
|
83
|
+
const index = args.findIndex(
|
|
84
|
+
arg => arg === "--config" || arg === "-c" || arg.startsWith("--config=")
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (index === -1) return null;
|
|
88
|
+
|
|
89
|
+
const arg = args[index];
|
|
90
|
+
|
|
91
|
+
if (arg === "--config" || arg === "-c") {
|
|
92
|
+
return args[index + 1] || null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (arg.startsWith("--config=")) {
|
|
96
|
+
return arg.substring("--config=".length);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return null;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const extractReporterConfigFromPlaywright = configPath => {
|
|
103
|
+
try {
|
|
104
|
+
const extractorScript = path.join(__dirname, "extractReporterConfig.mjs");
|
|
105
|
+
const configArg = configPath ? `"${configPath}"` : "";
|
|
106
|
+
|
|
107
|
+
const command = `node --import tsx "${extractorScript}" ${configArg}`;
|
|
108
|
+
|
|
109
|
+
const result = execSync(command, {
|
|
110
|
+
encoding: "utf-8",
|
|
111
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
112
|
+
cwd: process.cwd(),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!result || !result.trim()) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const parsed = JSON.parse(result.trim());
|
|
120
|
+
return parsed.success ? parsed.options : null;
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const configPath = extractConfigPath(playwrightArgs);
|
|
127
|
+
const reporterOptions = extractReporterConfigFromPlaywright(configPath) || {};
|
|
128
|
+
|
|
129
|
+
const projectKey =
|
|
130
|
+
neetoOptions.projectKey ||
|
|
131
|
+
reporterOptions.projectKey ||
|
|
132
|
+
process.env.NEETO_PROJECT_KEY;
|
|
133
|
+
const apiKey =
|
|
134
|
+
neetoOptions.apiKey || reporterOptions.apiKey || process.env.NEETO_API_KEY;
|
|
135
|
+
const ciBuildId =
|
|
136
|
+
neetoOptions.ciBuildId ||
|
|
137
|
+
reporterOptions.ciBuildId ||
|
|
138
|
+
process.env.NEETO_CI_BUILD_ID;
|
|
63
139
|
|
|
64
140
|
const missingOptions = [];
|
|
65
141
|
if (!projectKey) missingOptions.push("--projectKey or NEETO_PROJECT_KEY");
|
|
@@ -76,11 +152,10 @@ if (missingOptions.length > 0) {
|
|
|
76
152
|
console.error(
|
|
77
153
|
`Or set environment variables: NEETO_PROJECT_KEY, NEETO_API_KEY, NEETO_CI_BUILD_ID`
|
|
78
154
|
);
|
|
155
|
+
console.error(`Or define them in your Playwright config reporter options.`);
|
|
79
156
|
process.exit(1);
|
|
80
157
|
}
|
|
81
158
|
|
|
82
|
-
const playwrightArgs = remainingArgs;
|
|
83
|
-
|
|
84
159
|
const escapeShellArg = arg => {
|
|
85
160
|
if (arg.includes(" ") || arg.includes('"') || arg.includes("'")) {
|
|
86
161
|
return `"${arg.replace(/"/g, '\\"')}"`;
|
|
@@ -156,6 +231,10 @@ env.NEETO_PROJECT_KEY = projectKey;
|
|
|
156
231
|
env.NEETO_API_KEY = apiKey;
|
|
157
232
|
env.NEETO_CI_BUILD_ID = ciBuildId;
|
|
158
233
|
|
|
234
|
+
if (reporterOptions.baseURL) {
|
|
235
|
+
env.NEETO_PLAYDASH_API_BASE_URL = reporterOptions.baseURL;
|
|
236
|
+
}
|
|
237
|
+
|
|
159
238
|
if (currentShard !== null) {
|
|
160
239
|
env.NEETO_PLAYDASH_CURRENT_SHARD = currentShard.toString();
|
|
161
240
|
}
|
|
@@ -164,6 +243,24 @@ if (totalShards !== null) {
|
|
|
164
243
|
env.NEETO_PLAYDASH_TOTAL_SHARDS = totalShards.toString();
|
|
165
244
|
}
|
|
166
245
|
|
|
246
|
+
if (reporterOptions.tags) {
|
|
247
|
+
env.NEETO_PLAYDASH_TAGS = Array.isArray(reporterOptions.tags)
|
|
248
|
+
? reporterOptions.tags.join(",")
|
|
249
|
+
: reporterOptions.tags;
|
|
250
|
+
}
|
|
251
|
+
if (reporterOptions.title) {
|
|
252
|
+
env.NEETO_PLAYDASH_TITLE = reporterOptions.title;
|
|
253
|
+
}
|
|
254
|
+
if (reporterOptions.commitId) {
|
|
255
|
+
env.NEETO_PLAYDASH_COMMIT_ID = reporterOptions.commitId;
|
|
256
|
+
}
|
|
257
|
+
if (reporterOptions.author) {
|
|
258
|
+
env.NEETO_PLAYDASH_AUTHOR = reporterOptions.author;
|
|
259
|
+
}
|
|
260
|
+
if (reporterOptions.branch) {
|
|
261
|
+
env.NEETO_PLAYDASH_BRANCH = reporterOptions.branch;
|
|
262
|
+
}
|
|
263
|
+
|
|
167
264
|
const finalArgs = [...filteredPlaywrightArgs, "--reporter=json", "--list"];
|
|
168
265
|
const escapedFinalArgs = finalArgs.map(escapeShellArg);
|
|
169
266
|
const listCommand = `npx playwright ${escapedFinalArgs.join(" ")}`;
|
|
@@ -196,13 +293,12 @@ child.on("close", async code => {
|
|
|
196
293
|
try {
|
|
197
294
|
const json = JSON.parse(jsonString);
|
|
198
295
|
const tests = extractTestTitlesAndIds(json);
|
|
199
|
-
const historyIds = R.pluck("id", tests);
|
|
200
296
|
|
|
201
297
|
const smartOrchestrationPayload = {
|
|
202
298
|
total_shards: totalShards,
|
|
203
299
|
current_shard: currentShard,
|
|
204
300
|
ci_build_id: ciBuildId,
|
|
205
|
-
|
|
301
|
+
tests,
|
|
206
302
|
};
|
|
207
303
|
|
|
208
304
|
const smartOrchestrationResponse = await smartOrchestrationApi.create({
|