autotel-vitest 0.4.48 → 0.4.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,18 +40,30 @@ yarn add autotel autotel-vitest
40
40
 
41
41
  ## Quick Start
42
42
 
43
- ### 1. Initialize autotel in `globalSetup`
43
+ ### 1. Initialize autotel in a setup file
44
44
 
45
45
  ```ts
46
- // globalSetup.ts
46
+ // vitest.setup.ts
47
47
  import { init } from 'autotel';
48
+ import { SimpleSpanProcessor } from 'autotel/processors';
49
+ import { otelTestCollector } from 'autotel-vitest';
48
50
 
49
- export default function globalSetup() {
50
- init({ service: 'unit-tests' });
51
- }
51
+ init({
52
+ service: 'unit-tests',
53
+ spanProcessors: [new SimpleSpanProcessor(otelTestCollector)],
54
+ });
52
55
  ```
53
56
 
54
- ### 2. Register `globalSetup` in Vitest config
57
+ Two details matter here:
58
+
59
+ - **`setupFiles`, not `globalSetup`.** Spans are created in the worker that runs
60
+ the test, and worker module state is not shared with the main process where
61
+ `globalSetup` runs.
62
+ - **Pass `otelTestCollector` to `init`.** OpenTelemetry SDK 2.x removed
63
+ `addSpanProcessor`, so a processor can only be registered when the provider is
64
+ built. This is the collector the fixture drains after each test.
65
+
66
+ ### 2. Register the setup file in Vitest config
55
67
 
56
68
  ```ts
57
69
  // vitest.config.ts
@@ -59,7 +71,7 @@ import { defineConfig } from 'vitest/config';
59
71
 
60
72
  export default defineConfig({
61
73
  test: {
62
- globalSetup: './globalSetup.ts',
74
+ setupFiles: ['./vitest.setup.ts'],
63
75
  },
64
76
  });
65
77
  ```
@@ -83,6 +95,9 @@ The fixture is `auto: true`, so every test gets a parent span automatically.
83
95
  - Span attributes: `test.name`, `test.file`, `test.suite`
84
96
  - If a test throws, the span is marked as error and records the exception
85
97
  - Any `trace()` / `span()` calls in the test flow become children of the active test span
