@stanterprise/playwright-reporter 0.0.1 → 0.0.6

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.
@@ -0,0 +1,7 @@
1
+ import { StanterpriseReporterOptions } from "../types";
2
+ import * as grpc from "@grpc/grpc-js";
3
+ export default function getClient(options: StanterpriseReporterOptions): grpc.Client | null;
4
+ export declare function reportUnary(options: StanterpriseReporterOptions, grpcClient: grpc.Client, path: string, message: {
5
+ serialize?: (w?: any) => Uint8Array;
6
+ serializeBinary?: () => Uint8Array;
7
+ }, deadlineMs?: number): Promise<Buffer>;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.default = getClient;
37
+ exports.reportUnary = reportUnary;
38
+ const grpc = __importStar(require("@grpc/grpc-js"));
39
+ function getClient(options) {
40
+ try {
41
+ return new grpc.Client(options.grpcAddress, grpc.credentials.createInsecure());
42
+ }
43
+ catch (e) {
44
+ console.error("Failed to create gRPC client", e);
45
+ }
46
+ return null;
47
+ }
48
+ // Helper: generic unary call using raw method path
49
+ async function reportUnary(options, grpcClient, path, message, deadlineMs = 1000) {
50
+ if (!options.grpcEnabled || !grpcClient) {
51
+ return Buffer.alloc(0);
52
+ }
53
+ const reqSerialize = (arg) => {
54
+ const m = arg;
55
+ const bytes = m?.serializeBinary
56
+ ? m.serializeBinary()
57
+ : m?.serialize
58
+ ? m.serialize()
59
+ : new Uint8Array(0);
60
+ return Buffer.from(bytes);
61
+ };
62
+ const resDeserialize = (bytes) => bytes;
63
+ const metadata = new grpc.Metadata();
64
+ const callOptions = {
65
+ deadline: new Date(Date.now() + deadlineMs),
66
+ };
67
+ return new Promise((resolve, reject) => {
68
+ try {
69
+ grpcClient.makeUnaryRequest(path, reqSerialize, resDeserialize, message, metadata, callOptions, (err, response) => {
70
+ if (err)
71
+ return reject(err);
72
+ resolve(response);
73
+ });
74
+ }
75
+ catch (e) {
76
+ reject(e);
77
+ }
78
+ });
79
+ }
@@ -0,0 +1,9 @@
1
+ import { handleOnBeginEvent } from "./onBeginHandler";
2
+ import { handleOnEndEvent } from "./onEndHandler";
3
+ import { handleOnStepBeginEvent } from "./onStepBeginHandler";
4
+ import { handleOnStepEndEvent } from "./onStepEndHandler";
5
+ import { handleOnTestBeginEvent } from "./onTestBeginHandler";
6
+ import { handleOnTestEndEvent } from "./onTestEndHandler";
7
+ import { handleOnTestFailEvent } from "./onTestFailHandler";
8
+ import { handleOnErrorEvent } from "./onErrorHandler";
9
+ export { handleOnBeginEvent, handleOnEndEvent, handleOnStepBeginEvent, handleOnStepEndEvent, handleOnTestBeginEvent, handleOnTestEndEvent, handleOnTestFailEvent, handleOnErrorEvent, };
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleOnErrorEvent = exports.handleOnTestFailEvent = exports.handleOnTestEndEvent = exports.handleOnTestBeginEvent = exports.handleOnStepEndEvent = exports.handleOnStepBeginEvent = exports.handleOnEndEvent = exports.handleOnBeginEvent = void 0;
4
+ const onBeginHandler_1 = require("./onBeginHandler");
5
+ Object.defineProperty(exports, "handleOnBeginEvent", { enumerable: true, get: function () { return onBeginHandler_1.handleOnBeginEvent; } });
6
+ const onEndHandler_1 = require("./onEndHandler");
7
+ Object.defineProperty(exports, "handleOnEndEvent", { enumerable: true, get: function () { return onEndHandler_1.handleOnEndEvent; } });
8
+ const onStepBeginHandler_1 = require("./onStepBeginHandler");
9
+ Object.defineProperty(exports, "handleOnStepBeginEvent", { enumerable: true, get: function () { return onStepBeginHandler_1.handleOnStepBeginEvent; } });
10
+ const onStepEndHandler_1 = require("./onStepEndHandler");
11
+ Object.defineProperty(exports, "handleOnStepEndEvent", { enumerable: true, get: function () { return onStepEndHandler_1.handleOnStepEndEvent; } });
12
+ const onTestBeginHandler_1 = require("./onTestBeginHandler");
13
+ Object.defineProperty(exports, "handleOnTestBeginEvent", { enumerable: true, get: function () { return onTestBeginHandler_1.handleOnTestBeginEvent; } });
14
+ const onTestEndHandler_1 = require("./onTestEndHandler");
15
+ Object.defineProperty(exports, "handleOnTestEndEvent", { enumerable: true, get: function () { return onTestEndHandler_1.handleOnTestEndEvent; } });
16
+ const onTestFailHandler_1 = require("./onTestFailHandler");
17
+ Object.defineProperty(exports, "handleOnTestFailEvent", { enumerable: true, get: function () { return onTestFailHandler_1.handleOnTestFailEvent; } });
18
+ const onErrorHandler_1 = require("./onErrorHandler");
19
+ Object.defineProperty(exports, "handleOnErrorEvent", { enumerable: true, get: function () { return onErrorHandler_1.handleOnErrorEvent; } });
@@ -0,0 +1,5 @@
1
+ import { FullConfig } from "@playwright/test";
2
+ import { Suite } from "@playwright/test/reporter";
3
+ import { StanterpriseReporterOptions } from "../types";
4
+ import * as grpc from "@grpc/grpc-js";
5
+ export declare function handleOnBeginEvent(config: FullConfig, suite: Suite, name: string, runId: string, client: grpc.Client, options: StanterpriseReporterOptions): void;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleOnBeginEvent = handleOnBeginEvent;
4
+ const events_1 = require("@stanterprise/protobuf/testsystem/v1/events");
5
+ const grpcClient_1 = require("../client/grpcClient");
6
+ const entities_1 = require("@stanterprise/protobuf/testsystem/v1/entities");
7
+ const utils_1 = require("../utils");
8
+ function handleOnBeginEvent(config, suite, name, runId, client, options) {
9
+ // Report root suite and all child suites recursively
10
+ const request = new events_1.MapTestRunEventRequest({
11
+ run_id: runId,
12
+ name: name,
13
+ test_suites: mapSuites(suite, runId),
14
+ total_tests: suite.allTests().length,
15
+ });
16
+ (0, grpcClient_1.reportUnary)(options, client, "/testsystem.v1.observer.TestEventCollector/MapTestRun", request, options.grpcTimeout).catch((e) => {
17
+ const details = e instanceof Error ? `${e.message}` : String(e);
18
+ console.warn(`onBegin. gRPC disabled for the remainder of the run. Address=${options.grpcAddress}. Details: ${details}`);
19
+ });
20
+ }
21
+ function mapSuites(suite, runId) {
22
+ let allSuites = getAllSuites(suite);
23
+ return allSuites.map((currentSuite) => mapSingleSuite(currentSuite, runId));
24
+ }
25
+ function getAllSuites(suite) {
26
+ let suites = [];
27
+ suites.push(suite);
28
+ suite.suites.forEach((childSuite) => {
29
+ suites = suites.concat(getAllSuites(childSuite));
30
+ });
31
+ return suites;
32
+ }
33
+ function mapSingleSuite(suite, runId) {
34
+ const suiteId = (0, utils_1.generateSuiteId)(suite);
35
+ const parentSuiteId = suite.parent
36
+ ? (0, utils_1.generateSuiteId)(suite.parent)
37
+ : undefined;
38
+ let type;
39
+ switch (suite.type) {
40
+ case "root":
41
+ type = entities_1.SuiteType.ROOT;
42
+ break;
43
+ case "describe":
44
+ type = entities_1.SuiteType.SUBSUITE;
45
+ break;
46
+ case "file":
47
+ type = entities_1.SuiteType.SUBSUITE;
48
+ break;
49
+ case "project":
50
+ type = entities_1.SuiteType.PROJECT;
51
+ break;
52
+ }
53
+ return new entities_1.TestSuiteRun({
54
+ id: suiteId,
55
+ name: suite.title,
56
+ run_id: runId,
57
+ parent_suite_id: parentSuiteId,
58
+ test_cases: suite.tests.map((test) => new entities_1.TestCaseRun({
59
+ id: test.id,
60
+ name: test.title,
61
+ run_id: runId,
62
+ })),
63
+ test_case_ids: suite.tests.map((test) => test.id),
64
+ type: type,
65
+ // start_time: createTimestamp(startTime),
66
+ // metadata: metadata,
67
+ });
68
+ }
@@ -0,0 +1,4 @@
1
+ import { FullResult } from "@playwright/test/reporter";
2
+ import { StanterpriseReporterOptions } from "../types";
3
+ import * as grpc from "@grpc/grpc-js";
4
+ export declare function handleOnEndEvent(result: FullResult, runId: string, client: grpc.Client, options: StanterpriseReporterOptions): void;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleOnEndEvent = handleOnEndEvent;
4
+ const grpcClient_1 = require("../client/grpcClient");
5
+ const utils_1 = require("../utils");
6
+ const events_1 = require("@stanterprise/protobuf/testsystem/v1/events");
7
+ const common_1 = require("@stanterprise/protobuf/testsystem/v1/common");
8
+ function mapTestStatus(status) {
9
+ switch (status) {
10
+ case "passed":
11
+ return common_1.TestStatus.PASSED;
12
+ case "failed":
13
+ return common_1.TestStatus.FAILED;
14
+ case "timedout":
15
+ return common_1.TestStatus.TIMEDOUT;
16
+ case "interrupted":
17
+ return common_1.TestStatus.INTERRUPTED;
18
+ default:
19
+ return common_1.TestStatus.UNKNOWN;
20
+ }
21
+ }
22
+ function handleOnEndEvent(result, runId, client, options) {
23
+ const request = new events_1.TestRunEndEventRequest({
24
+ run_id: runId,
25
+ final_status: mapTestStatus(result.status),
26
+ start_time: (0, utils_1.createTimestampFromMs)(result.startTime.getTime()),
27
+ duration: (0, utils_1.createDuration)(result.duration),
28
+ });
29
+ (0, grpcClient_1.reportUnary)(options, client, "/testsystem.v1.observer.TestEventCollector/ReportEnd", request, options.grpcTimeout).catch((error) => {
30
+ if (error instanceof Error) {
31
+ console.error(`Failed to report end event: ${error.message}`, {
32
+ stack: error.stack,
33
+ });
34
+ }
35
+ else {
36
+ console.error("Failed to report end event: Unknown error type", {
37
+ error,
38
+ });
39
+ }
40
+ });
41
+ }
@@ -0,0 +1,4 @@
1
+ import { TestError } from "@playwright/test/reporter";
2
+ import { StanterpriseReporterOptions } from "../types";
3
+ import * as grpc from "@grpc/grpc-js";
4
+ export declare function handleOnErrorEvent(error: TestError, runId: string, grpcClient: grpc.Client, options: StanterpriseReporterOptions): void;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleOnErrorEvent = handleOnErrorEvent;
4
+ const events_1 = require("@stanterprise/protobuf/testsystem/v1/events");
5
+ const timeHelpers_1 = require("../utils/timeHelpers");
6
+ const grpcClient_1 = require("../client/grpcClient");
7
+ function handleOnErrorEvent(error, runId, grpcClient, options) {
8
+ const request = new events_1.StdErrorEventRequest({
9
+ run_id: runId,
10
+ message: error.message,
11
+ timestamp: (0, timeHelpers_1.createTimestampFromMs)(Date.now()),
12
+ });
13
+ // Fire-and-forget to avoid slowing tests
14
+ (0, grpcClient_1.reportUnary)(options, grpcClient, "/testsystem.v1.observer.TestEventCollector/ReportStdError", request, options.grpcTimeout).catch((e) => console.error("Failed to report standard error", e));
15
+ }
@@ -0,0 +1,4 @@
1
+ import { TestCase, TestResult, TestStep } from "@playwright/test/reporter";
2
+ import { StanterpriseReporterOptions } from "../types";
3
+ import * as grpc from "@grpc/grpc-js";
4
+ export declare function handleOnStepBeginEvent(test: TestCase, result: TestResult, step: TestStep, runId: string, client: grpc.Client, options: StanterpriseReporterOptions): void;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleOnStepBeginEvent = handleOnStepBeginEvent;
4
+ const entities_1 = require("@stanterprise/protobuf/testsystem/v1/entities");
5
+ const events_1 = require("@stanterprise/protobuf/testsystem/v1/events");
6
+ const utils_1 = require("../utils");
7
+ const grpcClient_1 = require("../client/grpcClient");
8
+ const utils_2 = require("../utils");
9
+ function handleOnStepBeginEvent(test, result, step, runId, client, options) {
10
+ // Build metadata from step annotations
11
+ const metadata = new Map();
12
+ metadata.set("category", step.category);
13
+ step.annotations.forEach((annotation, index) => {
14
+ metadata.set(`annotation_${index}_type`, annotation.type);
15
+ if (annotation.description) {
16
+ metadata.set(`annotation_${index}_description`, annotation.description);
17
+ }
18
+ });
19
+ // Get parent step ID if this step has a parent
20
+ const stepId = (0, utils_2.generateStepId)(step, test);
21
+ // Build and send the StepBegin event
22
+ const request = new events_1.StepBeginEventRequest({
23
+ step: new entities_1.StepRun({
24
+ id: stepId,
25
+ run_id: runId,
26
+ test_case_run_id: test.id,
27
+ title: step.title,
28
+ type: step.category,
29
+ start_time: (0, utils_1.createTimestamp)(step.startTime),
30
+ location: step.location
31
+ ? `${step.location.file}:${step.location.line}:${step.location.column}`
32
+ : "",
33
+ metadata: metadata,
34
+ parent_step_id: step.parent
35
+ ? (0, utils_2.generateStepId)(step.parent, test)
36
+ : undefined,
37
+ worker_index: result.workerIndex.toString(),
38
+ }),
39
+ });
40
+ // Fire-and-forget to avoid slowing tests
41
+ (0, grpcClient_1.reportUnary)(options, client, "/testsystem.v1.observer.TestEventCollector/ReportStepBegin", request, options.grpcTimeout).catch((e) => console.error("Failed to report step begin", e));
42
+ }
@@ -0,0 +1,4 @@
1
+ import { TestCase, TestResult, TestStep } from "@playwright/test/reporter";
2
+ import { StanterpriseReporterOptions } from "../types";
3
+ import * as grpc from "@grpc/grpc-js";
4
+ export declare function handleOnStepEndEvent(test: TestCase, result: TestResult, step: TestStep, runId: string, client: grpc.Client, options: StanterpriseReporterOptions): void;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleOnStepEndEvent = handleOnStepEndEvent;
4
+ const entities_1 = require("@stanterprise/protobuf/testsystem/v1/entities");
5
+ const events_1 = require("@stanterprise/protobuf/testsystem/v1/events");
6
+ const utils_1 = require("../utils");
7
+ const grpcClient_1 = require("../client/grpcClient");
8
+ const utils_2 = require("../utils");
9
+ function handleOnStepEndEvent(test, result, step, runId, client, options) {
10
+ // Map step error to status
11
+ const stepStatus = (0, utils_1.mapStepStatus)(!!step.error);
12
+ // Build metadata from step annotations
13
+ const metadata = new Map();
14
+ metadata.set("category", step.category);
15
+ step.annotations.forEach((annotation, index) => {
16
+ metadata.set(`annotation_${index}_type`, annotation.type);
17
+ if (annotation.description) {
18
+ metadata.set(`annotation_${index}_description`, annotation.description);
19
+ }
20
+ });
21
+ const stepId = (0, utils_2.generateStepId)(step, test);
22
+ // Build and send the StepEnd event
23
+ const request = new events_1.StepEndEventRequest({
24
+ step: new entities_1.StepRun({
25
+ id: stepId,
26
+ run_id: runId,
27
+ test_case_run_id: test.id,
28
+ title: step.title,
29
+ type: step.category,
30
+ start_time: (0, utils_1.createTimestamp)(step.startTime),
31
+ duration: (0, utils_1.createDuration)(step.duration),
32
+ status: stepStatus,
33
+ error: step.error ? step.error.message || "" : "",
34
+ errors: step.error ? [step.error.message || ""] : [],
35
+ location: step.location
36
+ ? `${step.location.file}:${step.location.line}:${step.location.column}`
37
+ : "",
38
+ metadata: metadata,
39
+ parent_step_id: step.parent
40
+ ? (0, utils_2.generateStepId)(step.parent, test)
41
+ : undefined,
42
+ worker_index: result.workerIndex.toString(),
43
+ }),
44
+ });
45
+ // Fire-and-forget to avoid slowing tests
46
+ (0, grpcClient_1.reportUnary)(options, client, "/testsystem.v1.observer.TestEventCollector/ReportStepEnd", request, options.grpcTimeout).catch((e) => console.error("Failed to report step end", e));
47
+ }
@@ -0,0 +1,4 @@
1
+ import { TestCase, TestResult } from "@playwright/test/reporter";
2
+ import { StanterpriseReporterOptions } from "../types";
3
+ import * as grpc from "@grpc/grpc-js";
4
+ export declare function handleOnTestBeginEvent(test: TestCase, result: TestResult, runId: string, client: grpc.Client, options: StanterpriseReporterOptions): void;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleOnTestBeginEvent = handleOnTestBeginEvent;
4
+ const entities_1 = require("@stanterprise/protobuf/testsystem/v1/entities");
5
+ const events_1 = require("@stanterprise/protobuf/testsystem/v1/events");
6
+ const utils_1 = require("../utils");
7
+ const grpcClient_1 = require("../client/grpcClient");
8
+ function handleOnTestBeginEvent(test, result, runId, client, options) {
9
+ // Build and send the TestBegin event via generic unary call.
10
+ const request = new events_1.TestBeginEventRequest({
11
+ test_case: new entities_1.TestCaseRun({
12
+ id: test.id,
13
+ name: test.title,
14
+ run_id: runId,
15
+ test_suite_run_id: (0, utils_1.generateSuiteId)(test.parent),
16
+ start_time: (0, utils_1.createTimestamp)(result.startTime),
17
+ tags: test.tags,
18
+ }),
19
+ });
20
+ // Fire-and-forget to avoid slowing tests; log errors once.
21
+ (0, grpcClient_1.reportUnary)(options, client, "/testsystem.v1.observer.TestEventCollector/ReportTestBegin", request, options.grpcTimeout).catch((e) => {
22
+ const details = e instanceof Error ? `${e.message}` : String(e);
23
+ console.warn(`Failed to report test begin. Details: ${details}`);
24
+ });
25
+ }
@@ -0,0 +1,4 @@
1
+ import { TestCase, TestResult } from "@playwright/test/reporter";
2
+ import { StanterpriseReporterOptions } from "../types";
3
+ import * as grpc from "@grpc/grpc-js";
4
+ export declare function handleOnTestEndEvent(test: TestCase, result: TestResult, runId: string, client: grpc.Client, options: StanterpriseReporterOptions): void;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleOnTestEndEvent = handleOnTestEndEvent;
4
+ const entities_1 = require("@stanterprise/protobuf/testsystem/v1/entities");
5
+ const events_1 = require("@stanterprise/protobuf/testsystem/v1/events");
6
+ const utils_1 = require("../utils");
7
+ const grpcClient_1 = require("../client/grpcClient");
8
+ const utils_2 = require("../utils");
9
+ function handleOnTestEndEvent(test, result, runId, client, options) {
10
+ // Map Playwright test status to protobuf TestStatus
11
+ const testStatus = (0, utils_1.mapTestStatus)(result.status);
12
+ // Process attachments (screenshots, videos, etc.)
13
+ const attachments = (0, utils_1.processAttachments)(result);
14
+ // Extract error information if the test failed
15
+ const { errorMessage, stackTrace, errors } = (0, utils_1.extractErrorInfo)(result);
16
+ // Get test suite run ID from parent suite if available, ensuring suite is reported
17
+ const suiteId = (0, utils_2.generateSuiteId)(test.parent);
18
+ // Build metadata from test annotations and result metadata
19
+ const metadata = new Map();
20
+ test.annotations.forEach((annotation, index) => {
21
+ metadata.set(`annotation_${index}_type`, annotation.type);
22
+ if (annotation.description) {
23
+ metadata.set(`annotation_${index}_description`, annotation.description);
24
+ }
25
+ });
26
+ result.annotations.forEach((annotation, index) => {
27
+ metadata.set(`result_annotation_${index}_type`, annotation.type);
28
+ if (annotation.description) {
29
+ metadata.set(`result_annotation_${index}_description`, annotation.description);
30
+ }
31
+ });
32
+ // Build and send the TestEnd event
33
+ const request = new events_1.TestEndEventRequest({
34
+ test_case: new entities_1.TestCaseRun({
35
+ id: test.id,
36
+ name: test.title,
37
+ run_id: runId,
38
+ test_suite_run_id: suiteId,
39
+ status: testStatus,
40
+ start_time: (0, utils_1.createTimestamp)(result.startTime),
41
+ attachments: attachments,
42
+ error_message: errorMessage,
43
+ stack_trace: stackTrace,
44
+ errors: errors,
45
+ metadata: metadata,
46
+ tags: test.tags,
47
+ }),
48
+ });
49
+ // Fire-and-forget to avoid slowing tests
50
+ (0, grpcClient_1.reportUnary)(options, client, "/testsystem.v1.observer.TestEventCollector/ReportTestEnd", request, options.grpcTimeout).catch((e) => console.error("Failed to report test end", e));
51
+ }
@@ -0,0 +1,4 @@
1
+ import { TestCase, TestResult } from "@playwright/test/reporter";
2
+ import { StanterpriseReporterOptions } from "../types";
3
+ import * as grpc from "@grpc/grpc-js";
4
+ export declare function handleOnTestFailEvent(test: TestCase, result: TestResult, runId: string, grpcClient: grpc.Client, options: StanterpriseReporterOptions): void;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleOnTestFailEvent = handleOnTestFailEvent;
4
+ const utils_1 = require("../utils");
5
+ const events_1 = require("@stanterprise/protobuf/testsystem/v1/events");
6
+ const grpcClient_1 = require("../client/grpcClient");
7
+ function handleOnTestFailEvent(test, result, runId, grpcClient, options) {
8
+ // Extract failure details
9
+ const { errorMessage: failureMessage, stackTrace } = (0, utils_1.extractErrorInfo)(result);
10
+ // Process attachments for failed tests
11
+ const attachments = (0, utils_1.processAttachments)(result);
12
+ // Build and send the TestFailure event
13
+ const request = new events_1.TestFailureEventRequest({
14
+ test_id: test.id,
15
+ run_id: runId,
16
+ failure_message: failureMessage,
17
+ stack_trace: stackTrace,
18
+ timestamp: (0, utils_1.createTimestampFromMs)(Date.now()),
19
+ attachments: attachments,
20
+ });
21
+ // Fire-and-forget to avoid slowing tests
22
+ (0, grpcClient_1.reportUnary)(options, grpcClient, "/testsystem.v1.observer.TestEventCollector/ReportTestFailure", request, options.grpcTimeout).catch((e) => console.error("Failed to report test failure", e));
23
+ }
@@ -2,20 +2,12 @@ import type { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult, Tes
2
2
  import { StanterpriseReporterOptions } from "./types";
3
3
  export default class StanterpriseReporter implements Reporter {
4
4
  private grpcClient;
5
- private grpcAddress;
6
- private grpcEnabled;
7
- private grpcTimeout;
8
- private grpcFirstErrorLogged;
9
- private verbose;
5
+ private options;
10
6
  private runId;
11
7
  private runStartTime;
12
- private rootSuite;
13
- private reportedSuites;
14
8
  constructor(options?: StanterpriseReporterOptions);
15
9
  onBegin(config: FullConfig, suite: Suite): void;
16
10
  onExit(): Promise<void>;
17
- getRunId(): string;
18
- getRunStartTime(): Date;
19
11
  onEnd(result: FullResult): Promise<{
20
12
  status?: FullResult["status"];
21
13
  } | undefined | void> | void;
@@ -27,10 +19,4 @@ export default class StanterpriseReporter implements Reporter {
27
19
  onError(error: TestError): void;
28
20
  onStdErr(chunk: string | Buffer, test: void | TestCase, result: void | TestResult): void;
29
21
  onStdOut(chunk: string | Buffer, test: void | TestCase, result: void | TestResult): void;
30
- private getSuiteId;
31
- private reportSuiteBegin;
32
- private reportSuiteEnd;
33
- private mapSuiteStatus;
34
- private logGrpcErrorOnce;
35
- private reportUnary;
36
22
  }
package/dist/reporter.js CHANGED
@@ -1,104 +1,54 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
35
5
  Object.defineProperty(exports, "__esModule", { value: true });
36
- const protobuf_1 = require("@stanterprise/protobuf");
37
- const grpc = __importStar(require("@grpc/grpc-js"));
38
6
  const crypto_1 = require("crypto");
39
- const utils_1 = require("./utils");
40
- // Create shortcuts for the protobuf classes
41
- const EventsNS = protobuf_1.events.v1.events;
42
- const TestCaseEntities = protobuf_1.testCase.v1.entities;
43
- const TestSuiteEntities = protobuf_1.testSuite.v1.entities;
44
- const TestStatus = protobuf_1.common.v1.common.TestStatus;
7
+ const optionsMapper_1 = __importDefault(require("./utils/optionsMapper"));
8
+ const grpcClient_1 = __importDefault(require("./client/grpcClient"));
9
+ const handlers_1 = require("./handlers");
45
10
  class StanterpriseReporter {
46
11
  constructor(options = {}) {
47
12
  // Generic gRPC client (we call unary methods by path directly).
48
13
  this.grpcClient = null;
49
- this.grpcFirstErrorLogged = false;
14
+ this.options = {};
50
15
  // Generate a unique run ID for this test run
51
16
  this.runId = "";
52
17
  this.runStartTime = new Date();
53
- this.rootSuite = null;
54
- // Track reported suites to ensure we only report each suite once and can reference correct IDs
55
- this.reportedSuites = new Map();
56
- this.grpcAddress =
57
- options.grpcAddress ||
58
- process.env.STANTERPRISE_GRPC_ADDRESS ||
59
- "localhost:50051";
60
- this.grpcEnabled =
61
- options.grpcEnabled ??
62
- (process.env.STANTERPRISE_GRPC_ENABLED || "true").toLowerCase() !==
63
- "false";
64
- this.grpcTimeout = options.grpcTimeout || 1000;
65
- this.verbose = options.verbose || false;
66
- if (this.verbose) {
18
+ this.options = (0, optionsMapper_1.default)(options);
19
+ if (this.options.verbose) {
67
20
  console.log("Stanterprise Reporter: Initialized with options:", {
68
- grpcAddress: this.grpcAddress,
69
- grpcEnabled: this.grpcEnabled,
70
- grpcTimeout: this.grpcTimeout,
71
- verbose: this.verbose,
21
+ grpcAddress: this.options.grpcAddress,
22
+ grpcEnabled: this.options.grpcEnabled,
23
+ grpcTimeout: this.options.grpcTimeout,
24
+ verbose: this.options.verbose,
72
25
  });
73
26
  }
27
+ // Generate a UUID for runId
28
+ this.runId = process.env.STANTERPRISE_RUN_ID || (0, crypto_1.randomUUID)();
74
29
  }
75
30
  onBegin(config, suite) {
76
- // Generate a UUID for runId
77
- this.runId = (0, crypto_1.randomUUID)();
78
- this.rootSuite = suite;
79
31
  // Lazily create the client if enabled.
80
- if (this.grpcEnabled) {
81
- try {
82
- this.grpcClient = new grpc.Client(this.grpcAddress, grpc.credentials.createInsecure());
83
- }
84
- catch (e) {
85
- this.logGrpcErrorOnce("Failed to create gRPC client", e);
32
+ if (this.options.grpcEnabled) {
33
+ this.grpcClient = (0, grpcClient_1.default)(this.options);
34
+ if (!this.grpcClient) {
35
+ console.warn(`Stanterprise Reporter: Failed to create gRPC client. Disabling gRPC reporting.`);
36
+ this.options.grpcEnabled = false;
86
37
  }
87
38
  }
88
39
  else {
89
- if (this.verbose) {
40
+ if (this.options.verbose) {
90
41
  console.log("Stanterprise Reporter: gRPC disabled via STANTERPRISE_GRPC_ENABLED=false");
91
42
  }
92
43
  }
93
- if (this.verbose) {
44
+ if (this.options.verbose) {
94
45
  console.log(`Stanterprise Reporter: Test run started with ID: ${this.runId}`);
95
46
  console.log(`Number of tests: ${suite.allTests().length}`);
96
47
  console.log(`Run started at: ${this.runStartTime.toISOString()}`);
97
48
  }
98
- // Report root suite begin and track its ID
99
- const rootSuiteId = this.getSuiteId(suite);
100
- // Report the suite begin event
101
- this.reportSuiteBegin(suite, rootSuiteId);
49
+ if (this.options.grpcEnabled) {
50
+ (0, handlers_1.handleOnBeginEvent)(config, suite, process.env.STANTERPRISE_TEST_RUN_NAME || "Playwright Test Run", this.runId, this.grpcClient, this.options);
51
+ }
102
52
  }
103
53
  async onExit() {
104
54
  console.log(`Stanterprise Reporter: Test run completed - Run ID: ${this.runId}`);
@@ -110,207 +60,66 @@ class StanterpriseReporter {
110
60
  console.error("Stanterprise Reporter: Error during gRPC client cleanup in onExit:", e);
111
61
  }
112
62
  }
113
- // Getter to access the current run ID
114
- getRunId() {
115
- return this.runId;
116
- }
117
- // Getter to access the run start time
118
- getRunStartTime() {
119
- return this.runStartTime;
120
- }
121
63
  onEnd(result) {
122
64
  const runDuration = Date.now() - this.runStartTime.getTime();
123
- console.log(`Stanterprise Reporter: Test run ended - Run ID: ${this.runId}`);
124
- console.log(`Final result: ${result.status}`);
125
- console.log(`Run duration: ${runDuration}ms (Playwright duration: ${result.duration}ms)`);
126
- console.log(`Run start time: ${this.runStartTime.toISOString()}`);
127
- console.log(`Playwright start time: ${result.startTime.toISOString()}`);
128
- // Report root suite end
129
- if (this.rootSuite) {
130
- this.reportSuiteEnd(this.rootSuite, result);
65
+ if (this.options.verbose) {
66
+ console.log(`Stanterprise Reporter: Test run ended - Run ID: ${this.runId}`);
67
+ console.log(`Final result: ${result.status}`);
68
+ console.log(`Run duration: ${runDuration}ms (Playwright duration: ${result.duration}ms)`);
69
+ console.log(`Run start time: ${this.runStartTime.toISOString()}`);
70
+ console.log(`Playwright start time: ${result.startTime.toISOString()}`);
71
+ }
72
+ if (this.options.grpcEnabled) {
73
+ (0, handlers_1.handleOnEndEvent)(result, this.runId, this.grpcClient, this.options);
131
74
  }
132
75
  return Promise.resolve();
133
76
  }
134
77
  onTestBegin(test, result) {
135
78
  // Create unique test execution ID combining run ID and test ID
136
- const uniqueTestExecutionId = `${this.runId}-${test.id}`;
137
- console.log(`Stanterprise Reporter: Test started - ${test.title}`);
138
- console.log(` Run ID: ${this.runId}`);
139
- console.log(` Test ID (static): ${test.id}`);
140
- console.log(` Unique Execution ID: ${uniqueTestExecutionId}`);
141
- // Get test suite run ID from parent suite if available, ensuring suite is reported
142
- const testSuiteRunId = test.parent ? this.getSuiteId(test.parent) : "";
143
- // Build metadata from test annotations
144
- const metadata = new Map();
145
- test.annotations.forEach((annotation, index) => {
146
- metadata.set(`annotation_${index}_type`, annotation.type);
147
- if (annotation.description) {
148
- metadata.set(`annotation_${index}_description`, annotation.description);
149
- }
150
- });
151
- // Build and send the TestBegin event via generic unary call.
152
- const request = new EventsNS.TestBeginEventRequest({
153
- test_case: new TestCaseEntities.TestCaseRun({
154
- id: test.id,
155
- name: test.title,
156
- run_id: this.runId,
157
- test_suite_run_id: testSuiteRunId,
158
- start_time: (0, utils_1.createTimestamp)(result.startTime),
159
- metadata: metadata,
160
- tags: test.tags,
161
- }),
162
- });
163
- // Fire-and-forget to avoid slowing tests; log errors once.
164
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportTestBegin", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report test begin", e));
79
+ if (this.options.verbose) {
80
+ console.log(`Stanterprise Reporter: Test started - ${test.title}`);
81
+ console.log(` Run ID: ${this.runId}`);
82
+ console.log(` Test ID: ${test.id}`);
83
+ }
84
+ if (this.options.grpcEnabled) {
85
+ (0, handlers_1.handleOnTestBeginEvent)(test, result, this.runId, this.grpcClient, this.options);
86
+ }
165
87
  }
166
88
  async onStepBegin(test, result, step) {
167
- const uniqueTestExecutionId = `${this.runId}-${test.id}`;
168
- const uniqueStepId = `${uniqueTestExecutionId}-${step.title}-${step.startTime.getTime()}`;
169
- console.log(`Stanterprise Reporter: Step started - ${step.title}`);
170
- console.log(` Category: ${step.category}`);
171
- // Build metadata from step annotations
172
- const metadata = new Map();
173
- metadata.set("category", step.category);
174
- step.annotations.forEach((annotation, index) => {
175
- metadata.set(`annotation_${index}_type`, annotation.type);
176
- if (annotation.description) {
177
- metadata.set(`annotation_${index}_description`, annotation.description);
178
- }
179
- });
180
- // Get parent step ID if this step has a parent
181
- const parentStepId = step.parent
182
- ? `${uniqueTestExecutionId}-${step.parent.title}-${step.parent.startTime.getTime()}`
183
- : "";
184
- // Build and send the StepBegin event
185
- const request = new EventsNS.StepBeginEventRequest({
186
- step: new TestCaseEntities.StepRun({
187
- id: uniqueStepId,
188
- run_id: this.runId,
189
- test_case_run_id: uniqueTestExecutionId,
190
- title: step.title,
191
- type: step.category,
192
- start_time: (0, utils_1.createTimestamp)(step.startTime),
193
- location: step.location
194
- ? `${step.location.file}:${step.location.line}:${step.location.column}`
195
- : "",
196
- metadata: metadata,
197
- parent_step_id: parentStepId,
198
- worker_index: result.workerIndex.toString(),
199
- }),
200
- });
201
- // Fire-and-forget to avoid slowing tests
202
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportStepBegin", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report step begin", e));
89
+ if (this.options.verbose) {
90
+ console.log(`Stanterprise Reporter: Step started - ${step.title}`);
91
+ console.log(` Category: ${step.category}`);
92
+ }
93
+ if (this.options.grpcEnabled) {
94
+ (0, handlers_1.handleOnStepBeginEvent)(test, result, step, this.runId, this.grpcClient, this.options);
95
+ }
203
96
  }
204
97
  onStepEnd(test, result, step) {
205
- const uniqueTestExecutionId = `${this.runId}-${test.id}`;
206
- const uniqueStepId = `${uniqueTestExecutionId}-${step.title}-${step.startTime.getTime()}`;
207
- console.log(`Stanterprise Reporter: Step ended - ${step.title}`);
208
- console.log(` Duration: ${step.duration}ms`);
209
- // Map step error to status
210
- const stepStatus = (0, utils_1.mapStepStatus)(!!step.error);
211
- // Build metadata from step annotations
212
- const metadata = new Map();
213
- metadata.set("category", step.category);
214
- step.annotations.forEach((annotation, index) => {
215
- metadata.set(`annotation_${index}_type`, annotation.type);
216
- if (annotation.description) {
217
- metadata.set(`annotation_${index}_description`, annotation.description);
218
- }
219
- });
220
- // Get parent step ID if this step has a parent
221
- const parentStepId = step.parent
222
- ? `${uniqueTestExecutionId}-${step.parent.title}-${step.parent.startTime.getTime()}`
223
- : "";
224
- // Build and send the StepEnd event
225
- const request = new EventsNS.StepEndEventRequest({
226
- step: new TestCaseEntities.StepRun({
227
- id: uniqueStepId,
228
- run_id: this.runId,
229
- test_case_run_id: uniqueTestExecutionId,
230
- title: step.title,
231
- type: step.category,
232
- start_time: (0, utils_1.createTimestamp)(step.startTime),
233
- duration: (0, utils_1.createDuration)(step.duration),
234
- status: stepStatus,
235
- error: step.error ? step.error.message || "" : "",
236
- errors: step.error ? [step.error.message || ""] : [],
237
- location: step.location
238
- ? `${step.location.file}:${step.location.line}:${step.location.column}`
239
- : "",
240
- metadata: metadata,
241
- parent_step_id: parentStepId,
242
- worker_index: result.workerIndex.toString(),
243
- }),
244
- });
245
- // Fire-and-forget to avoid slowing tests
246
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportStepEnd", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report step end", e));
98
+ if (this.options.verbose) {
99
+ console.log(`Stanterprise Reporter: Step ended - ${step.title}`);
100
+ console.log(` Duration: ${step.duration}ms`);
101
+ }
102
+ if (this.options.grpcEnabled) {
103
+ (0, handlers_1.handleOnStepEndEvent)(test, result, step, this.runId, this.grpcClient, this.options);
104
+ }
247
105
  }
248
106
  onTestEnd(test, result) {
249
- const uniqueTestExecutionId = `${this.runId}-${test.id}`;
250
- console.log(`Stanterprise Reporter: Test ended - ${test.title}`);
251
- console.log(` Status: ${result.status}`);
252
- console.log(` Duration: ${result.duration}ms`);
253
- // Map Playwright test status to protobuf TestStatus
254
- const testStatus = (0, utils_1.mapTestStatus)(result.status);
255
- // Process attachments (screenshots, videos, etc.)
256
- const attachments = (0, utils_1.processAttachments)(result);
257
- // Extract error information if the test failed
258
- const { errorMessage, stackTrace, errors } = (0, utils_1.extractErrorInfo)(result);
259
- // Get test suite run ID from parent suite if available, ensuring suite is reported
260
- const testSuiteRunId = test.parent ? this.getSuiteId(test.parent) : "";
261
- // Build metadata from test annotations and result metadata
262
- const metadata = new Map();
263
- test.annotations.forEach((annotation, index) => {
264
- metadata.set(`annotation_${index}_type`, annotation.type);
265
- if (annotation.description) {
266
- metadata.set(`annotation_${index}_description`, annotation.description);
267
- }
268
- });
269
- result.annotations.forEach((annotation, index) => {
270
- metadata.set(`result_annotation_${index}_type`, annotation.type);
271
- if (annotation.description) {
272
- metadata.set(`result_annotation_${index}_description`, annotation.description);
273
- }
274
- });
275
- // Build and send the TestEnd event
276
- const request = new EventsNS.TestEndEventRequest({
277
- test_case: new TestCaseEntities.TestCaseRun({
278
- id: test.id,
279
- name: test.title,
280
- run_id: this.runId,
281
- test_suite_run_id: testSuiteRunId,
282
- status: testStatus,
283
- start_time: (0, utils_1.createTimestamp)(result.startTime),
284
- attachments: attachments,
285
- error_message: errorMessage,
286
- stack_trace: stackTrace,
287
- errors: errors,
288
- metadata: metadata,
289
- tags: test.tags,
290
- }),
291
- });
292
- // Fire-and-forget to avoid slowing tests
293
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportTestEnd", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report test end", e));
107
+ if (this.options.verbose) {
108
+ console.log(`Stanterprise Reporter: Test ended - ${test.title}`);
109
+ console.log(` Status: ${result.status}`);
110
+ console.log(` Duration: ${result.duration}ms`);
111
+ }
112
+ if (this.options.grpcEnabled) {
113
+ (0, handlers_1.handleOnTestEndEvent)(test, result, this.runId, this.grpcClient, this.options);
114
+ }
294
115
  }
295
116
  onTestFail(test, result) {
296
- const uniqueTestExecutionId = `${this.runId}-${test.id}`;
297
- if (this.verbose) {
117
+ if (this.options.verbose) {
298
118
  console.log(`Stanterprise Reporter: Test failed - ${test.title}`);
299
119
  }
300
- // Extract failure details
301
- const { errorMessage: failureMessage, stackTrace } = (0, utils_1.extractErrorInfo)(result);
302
- // Process attachments for failed tests
303
- const attachments = (0, utils_1.processAttachments)(result);
304
- // Build and send the TestFailure event
305
- const request = new EventsNS.TestFailureEventRequest({
306
- test_id: uniqueTestExecutionId,
307
- failure_message: failureMessage,
308
- stack_trace: stackTrace,
309
- timestamp: (0, utils_1.createTimestampFromMs)(Date.now()),
310
- attachments: attachments,
311
- });
312
- // Fire-and-forget to avoid slowing tests
313
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportTestFailure", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report test failure", e));
120
+ if (this.options.grpcEnabled) {
121
+ (0, handlers_1.handleOnTestFailEvent)(test, result, this.runId, this.grpcClient, this.options);
122
+ }
314
123
  }
