@testdino/playwright 1.0.9 → 1.0.11
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/tdpw.js +2 -2
- package/dist/cli/index.mjs +44 -21
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +304 -162
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +204 -77
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +0 -929
- package/dist/cli/index.js.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
import { randomUUID, createHash } from 'crypto';
|
|
2
|
-
import { existsSync, readFileSync, statSync, createReadStream } from 'fs';
|
|
3
|
-
import WebSocket from 'ws';
|
|
4
|
-
import axios from 'axios';
|
|
5
|
-
import { basename, extname, relative } from 'path';
|
|
6
|
-
import { rm, mkdir, readFile } from 'fs/promises';
|
|
7
|
-
import { execa } from 'execa';
|
|
8
|
-
import { type, release, platform, cpus, totalmem, hostname } from 'os';
|
|
9
|
-
import { version } from 'process';
|
|
10
|
-
import istanbulCoverage from 'istanbul-lib-coverage';
|
|
11
|
-
import picomatch from 'picomatch';
|
|
12
|
-
import { createContext } from 'istanbul-lib-report';
|
|
13
|
-
import { create } from 'istanbul-reports';
|
|
14
|
-
import { test as test$1 } from '@playwright/test';
|
|
15
|
-
export { expect } from '@playwright/test';
|
|
16
|
-
import chalk from 'chalk';
|
|
17
|
-
|
|
18
1
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
19
2
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
20
3
|
}) : x)(function(x) {
|
|
@@ -22,6 +5,13 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
22
5
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
23
6
|
});
|
|
24
7
|
|
|
8
|
+
// src/reporter/index.ts
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
10
|
+
import { readFileSync, existsSync } from "fs";
|
|
11
|
+
|
|
12
|
+
// src/streaming/websocket.ts
|
|
13
|
+
import WebSocket from "ws";
|
|
14
|
+
|
|
25
15
|
// src/reporter/errors.ts
|
|
26
16
|
var TestDinoServerError = class extends Error {
|
|
27
17
|
code;
|
|
@@ -284,7 +274,11 @@ var WebSocketClient = class {
|
|
|
284
274
|
total: Number(details.total) || 0,
|
|
285
275
|
resetDate: details.resetDate?.toString(),
|
|
286
276
|
canPartialSubmit: Boolean(details.canPartialSubmit),
|
|
287
|
-
allowedCount: Number(details.allowedCount) || 0
|
|
277
|
+
allowedCount: Number(details.allowedCount) || 0,
|
|
278
|
+
projectName: details.projectName?.toString(),
|
|
279
|
+
projectLimit: details.projectLimit != null ? Number(details.projectLimit) : void 0,
|
|
280
|
+
projectUsed: details.projectUsed != null ? Number(details.projectUsed) : void 0,
|
|
281
|
+
projectBorrowed: details.projectBorrowed != null ? Number(details.projectBorrowed) : void 0
|
|
288
282
|
})
|
|
289
283
|
);
|
|
290
284
|
} else if (errorCode === "QUOTA_EXHAUSTED" && errorObj.details && typeof errorObj.details === "object") {
|
|
@@ -373,6 +367,12 @@ var WebSocketClient = class {
|
|
|
373
367
|
}
|
|
374
368
|
}
|
|
375
369
|
};
|
|
370
|
+
|
|
371
|
+
// src/streaming/http.ts
|
|
372
|
+
import axios from "axios";
|
|
373
|
+
|
|
374
|
+
// src/utils/index.ts
|
|
375
|
+
import { relative } from "path";
|
|
376
376
|
function normalizePath(filePath, rootDir) {
|
|
377
377
|
if (rootDir && filePath.startsWith(rootDir)) {
|
|
378
378
|
return relative(rootDir, filePath);
|
|
@@ -430,6 +430,14 @@ var HttpClient = class {
|
|
|
430
430
|
await this.client.post("/events", { events });
|
|
431
431
|
return;
|
|
432
432
|
} catch (error) {
|
|
433
|
+
if (axios.isAxiosError(error) && error.response?.status === 402) {
|
|
434
|
+
const data = error.response.data;
|
|
435
|
+
const details = data?.details;
|
|
436
|
+
if (data?.error === "QUOTA_EXCEEDED" && details) {
|
|
437
|
+
throw new QuotaExceededError(data.message || "Quota exceeded", details);
|
|
438
|
+
}
|
|
439
|
+
throw new QuotaExhaustedError(data?.message || "Quota exceeded", details);
|
|
440
|
+
}
|
|
433
441
|
lastError = new Error(this.getErrorMessage(error));
|
|
434
442
|
if (attempt < this.options.maxRetries - 1) {
|
|
435
443
|
const delay = this.options.retryDelay * Math.pow(2, attempt);
|
|
@@ -541,6 +549,9 @@ var EventBuffer = class {
|
|
|
541
549
|
}
|
|
542
550
|
};
|
|
543
551
|
|
|
552
|
+
// src/metadata/git.ts
|
|
553
|
+
import { readFile } from "fs/promises";
|
|
554
|
+
|
|
544
555
|
// src/metadata/base.ts
|
|
545
556
|
var BaseMetadataCollector = class {
|
|
546
557
|
name;
|
|
@@ -626,6 +637,16 @@ var BaseMetadataCollector = class {
|
|
|
626
637
|
};
|
|
627
638
|
|
|
628
639
|
// src/metadata/git.ts
|
|
640
|
+
var execaPromise = null;
|
|
641
|
+
async function getExeca() {
|
|
642
|
+
if (!execaPromise) {
|
|
643
|
+
execaPromise = import("execa").then((m) => m.execa).catch((error) => {
|
|
644
|
+
console.error("Failed to import execa:", error.message);
|
|
645
|
+
throw new Error("Failed to load execa. Ensure execa is installed: npm install execa");
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
return execaPromise;
|
|
649
|
+
}
|
|
629
650
|
var GitMetadataCollector = class extends BaseMetadataCollector {
|
|
630
651
|
options;
|
|
631
652
|
constructor(options = {}) {
|
|
@@ -718,6 +739,7 @@ var GitMetadataCollector = class extends BaseMetadataCollector {
|
|
|
718
739
|
const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
|
719
740
|
if (!isCI) return;
|
|
720
741
|
try {
|
|
742
|
+
const execa = await getExeca();
|
|
721
743
|
await execa("git", ["config", "--global", "--add", "safe.directory", this.options.cwd], {
|
|
722
744
|
timeout: this.options.timeout,
|
|
723
745
|
reject: true
|
|
@@ -1017,6 +1039,7 @@ var GitMetadataCollector = class extends BaseMetadataCollector {
|
|
|
1017
1039
|
* Execute git command with timeout
|
|
1018
1040
|
*/
|
|
1019
1041
|
async execGit(args) {
|
|
1042
|
+
const execa = await getExeca();
|
|
1020
1043
|
const { stdout } = await this.withTimeout(
|
|
1021
1044
|
execa("git", args, {
|
|
1022
1045
|
cwd: this.options.cwd,
|
|
@@ -1029,6 +1052,9 @@ var GitMetadataCollector = class extends BaseMetadataCollector {
|
|
|
1029
1052
|
return stdout.trim();
|
|
1030
1053
|
}
|
|
1031
1054
|
};
|
|
1055
|
+
|
|
1056
|
+
// src/metadata/ci.ts
|
|
1057
|
+
import { type as osType, release as osRelease } from "os";
|
|
1032
1058
|
var CIMetadataCollector = class extends BaseMetadataCollector {
|
|
1033
1059
|
constructor(_options = {}) {
|
|
1034
1060
|
super("ci");
|
|
@@ -1106,9 +1132,9 @@ var CIMetadataCollector = class extends BaseMetadataCollector {
|
|
|
1106
1132
|
collectEnvironment() {
|
|
1107
1133
|
try {
|
|
1108
1134
|
return {
|
|
1109
|
-
name:
|
|
1135
|
+
name: osType(),
|
|
1110
1136
|
type: process.platform,
|
|
1111
|
-
os: `${
|
|
1137
|
+
os: `${osType()} ${osRelease()}`,
|
|
1112
1138
|
node: process.version
|
|
1113
1139
|
};
|
|
1114
1140
|
} catch {
|
|
@@ -1116,6 +1142,10 @@ var CIMetadataCollector = class extends BaseMetadataCollector {
|
|
|
1116
1142
|
}
|
|
1117
1143
|
}
|
|
1118
1144
|
};
|
|
1145
|
+
|
|
1146
|
+
// src/metadata/system.ts
|
|
1147
|
+
import { platform, release, cpus, totalmem, hostname } from "os";
|
|
1148
|
+
import { version } from "process";
|
|
1119
1149
|
var SystemMetadataCollector = class extends BaseMetadataCollector {
|
|
1120
1150
|
constructor(_options = {}) {
|
|
1121
1151
|
super("system");
|
|
@@ -1223,6 +1253,9 @@ var SystemMetadataCollector = class extends BaseMetadataCollector {
|
|
|
1223
1253
|
}
|
|
1224
1254
|
}
|
|
1225
1255
|
};
|
|
1256
|
+
|
|
1257
|
+
// src/metadata/playwright.ts
|
|
1258
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1226
1259
|
var PlaywrightMetadataCollector = class extends BaseMetadataCollector {
|
|
1227
1260
|
options;
|
|
1228
1261
|
constructor(options = {}) {
|
|
@@ -1272,7 +1305,7 @@ var PlaywrightMetadataCollector = class extends BaseMetadataCollector {
|
|
|
1272
1305
|
return void 0;
|
|
1273
1306
|
}
|
|
1274
1307
|
const packageJsonContent = await this.withTimeout(
|
|
1275
|
-
|
|
1308
|
+
readFile2(packageJsonPath, "utf-8"),
|
|
1276
1309
|
this.options.timeout,
|
|
1277
1310
|
"Playwright package.json read"
|
|
1278
1311
|
);
|
|
@@ -1695,6 +1728,9 @@ function createMetadataCollector(playwrightConfig, playwrightSuite) {
|
|
|
1695
1728
|
);
|
|
1696
1729
|
return aggregator;
|
|
1697
1730
|
}
|
|
1731
|
+
|
|
1732
|
+
// src/uploads/sas-token-client.ts
|
|
1733
|
+
import axios2 from "axios";
|
|
1698
1734
|
var SASTokenClient = class {
|
|
1699
1735
|
client;
|
|
1700
1736
|
options;
|
|
@@ -1704,7 +1740,7 @@ var SASTokenClient = class {
|
|
|
1704
1740
|
retryDelay: 1e3,
|
|
1705
1741
|
...options
|
|
1706
1742
|
};
|
|
1707
|
-
this.client =
|
|
1743
|
+
this.client = axios2.create({
|
|
1708
1744
|
baseURL: this.options.serverUrl,
|
|
1709
1745
|
headers: {
|
|
1710
1746
|
"Content-Type": "application/json",
|
|
@@ -1746,7 +1782,7 @@ var SASTokenClient = class {
|
|
|
1746
1782
|
* Extract error message from various error types
|
|
1747
1783
|
*/
|
|
1748
1784
|
getErrorMessage(error) {
|
|
1749
|
-
if (
|
|
1785
|
+
if (axios2.isAxiosError(error)) {
|
|
1750
1786
|
if (error.response?.status === 401) {
|
|
1751
1787
|
return "Invalid API key for artifact uploads";
|
|
1752
1788
|
}
|
|
@@ -1764,6 +1800,11 @@ var SASTokenClient = class {
|
|
|
1764
1800
|
return String(error);
|
|
1765
1801
|
}
|
|
1766
1802
|
};
|
|
1803
|
+
|
|
1804
|
+
// src/uploads/artifact-uploader.ts
|
|
1805
|
+
import { createReadStream, statSync } from "fs";
|
|
1806
|
+
import { basename, extname } from "path";
|
|
1807
|
+
import axios3 from "axios";
|
|
1767
1808
|
var ArtifactUploader = class {
|
|
1768
1809
|
sasToken;
|
|
1769
1810
|
options;
|
|
@@ -1909,7 +1950,7 @@ var ArtifactUploader = class {
|
|
|
1909
1950
|
async doUpload(filePath, uploadUrl, contentType) {
|
|
1910
1951
|
const fileStream = createReadStream(filePath);
|
|
1911
1952
|
const stats = statSync(filePath);
|
|
1912
|
-
await
|
|
1953
|
+
await axios3.put(uploadUrl, fileStream, {
|
|
1913
1954
|
headers: {
|
|
1914
1955
|
"Content-Type": contentType,
|
|
1915
1956
|
"Content-Length": stats.size,
|
|
@@ -1935,6 +1976,10 @@ var ArtifactUploader = class {
|
|
|
1935
1976
|
return this.sasToken.uniqueId;
|
|
1936
1977
|
}
|
|
1937
1978
|
};
|
|
1979
|
+
|
|
1980
|
+
// src/code-coverage/merger.ts
|
|
1981
|
+
import istanbulCoverage from "istanbul-lib-coverage";
|
|
1982
|
+
import picomatch from "picomatch";
|
|
1938
1983
|
function toIstanbulMapData(map) {
|
|
1939
1984
|
return map;
|
|
1940
1985
|
}
|
|
@@ -2051,6 +2096,9 @@ function extractMetric(metric) {
|
|
|
2051
2096
|
pct: metric.pct
|
|
2052
2097
|
};
|
|
2053
2098
|
}
|
|
2099
|
+
|
|
2100
|
+
// src/code-coverage/compact.ts
|
|
2101
|
+
import { createHash } from "crypto";
|
|
2054
2102
|
function extractCompactCounts(coverageMapJSON, gitRoot) {
|
|
2055
2103
|
const files = {};
|
|
2056
2104
|
let fileCount = 0;
|
|
@@ -2088,6 +2136,11 @@ function computeShapeHash(fileCoverage) {
|
|
|
2088
2136
|
};
|
|
2089
2137
|
return createHash("sha256").update(JSON.stringify(shape)).digest("hex").slice(0, 12);
|
|
2090
2138
|
}
|
|
2139
|
+
|
|
2140
|
+
// src/code-coverage/html-report.ts
|
|
2141
|
+
import { createContext } from "istanbul-lib-report";
|
|
2142
|
+
import { create as createReporter } from "istanbul-reports";
|
|
2143
|
+
import { mkdir, rm } from "fs/promises";
|
|
2091
2144
|
async function generateIstanbulHtmlReport(coverageMerger, options) {
|
|
2092
2145
|
await rm(options.outputDir, { recursive: true, force: true }).catch(() => {
|
|
2093
2146
|
});
|
|
@@ -2103,13 +2156,16 @@ async function generateIstanbulHtmlReport(coverageMerger, options) {
|
|
|
2103
2156
|
},
|
|
2104
2157
|
coverageMap
|
|
2105
2158
|
});
|
|
2106
|
-
const reporter =
|
|
2159
|
+
const reporter = createReporter("html", {
|
|
2107
2160
|
skipEmpty: false,
|
|
2108
2161
|
subdir: ""
|
|
2109
2162
|
});
|
|
2110
2163
|
reporter.execute(context);
|
|
2111
2164
|
return `${options.outputDir}/index.html`;
|
|
2112
2165
|
}
|
|
2166
|
+
|
|
2167
|
+
// src/code-coverage/fixtures.ts
|
|
2168
|
+
import { test as base, expect } from "@playwright/test";
|
|
2113
2169
|
var COVERAGE_EXTRACT_TIMEOUT_MS = 3e4;
|
|
2114
2170
|
async function extractCoverageFromPage(page, timeoutMs = COVERAGE_EXTRACT_TIMEOUT_MS) {
|
|
2115
2171
|
return Promise.race([
|
|
@@ -2138,9 +2194,30 @@ var coverageFixtures = {
|
|
|
2138
2194
|
{ auto: true }
|
|
2139
2195
|
]
|
|
2140
2196
|
};
|
|
2141
|
-
var test =
|
|
2197
|
+
var test = base.extend(
|
|
2142
2198
|
coverageFixtures
|
|
2143
2199
|
);
|
|
2200
|
+
|
|
2201
|
+
// src/reporter/log.ts
|
|
2202
|
+
var chalkPromise = null;
|
|
2203
|
+
async function getChalk() {
|
|
2204
|
+
if (!chalkPromise) {
|
|
2205
|
+
chalkPromise = import("chalk").then((m) => m.default).catch((error) => {
|
|
2206
|
+
console.warn("\u26A0\uFE0F TestDino: Failed to load chalk, using plain text output");
|
|
2207
|
+
console.debug("Chalk import error:", error.message);
|
|
2208
|
+
return {
|
|
2209
|
+
green: (s) => s,
|
|
2210
|
+
yellow: (s) => s,
|
|
2211
|
+
red: (s) => s,
|
|
2212
|
+
blue: (s) => s,
|
|
2213
|
+
cyan: (s) => s,
|
|
2214
|
+
dim: (s) => s,
|
|
2215
|
+
bold: (s) => s
|
|
2216
|
+
};
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
return chalkPromise;
|
|
2220
|
+
}
|
|
2144
2221
|
function stripAnsi(str) {
|
|
2145
2222
|
return str.replace(/\u001b\[[0-9;]*m/g, "");
|
|
2146
2223
|
}
|
|
@@ -2169,11 +2246,11 @@ function shortenPath(filePath) {
|
|
|
2169
2246
|
const parts = filePath.split("/");
|
|
2170
2247
|
return parts.length > 2 ? parts.slice(-2).join("/") : filePath;
|
|
2171
2248
|
}
|
|
2172
|
-
function colorPct(pct) {
|
|
2249
|
+
function colorPct(pct, chalk) {
|
|
2173
2250
|
const color = pct >= 80 ? chalk.green : pct >= 50 ? chalk.yellow : chalk.red;
|
|
2174
2251
|
return color(pct === 100 ? "100%" : `${pct.toFixed(1)}%`);
|
|
2175
2252
|
}
|
|
2176
|
-
function
|
|
2253
|
+
function printCoverageTableWithChalk(event, chalk) {
|
|
2177
2254
|
const { summary, files } = event;
|
|
2178
2255
|
const row = (content) => ` ${chalk.dim("\u2502")} ${content}`;
|
|
2179
2256
|
const nameW = 30;
|
|
@@ -2190,18 +2267,18 @@ function printCoverageTable(event) {
|
|
|
2190
2267
|
const short = name.length > nameW ? name.slice(0, nameW - 1) + "~" : name;
|
|
2191
2268
|
console.log(
|
|
2192
2269
|
row(
|
|
2193
|
-
` ${pad(short, nameW)}` + padStart(colorPct(file.statements.pct), colW) + padStart(colorPct(file.branches.pct), colW) + padStart(colorPct(file.functions.pct), colW) + padStart(colorPct(file.lines.pct), colW)
|
|
2270
|
+
` ${pad(short, nameW)}` + padStart(colorPct(file.statements.pct, chalk), colW) + padStart(colorPct(file.branches.pct, chalk), colW) + padStart(colorPct(file.functions.pct, chalk), colW) + padStart(colorPct(file.lines.pct, chalk), colW)
|
|
2194
2271
|
)
|
|
2195
2272
|
);
|
|
2196
2273
|
}
|
|
2197
2274
|
console.log(row(` ${chalk.dim("\u2500".repeat(nameW + colW * 4))}`));
|
|
2198
2275
|
console.log(
|
|
2199
2276
|
row(
|
|
2200
|
-
` ${chalk.bold(pad("All files", nameW))}` + padStart(colorPct(summary.statements.pct), colW) + padStart(colorPct(summary.branches.pct), colW) + padStart(colorPct(summary.functions.pct), colW) + padStart(colorPct(summary.lines.pct), colW)
|
|
2277
|
+
` ${chalk.bold(pad("All files", nameW))}` + padStart(colorPct(summary.statements.pct, chalk), colW) + padStart(colorPct(summary.branches.pct, chalk), colW) + padStart(colorPct(summary.functions.pct, chalk), colW) + padStart(colorPct(summary.lines.pct, chalk), colW)
|
|
2201
2278
|
)
|
|
2202
2279
|
);
|
|
2203
2280
|
}
|
|
2204
|
-
function
|
|
2281
|
+
function printRunSummaryWithChalk(result, streamingSuccess, data, chalk) {
|
|
2205
2282
|
const W = 72;
|
|
2206
2283
|
const topBorder = ` ${chalk.dim(`\u250C${"\u2500".repeat(W)}\u2510`)}`;
|
|
2207
2284
|
const bottomBorder = ` ${chalk.dim(`\u2514${"\u2500".repeat(W)}\u2518`)}`;
|
|
@@ -2250,7 +2327,7 @@ function printRunSummary(result, streamingSuccess, data) {
|
|
|
2250
2327
|
console.log(row(`${chalk.bold("Stream")} ${streamIcon} ${chalk.dim(`via ${transport}`)}`));
|
|
2251
2328
|
if (data.lastCoverageEvent) {
|
|
2252
2329
|
console.log(divider);
|
|
2253
|
-
|
|
2330
|
+
printCoverageTableWithChalk(data.lastCoverageEvent, chalk);
|
|
2254
2331
|
}
|
|
2255
2332
|
console.log(bottomBorder);
|
|
2256
2333
|
console.log("");
|
|
@@ -2265,8 +2342,14 @@ var createReporterLog = (options) => ({
|
|
|
2265
2342
|
console.log(`\u{1F50D} TestDino: ${msg}`);
|
|
2266
2343
|
}
|
|
2267
2344
|
},
|
|
2268
|
-
printRunSummary,
|
|
2269
|
-
|
|
2345
|
+
printRunSummary: async (result, streamingSuccess, data) => {
|
|
2346
|
+
const chalk = await getChalk();
|
|
2347
|
+
printRunSummaryWithChalk(result, streamingSuccess, data, chalk);
|
|
2348
|
+
},
|
|
2349
|
+
printCoverageTable: async (event) => {
|
|
2350
|
+
const chalk = await getChalk();
|
|
2351
|
+
printCoverageTableWithChalk(event, chalk);
|
|
2352
|
+
}
|
|
2270
2353
|
});
|
|
2271
2354
|
|
|
2272
2355
|
// src/reporter/index.ts
|
|
@@ -2290,6 +2373,7 @@ var TestdinoReporter = class {
|
|
|
2290
2373
|
isShuttingDown = false;
|
|
2291
2374
|
// Quota tracking
|
|
2292
2375
|
quotaExceeded = false;
|
|
2376
|
+
pendingQuotaError = null;
|
|
2293
2377
|
// Session ID from HTTP auth, passed to WebSocket for session reuse
|
|
2294
2378
|
sessionId = null;
|
|
2295
2379
|
// Artifact upload
|
|
@@ -2452,7 +2536,7 @@ var TestdinoReporter = class {
|
|
|
2452
2536
|
if (!this.quotaExceeded) {
|
|
2453
2537
|
this.quotaExceeded = true;
|
|
2454
2538
|
this.initFailed = true;
|
|
2455
|
-
this.
|
|
2539
|
+
this.pendingQuotaError = error;
|
|
2456
2540
|
}
|
|
2457
2541
|
} else {
|
|
2458
2542
|
this.log.error(`WebSocket error: ${error.message}`);
|
|
@@ -2492,7 +2576,7 @@ var TestdinoReporter = class {
|
|
|
2492
2576
|
this.initFailed = true;
|
|
2493
2577
|
if (error instanceof Error && "code" in error && isQuotaError(error)) {
|
|
2494
2578
|
this.quotaExceeded = true;
|
|
2495
|
-
this.
|
|
2579
|
+
this.pendingQuotaError = error;
|
|
2496
2580
|
} else if (errorMessage.includes("Authentication failed") || errorMessage.includes("401") || errorMessage.includes("Unauthorized")) {
|
|
2497
2581
|
this.printConfigurationError("Authentication failed - Invalid or expired token", [
|
|
2498
2582
|
"Verify your token is correct",
|
|
@@ -2716,6 +2800,9 @@ var TestdinoReporter = class {
|
|
|
2716
2800
|
if (this.pendingTestEndPromises.size > 0) {
|
|
2717
2801
|
await Promise.allSettled(Array.from(this.pendingTestEndPromises));
|
|
2718
2802
|
}
|
|
2803
|
+
if (this.pendingQuotaError) {
|
|
2804
|
+
this.printQuotaError(this.pendingQuotaError);
|
|
2805
|
+
}
|
|
2719
2806
|
this.log.success("Tests completed (quota limit reached; not streamed to TestDino)");
|
|
2720
2807
|
this.wsClient?.close();
|
|
2721
2808
|
this.removeSignalHandlers();
|
|
@@ -2804,8 +2891,15 @@ var TestdinoReporter = class {
|
|
|
2804
2891
|
this.log.success("All events sent (HTTP fallback)");
|
|
2805
2892
|
delivered = true;
|
|
2806
2893
|
} catch (httpError) {
|
|
2807
|
-
const
|
|
2808
|
-
|
|
2894
|
+
const isQuota = isQuotaError(httpError) || httpError instanceof Error && "code" in httpError && (httpError.code === "QUOTA_EXCEEDED" || httpError.code === "QUOTA_EXHAUSTED");
|
|
2895
|
+
if (isQuota) {
|
|
2896
|
+
this.quotaExceeded = true;
|
|
2897
|
+
this.pendingQuotaError = httpError;
|
|
2898
|
+
this.printQuotaError(httpError);
|
|
2899
|
+
} else {
|
|
2900
|
+
const httpErrorMessage = httpError instanceof Error ? httpError.message : String(httpError);
|
|
2901
|
+
this.log.error(`HTTP fallback also failed: ${httpErrorMessage}`);
|
|
2902
|
+
}
|
|
2809
2903
|
}
|
|
2810
2904
|
} else if (!delivered) {
|
|
2811
2905
|
this.log.warn("Server did not acknowledge run:end in time, events may be pending");
|
|
@@ -2825,7 +2919,7 @@ var TestdinoReporter = class {
|
|
|
2825
2919
|
useHttpFallback: this.useHttpFallback,
|
|
2826
2920
|
lastCoverageEvent: this.lastCoverageEvent
|
|
2827
2921
|
};
|
|
2828
|
-
this.log.printRunSummary(result, delivered, summaryData);
|
|
2922
|
+
await this.log.printRunSummary(result, delivered, summaryData);
|
|
2829
2923
|
if (this.coverageThresholdFailed && this.lastCoverageEvent) {
|
|
2830
2924
|
const failures = this.checkCoverageThresholds(this.lastCoverageEvent.summary);
|
|
2831
2925
|
this.log.error("Coverage thresholds not met:");
|
|
@@ -3112,45 +3206,75 @@ var TestdinoReporter = class {
|
|
|
3112
3206
|
const details = errorData.details;
|
|
3113
3207
|
const planName = details?.planName || "Unknown";
|
|
3114
3208
|
const resetDate = details?.resetDate;
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
Used: ${details.used || "Unknown"} executions`;
|
|
3124
|
-
if (resetDate) {
|
|
3125
|
-
message += `
|
|
3126
|
-
Limit Resets: ${new Date(resetDate).toLocaleDateString()}`;
|
|
3127
|
-
}
|
|
3128
|
-
} else if (errorData.code === "QUOTA_EXCEEDED") {
|
|
3209
|
+
const formattedResetDate = resetDate ? new Date(resetDate).toLocaleDateString("en-US", {
|
|
3210
|
+
day: "2-digit",
|
|
3211
|
+
month: "short",
|
|
3212
|
+
year: "numeric"
|
|
3213
|
+
}) : void 0;
|
|
3214
|
+
console.error("");
|
|
3215
|
+
console.error(border);
|
|
3216
|
+
if (errorData.code === "QUOTA_EXCEEDED") {
|
|
3129
3217
|
const exceeded = details;
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3218
|
+
const orgRemaining = (exceeded.total ?? 0) - (exceeded.used ?? 0);
|
|
3219
|
+
const effectiveLimit = (exceeded.projectLimit ?? 0) + (exceeded.projectBorrowed ?? 0);
|
|
3220
|
+
console.error(" \u274C TestDino Project Execution Limit Reached");
|
|
3221
|
+
console.error(border);
|
|
3222
|
+
console.error("");
|
|
3223
|
+
console.error(" The test case limit allocated to this project has been exceeded.");
|
|
3224
|
+
console.error("");
|
|
3225
|
+
console.error(" Project Usage:");
|
|
3226
|
+
if (exceeded.projectName) {
|
|
3227
|
+
console.error(` Project: ${exceeded.projectName}`);
|
|
3228
|
+
}
|
|
3229
|
+
if (exceeded.projectLimit != null) {
|
|
3230
|
+
console.error(
|
|
3231
|
+
` Limit: ${effectiveLimit} test cases${exceeded.projectBorrowed ? ` (${exceeded.projectLimit} allocated + ${exceeded.projectBorrowed} borrowed)` : ""}`
|
|
3232
|
+
);
|
|
3142
3233
|
}
|
|
3234
|
+
if (exceeded.projectUsed != null) {
|
|
3235
|
+
console.error(` Used: ${exceeded.projectUsed}`);
|
|
3236
|
+
}
|
|
3237
|
+
console.error(` This run: ${exceeded.totalTests || "Unknown"} test cases`);
|
|
3238
|
+
console.error(" Status: Project quota exhausted");
|
|
3239
|
+
console.error("");
|
|
3240
|
+
console.error(" Organization Usage:");
|
|
3241
|
+
console.error(` Plan: ${planName} (${exceeded.total || "Unknown"} test cases / month)`);
|
|
3242
|
+
console.error(` Used: ${exceeded.used || "Unknown"}`);
|
|
3243
|
+
console.error(` Remaining: ${orgRemaining}`);
|
|
3244
|
+
if (orgRemaining > 0) {
|
|
3245
|
+
console.error("");
|
|
3246
|
+
console.error(" Note:");
|
|
3247
|
+
console.error(` Your organization still has ${orgRemaining} test cases available,`);
|
|
3248
|
+
console.error(" but they are not allocated to this project.");
|
|
3249
|
+
}
|
|
3250
|
+
console.error("");
|
|
3251
|
+
console.error(" Solutions:");
|
|
3252
|
+
console.error(" 1. Allocate more test case quota to this project in");
|
|
3253
|
+
console.error(" Settings \u2192 Billing & Usage \u2192 Test Limits");
|
|
3254
|
+
console.error(" 2. Enable Auto Allocation to automatically distribute");
|
|
3255
|
+
console.error(" remaining organization quota");
|
|
3256
|
+
} else if (errorData.code === "QUOTA_EXHAUSTED") {
|
|
3257
|
+
console.error(" \u274C TestDino Organization Execution Limit Reached");
|
|
3258
|
+
console.error(border);
|
|
3259
|
+
console.error("");
|
|
3260
|
+
console.error(" Your organization has exhausted its monthly test case limit.");
|
|
3261
|
+
console.error("");
|
|
3262
|
+
console.error(" Organization Usage:");
|
|
3263
|
+
console.error(` Plan: ${planName} (${details.totalLimit || "Unknown"} test cases / month)`);
|
|
3264
|
+
console.error(` Used: ${details.used || "Unknown"}`);
|
|
3265
|
+
console.error(" Remaining: 0");
|
|
3266
|
+
console.error("");
|
|
3267
|
+
console.error(" Solutions:");
|
|
3268
|
+
console.error(" 1. Upgrade your plan to increase monthly test case limit");
|
|
3269
|
+
}
|
|
3270
|
+
if (formattedResetDate) {
|
|
3271
|
+
console.error("");
|
|
3272
|
+
console.error(" Monthly Reset:");
|
|
3273
|
+
console.error(` ${formattedResetDate}`);
|
|
3143
3274
|
}
|
|
3144
3275
|
console.error("");
|
|
3145
|
-
console.error(
|
|
3146
|
-
console.error("
|
|
3147
|
-
console.error(border);
|
|
3148
|
-
console.error(` ${message}`);
|
|
3149
|
-
console.error("");
|
|
3150
|
-
console.error(" Solutions:");
|
|
3151
|
-
console.error(" 1. Upgrade your plan to increase monthly limit");
|
|
3152
|
-
console.error(" 2. Wait for monthly limit reset");
|
|
3153
|
-
console.error(" 3. Visit https://testdino.com/pricing for plan options");
|
|
3276
|
+
console.error(" Docs: https://docs.testdino.com/platform/billing-and-usage/test-limits");
|
|
3277
|
+
console.error(" Pricing: https://testdino.com/pricing");
|
|
3154
3278
|
console.error(border);
|
|
3155
3279
|
console.error("");
|
|
3156
3280
|
}
|
|
@@ -3530,7 +3654,10 @@ var TestdinoReporter = class {
|
|
|
3530
3654
|
return failures;
|
|
3531
3655
|
}
|
|
3532
3656
|
};
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3657
|
+
export {
|
|
3658
|
+
coverageFixtures,
|
|
3659
|
+
TestdinoReporter as default,
|
|
3660
|
+
expect,
|
|
3661
|
+
test
|
|
3662
|
+
};
|
|
3536
3663
|
//# sourceMappingURL=index.mjs.map
|