autotel-playwright 0.4.34 → 0.4.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +266 -208
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -29
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +27 -29
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +240 -193
- package/dist/index.js.map +1 -1
- package/dist/reporter.cjs +63 -68
- package/dist/reporter.cjs.map +1 -1
- package/dist/reporter.d.cts +12 -27
- package/dist/reporter.d.cts.map +1 -0
- package/dist/reporter.d.ts +12 -27
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +58 -63
- package/dist/reporter.js.map +1 -1
- package/package.json +6 -5
package/dist/index.d.cts
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
export { expect } from '@playwright/test';
|
|
4
|
-
import { AutotelConfig } from 'autotel';
|
|
5
|
-
export { OtelTraceContext, enrichWithTraceContext, getTraceContext, isTracing, resolveTraceUrl } from 'autotel';
|
|
1
|
+
import { APIRequestContext, Page, TestInfo, expect } from "@playwright/test";
|
|
2
|
+
import { AutotelConfig, OtelTraceContext, enrichWithTraceContext, getTraceContext, isTracing, resolveTraceUrl } from "autotel";
|
|
6
3
|
|
|
4
|
+
//#region src/index.d.ts
|
|
7
5
|
/** Annotation type for custom span attributes: description should be "key=value" or "key=value1;key2=value2". */
|
|
8
6
|
declare const AUTOTEL_ATTRIBUTE_ANNOTATION = "autotel.attribute";
|
|
9
7
|
type OtelTestSpan = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
carrier: Record<string, string>;
|
|
9
|
+
apiBaseUrls: string[];
|
|
10
|
+
testInfo: TestInfo;
|
|
13
11
|
};
|
|
14
|
-
declare const test:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
},
|
|
19
|
-
|
|
12
|
+
declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
|
|
13
|
+
page: Page;
|
|
14
|
+
requestWithTrace: APIRequestContext;
|
|
15
|
+
_otelTestSpan: OtelTestSpan;
|
|
16
|
+
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
20
17
|
/**
|
|
21
18
|
* Runs a named step as a child span of the current test span. Use inside a test to get
|
|
22
19
|
* step-level spans (e.g. "step:login", "step:navigate") under the test span in the same trace.
|
|
@@ -41,16 +38,16 @@ declare function createGlobalSetup(initOptions?: AutotelConfig): () => Promise<v
|
|
|
41
38
|
* Serialized span returned by the test-spans endpoint (matches autotel-tanstack/testing SerializedSpan).
|
|
42
39
|
*/
|
|
43
40
|
interface SerializedSpan {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
41
|
+
name: string;
|
|
42
|
+
spanId: string;
|
|
43
|
+
traceId: string;
|
|
44
|
+
parentSpanId?: string;
|
|
45
|
+
attributes?: Record<string, unknown>;
|
|
46
|
+
status: {
|
|
47
|
+
code: number;
|
|
48
|
+
message?: string;
|
|
49
|
+
};
|
|
50
|
+
durationMs: number;
|
|
54
51
|
}
|
|
55
52
|
/**
|
|
56
53
|
* Creates a typed client for the test-spans HTTP endpoint.
|
|
@@ -74,10 +71,11 @@ interface SerializedSpan {
|
|
|
74
71
|
* ```
|
|
75
72
|
*/
|
|
76
73
|
declare function createTestSpansClient(baseUrl: string, options?: {
|
|
77
|
-
|
|
74
|
+
path?: string;
|
|
78
75
|
}): {
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
getSpans(request: APIRequestContext): Promise<SerializedSpan[]>;
|
|
77
|
+
clearSpans(request: APIRequestContext): Promise<void>;
|
|
81
78
|
};
|
|
82
|
-
|
|
83
|
-
export { AUTOTEL_ATTRIBUTE_ANNOTATION, type SerializedSpan, createGlobalSetup, createTestSpansClient, step, test };
|
|
79
|
+
//#endregion
|
|
80
|
+
export { AUTOTEL_ATTRIBUTE_ANNOTATION, type OtelTraceContext, SerializedSpan, createGlobalSetup, createTestSpansClient, enrichWithTraceContext, expect, getTraceContext, isTracing, resolveTraceUrl, step, test };
|
|
81
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;cAqGa,4BAAA;AAAA,KA8DR,YAAA;EACH,OAAA,EAAS,MAAA;EACT,WAAA;EACA,QAAA,EAAU,QAAQ;AAAA;AAAA,cAGP,IAAA,6BAAI,QAAA,4BAAA,kBAAA,8BAAA,qBAAA;QACT,IAAA;oBACY,iBAAA;iBACH,YAAA;AAAA;;;;;AAuGjB;;;;;;;;;;iBAAsB,IAAA,IAAQ,IAAA,UAAc,EAAA,QAAU,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;;;;;iBAoB3D,iBAAA,CAAkB,WAAA,GAAc,aAAA,SAAsB,OAAO;;AApBD;AAoB5E;UAciB,cAAA;EACf,IAAA;EACA,MAAA;EACA,OAAA;EACA,YAAA;EACA,UAAA,GAAa,MAAM;EACnB,MAAA;IAAU,IAAA;IAAc,OAAA;EAAA;EACxB,UAAA;AAAA;;;;;;;;;;;;AAAU;AAwBZ;;;;;;;;;iBAAgB,qBAAA,CACd,OAAA,UACA,OAAA;EAAY,IAAA;AAAA;EAEZ,QAAA,CAAS,OAAA,EAAS,iBAAA,GAAoB,OAAA,CAAQ,cAAA;EAC9C,UAAA,CAAW,OAAA,EAAS,iBAAA,GAAoB,OAAA;AAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
export { expect } from '@playwright/test';
|
|
4
|
-
import { AutotelConfig } from 'autotel';
|
|
5
|
-
export { OtelTraceContext, enrichWithTraceContext, getTraceContext, isTracing, resolveTraceUrl } from 'autotel';
|
|
1
|
+
import { APIRequestContext, Page, TestInfo, expect } from "@playwright/test";
|
|
2
|
+
import { AutotelConfig, OtelTraceContext, enrichWithTraceContext, getTraceContext, isTracing, resolveTraceUrl } from "autotel";
|
|
6
3
|
|
|
4
|
+
//#region src/index.d.ts
|
|
7
5
|
/** Annotation type for custom span attributes: description should be "key=value" or "key=value1;key2=value2". */
|
|
8
6
|
declare const AUTOTEL_ATTRIBUTE_ANNOTATION = "autotel.attribute";
|
|
9
7
|
type OtelTestSpan = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
carrier: Record<string, string>;
|
|
9
|
+
apiBaseUrls: string[];
|
|
10
|
+
testInfo: TestInfo;
|
|
13
11
|
};
|
|
14
|
-
declare const test:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
},
|
|
19
|
-
|
|
12
|
+
declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
|
|
13
|
+
page: Page;
|
|
14
|
+
requestWithTrace: APIRequestContext;
|
|
15
|
+
_otelTestSpan: OtelTestSpan;
|
|
16
|
+
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
20
17
|
/**
|
|
21
18
|
* Runs a named step as a child span of the current test span. Use inside a test to get
|
|
22
19
|
* step-level spans (e.g. "step:login", "step:navigate") under the test span in the same trace.
|
|
@@ -41,16 +38,16 @@ declare function createGlobalSetup(initOptions?: AutotelConfig): () => Promise<v
|
|
|
41
38
|
* Serialized span returned by the test-spans endpoint (matches autotel-tanstack/testing SerializedSpan).
|
|
42
39
|
*/
|
|
43
40
|
interface SerializedSpan {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
41
|
+
name: string;
|
|
42
|
+
spanId: string;
|
|
43
|
+
traceId: string;
|
|
44
|
+
parentSpanId?: string;
|
|
45
|
+
attributes?: Record<string, unknown>;
|
|
46
|
+
status: {
|
|
47
|
+
code: number;
|
|
48
|
+
message?: string;
|
|
49
|
+
};
|
|
50
|
+
durationMs: number;
|
|
54
51
|
}
|
|
55
52
|
/**
|
|
56
53
|
* Creates a typed client for the test-spans HTTP endpoint.
|
|
@@ -74,10 +71,11 @@ interface SerializedSpan {
|
|
|
74
71
|
* ```
|
|
75
72
|
*/
|
|
76
73
|
declare function createTestSpansClient(baseUrl: string, options?: {
|
|
77
|
-
|
|
74
|
+
path?: string;
|
|
78
75
|
}): {
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
getSpans(request: APIRequestContext): Promise<SerializedSpan[]>;
|
|
77
|
+
clearSpans(request: APIRequestContext): Promise<void>;
|
|
81
78
|
};
|
|
82
|
-
|
|
83
|
-
export { AUTOTEL_ATTRIBUTE_ANNOTATION, type SerializedSpan, createGlobalSetup, createTestSpansClient, step, test };
|
|
79
|
+
//#endregion
|
|
80
|
+
export { AUTOTEL_ATTRIBUTE_ANNOTATION, type OtelTraceContext, SerializedSpan, createGlobalSetup, createTestSpansClient, enrichWithTraceContext, expect, getTraceContext, isTracing, resolveTraceUrl, step, test };
|
|
81
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;cAqGa,4BAAA;AAAA,KA8DR,YAAA;EACH,OAAA,EAAS,MAAA;EACT,WAAA;EACA,QAAA,EAAU,QAAQ;AAAA;AAAA,cAGP,IAAA,6BAAI,QAAA,4BAAA,kBAAA,8BAAA,qBAAA;QACT,IAAA;oBACY,iBAAA;iBACH,YAAA;AAAA;;;;;AAuGjB;;;;;;;;;;iBAAsB,IAAA,IAAQ,IAAA,UAAc,EAAA,QAAU,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;;;;;iBAoB3D,iBAAA,CAAkB,WAAA,GAAc,aAAA,SAAsB,OAAO;;AApBD;AAoB5E;UAciB,cAAA;EACf,IAAA;EACA,MAAA;EACA,OAAA;EACA,YAAA;EACA,UAAA,GAAa,MAAM;EACnB,MAAA;IAAU,IAAA;IAAc,OAAA;EAAA;EACxB,UAAA;AAAA;;;;;;;;;;;;AAAU;AAwBZ;;;;;;;;;iBAAgB,qBAAA,CACd,OAAA,UACA,OAAA;EAAY,IAAA;AAAA;EAEZ,QAAA,CAAS,OAAA,EAAS,iBAAA,GAAoB,OAAA,CAAQ,cAAA;EAC9C,UAAA,CAAW,OAAA,EAAS,iBAAA,GAAoB,OAAA;AAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,214 +1,261 @@
|
|
|
1
|
-
import { test as test$1 } from
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { expect, test as test$1 } from "@playwright/test";
|
|
2
|
+
import { SpanStatusCode, context, enrichWithTraceContext, getAutotelTracerProvider, getTraceContext, getTracer, isTracing, otelTrace, propagation, resolveTraceUrl } from "autotel";
|
|
3
|
+
import { TestSpanCollector } from "autotel/test-span-collector";
|
|
4
|
+
import { SimpleSpanProcessor } from "autotel/processors";
|
|
5
|
+
//#region src/index.ts
|
|
6
|
+
/**
|
|
7
|
+
* autotel-playwright
|
|
8
|
+
*
|
|
9
|
+
* Playwright fixture that creates one OTel span per test and injects W3C trace
|
|
10
|
+
* context into requests to your API so "test → API" appears as one trace.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // globalSetup.ts: init({ service: 'e2e-tests' });
|
|
14
|
+
* // In spec:
|
|
15
|
+
* import { test, expect } from 'autotel-playwright';
|
|
16
|
+
* test('checks health', async ({ page }) => {
|
|
17
|
+
* await page.goto(API_BASE_URL + '/health'); // request gets traceparent
|
|
18
|
+
* });
|
|
19
|
+
* // Node-side API calls with trace context:
|
|
20
|
+
* test('api health', async ({ requestWithTrace }) => {
|
|
21
|
+
* const res = await requestWithTrace.get(API_BASE_URL + '/health');
|
|
22
|
+
* expect(res.ok()).toBeTruthy();
|
|
23
|
+
* });
|
|
24
|
+
*/
|
|
25
|
+
const TRACER_NAME = "playwright-tests";
|
|
26
|
+
const TRACER_VERSION = "0.1.0";
|
|
27
|
+
let collector = null;
|
|
12
28
|
function ensureCollector() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return collector;
|
|
29
|
+
if (!collector) {
|
|
30
|
+
collector = new TestSpanCollector();
|
|
31
|
+
const provider = getAutotelTracerProvider();
|
|
32
|
+
if ("addSpanProcessor" in provider) provider.addSpanProcessor(new SimpleSpanProcessor(collector));
|
|
33
|
+
}
|
|
34
|
+
return collector;
|
|
23
35
|
}
|
|
24
|
-
|
|
25
|
-
|
|
36
|
+
/** Env keys for API base URL (requests to this origin get trace context injected). */
|
|
37
|
+
const ENV_API_BASE_URL = "API_BASE_URL";
|
|
38
|
+
const ENV_API_ORIGIN = "AUTOTEL_PLAYWRIGHT_API_ORIGIN";
|
|
26
39
|
function getApiBaseUrls() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
const a = process.env[ENV_API_BASE_URL];
|
|
41
|
+
const b = process.env[ENV_API_ORIGIN];
|
|
42
|
+
const urls = [];
|
|
43
|
+
if (a) urls.push(a.replace(/\/$/, ""));
|
|
44
|
+
if (b) urls.push(b.replace(/\/$/, ""));
|
|
45
|
+
return [...new Set(urls)];
|
|
33
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Returns true if requestUrl should receive trace headers for the given apiBaseUrls.
|
|
49
|
+
* When a base URL includes a path (e.g. http://localhost:3000/api), only requests
|
|
50
|
+
* whose path starts with that path segment match; same-origin but different path
|
|
51
|
+
* (e.g. /health) must not match to avoid leaking trace context to unrelated endpoints.
|
|
52
|
+
*/
|
|
34
53
|
function urlMatchesApiOrigin(requestUrl, apiBaseUrls) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
if (apiBaseUrls.length === 0) return false;
|
|
55
|
+
try {
|
|
56
|
+
const u = new URL(requestUrl);
|
|
57
|
+
const requestOrigin = u.origin;
|
|
58
|
+
const requestPathname = u.pathname;
|
|
59
|
+
return apiBaseUrls.some((base) => {
|
|
60
|
+
try {
|
|
61
|
+
const b = new URL(base);
|
|
62
|
+
if (requestOrigin !== b.origin) return false;
|
|
63
|
+
const basePathname = b.pathname.replace(/\/$/, "") || "/";
|
|
64
|
+
if (basePathname === "/") return true;
|
|
65
|
+
return requestPathname === basePathname || requestPathname.startsWith(basePathname + "/");
|
|
66
|
+
} catch {
|
|
67
|
+
return requestUrl.startsWith(base);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
return apiBaseUrls.some((base) => requestUrl.startsWith(base));
|
|
72
|
+
}
|
|
54
73
|
}
|
|
55
|
-
|
|
74
|
+
/** Annotation type for custom span attributes: description should be "key=value" or "key=value1;key2=value2". */
|
|
75
|
+
const AUTOTEL_ATTRIBUTE_ANNOTATION = "autotel.attribute";
|
|
56
76
|
function setAttributesFromAnnotations(span, testInfo) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
for (const a of testInfo.annotations) {
|
|
78
|
+
if (a.type !== "autotel.attribute" || !a.description) continue;
|
|
79
|
+
const entries = a.description.split(";");
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const parts = entry.split("=");
|
|
82
|
+
if (parts.length >= 2) {
|
|
83
|
+
const key = parts[0].trim();
|
|
84
|
+
const value = parts.slice(1).join("=").trim();
|
|
85
|
+
span.setAttribute(key, value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
69
89
|
}
|
|
70
90
|
function mergeTraceHeaders(url, options, apiBaseUrls, carrier, testName) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
91
|
+
const opts = options ?? {};
|
|
92
|
+
if (!urlMatchesApiOrigin(url, apiBaseUrls)) return opts;
|
|
93
|
+
return {
|
|
94
|
+
...opts,
|
|
95
|
+
headers: {
|
|
96
|
+
...opts.headers,
|
|
97
|
+
...carrier,
|
|
98
|
+
"x-test-name": testName
|
|
99
|
+
}
|
|
100
|
+
};
|
|
77
101
|
}
|
|
102
|
+
/** Wraps APIRequestContext so requests to API_BASE_URL get trace context injected. */
|
|
78
103
|
function createRequestWithTrace(request, apiBaseUrls, carrier, testInfo) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
104
|
+
const merge = (url, options) => mergeTraceHeaders(url, options, apiBaseUrls, carrier, testInfo.title);
|
|
105
|
+
return {
|
|
106
|
+
get: (url, options) => request.get(url, merge(url, options)),
|
|
107
|
+
post: (url, options) => request.post(url, merge(url, options)),
|
|
108
|
+
put: (url, options) => request.put(url, merge(url, options)),
|
|
109
|
+
patch: (url, options) => request.patch(url, merge(url, options)),
|
|
110
|
+
delete: (url, options) => request.delete(url, merge(url, options)),
|
|
111
|
+
head: (url, options) => request.head(url, merge(url, options)),
|
|
112
|
+
fetch: (urlOrRequest, options) => request.fetch(urlOrRequest, merge(typeof urlOrRequest === "string" ? urlOrRequest : urlOrRequest.url(), options)),
|
|
113
|
+
storageState: (options) => request.storageState(options),
|
|
114
|
+
dispose: () => request.dispose()
|
|
115
|
+
};
|
|
91
116
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
await use(page);
|
|
154
|
-
},
|
|
155
|
-
requestWithTrace: async ({ request, _otelTestSpan }, use) => {
|
|
156
|
-
const wrapped = createRequestWithTrace(
|
|
157
|
-
request,
|
|
158
|
-
_otelTestSpan.apiBaseUrls,
|
|
159
|
-
_otelTestSpan.carrier,
|
|
160
|
-
_otelTestSpan.testInfo
|
|
161
|
-
);
|
|
162
|
-
await use(wrapped);
|
|
163
|
-
}
|
|
117
|
+
const test = test$1.extend({
|
|
118
|
+
_otelTestSpan: [async ({}, use, testInfo) => {
|
|
119
|
+
ensureCollector();
|
|
120
|
+
const apiBaseUrls = getApiBaseUrls();
|
|
121
|
+
const tracer = getTracer(TRACER_NAME, TRACER_VERSION);
|
|
122
|
+
const spanName = `e2e:${testInfo.title}`;
|
|
123
|
+
const span = tracer.startSpan(spanName, { attributes: {
|
|
124
|
+
"test.title": testInfo.title,
|
|
125
|
+
"test.project": testInfo.project.name,
|
|
126
|
+
"test.file": testInfo.file ?? "",
|
|
127
|
+
"test.line": testInfo.line ?? 0
|
|
128
|
+
} });
|
|
129
|
+
setAttributesFromAnnotations(span, testInfo);
|
|
130
|
+
const ctx = otelTrace.setSpan(context.active(), span);
|
|
131
|
+
const carrier = {};
|
|
132
|
+
context.with(ctx, () => {
|
|
133
|
+
propagation.inject(context.active(), carrier);
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
await context.with(ctx, () => use({
|
|
137
|
+
carrier,
|
|
138
|
+
apiBaseUrls,
|
|
139
|
+
testInfo
|
|
140
|
+
}));
|
|
141
|
+
} catch (error) {
|
|
142
|
+
span.setStatus({
|
|
143
|
+
code: SpanStatusCode.ERROR,
|
|
144
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
145
|
+
});
|
|
146
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
147
|
+
throw error;
|
|
148
|
+
} finally {
|
|
149
|
+
span.end();
|
|
150
|
+
const traceId = span.spanContext().traceId;
|
|
151
|
+
const rootSpanId = span.spanContext().spanId;
|
|
152
|
+
const spans = collector.drainTrace(traceId, rootSpanId);
|
|
153
|
+
if (spans.length > 0) testInfo.annotations.push({
|
|
154
|
+
type: "otel-spans",
|
|
155
|
+
description: JSON.stringify(spans)
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}, { scope: "test" }],
|
|
159
|
+
page: async ({ page, _otelTestSpan }, use) => {
|
|
160
|
+
const { carrier, apiBaseUrls, testInfo } = _otelTestSpan;
|
|
161
|
+
if (apiBaseUrls.length > 0) await page.route("**/*", async (route) => {
|
|
162
|
+
const request = route.request();
|
|
163
|
+
if (urlMatchesApiOrigin(request.url(), apiBaseUrls)) {
|
|
164
|
+
const headers = {
|
|
165
|
+
...request.headers(),
|
|
166
|
+
...carrier,
|
|
167
|
+
"x-test-name": testInfo.title
|
|
168
|
+
};
|
|
169
|
+
await route.continue({ headers });
|
|
170
|
+
} else await route.continue();
|
|
171
|
+
});
|
|
172
|
+
await use(page);
|
|
173
|
+
},
|
|
174
|
+
requestWithTrace: async ({ request, _otelTestSpan }, use) => {
|
|
175
|
+
await use(createRequestWithTrace(request, _otelTestSpan.apiBaseUrls, _otelTestSpan.carrier, _otelTestSpan.testInfo));
|
|
176
|
+
}
|
|
164
177
|
});
|
|
178
|
+
/**
|
|
179
|
+
* Runs a named step as a child span of the current test span. Use inside a test to get
|
|
180
|
+
* step-level spans (e.g. "step:login", "step:navigate") under the test span in the same trace.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* test('user flow', async ({ page }) => {
|
|
184
|
+
* await step('login', async () => {
|
|
185
|
+
* await page.click('button[type=submit]');
|
|
186
|
+
* });
|
|
187
|
+
* await step('open profile', async () => {
|
|
188
|
+
* await page.goto('/profile');
|
|
189
|
+
* });
|
|
190
|
+
* });
|
|
191
|
+
*/
|
|
165
192
|
async function step(name, fn) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
193
|
+
const span = getTracer(TRACER_NAME, TRACER_VERSION).startSpan(`step:${name}`, { attributes: { "step.name": name } });
|
|
194
|
+
try {
|
|
195
|
+
return await context.with(otelTrace.setSpan(context.active(), span), fn);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
span.setStatus({
|
|
198
|
+
code: SpanStatusCode.ERROR,
|
|
199
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
200
|
+
});
|
|
201
|
+
span.recordException(error instanceof Error ? error : new Error(String(error)));
|
|
202
|
+
throw error;
|
|
203
|
+
} finally {
|
|
204
|
+
span.end();
|
|
205
|
+
}
|
|
179
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Returns a function suitable for Playwright globalSetup that inits autotel.
|
|
209
|
+
* Call autotel.init() with the given options (or defaults) so test spans are exported.
|
|
210
|
+
*/
|
|
180
211
|
function createGlobalSetup(initOptions) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
212
|
+
return async () => {
|
|
213
|
+
const { init } = await import("autotel");
|
|
214
|
+
init({
|
|
215
|
+
service: "e2e-tests",
|
|
216
|
+
debug: true,
|
|
217
|
+
...initOptions
|
|
218
|
+
});
|
|
219
|
+
};
|
|
189
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Creates a typed client for the test-spans HTTP endpoint.
|
|
223
|
+
*
|
|
224
|
+
* Pairs with `createTestSpansHandlers()` from `autotel-tanstack/testing`.
|
|
225
|
+
*
|
|
226
|
+
* @param baseUrl - Base URL of the app under test (e.g. 'http://localhost:3100')
|
|
227
|
+
* @param options.path - Path of the test-spans endpoint (default: '/api/test-spans')
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* const spansClient = createTestSpansClient('http://localhost:3100');
|
|
232
|
+
*
|
|
233
|
+
* test('server function is traced', async ({ request }) => {
|
|
234
|
+
* await spansClient.clearSpans(request);
|
|
235
|
+
* await page.goto('/');
|
|
236
|
+
* // ... trigger action ...
|
|
237
|
+
* const spans = await spansClient.getSpans(request);
|
|
238
|
+
* expect(spans.find(s => s.name === 'sendMoney.handler')).toBeDefined();
|
|
239
|
+
* });
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
190
242
|
function createTestSpansClient(baseUrl, options) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (!res.ok()) {
|
|
206
|
-
throw new Error(`DELETE ${path} failed: ${res.status()}`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
};
|
|
243
|
+
const base = baseUrl.replace(/\/$/, "");
|
|
244
|
+
const path = options?.path ?? "/api/test-spans";
|
|
245
|
+
const url = `${base}${path}`;
|
|
246
|
+
return {
|
|
247
|
+
async getSpans(request) {
|
|
248
|
+
const res = await request.get(url);
|
|
249
|
+
if (!res.ok()) throw new Error(`GET ${path} failed: ${res.status()}`);
|
|
250
|
+
return (await res.json()).spans;
|
|
251
|
+
},
|
|
252
|
+
async clearSpans(request) {
|
|
253
|
+
const res = await request.delete(url);
|
|
254
|
+
if (!res.ok()) throw new Error(`DELETE ${path} failed: ${res.status()}`);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
210
257
|
}
|
|
258
|
+
//#endregion
|
|
259
|
+
export { AUTOTEL_ATTRIBUTE_ANNOTATION, createGlobalSetup, createTestSpansClient, enrichWithTraceContext, expect, getTraceContext, isTracing, resolveTraceUrl, step, test };
|
|
211
260
|
|
|
212
|
-
export { AUTOTEL_ATTRIBUTE_ANNOTATION, createGlobalSetup, createTestSpansClient, step, test };
|
|
213
|
-
//# sourceMappingURL=index.js.map
|
|
214
261
|
//# sourceMappingURL=index.js.map
|