315
124
  onError(error) {
316
125
  console.error("Stanterprise Reporter: An error occurred during the test run");
@@ -318,6 +127,9 @@ class StanterpriseReporter {
318
127
  if (error.stack) {
319
128
  console.error(`Stack trace: ${error.stack}`);
320
129
  }
130
+ if (this.options.grpcEnabled) {
131
+ (0, handlers_1.handleOnErrorEvent)(error, this.runId, this.grpcClient, this.options);
132
+ }
321
133
  }
322
134
  onStdErr(chunk, test, result) {
323
135
  console.error(`Stanterprise Reporter: Standard error output - ${chunk.toString()}`);
@@ -325,132 +137,5 @@ class StanterpriseReporter {
325
137
  onStdOut(chunk, test, result) {
326
138
  // console.log(`Stanterprise Reporter: Standard output - ${chunk.toString()}`);
327
139
  }
328
- // Helper: Get suite ID for a suite, ensuring it's been reported
329
- getSuiteId(suite) {
330
- // Check if we've already reported this suite
331
- if (this.reportedSuites.has(suite)) {
332
- return this.reportedSuites.get(suite);
333
- }
334
- // Generate a unique suite ID based on suite hierarchy
335
- const titlePath = suite.titlePath().join("/") || "root";
336
- const suiteId = `${this.runId}-suite-${titlePath}`;
337
- // Track that we've reported this suite
338
- this.reportedSuites.set(suite, suiteId);
339
- return suiteId;
340
- }
341
- // Helper: Report suite begin event
342
- reportSuiteBegin(suite, suiteId) {
343
- const id = suiteId || `${this.runId}-suite-${suite.title || "root"}`;
344
- console.log(`Stanterprise Reporter: Suite started - ${suite.title || "root"}`);
345
- console.log(` Suite type: ${suite.type}`);
346
- // Build metadata from suite location
347
- const metadata = new Map();
348
- metadata.set("suite_type", suite.type);
349
- if (suite.location) {
350
- metadata.set("location_file", suite.location.file);
351
- metadata.set("location_line", suite.location.line.toString());
352
- metadata.set("location_column", suite.location.column.toString());
353
- }
354
- // Build and send the SuiteBegin event
355
- const request = new EventsNS.SuiteBeginEventRequest({
356
- suite: new TestSuiteEntities.TestSuiteRun({
357
- id: id,
358
- name: suite.title || "root",
359
- start_time: (0, utils_1.createTimestamp)(this.runStartTime),
360
- metadata: metadata,
361
- }),
362
- });
363
- // Fire-and-forget to avoid slowing tests
364
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportSuiteBegin", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report suite begin", e));
365
- }
366
- // Helper: Report suite end event
367
- reportSuiteEnd(suite, result) {
368
- const suiteId = `${this.runId}-suite-${suite.title || "root"}`;
369
- const endTime = new Date();
370
- const duration = endTime.getTime() - this.runStartTime.getTime();
371
- console.log(`Stanterprise Reporter: Suite ended - ${suite.title || "root"}`);
372
- console.log(` Duration: ${duration}ms`);
373
- // Map result status to protobuf TestStatus
374
- const suiteStatus = this.mapSuiteStatus(result.status);
375
- // Build metadata from suite location
376
- const metadata = new Map();
377
- metadata.set("suite_type", suite.type);
378
- metadata.set("final_result", result.status);
379
- if (suite.location) {
380
- metadata.set("location_file", suite.location.file);
381
- metadata.set("location_line", suite.location.line.toString());
382
- metadata.set("location_column", suite.location.column.toString());
383
- }
384
- // Build and send the SuiteEnd event
385
- const request = new EventsNS.SuiteEndEventRequest({
386
- suite: new TestSuiteEntities.TestSuiteRun({
387
- id: suiteId,
388
- name: suite.title || "root",
389
- start_time: (0, utils_1.createTimestamp)(this.runStartTime),
390
- end_time: (0, utils_1.createTimestamp)(endTime),
391
- duration: (0, utils_1.createDuration)(duration),
392
- status: suiteStatus,
393
- metadata: metadata,
394
- }),
395
- });
396
- // Fire-and-forget to avoid slowing tests
397
- this.reportUnary("/testsystem.v1.observer.TestEventCollector/ReportSuiteEnd", request, this.grpcTimeout).catch((e) => this.logGrpcErrorOnce("Failed to report suite end", e));
398
- }
399
- // Helper: Map FullResult status to protobuf TestStatus
400
- mapSuiteStatus(status) {
401
- switch (status) {
402
- case "passed":
403
- return TestStatus.PASSED;
404
- case "failed":
405
- return TestStatus.FAILED;
406
- case "timedout":
407
- return TestStatus.FAILED;
408
- case "interrupted":
409
- return TestStatus.BROKEN;
410
- default:
411
- return TestStatus.UNKNOWN;
412
- }
413
- }
414
- // Helper: log first gRPC error once and disable further attempts
415
- logGrpcErrorOnce(prefix, err) {
416
- if (this.grpcFirstErrorLogged)
417
- return;
418
- this.grpcFirstErrorLogged = true;
419
- const details = err instanceof Error ? `${err.message}` : String(err);
420
- console.warn(`${prefix}. gRPC disabled for the remainder of the run. Address=${this.grpcAddress}. Details: ${details}`);
421
- this.grpcEnabled = false;
422
- }
423
- // Helper: generic unary call using raw method path
424
- async reportUnary(path, message, deadlineMs = 1000) {
425
- if (!this.grpcEnabled || !this.grpcClient) {
426
- return Buffer.alloc(0);
427
- }
428
- const reqSerialize = (arg) => {
429
- const m = arg;
430
- const bytes = m?.serializeBinary
431
- ? m.serializeBinary()
432
- : m?.serialize
433
- ? m.serialize()
434
- : new Uint8Array(0);
435
- return Buffer.from(bytes);
436
- };
437
- const resDeserialize = (bytes) => bytes;
438
- const metadata = new grpc.Metadata();
439
- const callOptions = {
440
- deadline: new Date(Date.now() + deadlineMs),
441
- };
442
- return new Promise((resolve, reject) => {
443
- try {
444
- this.grpcClient.makeUnaryRequest(path, reqSerialize, resDeserialize, message, metadata, callOptions, (err, response) => {
445
- if (err)
446
- return reject(err);
447
- resolve(response);
448
- });
449
- }
450
- catch (e) {
451
- reject(e);
452
- }
453
- });
454
- }
455
140
  }
