@sentinelqa/playwright-reporter 0.1.21 → 0.1.23
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/README.md +12 -1
- package/dist/localReport.js +5 -7
- package/dist/reportServer.d.ts +1 -0
- package/dist/reportServer.js +70 -0
- package/dist/reporter.js +69 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ If `SENTINEL_TOKEN` is not set, the reporter generates a local HTML debugging re
|
|
|
93
93
|
|
|
94
94
|
### Cloud mode
|
|
95
95
|
|
|
96
|
-
If `SENTINEL_TOKEN` is set, the reporter uploads the run to Sentinel instead of generating the local HTML report.
|
|
96
|
+
If `SENTINEL_TOKEN` is set in CI, the reporter uploads the run to Sentinel instead of generating the local HTML report.
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
99
|
SENTINEL_TOKEN=your_project_ingest_token npx playwright test
|
|
@@ -101,6 +101,17 @@ SENTINEL_TOKEN=your_project_ingest_token npx playwright test
|
|
|
101
101
|
|
|
102
102
|
For intentional uploads outside CI, also set `SENTINEL_UPLOAD_LOCAL=1` and provide the usual commit and run metadata expected by the uploader.
|
|
103
103
|
|
|
104
|
+
Example:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
SENTINEL_TOKEN=your_project_ingest_token \
|
|
108
|
+
SENTINEL_UPLOAD_LOCAL=1 \
|
|
109
|
+
GITHUB_SHA=abc123 \
|
|
110
|
+
GITHUB_REF_NAME=main \
|
|
111
|
+
GITHUB_RUN_ID=local-dev \
|
|
112
|
+
npx playwright test
|
|
113
|
+
```
|
|
114
|
+
|
|
104
115
|
## What `withSentinel()` does
|
|
105
116
|
|
|
106
117
|
- Preserves your existing reporter configuration
|
package/dist/localReport.js
CHANGED
|
@@ -927,13 +927,11 @@ const buildHtml = (tests, summary, extraArtifacts) => {
|
|
|
927
927
|
var tracePath = button.getAttribute("data-trace-path");
|
|
928
928
|
if (!tracePath) return;
|
|
929
929
|
try {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
);
|
|
936
|
-
}
|
|
930
|
+
var traceUrl = new URL(tracePath, window.location.href).href;
|
|
931
|
+
button.setAttribute(
|
|
932
|
+
"href",
|
|
933
|
+
"https://trace.playwright.dev/?trace=" + encodeURIComponent(traceUrl)
|
|
934
|
+
);
|
|
937
935
|
} catch (_error) {
|
|
938
936
|
// Keep the raw trace file link as fallback.
|
|
939
937
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const http_1 = __importDefault(require("http"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const MIME_TYPES = {
|
|
10
|
+
".css": "text/css; charset=utf-8",
|
|
11
|
+
".gif": "image/gif",
|
|
12
|
+
".html": "text/html; charset=utf-8",
|
|
13
|
+
".jpeg": "image/jpeg",
|
|
14
|
+
".jpg": "image/jpeg",
|
|
15
|
+
".js": "text/javascript; charset=utf-8",
|
|
16
|
+
".json": "application/json; charset=utf-8",
|
|
17
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
18
|
+
".mp4": "video/mp4",
|
|
19
|
+
".png": "image/png",
|
|
20
|
+
".svg": "image/svg+xml",
|
|
21
|
+
".txt": "text/plain; charset=utf-8",
|
|
22
|
+
".webm": "video/webm",
|
|
23
|
+
".xml": "application/xml; charset=utf-8",
|
|
24
|
+
".zip": "application/zip"
|
|
25
|
+
};
|
|
26
|
+
const rootIndex = process.argv.indexOf("--root");
|
|
27
|
+
const portIndex = process.argv.indexOf("--port");
|
|
28
|
+
if (rootIndex === -1 || portIndex === -1) {
|
|
29
|
+
throw new Error("Expected --root and --port arguments");
|
|
30
|
+
}
|
|
31
|
+
const rootDir = path_1.default.resolve(process.argv[rootIndex + 1] || process.cwd());
|
|
32
|
+
const port = Number(process.argv[portIndex + 1]);
|
|
33
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
34
|
+
throw new Error("Expected a valid port");
|
|
35
|
+
}
|
|
36
|
+
const send = (res, statusCode, body) => {
|
|
37
|
+
res.writeHead(statusCode, { "Content-Type": "text/plain; charset=utf-8" });
|
|
38
|
+
res.end(body);
|
|
39
|
+
};
|
|
40
|
+
const server = http_1.default.createServer((req, res) => {
|
|
41
|
+
const requestPath = req.url ? req.url.split("?")[0] : "/";
|
|
42
|
+
const decodedPath = decodeURIComponent(requestPath || "/");
|
|
43
|
+
const safePath = path_1.default.normalize(decodedPath).replace(/^(\.\.[/\\])+/, "");
|
|
44
|
+
const relativePath = safePath === "/" ? "/sentinel-report/index.html" : safePath;
|
|
45
|
+
const filePath = path_1.default.resolve(rootDir, `.${relativePath}`);
|
|
46
|
+
if (!filePath.startsWith(rootDir)) {
|
|
47
|
+
send(res, 403, "Forbidden");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let stat;
|
|
51
|
+
try {
|
|
52
|
+
stat = fs_1.default.statSync(filePath);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
send(res, 404, "Not Found");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (stat.isDirectory()) {
|
|
59
|
+
send(res, 404, "Not Found");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const extension = path_1.default.extname(filePath).toLowerCase();
|
|
63
|
+
res.writeHead(200, {
|
|
64
|
+
"Cache-Control": "no-store",
|
|
65
|
+
"Content-Length": stat.size,
|
|
66
|
+
"Content-Type": MIME_TYPES[extension] || "application/octet-stream"
|
|
67
|
+
});
|
|
68
|
+
fs_1.default.createReadStream(filePath).pipe(res);
|
|
69
|
+
});
|
|
70
|
+
server.listen(port, "127.0.0.1");
|
package/dist/reporter.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
const net_1 = __importDefault(require("net"));
|
|
5
7
|
const path_1 = __importDefault(require("path"));
|
|
6
8
|
const url_1 = require("url");
|
|
7
9
|
const node_1 = require("@sentinelqa/uploader/node");
|
|
@@ -26,6 +28,47 @@ const cyan = (value) => colorize(value, "36");
|
|
|
26
28
|
const yellow = (value) => colorize(value, "33");
|
|
27
29
|
const dim = (value) => colorize(value, "2");
|
|
28
30
|
const magenta = (value) => colorize(value, "35");
|
|
31
|
+
const getAvailablePort = () => {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const server = net_1.default.createServer();
|
|
34
|
+
server.unref();
|
|
35
|
+
server.on("error", reject);
|
|
36
|
+
server.listen(0, "127.0.0.1", () => {
|
|
37
|
+
const address = server.address();
|
|
38
|
+
if (!address || typeof address === "string") {
|
|
39
|
+
server.close(() => reject(new Error("Failed to acquire a local port")));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const port = address.port;
|
|
43
|
+
server.close((error) => {
|
|
44
|
+
if (error) {
|
|
45
|
+
reject(error);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
resolve(port);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const startLocalReportServer = async (localReportPath) => {
|
|
54
|
+
const port = await getAvailablePort();
|
|
55
|
+
const reportServerPath = require.resolve("./reportServer");
|
|
56
|
+
const rootDir = process.cwd();
|
|
57
|
+
const relativeReportPath = path_1.default.relative(rootDir, localReportPath).replace(/\\/g, "/");
|
|
58
|
+
const reportUrlPath = relativeReportPath.startsWith("/")
|
|
59
|
+
? relativeReportPath
|
|
60
|
+
: `/${relativeReportPath}`;
|
|
61
|
+
const child = (0, child_process_1.spawn)(process.execPath, [reportServerPath, "--root", rootDir, "--port", String(port)], {
|
|
62
|
+
detached: true,
|
|
63
|
+
stdio: "ignore"
|
|
64
|
+
});
|
|
65
|
+
child.unref();
|
|
66
|
+
return `http://127.0.0.1:${port}${reportUrlPath}`;
|
|
67
|
+
};
|
|
68
|
+
const updateRedirectPage = (redirectPath, targetUrl) => {
|
|
69
|
+
const escapedTarget = targetUrl.replace(/"/g, """);
|
|
70
|
+
require("fs").writeFileSync(redirectPath, `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta http-equiv="refresh" content="0; url=${escapedTarget}" /><title>Sentinel Playwright Reporter</title></head><body><p>Open <a href="${escapedTarget}">${escapedTarget}</a>.</p></body></html>`, "utf8");
|
|
71
|
+
};
|
|
29
72
|
class SentinelReporter {
|
|
30
73
|
constructor(options) {
|
|
31
74
|
this.failedCount = 0;
|
|
@@ -56,23 +99,29 @@ class SentinelReporter {
|
|
|
56
99
|
this.failedCount += 1;
|
|
57
100
|
}
|
|
58
101
|
}
|
|
59
|
-
printLocalReport(localReportPath) {
|
|
102
|
+
printLocalReport(localReportPath, localReportUrl) {
|
|
60
103
|
const relativeReportPath = path_1.default
|
|
61
104
|
.relative(process.cwd(), localReportPath)
|
|
62
105
|
.replace(/\\/g, "/");
|
|
63
106
|
const displayPath = relativeReportPath.startsWith(".")
|
|
64
107
|
? relativeReportPath
|
|
65
108
|
: `./${relativeReportPath}`;
|
|
66
|
-
const
|
|
109
|
+
const displayTarget = localReportUrl || displayPath;
|
|
110
|
+
const openCommand = `open ${displayTarget}`;
|
|
67
111
|
console.log("");
|
|
68
112
|
console.log(green("✔ Artifacts collected"));
|
|
69
113
|
console.log(green("✔ Sentinel HTML debugging report created"));
|
|
70
114
|
console.log("");
|
|
71
115
|
console.log(bold("Report"));
|
|
72
|
-
console.log(` ${cyan(formatTerminalLink(displayPath, (0, url_1.pathToFileURL)(localReportPath).href))}`);
|
|
116
|
+
console.log(` ${cyan(formatTerminalLink(localReportUrl || displayPath, localReportUrl || (0, url_1.pathToFileURL)(localReportPath).href))}`);
|
|
73
117
|
console.log("");
|
|
74
118
|
console.log(bold("Open"));
|
|
75
119
|
console.log(` ${cyan(openCommand)}`);
|
|
120
|
+
if (localReportUrl) {
|
|
121
|
+
console.log("");
|
|
122
|
+
console.log(yellow("Trace Viewer"));
|
|
123
|
+
console.log(` ${dim("Open the localhost report URL above so 'View Trace' works correctly.")}`);
|
|
124
|
+
}
|
|
76
125
|
console.log("");
|
|
77
126
|
console.log(yellow("Tip"));
|
|
78
127
|
console.log(` ${dim("Upload runs to Sentinel Cloud for CI history,")}`);
|
|
@@ -86,7 +135,9 @@ class SentinelReporter {
|
|
|
86
135
|
}
|
|
87
136
|
async onEnd() {
|
|
88
137
|
const hasSentinelToken = Boolean(process.env.SENTINEL_TOKEN);
|
|
89
|
-
|
|
138
|
+
const hasCiEnv = (0, node_1.hasSupportedCiEnv)(process.env);
|
|
139
|
+
const localUploadEnabled = (0, node_1.isLocalUploadEnabled)(process.env);
|
|
140
|
+
if (!hasSentinelToken || (!hasCiEnv && !localUploadEnabled)) {
|
|
90
141
|
const localReport = (0, localReport_1.generateLocalDebugReport)({
|
|
91
142
|
playwrightJsonPath: this.options.playwrightJsonPath,
|
|
92
143
|
playwrightReportDir: this.options.playwrightReportDir,
|
|
@@ -96,27 +147,26 @@ class SentinelReporter {
|
|
|
96
147
|
reportFileName: this.options.localReportFileName,
|
|
97
148
|
redirectFileName: this.options.localRedirectFileName
|
|
98
149
|
});
|
|
99
|
-
|
|
150
|
+
let localReportUrl;
|
|
151
|
+
try {
|
|
152
|
+
localReportUrl = await startLocalReportServer(localReport.htmlPath);
|
|
153
|
+
updateRedirectPage(localReport.redirectPath, localReportUrl);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
localReportUrl = undefined;
|
|
157
|
+
}
|
|
158
|
+
this.printLocalReport(localReport.htmlPath, localReportUrl);
|
|
100
159
|
console.log("");
|
|
160
|
+
if (hasSentinelToken && !hasCiEnv && !localUploadEnabled) {
|
|
161
|
+
console.log("Sentinel upload skipped for this local run.");
|
|
162
|
+
console.log("To upload local runs, set SENTINEL_UPLOAD_LOCAL=1 and provide the required CI metadata.");
|
|
163
|
+
console.log("");
|
|
164
|
+
}
|
|
101
165
|
return;
|
|
102
166
|
}
|
|
103
|
-
const hasCiEnv = (0, node_1.hasSupportedCiEnv)(process.env);
|
|
104
|
-
const localUploadEnabled = (0, node_1.isLocalUploadEnabled)(process.env);
|
|
105
167
|
console.log("");
|
|
106
168
|
console.log("Uploading failure artifacts to Sentinel...");
|
|
107
169
|
console.log("");
|
|
108
|
-
if (!hasCiEnv && !localUploadEnabled) {
|
|
109
|
-
console.log("Local upload mode detected.");
|
|
110
|
-
console.log("If this run is outside CI, set SENTINEL_UPLOAD_LOCAL=1 and provide the required CI metadata.");
|
|
111
|
-
console.log("");
|
|
112
|
-
console.log("Typical local upload environment variables:");
|
|
113
|
-
console.log("• SENTINEL_UPLOAD_LOCAL=1");
|
|
114
|
-
console.log("• CI_COMMIT_SHA or GITHUB_SHA");
|
|
115
|
-
console.log("• CI_COMMIT_REF_NAME or GITHUB_REF_NAME");
|
|
116
|
-
console.log("• CI_JOB_URL or a matching run URL");
|
|
117
|
-
console.log("• CI_PIPELINE_ID or GITHUB_RUN_ID");
|
|
118
|
-
console.log("");
|
|
119
|
-
}
|
|
120
170
|
const exitCode = await (0, node_1.runSentinelUpload)({
|
|
121
171
|
playwrightJsonPath: this.options.playwrightJsonPath,
|
|
122
172
|
playwrightReportDir: this.options.playwrightReportDir,
|