98
+ - The test's spans on `task.meta.otelSpans`, which
99
+ [executable-stories](https://github.com/jagreehal/executable-stories) renders
100
+ as a trace waterfall in its report with no extra wiring
86
101
 
87
102
  ## Optional: Reporter (runner-side spans)
88
103
 
@@ -94,7 +109,7 @@ import { defineConfig } from 'vitest/config';
94
109
 
95
110
  export default defineConfig({
96
111
  test: {
97
- globalSetup: './globalSetup.ts',
112
+ setupFiles: ['./vitest.setup.ts'],
98
113
  reporters: ['default', 'autotel-vitest/reporter'],
99
114
  },
100
115
  });
@@ -133,12 +148,14 @@ test('traces user creation', async () => {
133
148
 
134
149
  ## Troubleshooting
135
150
 
136
- - No spans exported: verify `init()` runs in `globalSetup` before tests start.
151
+ - No spans exported: verify `init()` runs in a `setupFiles` entry, not `globalSetup`, and that it is passed a processor wrapping `otelTestCollector`.
137
152
  - No child spans under test span: ensure your test imports `test` from `autotel-vitest`, not `vitest`.
138
153
  - Reporter not running: ensure `reporters` includes `'autotel-vitest/reporter'`.
154
+ - `task.meta.otelSpans` empty: a span is only in the collector after it is exported, which is asynchronous. The fixture flushes before it drains; if you read spans inside the test body, `await flush()` first.
139
155
 
140
156
  ## API
141
157
 
142
158
  - `test`: extended Vitest `test` with auto per-test span fixture
159
+ - `otelTestCollector`: the collector the fixture drains onto `task.meta.otelSpans`; pass it to `init()` (see Quick Start)
143
160
  - `expect`, `describe`, `beforeEach`, `afterEach`, `beforeAll`, `afterAll`: re-exported from `vitest`
144
161
  - `autotel/testing` helpers listed above: re-exported from this package
package/dist/index.cjs CHANGED
@@ -7,14 +7,54 @@ let autotel_testing = require("autotel/testing");
7
7
  //#region src/fixture.ts
8
8
  const TRACER_NAME = "vitest-tests";
9
9
  const TRACER_VERSION = "0.1.0";
10
- let collector = null;
10
+ /**
11
+ * The collector the fixture drains onto `task.meta.otelSpans`.
12
+ *
13
+ * OpenTelemetry SDK 2.x removed `addSpanProcessor`, so on 2.x a processor can
14
+ * only be registered when the provider is built. Hand this collector to
15
+ * `init()` in your setup file so the fixture has spans to drain:
16
+ *
17
+ * ```ts
18
+ * import { SimpleSpanProcessor } from 'autotel/processors';
19
+ * import { otelTestCollector } from 'autotel-vitest';
20
+ *
21
+ * init({
22
+ * service: 'unit-tests',
23
+ * spanProcessors: [new SimpleSpanProcessor(otelTestCollector)],
24
+ * });
25
+ * ```
26
+ */
27
+ const otelTestCollector = new autotel_test_span_collector.TestSpanCollector();
28
+ /**
29
+ * Make `ctx` the active context for the test body.
30
+ *
31
+ * `context.with(ctx, () => use(span))` does not work here. Vitest resolves a
32
+ * fixture's `use()` from the runner, not from inside the fixture's own call
33
+ * stack, so a context established around `use()` is not the one the test body
34
+ * observes — every span the test creates starts a new trace instead of
35
+ * parenting to the test span. Entering the context on this async resource
36
+ * instead is inherited by the test body and everything it awaits.
37
+ *
38
+ * ponytail: reads the context manager's private AsyncLocalStorage, since the
39
+ * OTel ContextManager interface has no callback-free entry point. Returns false
40
+ * on any manager that does not expose one, and the caller falls back to
41
+ * `context.with`.
42
+ */
43
+ function enterContext(ctx) {
44
+ const storage = autotel.context._getContextManager?.()?._asyncLocalStorage;
45
+ const enterWith = storage?.enterWith;
46
+ if (enterWith === void 0) return false;
47
+ enterWith.call(storage, ctx);
48
+ return true;
49
+ }
50
+ let attached = false;
11
51
  function ensureCollector() {
12
- if (!collector) {
13
- collector = new autotel_test_span_collector.TestSpanCollector();
52
+ if (!attached) {
53
+ attached = true;
14
54
  const provider = (0, autotel.getAutotelTracerProvider)();
15
- if ("addSpanProcessor" in provider) provider.addSpanProcessor(new autotel_processors.SimpleSpanProcessor(collector));
55
+ if ("addSpanProcessor" in provider) provider.addSpanProcessor(new autotel_processors.SimpleSpanProcessor(otelTestCollector));
16
56
  }
17
- return collector;
57
+ return otelTestCollector;
18
58
  }
19
59
  //#endregion
20
60
  //#region src/index.ts
@@ -43,16 +83,17 @@ const test = vitest.test.extend({ _otelTestSpan: [async ({ task }, use) => {
43
83
  } });
44
84
  const ctx = autotel.otelTrace.setSpan(autotel.context.active(), span);
45
85
  try {
46
- await autotel.context.with(ctx, () => use(span));
86
+ await (enterContext(ctx) ? use(span) : autotel.context.with(ctx, () => use(span)));
47
87
  } catch (error) {
48
88
  span.setStatus({ code: autotel.SpanStatusCode.ERROR });
49
89
  span.recordException(error instanceof Error ? error : new Error(String(error)));
50
90
  throw error;
51
91
  } finally {
52
92
  span.end();
93
+ await (0, autotel.flush)();
53
94
  const traceId = span.spanContext().traceId;
54
95
  const rootSpanId = span.spanContext().spanId;
55
- const spans = collector.drainTrace(traceId, rootSpanId);
96
+ const spans = otelTestCollector.drainTrace(traceId, rootSpanId);
56
97
  if (spans.length > 0) task.meta.otelSpans = spans;
57
98
  }
58
99
  }, { auto: true }] });
@@ -159,6 +200,7 @@ Object.defineProperty(exports, "isTracing", {
159
200
  return autotel.isTracing;
160
201
  }
161
202
  });