456
141
  exports.default = StanterpriseReporter;
@@ -0,0 +1,20 @@
1
+ import { Suite, TestCase, TestStep } from "@playwright/test/reporter";
2
+ /**
3
+ * Generates a unique suite ID based on the suite's title path.
4
+ * @param suite The Playwright Suite object.
5
+ * @returns A unique string ID for the suite.
6
+ */
7
+ export declare function generateSuiteId(suite: Suite): string;
8
+ /**
9
+ * Generates a unique test ID based on the test case. Currently uses the test's existing ID.
10
+ * @param test The Playwright TestCase object.
11
+ * @returns A unique string ID for the test case.
12
+ */
13
+ export declare function generateTestId(test: TestCase): string;
14
+ /**
15
+ * Generates a unique step ID based on the step and its parent test case.
16
+ * @param step The Playwright TestStep object.
17
+ * @param test The Playwright TestCase object that the step belongs to.
18
+ * @returns A unique string ID for the step.
19
+ */
20
+ export declare function generateStepId(step: TestStep, test: TestCase): string;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateSuiteId = generateSuiteId;
7
+ exports.generateTestId = generateTestId;
8
+ exports.generateStepId = generateStepId;
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ /**
11
+ * Generates a unique suite ID based on the suite's title path.
12
+ * @param suite The Playwright Suite object.
13
+ * @returns A unique string ID for the suite.
14
+ */
15
+ function generateSuiteId(suite) {
16
+ return crypto_1.default
17
+ .createHash("sha256")
18
+ .update(suite.titlePath().join(":"))
19
+ .digest("hex");
20
+ }
21
+ /**
22
+ * Generates a unique test ID based on the test case. Currently uses the test's existing ID.
23
+ * @param test The Playwright TestCase object.
24
+ * @returns A unique string ID for the test case.
25
+ */
26
+ function generateTestId(test) {
27
+ return test.id;
28
+ }
29
+ /**
30
+ * Generates a unique step ID based on the step and its parent test case.
31
+ * @param step The Playwright TestStep object.
32
+ * @param test The Playwright TestCase object that the step belongs to.
33
+ * @returns A unique string ID for the step.
34
+ */
35
+ function generateStepId(step, test) {
36
+ return crypto_1.default
37
+ .createHash("sha256")
38
+ .update(`${test.id}:${step.titlePath().join(":")}`)
39
+ .digest("hex");
40
+ }
@@ -4,3 +4,4 @@
4
4
  export * from "./statusMapper";
