@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/README.md CHANGED
@@ -121,11 +121,12 @@ npx playwright test
121
121
 
122
122
  ### TestDino Options
123
123
 
124
- | Option | CLI Flag | Environment Variable | Description |
125
- | --------- | --------------- | -------------------- | ------------------------------- |
126
- | `token` | `--token`, `-t` | `TESTDINO_TOKEN` | Authentication token (required) |
127
- | `debug` | `--debug` | `TESTDINO_DEBUG` | Enable debug logging |
128
- | `ciRunId` | `--ci-run-id` | - | Group sharded test runs |
124
+ | Option | CLI Flag | Environment Variable | Description |
125
+ | ----------- | ---------------- | -------------------- | --------------------------------------------------------------------------------------------------- |
126
+ | `token` | `--token`, `-t` | `TESTDINO_TOKEN` | Authentication token (required) |
127
+ | `debug` | `--debug` | `TESTDINO_DEBUG` | Enable debug logging |
128
+ | `ciRunId` | `--ci-run-id` | - | Group sharded test runs |
129
+ | `artifacts` | `--no-artifacts` | - | Upload artifacts (screenshots, videos, traces). Enabled by default; use `--no-artifacts` to disable |
129
130
 
130
131
  ### Configuration File
131
132
 
package/dist/index.d.mts CHANGED
@@ -588,6 +588,7 @@ declare class TestdinoReporter implements Reporter {
588
588
  private artifactsEnabled;
589
589
  private initPromise;
590
590
  private initFailed;
591
+ private pendingTestEndPromises;
591
592
  constructor(config?: TestdinoConfig);
592
593
  /**
593
594
  * Load configuration from CLI temp file if available
@@ -622,9 +623,16 @@ declare class TestdinoReporter implements Reporter {
622
623
  */
623
624
  onStepEnd(test: TestCase, result: TestResult, step: TestStep): Promise<void>;
624
625
  /**
625
- * Called after each test completes
626
+ /**
627
+ * Called after each test.
628
+ * Playwright does not await onTestEnd promises—pending work is awaited in onEnd.
629
+ */
630
+ onTestEnd(test: TestCase, result: TestResult): void;
631
+ /**
632
+ * Process test end event asynchronously
633
+ * Uploads attachments and adds test:end event to buffer
626
634
  */
627
- onTestEnd(test: TestCase, result: TestResult): Promise<void>;
635
+ private processTestEnd;
628
636
  /**
629
637
  * Called after all tests complete
630
638
  */
package/dist/index.d.ts CHANGED
@@ -588,6 +588,7 @@ declare class TestdinoReporter implements Reporter {
588
588
  private artifactsEnabled;
589
589
  private initPromise;
590
590
  private initFailed;
591
+ private pendingTestEndPromises;
591
592
  constructor(config?: TestdinoConfig);
592
593
  /**
593
594
  * Load configuration from CLI temp file if available
@@ -622,9 +623,16 @@ declare class TestdinoReporter implements Reporter {
622
623
  */
623
624
  onStepEnd(test: TestCase, result: TestResult, step: TestStep): Promise<void>;
624
625
  /**
625
- * Called after each test completes
626
+ /**
627
+ * Called after each test.
628
+ * Playwright does not await onTestEnd promises—pending work is awaited in onEnd.
629
+ */
630
+ onTestEnd(test: TestCase, result: TestResult): void;
631
+ /**
632
+ * Process test end event asynchronously
633
+ * Uploads attachments and adds test:end event to buffer
626
634
  */
627
- onTestEnd(test: TestCase, result: TestResult): Promise<void>;
635
+ private processTestEnd;
628
636
  /**
629
637
  * Called after all tests complete
630
638
  */
package/dist/index.js CHANGED
@@ -391,7 +391,7 @@ var EventBuffer = class {
391
391
  events = [];
392
392
  maxSize;
393
393
  onFlush;
394
- isFlushing = false;
394
+ flushPromise = null;
395
395
  constructor(options) {
396
396
  this.maxSize = options.maxSize || 10;
397
397
  this.onFlush = options.onFlush || (async () => {
@@ -409,12 +409,30 @@ var EventBuffer = class {
409
409
  }
410
410
  /**
411
411
  * Flush all buffered events
412
+ * If a flush is already in progress, waits for it to complete then flushes any new events
412
413
  */
413
414
  async flush() {
414
- if (this.isFlushing || this.events.length === 0) {
415
+ if (this.flushPromise) {
416
+ await this.flushPromise;
417
+ if (this.events.length > 0) {
418
+ return this.flush();
419
+ }
420
+ return;
421
+ }
422
+ if (this.events.length === 0) {
415
423
  return;
416
424
  }
417
- this.isFlushing = true;
425
+ this.flushPromise = this.doFlush();
426
+ try {
427
+ await this.flushPromise;
428
+ } finally {
429
+ this.flushPromise = null;
430
+ }
431
+ }
432
+ /**
433
+ * Internal flush implementation
434
+ */
435
+ async doFlush() {
418
436
  try {
419
437
  const eventsToFlush = [...this.events];
420
438
  this.events = [];
@@ -422,8 +440,6 @@ var EventBuffer = class {
422
440
  } catch (error) {
423
441
  console.error("Failed to flush events:", error);
424
442
  throw error;
425
- } finally {
426
- this.isFlushing = false;
427
443
  }
428
444
  }
429
445
  /**
@@ -1741,6 +1757,8 @@ var TestdinoReporter = class {
1741
1757
  // Deferred initialization - resolves true on success, false on failure
1742
1758
  initPromise = null;
1743
1759
  initFailed = false;
1760
+ // Promises for onTestEnd; must be awaited in onEnd to prevent data loss
1761
+ pendingTestEndPromises = /* @__PURE__ */ new Set();
1744
1762
  constructor(config = {}) {
1745
1763
  const cliConfig = this.loadCliConfig();
1746
1764
  this.config = { ...config, ...cliConfig };
@@ -2025,49 +2043,72 @@ var TestdinoReporter = class {
2025
2043
  await this.buffer.add(event);
2026
2044
  }
2027
2045
  /**
2028
- * Called after each test completes
2029
- */
2030
- async onTestEnd(test, result) {
2046
+ /**
2047
+ * Called after each test.
2048
+ * Playwright does not await onTestEnd promises—pending work is awaited in onEnd.
2049
+ */
2050
+ onTestEnd(test, result) {
2031
2051
  if (!this.initPromise || this.initFailed) return;
2032
- const attachmentsWithUrls = await this.uploadAttachments(result.attachments, test.id);
2033
- const event = {
2034
- type: "test:end",
2035
- runId: this.runId,
2036
- ...this.getEventMetadata(),
2037
- // Test Identification
2038
- testId: test.id,
2039
- // Status Information
2040
- status: result.status,
2041
- outcome: test.outcome(),
2042
- // Timing
2043
- duration: result.duration,
2044
- // Execution Context
2045
- retry: result.retry,
2046
- // Worker Information
2047
- workerIndex: result.workerIndex,
2048
- parallelIndex: result.parallelIndex,
2049
- // Test Metadata
2050
- annotations: test.annotations.map((a) => ({
2051
- type: a.type,
2052
- description: a.description
2053
- })),
2054
- // Error Information
2055
- errors: result.errors.map((e) => this.extractError(e)).filter((e) => e !== void 0),
2056
- // Step Summary
2057
- steps: this.extractTestStepsSummary(result),
2058
- // Attachments Metadata (with Azure URLs when uploaded)
2059
- attachments: attachmentsWithUrls,
2060
- // Console Output
2061
- stdout: result.stdout.length > 0 ? this.extractConsoleOutput(result.stdout) : void 0,
2062
- stderr: result.stderr.length > 0 ? this.extractConsoleOutput(result.stderr) : void 0
2063
- };
2064
- await this.buffer.add(event);
2052
+ const workPromise = this.processTestEnd(test, result);
2053
+ this.pendingTestEndPromises.add(workPromise);
2054
+ workPromise.finally(() => {
2055
+ this.pendingTestEndPromises.delete(workPromise);
2056
+ });
2057
+ }
2058
+ /**
2059
+ * Process test end event asynchronously
2060
+ * Uploads attachments and adds test:end event to buffer
2061
+ */
2062
+ async processTestEnd(test, result) {
2063
+ try {
2064
+ const attachmentsWithUrls = await this.uploadAttachments(result.attachments, test.id);
2065
+ const event = {
2066
+ type: "test:end",
2067
+ runId: this.runId,
2068
+ ...this.getEventMetadata(),
2069
+ // Test Identification
2070
+ testId: test.id,
2071
+ // Status Information
2072
+ status: result.status,
2073
+ outcome: test.outcome(),
2074
+ // Timing
2075
+ duration: result.duration,
2076
+ // Execution Context
2077
+ retry: result.retry,
2078
+ // Worker Information
2079
+ workerIndex: result.workerIndex,
2080
+ parallelIndex: result.parallelIndex,
2081
+ // Test Metadata
2082
+ annotations: test.annotations.map((a) => ({
2083
+ type: a.type,
2084
+ description: a.description
2085
+ })),
2086
+ // Error Information
2087
+ errors: result.errors.map((e) => this.extractError(e)).filter((e) => e !== void 0),
2088
+ // Step Summary
2089
+ steps: this.extractTestStepsSummary(result),
2090
+ // Attachments Metadata (with Azure URLs when uploaded)
2091
+ attachments: attachmentsWithUrls,
2092
+ // Console Output
2093
+ stdout: result.stdout.length > 0 ? this.extractConsoleOutput(result.stdout) : void 0,
2094
+ stderr: result.stderr.length > 0 ? this.extractConsoleOutput(result.stderr) : void 0
2095
+ };
2096
+ await this.buffer.add(event);
2097
+ } catch (error) {
2098
+ console.error(
2099
+ "\u274C TestDino: Failed to process test:end event:",
2100
+ error instanceof Error ? error.message : String(error)
2101
+ );
2102
+ }
2065
2103
  }
2066
2104
  /**
2067
2105
  * Called after all tests complete
2068
2106
  */
2069
2107
  async onEnd(result) {
2070
2108
  if (this.quotaExceeded) {
2109
+ if (this.pendingTestEndPromises.size > 0) {
2110
+ await Promise.allSettled(Array.from(this.pendingTestEndPromises));
2111
+ }
2071
2112
  console.log("\u2705 TestDino: Tests completed (quota limit reached; not streamed to TestDino)");
2072
2113
  this.wsClient?.close();
2073
2114
  this.removeSignalHandlers();
@@ -2077,10 +2118,17 @@ var TestdinoReporter = class {
2077
2118
  const success = await this.initPromise;
2078
2119
  if (!success) {
2079
2120
  this.buffer?.clear();
2121
+ this.pendingTestEndPromises.clear();
2080
2122
  this.wsClient?.close();
2081
2123
  this.removeSignalHandlers();
2082
2124
  return;
2083
2125
  }
2126
+ if (this.pendingTestEndPromises.size > 0) {
2127
+ if (this.config.debug) {
2128
+ console.log(`\u{1F50D} TestDino: Waiting for ${this.pendingTestEndPromises.size} pending test:end events...`);
2129
+ }
2130
+ await Promise.allSettled(Array.from(this.pendingTestEndPromises));
2131
+ }
2084
2132
  const event = {
2085
2133
  type: "run:end",
2086
2134
  runId: this.runId,
@@ -2545,6 +2593,17 @@ var TestdinoReporter = class {
2545
2593
  if (!this.initPromise) {
2546
2594
  process.exit(exitCode);
2547
2595
  }
2596
+ const waitForPending = async () => {
2597
+ if (this.pendingTestEndPromises.size > 0) {
2598
+ try {
2599
+ await Promise.race([
2600
+ Promise.allSettled(Array.from(this.pendingTestEndPromises)),
2601
+ this.timeoutPromise(2e3, "Pending events timeout")
2602
+ ]);
2603
+ } catch {
2604
+ }
2605
+ }
2606
+ };
2548
2607
  const event = {
2549
2608
  type: "run:end",
2550
2609
  runId: this.runId,
@@ -2564,6 +2623,7 @@ var TestdinoReporter = class {
2564
2623
  }, 3e3);
2565
2624
  const sendAndExit = async () => {
2566
2625
  try {
2626
+ await waitForPending();
2567
2627
  await Promise.race([this.sendInterruptionEvent(event), this.timeoutPromise(2500, "Send timeout")]);
2568
2628
  console.log("\u2705 TestDino: Interruption event sent");
2569
2629
  } catch (error) {