203
+ exports.otelTestCollector = otelTestCollector;
162
204
  Object.defineProperty(exports, "resolveTraceUrl", {
163
205
  enumerable: true,
164
206
  get: function() {
package/dist/index.d.cts CHANGED
@@ -1,6 +1,33 @@
1
+ import { OtelTraceContext, enrichWithTraceContext, getTraceContext, isTracing, resolveTraceUrl } from "autotel";
2
+ import { SerializedSpan, TestSpanCollector } from "autotel/test-span-collector";
1
3
  import { afterAll, afterEach, beforeAll, beforeEach, describe, expect } from "vitest";
2
4
  import { LogCollector, LogEntry, TestSpan, TraceCollector, assertNoErrors, assertTraceCreated, assertTraceDuration, assertTraceFailed, assertTraceSucceeded, createMockLogger, createTraceCollector, getTraceDuration, waitForTrace } from "autotel/testing";
3
- import { OtelTraceContext, enrichWithTraceContext, getTraceContext, isTracing, resolveTraceUrl } from "autotel";
5
+ //#region src/fixture.d.ts
6
+ declare module 'vitest' {
7
+ interface TaskMeta {
8
+ /** The spans this test recorded, drained by the `otelSpan` fixture. */
9
+ otelSpans?: Array<SerializedSpan>;
10
+ }
11
+ }
12
+ /**
13
+ * The collector the fixture drains onto `task.meta.otelSpans`.
14
+ *
15
+ * OpenTelemetry SDK 2.x removed `addSpanProcessor`, so on 2.x a processor can
16
+ * only be registered when the provider is built. Hand this collector to
17
+ * `init()` in your setup file so the fixture has spans to drain:
18
+ *
19
+ * ```ts
20
+ * import { SimpleSpanProcessor } from 'autotel/processors';
21
+ * import { otelTestCollector } from 'autotel-vitest';
22
+ *
23
+ * init({
24
+ * service: 'unit-tests',
25
+ * spanProcessors: [new SimpleSpanProcessor(otelTestCollector)],
26
+ * });
27
+ * ```
28
+ */
29
+ declare const otelTestCollector: TestSpanCollector;
30
+ //#endregion
4
31
  //#region src/index.d.ts
5
32
  /**
6
33
  * autotel-vitest
@@ -20,4 +47,4 @@ import { OtelTraceContext, enrichWithTraceContext, getTraceContext, isTracing, r
20
47
  */
21
48
  declare const test: import("vitest").TestAPI<Record<string, any> & object>;
22
49
  //#endregion
23
- export { type LogCollector, type LogEntry, type OtelTraceContext, type TestSpan, type TraceCollector, afterAll, afterEach, assertNoErrors, assertTraceCreated, assertTraceDuration, assertTraceFailed, assertTraceSucceeded, beforeAll, beforeEach, createMockLogger, createTraceCollector, describe, enrichWithTraceContext, expect, getTraceContext, getTraceDuration, isTracing, resolveTraceUrl, test, waitForTrace };
50
+ export { type LogCollector, type LogEntry, type OtelTraceContext, type TestSpan, type TraceCollector, afterAll, afterEach, assertNoErrors, assertTraceCreated, assertTraceDuration, assertTraceFailed, assertTraceSucceeded, beforeAll, beforeEach, createMockLogger, createTraceCollector, describe, enrichWithTraceContext, expect, getTraceContext, getTraceDuration, isTracing, otelTestCollector, resolveTraceUrl, test, waitForTrace };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,33 @@
1
1
  import { afterAll, afterEach, beforeAll, beforeEach, describe, expect } from "vitest";
2
2
  import { OtelTraceContext, enrichWithTraceContext, getTraceContext, isTracing, resolveTraceUrl } from "autotel";
3
+ import { SerializedSpan, TestSpanCollector } from "autotel/test-span-collector";
3
4
  import { LogCollector, LogEntry, TestSpan, TraceCollector, assertNoErrors, assertTraceCreated, assertTraceDuration, assertTraceFailed, assertTraceSucceeded, createMockLogger, createTraceCollector, getTraceDuration, waitForTrace } from "autotel/testing";
5
+ //#region src/fixture.d.ts
6
+ declare module 'vitest' {
7
+ interface TaskMeta {
8
+ /** The spans this test recorded, drained by the `otelSpan` fixture. */
9
+ otelSpans?: Array<SerializedSpan>;
10
+ }
11
+ }
12
+ /**
13
+ * The collector the fixture drains onto `task.meta.otelSpans`.
14
+ *
15
+ * OpenTelemetry SDK 2.x removed `addSpanProcessor`, so on 2.x a processor can
16
+ * only be registered when the provider is built. Hand this collector to
17
+ * `init()` in your setup file so the fixture has spans to drain:
18
+ *
19
+ * ```ts
20
+ * import { SimpleSpanProcessor } from 'autotel/processors';
21
+ * import { otelTestCollector } from 'autotel-vitest';
22
+ *
23
+ * init({
24
+ * service: 'unit-tests',
25
+ * spanProcessors: [new SimpleSpanProcessor(otelTestCollector)],
26
+ * });
27
+ * ```
28
+ */
29
+ declare const otelTestCollector: TestSpanCollector;
30
+ //#endregion
4
31
  //#region src/index.d.ts
5
32
  /**
6
33
  * autotel-vitest
@@ -20,4 +47,4 @@ import { LogCollector, LogEntry, TestSpan, TraceCollector, assertNoErrors, asser
20
47
  */
21
48
  declare const test: import("vitest").TestAPI<Record<string, any> & object>;
22
49
  //#endregion
23
- export { type LogCollector, type LogEntry, type OtelTraceContext, type TestSpan, type TraceCollector, afterAll, afterEach, assertNoErrors, assertTraceCreated, assertTraceDuration, assertTraceFailed, assertTraceSucceeded, beforeAll, beforeEach, createMockLogger, createTraceCollector, describe, enrichWithTraceContext, expect, getTraceContext, getTraceDuration, isTracing, resolveTraceUrl, test, waitForTrace };
50
+ export { type LogCollector, type LogEntry, type OtelTraceContext, type TestSpan, type TraceCollector, afterAll, afterEach, assertNoErrors, assertTraceCreated, assertTraceDuration, assertTraceFailed, assertTraceSucceeded, beforeAll, beforeEach, createMockLogger, createTraceCollector, describe, enrichWithTraceContext, expect, getTraceContext, getTraceDuration, isTracing, otelTestCollector, resolveTraceUrl, test, waitForTrace };
package/dist/index.js CHANGED
@@ -1,19 +1,59 @@
1
1
  import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test as test$1 } from "vitest";
2
- import { SpanStatusCode, context, enrichWithTraceContext, getAutotelTracerProvider, getTraceContext, getTracer, isTracing, otelTrace, resolveTraceUrl } from "autotel";
2
+ import { SpanStatusCode, context, enrichWithTraceContext, flush, getAutotelTracerProvider, getTraceContext, getTracer, isTracing, otelTrace, resolveTraceUrl } from "autotel";
3
3
  import { TestSpanCollector } from "autotel/test-span-collector";
4
4
  import { SimpleSpanProcessor } from "autotel/processors";
5
5
  import { assertNoErrors, assertTraceCreated, assertTraceDuration, assertTraceFailed, assertTraceSucceeded, createMockLogger, createTraceCollector, getTraceDuration, waitForTrace } from "autotel/testing";
6
6
  //#region src/fixture.ts
7
7
  const TRACER_NAME = "vitest-tests";
8
8
  const TRACER_VERSION = "0.1.0";
9
- let collector = null;
9
+ /**
10
+ * The collector the fixture drains onto `task.meta.otelSpans`.
11
+ *
12
+ * OpenTelemetry SDK 2.x removed `addSpanProcessor`, so on 2.x a processor can
13
+ * only be registered when the provider is built. Hand this collector to
14
+ * `init()` in your setup file so the fixture has spans to drain:
15
+ *
16
+ * ```ts
17
+ * import { SimpleSpanProcessor } from 'autotel/processors';
18
+ * import { otelTestCollector } from 'autotel-vitest';
19
+ *
20
+ * init({
21
+ * service: 'unit-tests',
22
+ * spanProcessors: [new SimpleSpanProcessor(otelTestCollector)],
23
+ * });
24
+ * ```
25
+ */
26
+ const otelTestCollector = new TestSpanCollector();
27
+ /**
28
+ * Make `ctx` the active context for the test body.
29
+ *
30
+ * `context.with(ctx, () => use(span))` does not work here. Vitest resolves a
31
+ * fixture's `use()` from the runner, not from inside the fixture's own call
32
+ * stack, so a context established around `use()` is not the one the test body
33
+ * observes — every span the test creates starts a new trace instead of
34
+ * parenting to the test span. Entering the context on this async resource
35
+ * instead is inherited by the test body and everything it awaits.
36
+ *
37
+ * ponytail: reads the context manager's private AsyncLocalStorage, since the
38
+ * OTel ContextManager interface has no callback-free entry point. Returns false
39
+ * on any manager that does not expose one, and the caller falls back to
40
+ * `context.with`.
41
+ */
42
+ function enterContext(ctx) {
43
+ const storage = context._getContextManager?.()?._asyncLocalStorage;
44
+ const enterWith = storage?.enterWith;
45
+ if (enterWith === void 0) return false;
46
+ enterWith.call(storage, ctx);
47
+ return true;
48
+ }
49
+ let attached = false;
10
50
  function ensureCollector() {
11
- if (!collector) {
12
- collector = new TestSpanCollector();
51
+ if (!attached) {
52
+ attached = true;
13
53
  const provider = getAutotelTracerProvider();
14
- if ("addSpanProcessor" in provider) provider.addSpanProcessor(new SimpleSpanProcessor(collector));
54
+ if ("addSpanProcessor" in provider) provider.addSpanProcessor(new SimpleSpanProcessor(otelTestCollector));
15
55
  }
16
- return collector;
56
+ return otelTestCollector;
17
57
  }
18
58
  //#endregion
19
59
  //#region src/index.ts
@@ -42,18 +82,19 @@ const test = test$1.extend({ _otelTestSpan: [async ({ task }, use) => {
42
82
  } });
43
83
  const ctx = otelTrace.setSpan(context.active(), span);
44
84
  try {
45
- await context.with(ctx, () => use(span));
85
+ await (enterContext(ctx) ? use(span) : context.with(ctx, () => use(span)));
46
86
  } catch (error) {
47
87
  span.setStatus({ code: SpanStatusCode.ERROR });
48
88
  span.recordException(error instanceof Error ? error : new Error(String(error)));
49
89
  throw error;
50
90
  } finally {
51
91
  span.end();
92
+ await flush();
52
93
  const traceId = span.spanContext().traceId;
53
94
  const rootSpanId = span.spanContext().spanId;
54
- const spans = collector.drainTrace(traceId, rootSpanId);
95
+ const spans = otelTestCollector.drainTrace(traceId, rootSpanId);
55
96
  if (spans.length > 0) task.meta.otelSpans = spans;
56
97
  }
57
98
  }, { auto: true }] });