5
5
  export * from "./attachmentProcessor";
6
6
  export * from "./timeHelpers";
7
+ export * from "./idGenerators";
@@ -20,3 +20,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
20
20
  __exportStar(require("./statusMapper"), exports);
21
21
  __exportStar(require("./attachmentProcessor"), exports);
22
22
  __exportStar(require("./timeHelpers"), exports);
23
+ __exportStar(require("./idGenerators"), exports);
@@ -0,0 +1,7 @@
1
+ import { StanterpriseReporterOptions } from "../types";
2
+ /**
3
+ * Define and map reporter options from provided options and environment variables. Apply defaults where necessary.
4
+ * @param providedOptions Options provided during reporter initialization
5
+ * @returns Mapped and completed StanterpriseReporterOptions
6
+ */
7
+ export default function defineOptions(providedOptions: StanterpriseReporterOptions): StanterpriseReporterOptions;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = defineOptions;
4
+ /**
5
+ * Define and map reporter options from provided options and environment variables. Apply defaults where necessary.
6
+ * @param providedOptions Options provided during reporter initialization
7
+ * @returns Mapped and completed StanterpriseReporterOptions
8
+ */
9
+ function defineOptions(providedOptions) {
10
+ const result = {};
11
+ result.grpcAddress =
12
+ providedOptions.grpcAddress ||
13
+ process.env.STANTERPRISE_GRPC_ADDRESS ||
14
+ "localhost:50051";
15
+ result.grpcEnabled =
16
+ providedOptions.grpcEnabled ??
17
+ (process.env.STANTERPRISE_GRPC_ENABLED || "true").toLowerCase() !== "false";
18
+ result.grpcTimeout = providedOptions.grpcTimeout || 1000;
19
+ result.verbose = providedOptions.verbose || false;
20
+ return result;
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stanterprise/playwright-reporter",
3
- "version": "0.0.1",
3
+ "version": "0.0.6",
4
4
  "description": "Custom Playwright test reporter that sends test results to Stanterprise via gRPC",
5
5
  "homepage": "https://github.com/stanterprise/stanterprise-playwright-reporter#readme",
6
6
  "bugs": {
@@ -16,8 +16,7 @@
16
16
  "main": "dist/index.js",
17
17
  "types": "dist/index.d.ts",
18
18
  "files": [
19
- "dist/**/*",
20
- "README.md"
19
+ "dist/**/*"
21
20
  ],
22
21
  "keywords": [
23
22
  "playwright",
@@ -54,6 +53,6 @@
54
53
  "@grpc/grpc-js": "^1.14.1",
55
54
  "@protobuf-ts/grpc-transport": "^2.11.1",
56
55
  "@protobuf-ts/grpcweb-transport": "^2.11.1",
57
- "@stanterprise/protobuf": "^0.0.11"
56
+ "@stanterprise/protobuf": "^0.0.18"
58
57
  }
59
58
  }
@@ -1 +0,0 @@
1
- export {};
@@ -1,4 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const protobuf_1 = require("@stanterprise/protobuf");
4
- const SuiteType = protobuf_1.testSuite.v1.entities.SuiteType;