@testdino/playwright 1.0.1 → 1.0.3

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
@@ -384,7 +384,7 @@ var EventBuffer = class {
384
384
  events = [];
385
385
  maxSize;
386
386
  onFlush;
387
- isFlushing = false;
387
+ flushPromise = null;
388
388
  constructor(options) {
389
389
  this.maxSize = options.maxSize || 10;
390
390
  this.onFlush = options.onFlush || (async () => {
@@ -402,12 +402,30 @@ var EventBuffer = class {
402
402
  }
403
403
  /**
404
404
  * Flush all buffered events
405
+ * If a flush is already in progress, waits for it to complete then flushes any new events
405
406
  */
406
407
  async flush() {
407
- if (this.isFlushing || this.events.length === 0) {
408
+ if (this.flushPromise) {
409
+ await this.flushPromise;
410
+ if (this.events.length > 0) {
411
+ return this.flush();
412
+ }
413
+ return;
414
+ }
415
+ if (this.events.length === 0) {
408
416
  return;
409
417
  }
410
- this.isFlushing = true;
418
+ this.flushPromise = this.doFlush();
419
+ try {
420
+ await this.flushPromise;
421
+ } finally {
422
+ this.flushPromise = null;
423
+ }
424
+ }
425
+ /**
426
+ * Internal flush implementation
427
+ */
428
+ async doFlush() {
411
429
  try {
412
430
  const eventsToFlush = [...this.events];
413
431
  this.events = [];
@@ -415,8 +433,6 @@ var EventBuffer = class {
415
433
  } catch (error) {
416
434
  console.error("Failed to flush events:", error);
417
435
  throw error;
418
- } finally {
419
- this.isFlushing = false;
420
436
  }
421
437
  }
422
438
  /**
@@ -1734,6 +1750,8 @@ var TestdinoReporter = class {
1734
1750
  // Deferred initialization - resolves true on success, false on failure
1735
1751
  initPromise = null;
1736
1752
  initFailed = false;
1753
+ // Promises for onTestEnd; must be awaited in onEnd to prevent data loss
1754
+ pendingTestEndPromises = /* @__PURE__ */ new Set();
1737
1755
  constructor(config = {}) {
1738
1756
  const cliConfig = this.loadCliConfig();
1739
1757
  this.config = { ...config, ...cliConfig };
@@ -2018,49 +2036,72 @@ var TestdinoReporter = class {
2018
2036
  await this.buffer.add(event);
2019
2037
  }
2020
2038
  /**
2021
- * Called after each test completes
2022
- */
2023
- async onTestEnd(test, result) {
2039
+ /**
2040
+ * Called after each test.
2041
+ * Playwright does not await onTestEnd promises—pending work is awaited in onEnd.
2042
+ */
2043
+ onTestEnd(test, result) {
2024
2044
  if (!this.initPromise || this.initFailed) return;
2025
- const attachmentsWithUrls = await this.uploadAttachments(result.attachments, test.id);
2026
- const event = {
2027
- type: "test:end",
2028
- runId: this.runId,
2029
- ...this.getEventMetadata(),
2030
- // Test Identification
2031
- testId: test.id,
2032
- // Status Information
2033
- status: result.status,
2034
- outcome: test.outcome(),
2035
- // Timing
2036
- duration: result.duration,
2037
- // Execution Context
2038
- retry: result.retry,
2039
- // Worker Information
2040
- workerIndex: result.workerIndex,
2041
- parallelIndex: result.parallelIndex,
2042
- // Test Metadata
2043
- annotations: test.annotations.map((a) => ({
2044
- type: a.type,
2045
- description: a.description
2046
- })),
2047
- // Error Information
2048
- errors: result.errors.map((e) => this.extractError(e)).filter((e) => e !== void 0),
2049
- // Step Summary
2050
- steps: this.extractTestStepsSummary(result),
2051
- // Attachments Metadata (with Azure URLs when uploaded)
2052
- attachments: attachmentsWithUrls,
2053
- // Console Output
2054
- stdout: result.stdout.length > 0 ? this.extractConsoleOutput(result.stdout) : void 0,
2055
- stderr: result.stderr.length > 0 ? this.extractConsoleOutput(result.stderr) : void 0
2056
- };
2057
- await this.buffer.add(event);
2045
+ const workPromise = this.processTestEnd(test, result);
2046
+ this.pendingTestEndPromises.add(workPromise);
2047
+ workPromise.finally(() => {
2048
+ this.pendingTestEndPromises.delete(workPromise);
2049
+ });
2050
+ }
2051
+ /**
2052
+ * Process test end event asynchronously
2053
+ * Uploads attachments and adds test:end event to buffer
2054
+ */
2055
+ async processTestEnd(test, result) {
2056
+ try {
2057
+ const attachmentsWithUrls = await this.uploadAttachments(result.attachments, test.id);
2058
+ const event = {
2059
+ type: "test:end",
2060
+ runId: this.runId,
2061
+ ...this.getEventMetadata(),
2062
+ // Test Identification
2063
+ testId: test.id,
2064
+ // Status Information
2065
+ status: result.status,
2066
+ outcome: test.outcome(),
2067
+ // Timing
2068
+ duration: result.duration,
2069
+ // Execution Context
2070
+ retry: result.retry,
2071
+ // Worker Information
2072
+ workerIndex: result.workerIndex,
2073
+ parallelIndex: result.parallelIndex,
2074
+ // Test Metadata
2075
+ annotations: test.annotations.map((a) => ({
2076
+ type: a.type,
2077
+ description: a.description
2078
+ })),
2079
+ // Error Information
2080
+ errors: result.errors.map((e) => this.extractError(e)).filter((e) => e !== void 0),
2081
+ // Step Summary
2082
+ steps: this.extractTestStepsSummary(result),
2083
+ // Attachments Metadata (with Azure URLs when uploaded)
2084
+ attachments: attachmentsWithUrls,
2085
+ // Console Output
2086
+ stdout: result.stdout.length > 0 ? this.extractConsoleOutput(result.stdout) : void 0,
2087
+ stderr: result.stderr.length > 0 ? this.extractConsoleOutput(result.stderr) : void 0
2088
+ };
2089
+ await this.buffer.add(event);
2090
+ } catch (error) {
2091
+ console.error(
2092
+ "\u274C TestDino: Failed to process test:end event:",
2093
+ error instanceof Error ? error.message : String(error)
2094
+ );
2095
+ }
2058
2096
  }
2059
2097
  /**
2060
2098
  * Called after all tests complete
2061
2099
  */
2062
2100
  async onEnd(result) {
2063
2101
  if (this.quotaExceeded) {
2102
+ if (this.pendingTestEndPromises.size > 0) {
2103
+ await Promise.allSettled(Array.from(this.pendingTestEndPromises));
2104
+ }
2064
2105
  console.log("\u2705 TestDino: Tests completed (quota limit reached; not streamed to TestDino)");
2065
2106
  this.wsClient?.close();
2066
2107
  this.removeSignalHandlers();
@@ -2070,10 +2111,17 @@ var TestdinoReporter = class {
2070
2111
  const success = await this.initPromise;
2071
2112
  if (!success) {
2072
2113
  this.buffer?.clear();
2114
+ this.pendingTestEndPromises.clear();
2073
2115
  this.wsClient?.close();
2074
2116
  this.removeSignalHandlers();
2075
2117
  return;
2076
2118
  }
2119
+ if (this.pendingTestEndPromises.size > 0) {
2120
+ if (this.config.debug) {
2121
+ console.log(`\u{1F50D} TestDino: Waiting for ${this.pendingTestEndPromises.size} pending test:end events...`);
2122
+ }
2123
+ await Promise.allSettled(Array.from(this.pendingTestEndPromises));
2124
+ }
2077
2125
  const event = {
2078
2126
  type: "run:end",
2079
2127
  runId: this.runId,
@@ -2538,6 +2586,17 @@ var TestdinoReporter = class {
2538
2586
  if (!this.initPromise) {
2539
2587
  process.exit(exitCode);
2540
2588
  }
2589
+ const waitForPending = async () => {
2590
+ if (this.pendingTestEndPromises.size > 0) {
2591
+ try {
2592
+ await Promise.race([
2593
+ Promise.allSettled(Array.from(this.pendingTestEndPromises)),
2594
+ this.timeoutPromise(2e3, "Pending events timeout")
2595
+ ]);
2596
+ } catch {
2597
+ }
2598
+ }
2599
+ };
2541
2600
  const event = {
2542
2601
  type: "run:end",
2543
2602
  runId: this.runId,
@@ -2557,6 +2616,7 @@ var TestdinoReporter = class {
2557
2616
  }, 3e3);
2558
2617
  const sendAndExit = async () => {
2559
2618
  try {
2619
+ await waitForPending();
2560
2620
  await Promise.race([this.sendInterruptionEvent(event), this.timeoutPromise(2500, "Send timeout")]);
2561
2621
  console.log("\u2705 TestDino: Interruption event sent");
2562
2622
  } catch (error) {