@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 +6 -5
- package/dist/index.d.mts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +101 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +101 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,11 +121,12 @@ npx playwright test
|
|
|
121
121
|
|
|
122
122
|
### TestDino Options
|
|
123
123
|
|
|
124
|
-
| Option
|
|
125
|
-
|
|
|
126
|
-
| `token`
|
|
127
|
-
| `debug`
|
|
128
|
-
| `ciRunId`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
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
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
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) {
|