@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/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: type(),
1135
+ name: osType(),
1110
1136
  type: process.platform,
1111
- os: `${type()} ${release()}`,
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
- readFile(packageJsonPath, "utf-8"),
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 = axios.create({
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 (axios.isAxiosError(error)) {
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 axios.put(uploadUrl, fileStream, {
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 = create("html", {
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 = test$1.extend(
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 printCoverageTable(event) {
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 printRunSummary(result, streamingSuccess, data) {
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
- printCoverageTable(data.lastCoverageEvent);
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
- printCoverageTable
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.printQuotaError(error);
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.printQuotaError(error);
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 httpErrorMessage = httpError instanceof Error ? httpError.message : String(httpError);
2808
- this.log.error(`HTTP fallback also failed: ${httpErrorMessage}`);
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
- let message = "Execution quota exceeded";
3116
- message += `
3117
-
3118
- Current Plan: ${planName}`;
3119
- if (errorData.code === "QUOTA_EXHAUSTED") {
3120
- message += `
3121
- Monthly Limit: ${details.totalLimit || "Unknown"} executions`;
3122
- message += `
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
- message += `
3131
- Monthly Limit: ${exceeded.total || "Unknown"} executions`;
3132
- message += `
3133
- Used: ${exceeded.used || "Unknown"} executions`;
3134
- const remaining = (exceeded.total ?? 0) - (exceeded.used ?? 0);
3135
- message += `
3136
- Remaining: ${remaining} executions`;
3137
- message += `
3138
- Tests in this run: ${exceeded.totalTests || "Unknown"}`;
3139
- if (resetDate) {
3140
- message += `
3141
- Limit Resets: ${new Date(resetDate).toLocaleDateString()}`;
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(border);
3146
- console.error(" \u274C TestDino Execution Limit Reached");
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
- export { coverageFixtures, TestdinoReporter as default, test };
3535
- //# sourceMappingURL=index.mjs.map
3657
+ export {
3658
+ coverageFixtures,
3659
+ TestdinoReporter as default,
3660
+ expect,
3661
+ test
3662
+ };
3536
3663
  //# sourceMappingURL=index.mjs.map