@testpulse.run/reporter 0.1.6 → 0.2.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 +52 -20
- package/dist/index.cjs +57 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -2
- package/dist/index.js +36 -1
- package/package.json +22 -44
- package/dist/fixtures/createTestPulseTest.d.ts +0 -14
- package/dist/fixtures/createTestPulseTest.js +0 -33
- package/dist/fixtures/options.d.ts +0 -2
- package/dist/fixtures/options.js +0 -36
- package/dist/fixtures/runtime.d.ts +0 -3
- package/dist/fixtures/runtime.js +0 -6
- package/dist/fixtures/types.d.ts +0 -8
- package/dist/fixtures/types.js +0 -1
- package/dist/fixtures.d.ts +0 -2
- package/dist/fixtures.js +0 -1
- package/dist/live/CaptureScheduler.d.ts +0 -22
- package/dist/live/CaptureScheduler.js +0 -87
- package/dist/live/EncoderProcess.d.ts +0 -5
- package/dist/live/EncoderProcess.js +0 -61
- package/dist/live/LiveCaptureService.d.ts +0 -2
- package/dist/live/LiveCaptureService.js +0 -103
- package/dist/live/LiveSessionResolver.d.ts +0 -2
- package/dist/live/LiveSessionResolver.js +0 -86
- package/dist/live/LiveSessionState.d.ts +0 -6
- package/dist/live/LiveSessionState.js +0 -20
- package/dist/live/Mp4FragmentParser.d.ts +0 -4
- package/dist/live/Mp4FragmentParser.js +0 -32
- package/dist/live/SegmentQueue.d.ts +0 -9
- package/dist/live/SegmentQueue.js +0 -32
- package/dist/live/StreamUploader.d.ts +0 -21
- package/dist/live/StreamUploader.js +0 -78
- package/dist/live/telemetry.d.ts +0 -3
- package/dist/live/telemetry.js +0 -15
- package/dist/live/types.d.ts +0 -42
- package/dist/live/types.js +0 -1
- package/dist/reporter/ArtifactUploader.d.ts +0 -13
- package/dist/reporter/ArtifactUploader.js +0 -48
- package/dist/reporter/EventQueue.d.ts +0 -7
- package/dist/reporter/EventQueue.js +0 -15
- package/dist/reporter/GitRuntimeMetadataResolver.d.ts +0 -11
- package/dist/reporter/GitRuntimeMetadataResolver.js +0 -91
- package/dist/reporter/PlaywrightReporter.d.ts +0 -22
- package/dist/reporter/PlaywrightReporter.js +0 -142
- package/dist/reporter/RunClient.d.ts +0 -19
- package/dist/reporter/RunClient.js +0 -42
- package/dist/reporter/types.d.ts +0 -25
- package/dist/reporter/types.js +0 -1
- package/dist/shared/endpoint.d.ts +0 -1
- package/dist/shared/endpoint.js +0 -15
- package/dist/shared/http.d.ts +0 -2
- package/dist/shared/http.js +0 -17
package/README.md
CHANGED
|
@@ -1,40 +1,72 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://testpulse.run">
|
|
3
|
+
<img src="https://testpulse.run/images/logo-testpulse.svg" alt="TestPulse.run" height="48">
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://testpulse.run">testpulse.run</a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
1
11
|
# @testpulse.run/reporter
|
|
2
12
|
|
|
3
|
-
Playwright reporter for
|
|
13
|
+
Official Playwright reporter for TestPulse.
|
|
4
14
|
|
|
5
|
-
|
|
15
|
+
This package sends Playwright test events to the TestPulse API using the JSONL event pipeline. It is the package most projects should install.
|
|
6
16
|
|
|
7
|
-
|
|
8
|
-
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install -D @testpulse.run/reporter @playwright/test
|
|
9
21
|
```
|
|
10
22
|
|
|
11
|
-
##
|
|
23
|
+
## Playwright Configuration
|
|
12
24
|
|
|
13
25
|
```ts
|
|
14
26
|
import { defineConfig } from "@playwright/test";
|
|
15
27
|
|
|
16
28
|
export default defineConfig({
|
|
17
|
-
reporter: [
|
|
18
|
-
["list"],
|
|
19
|
-
["@testpulse.run/reporter", {
|
|
20
|
-
projectId: "demo-project",
|
|
21
|
-
apiKey: "<project-api-key>"
|
|
22
|
-
}]
|
|
23
|
-
]
|
|
29
|
+
reporter: [["@testpulse.run/reporter"]]
|
|
24
30
|
});
|
|
25
31
|
```
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
## Authentication
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
Set your API key in the environment used by Playwright:
|
|
30
36
|
|
|
31
|
-
```
|
|
32
|
-
|
|
37
|
+
```sh
|
|
38
|
+
TESTPULSE_API_KEY=your-api-key npx playwright test
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The reporter sends events directly to TestPulse.
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
## Options
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
export default defineConfig({
|
|
47
|
+
reporter: [
|
|
48
|
+
[
|
|
49
|
+
"@testpulse.run/reporter",
|
|
50
|
+
{
|
|
51
|
+
apiKey: process.env.TESTPULSE_API_KEY,
|
|
52
|
+
storageStreamId: "events",
|
|
53
|
+
batchSize: 100,
|
|
54
|
+
flushIntervalMs: 250
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
]
|
|
37
58
|
});
|
|
38
59
|
```
|
|
39
60
|
|
|
40
|
-
|
|
61
|
+
- `apiKey`: TestPulse API key. Defaults to `TESTPULSE_API_KEY`.
|
|
62
|
+
- `storageStreamId`: event stream id. Defaults to `TESTPULSE_STORAGE_STREAM_ID` or `events`.
|
|
63
|
+
- `runId`: explicit run identifier. A process/time based id is generated by default.
|
|
64
|
+
- `batchSize`: number of events per HTTP batch.
|
|
65
|
+
- `flushIntervalMs`: background flush interval for pending events.
|
|
66
|
+
- `sink`: custom event sink, mainly useful for tests and advanced integrations.
|
|
67
|
+
|
|
68
|
+
## Related Packages
|
|
69
|
+
|
|
70
|
+
- `@testpulse.run/playwright-jsonl-reporter`: JSONL reporter and sink implementations.
|
|
71
|
+
- `@testpulse.run/playwright-console-reporter`: compact terminal reporter.
|
|
72
|
+
- `@testpulse.run/playwright-core`: lower-level reporter event adapter.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
FileSystemEventSink: () => import_playwright_jsonl_reporter2.FileSystemEventSink,
|
|
24
|
+
HttpEventSink: () => import_playwright_jsonl_reporter2.HttpEventSink,
|
|
25
|
+
JsonlReporter: () => import_playwright_jsonl_reporter2.JsonlReporter,
|
|
26
|
+
TestPulseReporter: () => TestPulseReporter,
|
|
27
|
+
default: () => TestPulseReporter
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
var import_playwright_jsonl_reporter = require("@testpulse.run/playwright-jsonl-reporter");
|
|
31
|
+
var import_playwright_jsonl_reporter2 = require("@testpulse.run/playwright-jsonl-reporter");
|
|
32
|
+
var DEFAULT_BASE_URL = "https://api.testpulse.run/";
|
|
33
|
+
var DEFAULT_STORAGE_STREAM_ID = "events";
|
|
34
|
+
var TestPulseReporter = class extends import_playwright_jsonl_reporter.JsonlReporter {
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
const sink = options.sink ?? new import_playwright_jsonl_reporter.HttpEventSink({
|
|
37
|
+
baseUrl: options.baseUrl ?? process.env.TESTPULSE_API_URL ?? DEFAULT_BASE_URL,
|
|
38
|
+
apiKey: options.apiKey ?? process.env.TESTPULSE_API_KEY,
|
|
39
|
+
storageStreamId: options.storageStreamId ?? process.env.TESTPULSE_STORAGE_STREAM_ID ?? DEFAULT_STORAGE_STREAM_ID,
|
|
40
|
+
writerId: options.writerId,
|
|
41
|
+
flushIntervalMs: options.flushIntervalMs,
|
|
42
|
+
batchSize: options.batchSize,
|
|
43
|
+
fetch: options.fetch
|
|
44
|
+
});
|
|
45
|
+
super({
|
|
46
|
+
...options,
|
|
47
|
+
sink
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
52
|
+
0 && (module.exports = {
|
|
53
|
+
FileSystemEventSink,
|
|
54
|
+
HttpEventSink,
|
|
55
|
+
JsonlReporter,
|
|
56
|
+
TestPulseReporter
|
|
57
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { JsonlReporter, JsonlReporterOptions, HttpEventSinkOptions, EventSink } from '@testpulse.run/playwright-jsonl-reporter';
|
|
2
|
+
export { EventSink, EventSinkMetadata, FileSystemEventSink, HttpEventIngestResponse, HttpEventSink, HttpEventSinkOptions, JsonlAttachment, JsonlError, JsonlEvent, JsonlEventName, JsonlEventStream, JsonlReporter, JsonlReporterOptions, JsonlTestOutcome } from '@testpulse.run/playwright-jsonl-reporter';
|
|
3
|
+
|
|
4
|
+
interface TestPulseReporterOptions extends Omit<JsonlReporterOptions, "sink"> {
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
storageStreamId?: string;
|
|
8
|
+
writerId?: string;
|
|
9
|
+
flushIntervalMs?: number;
|
|
10
|
+
batchSize?: number;
|
|
11
|
+
fetch?: HttpEventSinkOptions["fetch"];
|
|
12
|
+
sink?: EventSink;
|
|
13
|
+
}
|
|
14
|
+
declare class TestPulseReporter extends JsonlReporter {
|
|
15
|
+
constructor(options?: TestPulseReporterOptions);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { TestPulseReporter, type TestPulseReporterOptions, TestPulseReporter as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
1
|
+
import { JsonlReporter, JsonlReporterOptions, HttpEventSinkOptions, EventSink } from '@testpulse.run/playwright-jsonl-reporter';
|
|
2
|
+
export { EventSink, EventSinkMetadata, FileSystemEventSink, HttpEventIngestResponse, HttpEventSink, HttpEventSinkOptions, JsonlAttachment, JsonlError, JsonlEvent, JsonlEventName, JsonlEventStream, JsonlReporter, JsonlReporterOptions, JsonlTestOutcome } from '@testpulse.run/playwright-jsonl-reporter';
|
|
3
|
+
|
|
4
|
+
interface TestPulseReporterOptions extends Omit<JsonlReporterOptions, "sink"> {
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
storageStreamId?: string;
|
|
8
|
+
writerId?: string;
|
|
9
|
+
flushIntervalMs?: number;
|
|
10
|
+
batchSize?: number;
|
|
11
|
+
fetch?: HttpEventSinkOptions["fetch"];
|
|
12
|
+
sink?: EventSink;
|
|
13
|
+
}
|
|
14
|
+
declare class TestPulseReporter extends JsonlReporter {
|
|
15
|
+
constructor(options?: TestPulseReporterOptions);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { TestPulseReporter, type TestPulseReporterOptions, TestPulseReporter as default };
|
package/dist/index.js
CHANGED
|
@@ -1 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
HttpEventSink,
|
|
4
|
+
JsonlReporter
|
|
5
|
+
} from "@testpulse.run/playwright-jsonl-reporter";
|
|
6
|
+
import {
|
|
7
|
+
FileSystemEventSink,
|
|
8
|
+
HttpEventSink as HttpEventSink2,
|
|
9
|
+
JsonlReporter as JsonlReporter2
|
|
10
|
+
} from "@testpulse.run/playwright-jsonl-reporter";
|
|
11
|
+
var DEFAULT_BASE_URL = "https://api.testpulse.run/";
|
|
12
|
+
var DEFAULT_STORAGE_STREAM_ID = "events";
|
|
13
|
+
var TestPulseReporter = class extends JsonlReporter {
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
const sink = options.sink ?? new HttpEventSink({
|
|
16
|
+
baseUrl: options.baseUrl ?? process.env.TESTPULSE_API_URL ?? DEFAULT_BASE_URL,
|
|
17
|
+
apiKey: options.apiKey ?? process.env.TESTPULSE_API_KEY,
|
|
18
|
+
storageStreamId: options.storageStreamId ?? process.env.TESTPULSE_STORAGE_STREAM_ID ?? DEFAULT_STORAGE_STREAM_ID,
|
|
19
|
+
writerId: options.writerId,
|
|
20
|
+
flushIntervalMs: options.flushIntervalMs,
|
|
21
|
+
batchSize: options.batchSize,
|
|
22
|
+
fetch: options.fetch
|
|
23
|
+
});
|
|
24
|
+
super({
|
|
25
|
+
...options,
|
|
26
|
+
sink
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export {
|
|
31
|
+
FileSystemEventSink,
|
|
32
|
+
HttpEventSink2 as HttpEventSink,
|
|
33
|
+
JsonlReporter2 as JsonlReporter,
|
|
34
|
+
TestPulseReporter,
|
|
35
|
+
TestPulseReporter as default
|
|
36
|
+
};
|
package/package.json
CHANGED
|
@@ -1,65 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testpulse.run/reporter",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Playwright reporter
|
|
5
|
-
"
|
|
6
|
-
"main": "./dist/index.js",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
8
|
-
"sideEffects": false,
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/mevodo/mv-testpulse.git",
|
|
12
|
-
"directory": "sdks/reporter"
|
|
13
|
-
},
|
|
14
|
-
"homepage": "https://github.com/mevodo/mv-testpulse/tree/main/sdks/reporter",
|
|
15
|
-
"bugs": {
|
|
16
|
-
"url": "https://github.com/mevodo/mv-testpulse/issues"
|
|
17
|
-
},
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "Preconfigured TestPulse Playwright reporter using the JSONL reporter with HTTP ingest.",
|
|
5
|
+
"homepage": "https://testpulse.run",
|
|
18
6
|
"keywords": [
|
|
19
7
|
"testpulse",
|
|
20
8
|
"playwright",
|
|
21
|
-
"reporter",
|
|
9
|
+
"playwright-reporter",
|
|
10
|
+
"test-reporter",
|
|
11
|
+
"test-automation",
|
|
22
12
|
"testing",
|
|
23
|
-
"
|
|
13
|
+
"ci",
|
|
14
|
+
"typescript"
|
|
24
15
|
],
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
"node": ">=18"
|
|
30
|
-
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.cjs",
|
|
18
|
+
"module": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
31
20
|
"exports": {
|
|
32
21
|
".": {
|
|
33
22
|
"types": "./dist/index.d.ts",
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
"./fixtures": {
|
|
37
|
-
"types": "./dist/fixtures.d.ts",
|
|
38
|
-
"default": "./dist/fixtures.js"
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"require": "./dist/index.cjs"
|
|
39
25
|
}
|
|
40
26
|
},
|
|
41
27
|
"files": [
|
|
42
|
-
"dist"
|
|
43
|
-
"README.md"
|
|
28
|
+
"dist"
|
|
44
29
|
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
45
33
|
"scripts": {
|
|
46
|
-
"build": "
|
|
47
|
-
"prepare": "npm run build",
|
|
48
|
-
"prepack": "npm run build",
|
|
49
|
-
"pack:check": "npm pack --dry-run",
|
|
34
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
50
35
|
"test": "vitest run"
|
|
51
36
|
},
|
|
52
|
-
"peerDependencies": {
|
|
53
|
-
"@playwright/test": ">=1.40.0"
|
|
54
|
-
},
|
|
55
37
|
"dependencies": {
|
|
56
|
-
"
|
|
38
|
+
"@testpulse.run/playwright-jsonl-reporter": "0.2.3"
|
|
57
39
|
},
|
|
58
|
-
"
|
|
59
|
-
"@playwright/test": "
|
|
60
|
-
"@roamhq/wrtc": "^0.10.0",
|
|
61
|
-
"@types/node": "^24.6.0",
|
|
62
|
-
"typescript": "^5.9.3",
|
|
63
|
-
"vitest": "^3.2.4"
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@playwright/test": ">=1.40.0"
|
|
64
42
|
}
|
|
65
43
|
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Page } from "@playwright/test";
|
|
2
|
-
import { startLiveCapture } from "../live/LiveCaptureService.js";
|
|
3
|
-
import type { LiveCaptureHandle, LivePage } from "../live/types.js";
|
|
4
|
-
import type { TestPulseFixturesOptions } from "./types.js";
|
|
5
|
-
type StartLiveCaptureFn = (page: LivePage, options?: Parameters<typeof startLiveCapture>[1]) => Promise<LiveCaptureHandle>;
|
|
6
|
-
export declare function createAutoLiveCapturePageFixture(options?: TestPulseFixturesOptions, startCapture?: StartLiveCaptureFn): ({ page }: {
|
|
7
|
-
page: Page;
|
|
8
|
-
}, use: (page: Page) => Promise<void>, testInfo: {
|
|
9
|
-
title: string;
|
|
10
|
-
}) => Promise<void>;
|
|
11
|
-
export declare function createTestPulseTest(options?: TestPulseFixturesOptions): import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
12
|
-
declare const expect: import("@playwright/test").Expect<{}>;
|
|
13
|
-
export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
14
|
-
export { expect };
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { startLiveCapture } from "../live/LiveCaptureService.js";
|
|
2
|
-
import { resolveTestPulseFixturesOptions } from "./options.js";
|
|
3
|
-
import { loadConsumerPlaywrightTest } from "./runtime.js";
|
|
4
|
-
export function createAutoLiveCapturePageFixture(options = {}, startCapture = startLiveCapture) {
|
|
5
|
-
return async ({ page }, use, testInfo) => {
|
|
6
|
-
const resolved = resolveTestPulseFixturesOptions(options);
|
|
7
|
-
let capture = null;
|
|
8
|
-
try {
|
|
9
|
-
capture = await startCapture(page, resolved.captureOptions);
|
|
10
|
-
}
|
|
11
|
-
catch (error) {
|
|
12
|
-
if (!resolved.swallowErrors) {
|
|
13
|
-
throw error;
|
|
14
|
-
}
|
|
15
|
-
console.warn(`[TestPulse] Auto live capture skipped for test "${testInfo.title}": ${error instanceof Error ? error.message : String(error)}`);
|
|
16
|
-
}
|
|
17
|
-
try {
|
|
18
|
-
await use(page);
|
|
19
|
-
}
|
|
20
|
-
finally {
|
|
21
|
-
await capture?.stop();
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
export function createTestPulseTest(options = {}) {
|
|
26
|
-
const { test: base } = loadConsumerPlaywrightTest();
|
|
27
|
-
return base.extend({
|
|
28
|
-
page: createAutoLiveCapturePageFixture(options)
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
const { expect } = loadConsumerPlaywrightTest();
|
|
32
|
-
export const test = createTestPulseTest();
|
|
33
|
-
export { expect };
|
package/dist/fixtures/options.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { resolveTestPulseEndpoint } from "../shared/endpoint.js";
|
|
2
|
-
export function resolveTestPulseFixturesOptions(options = {}) {
|
|
3
|
-
const swallowErrors = options.swallowErrors ?? true;
|
|
4
|
-
return {
|
|
5
|
-
swallowErrors,
|
|
6
|
-
captureOptions: {
|
|
7
|
-
endpoint: resolveTestPulseEndpoint(options.endpoint),
|
|
8
|
-
projectId: trim(options.projectId),
|
|
9
|
-
apiKey: trim(options.apiKey),
|
|
10
|
-
runId: trim(options.runId),
|
|
11
|
-
streamPublisherToken: trim(options.streamPublisherToken),
|
|
12
|
-
sessionKey: trim(options.sessionKey),
|
|
13
|
-
stateDirectory: trim(options.stateDirectory),
|
|
14
|
-
intervalMs: normalizeOptionalNumber(options.intervalMs),
|
|
15
|
-
profile: parseProfile(options.profile),
|
|
16
|
-
ffmpegPath: trim(options.ffmpegPath)
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
function trim(value) {
|
|
21
|
-
const trimmed = value?.trim();
|
|
22
|
-
return trimmed ? trimmed : undefined;
|
|
23
|
-
}
|
|
24
|
-
function normalizeOptionalNumber(value) {
|
|
25
|
-
if (value === undefined) {
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
return Number.isFinite(value) && value > 0
|
|
29
|
-
? value
|
|
30
|
-
: undefined;
|
|
31
|
-
}
|
|
32
|
-
function parseProfile(value) {
|
|
33
|
-
return value === "focus" || value === "grid"
|
|
34
|
-
? value
|
|
35
|
-
: undefined;
|
|
36
|
-
}
|
package/dist/fixtures/runtime.js
DELETED
package/dist/fixtures/types.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { LiveCaptureOptions } from "../live/types.js";
|
|
2
|
-
export interface TestPulseFixturesOptions extends LiveCaptureOptions {
|
|
3
|
-
swallowErrors?: boolean;
|
|
4
|
-
}
|
|
5
|
-
export interface ResolvedTestPulseFixturesOptions {
|
|
6
|
-
swallowErrors: boolean;
|
|
7
|
-
captureOptions: LiveCaptureOptions;
|
|
8
|
-
}
|
package/dist/fixtures/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/fixtures.d.ts
DELETED
package/dist/fixtures.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { createAutoLiveCapturePageFixture, createTestPulseTest, expect, test } from "./fixtures/createTestPulseTest.js";
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
|
-
import type { LivePage, StreamDescriptor, TimingKey } from "./types.js";
|
|
3
|
-
export declare class CaptureScheduler {
|
|
4
|
-
private readonly page;
|
|
5
|
-
private readonly encoder;
|
|
6
|
-
private readonly descriptor;
|
|
7
|
-
private readonly intervalMs;
|
|
8
|
-
private readonly logTimingOnce;
|
|
9
|
-
private stopped;
|
|
10
|
-
private frameWritable;
|
|
11
|
-
private latestFrame;
|
|
12
|
-
private captureInFlight;
|
|
13
|
-
private encoderTimer;
|
|
14
|
-
private captureTimer;
|
|
15
|
-
constructor(page: LivePage, encoder: ChildProcessWithoutNullStreams, descriptor: StreamDescriptor, intervalMs: number | undefined, logTimingOnce: (key: TimingKey, extra?: Record<string, number | string>) => void);
|
|
16
|
-
captureInitialFrame(): Promise<Buffer | null>;
|
|
17
|
-
writeInitialFrame(frame: Buffer | null): void;
|
|
18
|
-
start(): void;
|
|
19
|
-
stop(): void;
|
|
20
|
-
private captureFrame;
|
|
21
|
-
private writeFrameToEncoder;
|
|
22
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
export class CaptureScheduler {
|
|
2
|
-
page;
|
|
3
|
-
encoder;
|
|
4
|
-
descriptor;
|
|
5
|
-
intervalMs;
|
|
6
|
-
logTimingOnce;
|
|
7
|
-
stopped = false;
|
|
8
|
-
frameWritable = true;
|
|
9
|
-
latestFrame = null;
|
|
10
|
-
captureInFlight = null;
|
|
11
|
-
encoderTimer = null;
|
|
12
|
-
captureTimer = null;
|
|
13
|
-
constructor(page, encoder, descriptor, intervalMs, logTimingOnce) {
|
|
14
|
-
this.page = page;
|
|
15
|
-
this.encoder = encoder;
|
|
16
|
-
this.descriptor = descriptor;
|
|
17
|
-
this.intervalMs = intervalMs;
|
|
18
|
-
this.logTimingOnce = logTimingOnce;
|
|
19
|
-
this.encoder.stdin.on("drain", () => {
|
|
20
|
-
this.frameWritable = true;
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
async captureInitialFrame() {
|
|
24
|
-
return this.captureFrame();
|
|
25
|
-
}
|
|
26
|
-
writeInitialFrame(frame) {
|
|
27
|
-
this.writeFrameToEncoder(frame);
|
|
28
|
-
}
|
|
29
|
-
start() {
|
|
30
|
-
const encoderIntervalMs = Math.max(20, Math.round(1000 / this.descriptor.framesPerSecond));
|
|
31
|
-
const captureIntervalMs = Math.max(20, this.intervalMs ?? (this.descriptor.profile === "grid" ? 250 : 83));
|
|
32
|
-
this.encoderTimer = setInterval(() => {
|
|
33
|
-
if (this.stopped) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
// Keep the encoder fed at a stable cadence so fragmented MP4 emits promptly
|
|
37
|
-
// even when screenshot capture itself is running at a lower interval.
|
|
38
|
-
this.writeFrameToEncoder(this.latestFrame);
|
|
39
|
-
}, encoderIntervalMs);
|
|
40
|
-
this.captureTimer = setInterval(() => {
|
|
41
|
-
if (this.stopped) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
void this.captureFrame()
|
|
45
|
-
.then(() => undefined)
|
|
46
|
-
.catch(() => {
|
|
47
|
-
// Best-effort live capture should not fail the run.
|
|
48
|
-
});
|
|
49
|
-
}, captureIntervalMs);
|
|
50
|
-
}
|
|
51
|
-
stop() {
|
|
52
|
-
this.stopped = true;
|
|
53
|
-
if (this.encoderTimer) {
|
|
54
|
-
clearInterval(this.encoderTimer);
|
|
55
|
-
}
|
|
56
|
-
if (this.captureTimer) {
|
|
57
|
-
clearInterval(this.captureTimer);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
async captureFrame() {
|
|
61
|
-
if (this.stopped) {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
if (!this.captureInFlight) {
|
|
65
|
-
this.captureInFlight = this.page.screenshot({ type: "jpeg", quality: 60, scale: "css" })
|
|
66
|
-
.then((screenshot) => {
|
|
67
|
-
const frame = Buffer.from(screenshot);
|
|
68
|
-
this.latestFrame = frame;
|
|
69
|
-
this.logTimingOnce("firstScreenshotTaken", { bytes: frame.length });
|
|
70
|
-
return frame;
|
|
71
|
-
})
|
|
72
|
-
.catch(() => null)
|
|
73
|
-
.finally(() => {
|
|
74
|
-
this.captureInFlight = null;
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
return this.captureInFlight;
|
|
78
|
-
}
|
|
79
|
-
writeFrameToEncoder(frame) {
|
|
80
|
-
if (this.stopped || !frame || !this.frameWritable) {
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
this.frameWritable = this.encoder.stdin.write(frame);
|
|
84
|
-
this.logTimingOnce("firstFrameWrittenToEncoder", { bytes: frame.length });
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
|
-
import type { StreamDescriptor } from "./types.js";
|
|
3
|
-
export declare function startEncoderProcess(descriptor: StreamDescriptor, ffmpegPath?: string): ChildProcessWithoutNullStreams;
|
|
4
|
-
export declare function resolveFfmpegPath(ffmpegPath?: string): string;
|
|
5
|
-
export declare function waitForEncoder(encoder: ChildProcessWithoutNullStreams): Promise<void>;
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { once } from "node:events";
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
|
-
const require = createRequire(import.meta.url);
|
|
5
|
-
export function startEncoderProcess(descriptor, ffmpegPath) {
|
|
6
|
-
const gopFrames = Math.max(1, Math.round(descriptor.framesPerSecond * descriptor.gopDurationMs / 1000));
|
|
7
|
-
const ffmpeg = spawn(resolveFfmpegPath(ffmpegPath), [
|
|
8
|
-
"-loglevel", "error",
|
|
9
|
-
"-f", "image2pipe",
|
|
10
|
-
"-r", String(descriptor.framesPerSecond),
|
|
11
|
-
"-i", "pipe:0",
|
|
12
|
-
"-an",
|
|
13
|
-
"-vf", `scale=${descriptor.width}:${descriptor.height}:force_original_aspect_ratio=decrease,pad=${descriptor.width}:${descriptor.height}:(ow-iw)/2:(oh-ih)/2`,
|
|
14
|
-
"-c:v", "libx264",
|
|
15
|
-
"-preset", "veryfast",
|
|
16
|
-
"-tune", "zerolatency",
|
|
17
|
-
"-pix_fmt", "yuv420p",
|
|
18
|
-
"-profile:v", "baseline",
|
|
19
|
-
"-level", "3.0",
|
|
20
|
-
"-g", String(gopFrames),
|
|
21
|
-
"-keyint_min", String(gopFrames),
|
|
22
|
-
"-sc_threshold", "0",
|
|
23
|
-
"-force_key_frames", `expr:gte(t,n_forced*${descriptor.gopDurationMs / 1000})`,
|
|
24
|
-
"-b:v", `${descriptor.targetBitrateKbps}k`,
|
|
25
|
-
"-maxrate", `${descriptor.targetBitrateKbps}k`,
|
|
26
|
-
"-bufsize", `${descriptor.targetBitrateKbps * 2}k`,
|
|
27
|
-
"-movflags", "frag_keyframe+empty_moov+default_base_moof+separate_moof",
|
|
28
|
-
"-frag_duration", String(descriptor.segmentDurationMs * 1000),
|
|
29
|
-
"-f", "mp4",
|
|
30
|
-
"pipe:1"
|
|
31
|
-
], {
|
|
32
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
33
|
-
});
|
|
34
|
-
ffmpeg.once("error", (error) => {
|
|
35
|
-
throw error;
|
|
36
|
-
});
|
|
37
|
-
return ffmpeg;
|
|
38
|
-
}
|
|
39
|
-
export function resolveFfmpegPath(ffmpegPath) {
|
|
40
|
-
const configuredPath = ffmpegPath?.trim();
|
|
41
|
-
if (configuredPath) {
|
|
42
|
-
return configuredPath;
|
|
43
|
-
}
|
|
44
|
-
try {
|
|
45
|
-
const bundledPath = require("ffmpeg-static");
|
|
46
|
-
if (typeof bundledPath === "string" && bundledPath.trim()) {
|
|
47
|
-
return bundledPath;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
// Fall back to PATH lookup below.
|
|
52
|
-
}
|
|
53
|
-
return "ffmpeg";
|
|
54
|
-
}
|
|
55
|
-
export async function waitForEncoder(encoder) {
|
|
56
|
-
const [code] = await once(encoder, "close");
|
|
57
|
-
if (code && code !== 0) {
|
|
58
|
-
const stderr = encoder.stderr.read()?.toString() ?? "";
|
|
59
|
-
throw new Error(`ffmpeg exited with code ${code}. ${stderr}`.trim());
|
|
60
|
-
}
|
|
61
|
-
}
|