58
99
  //#endregion
59
- export { afterAll, afterEach, assertNoErrors, assertTraceCreated, assertTraceDuration, assertTraceFailed, assertTraceSucceeded, beforeAll, beforeEach, createMockLogger, createTraceCollector, describe, enrichWithTraceContext, expect, getTraceContext, getTraceDuration, isTracing, resolveTraceUrl, test, waitForTrace };
100
+ export { afterAll, afterEach, assertNoErrors, assertTraceCreated, assertTraceDuration, assertTraceFailed, assertTraceSucceeded, beforeAll, beforeEach, createMockLogger, createTraceCollector, describe, enrichWithTraceContext, expect, getTraceContext, getTraceDuration, isTracing, otelTestCollector, resolveTraceUrl, test, waitForTrace };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autotel-vitest",
3
- "version": "0.4.48",
3
+ "version": "0.4.50",
4
4
  "description": "Vitest fixture for OpenTelemetry: one span per test so all instrumented code is filterable by test run",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -24,7 +24,7 @@
24
24
  "README.md"
25
25
  ],
26
26
  "dependencies": {
27
- "autotel": "6.4.1"
27
+ "autotel": "7.0.0"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "vitest": ">=4.1.10"
@@ -36,6 +36,8 @@
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^26.1.2",
39
+ "executable-stories-formatters": "1.10.0",
40
+ "executable-stories-vitest": "8.6.5",
39
41
  "tsdown": "^0.22.14",
40
42
  "typescript": "^6.0.3",
41
43
  "vitest": "^4.1.10"
@@ -51,6 +53,7 @@
51
53
  "type-check": "tsc --noEmit",
52
54
  "test": "vitest run",
53
55
  "test:compat": "vitest run --config integration/vitest.config.ts",
56
+ "test:stories": "vitest run --config vitest.stories.config.ts",
54
57
  "test:watch": "vitest",
55
58
  "lint": "eslint src",
56
59
  "clean": "rimraf dist"