@open-mercato/telemetry 0.10.1-develop.7826.1.5e82b7c668 → 0.10.1-develop.7833.1.d6cbf0e3ab
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.
|
@@ -34,6 +34,10 @@ async function otelApiIsRegistered(page) {
|
|
|
34
34
|
}
|
|
35
35
|
test.describe("TC-TELEMETRY-002: browser RUM runtime", () => {
|
|
36
36
|
test("exports document-load and fetch spans without breaking either fetch path", async ({ page, baseURL }) => {
|
|
37
|
+
test.skip(
|
|
38
|
+
process.env.OM_TEST_BROWSER_TELEMETRY_MODE !== "opt-in",
|
|
39
|
+
"OM_TEST_BROWSER_TELEMETRY_MODE=opt-in is not configured for this lane."
|
|
40
|
+
);
|
|
37
41
|
const origin = new URL(baseURL ?? "http://localhost:3000").origin;
|
|
38
42
|
await page.context().addCookies([{ name: OPT_IN_COOKIE, value: "on", url: origin }]);
|
|
39
43
|
const ingest = recordIngest(page);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/telemetry/__integration__/TC-TELEMETRY-002.spec.ts"],
|
|
4
|
-
"sourcesContent": ["import { expect, test, type Page, type Request as PlaywrightRequest } from '@playwright/test'\nimport { login } from '@open-mercato/core/helpers/integration/auth'\n\n/**\n * TC-TELEMETRY-002: browser RUM in a real browser.\n *\n * TC-TELEMETRY-001 drives the proxy route with `request.post` \u2014 a useful API contract test that\n * never opens a page. This spec covers what only a browser can: that the SDK actually boots, that\n * the `__omOriginalFetch` patch dance leaves both fetch paths working (a recursion here hangs every\n * API call in the backoffice), that batches reach the proxy, and \u2014 the mirror image \u2014 that a\n * default environment ships none of it.\n *\n * Browser RUM is normally an environment-wide env switch, which a spec cannot flip for one page.\n * The integration environment therefore sets `OM_TEST_BROWSER_TELEMETRY_MODE=opt-in` on top of\n * `OM_TEST_MODE=1`, and only then does the server honour an `om_test_browser_telemetry=on` cookie.\n * Both variables are absent in production. Same escape hatch the auth rate limiter already uses.\n *\n * No collector is configured here, so the proxy answers 204 (accept-and-drop) rather than 202 \u2014\n * which is why the span assertions read the batch the *browser posted* instead of anything a sink\n * received. That is the stronger assertion anyway: it is the browser's output under test.\n */\n\nconst TRACES_PATH = '/api/telemetry/browser-traces'\nconst OPT_IN_COOKIE = 'om_test_browser_telemetry'\n\ntype IngestRecord = { status: number; body: string }\n\n/** Records every proxy call the page makes, with the payload the exporter actually produced. */\nfunction recordIngest(page: Page): IngestRecord[] {\n const records: IngestRecord[] = []\n page.on('response', async (response) => {\n if (!response.url().includes(TRACES_PATH)) return\n const request: PlaywrightRequest = response.request()\n if (request.method() !== 'POST') return\n records.push({ status: response.status(), body: request.postData() ?? '' })\n })\n return records\n}\n\n/**\n * Records the same-origin API GETs the backoffice itself made and that answered 200. Replaying a\n * real one beats naming an endpoint here: the set of routes is not this spec's business, and a\n * path that quietly stops existing would fail the test for the wrong reason.\n */\nfunction recordApiGets(page: Page): string[] {\n const urls: string[] = []\n page.on('response', (response) => {\n const request = response.request()\n if (request.method() !== 'GET') return\n if (response.status() !== 200) return\n try {\n const { pathname } = new URL(response.url())\n if (!pathname.startsWith('/api/') || pathname.startsWith(TRACES_PATH)) return\n urls.push(response.url())\n } catch {\n // A non-absolute or exotic URL is simply not a candidate for replay.\n }\n })\n return urls\n}\n\n/** The web SDK registers itself on this global symbol; its presence is the \"SDK booted\" signal. */\nasync function otelApiIsRegistered(page: Page): Promise<boolean> {\n return page.evaluate(() =>\n Object.getOwnPropertySymbols(globalThis).some((symbol) => symbol.toString().includes('opentelemetry.js.api')),\n )\n}\n\ntest.describe('TC-TELEMETRY-002: browser RUM runtime', () => {\n test('exports document-load and fetch spans without breaking either fetch path', async ({ page, baseURL }) => {\n const origin = new URL(baseURL ?? 'http://localhost:3000').origin\n await page.context().addCookies([{ name: OPT_IN_COOKIE, value: 'on', url: origin }])\n const ingest = recordIngest(page)\n const apiGets = recordApiGets(page)\n\n await login(page, 'admin')\n await page.goto('/backend')\n await expect.poll(() => otelApiIsRegistered(page), {\n message: 'the web SDK must boot once the server hands the layout a non-null config',\n timeout: 15_000,\n }).toBe(true)\n\n // The patch dance, asserted where it actually runs. `@open-mercato/ui`'s api utils stash the\n // pristine fetch on `__omOriginalFetch` and put a wrapper on `window.fetch`; the SDK has to\n // instrument the stash rather than the wrapper, or direct `apiFetch` callers (DataTable,\n // CrudForm, react-query) produce no spans at all.\n const patchState = await page.evaluate(() => {\n const w = window as Window & { __omOriginalFetch?: typeof window.fetch }\n return {\n hasStash: typeof w.__omOriginalFetch === 'function',\n // Identical would mean the wrapper was handed back to itself \u2014 an infinite recursion on\n // the first request through `apiFetch`.\n stashIsWrapper: w.__omOriginalFetch === window.fetch,\n }\n })\n expect(patchState.hasStash, '@open-mercato/ui installs the fetch wrapper on every backoffice page').toBe(true)\n expect(patchState.stashIsWrapper, 'instrumenting the wrapper instead of the native fetch recurses forever').toBe(false)\n\n // A DataTable page drives `apiFetch` (which reads the instrumented stash) end to end. Waiting\n // on the API call it makes, rather than on a DOM role, keeps this assertion about the thing\n // under test: that the swap left `apiFetch` able to reach the API at all.\n await expect.poll(() => apiGets.length, {\n message: 'the backoffice must still reach its own API through the patched fetch',\n timeout: 30_000,\n }).toBeGreaterThan(0)\n await page.goto('/backend/customers/people')\n await expect(page.locator('table, [role=\"grid\"]').first()).toBeVisible({ timeout: 30_000 })\n\n // Replay one of the page's OWN successful GETs down both paths. A hang here IS the recursion\n // bug, hence the tight budget; a non-200 means the swap broke the wrapper's auth handling.\n const replayUrl = apiGets[apiGets.length - 1]\n const replay = await page.evaluate(async (url: string) => {\n const w = window as Window & { __omOriginalFetch?: typeof window.fetch }\n const withTimeout = async (call: Promise<Response>) => {\n const timeout = new Promise<never>((_, reject) => setTimeout(() => reject(new Error('timeout')), 10_000))\n return (await Promise.race([call, timeout])).status\n }\n return {\n viaWindow: await withTimeout(window.fetch(url)),\n viaStash: await withTimeout(w.__omOriginalFetch!(url)),\n }\n }, replayUrl)\n expect(replay.viaWindow, `window.fetch must still reach ${replayUrl} through the framework wrapper`).toBe(200)\n expect(replay.viaStash, `the instrumented native fetch must still reach ${replayUrl}`).toBe(200)\n\n // Exports are batched on a 3s delay, and a flush is forced when the page is hidden.\n await page.evaluate(() => {\n Object.defineProperty(document, 'visibilityState', { value: 'hidden', configurable: true })\n document.dispatchEvent(new Event('visibilitychange'))\n })\n await expect.poll(() => ingest.length, {\n message: 'the exporter must post its batches to the same-origin proxy',\n timeout: 20_000,\n }).toBeGreaterThan(0)\n\n // Every answer must be one the OTLP exporter does NOT retry (429/502/503/504 would storm).\n for (const record of ingest) {\n expect([202, 204], `unexpected ingest status ${record.status}`).toContain(record.status)\n }\n\n const spanNames = ingest.flatMap((record) => {\n try {\n const payload = JSON.parse(record.body) as {\n resourceSpans?: Array<{\n resource?: { attributes?: Array<{ key: string; value?: { stringValue?: string } }> }\n scopeSpans?: Array<{ spans?: Array<{ name: string }> }>\n }>\n }\n for (const resourceSpan of payload.resourceSpans ?? []) {\n const serviceName = resourceSpan.resource?.attributes?.find((a) => a.key === 'service.name')\n expect(\n serviceName?.value?.stringValue,\n 'browser spans must carry their own service name so they sort apart from server spans',\n ).toMatch(/-browser$/)\n }\n return (payload.resourceSpans ?? []).flatMap((r) => (r.scopeSpans ?? []).flatMap((s) => s.spans ?? [])).map((s) => s.name)\n } catch {\n return []\n }\n })\n expect(spanNames, 'document-load instrumentation must produce the navigation span').toContain('documentLoad')\n expect(spanNames, 'document-load instrumentation must produce the document fetch span').toContain('documentFetch')\n })\n\n test('ships nothing at all without the opt-in \u2014 the default path must cost a disabled app zero bytes', async ({ page }) => {\n // No cookie: this is the shipped default, and the mirror image of the case above. It is the\n // assertion that protects the \"zero client-bundle cost while off\" promise from regressing.\n const ingest = recordIngest(page)\n\n await login(page, 'admin')\n await page.goto('/backend')\n await page.goto('/backend/customers/people')\n await expect(page.locator('table, [role=\"grid\"]').first()).toBeVisible({ timeout: 30_000 })\n await page.waitForTimeout(5_000)\n\n expect(await otelApiIsRegistered(page), 'the web SDK chunk must never be requested while RUM is off').toBe(false)\n expect(ingest, 'a disabled app must not post a single batch').toHaveLength(0)\n })\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,QAAQ,YAA0D;AAC3E,SAAS,aAAa;AAqBtB,MAAM,cAAc;AACpB,MAAM,gBAAgB;AAKtB,SAAS,aAAa,MAA4B;AAChD,QAAM,UAA0B,CAAC;AACjC,OAAK,GAAG,YAAY,OAAO,aAAa;AACtC,QAAI,CAAC,SAAS,IAAI,EAAE,SAAS,WAAW,EAAG;AAC3C,UAAM,UAA6B,SAAS,QAAQ;AACpD,QAAI,QAAQ,OAAO,MAAM,OAAQ;AACjC,YAAQ,KAAK,EAAE,QAAQ,SAAS,OAAO,GAAG,MAAM,QAAQ,SAAS,KAAK,GAAG,CAAC;AAAA,EAC5E,CAAC;AACD,SAAO;AACT;AAOA,SAAS,cAAc,MAAsB;AAC3C,QAAM,OAAiB,CAAC;AACxB,OAAK,GAAG,YAAY,CAAC,aAAa;AAChC,UAAM,UAAU,SAAS,QAAQ;AACjC,QAAI,QAAQ,OAAO,MAAM,MAAO;AAChC,QAAI,SAAS,OAAO,MAAM,IAAK;AAC/B,QAAI;AACF,YAAM,EAAE,SAAS,IAAI,IAAI,IAAI,SAAS,IAAI,CAAC;AAC3C,UAAI,CAAC,SAAS,WAAW,OAAO,KAAK,SAAS,WAAW,WAAW,EAAG;AACvE,WAAK,KAAK,SAAS,IAAI,CAAC;AAAA,IAC1B,QAAQ;AAAA,IAER;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAGA,eAAe,oBAAoB,MAA8B;AAC/D,SAAO,KAAK;AAAA,IAAS,MACnB,OAAO,sBAAsB,UAAU,EAAE,KAAK,CAAC,WAAW,OAAO,SAAS,EAAE,SAAS,sBAAsB,CAAC;AAAA,EAC9G;AACF;AAEA,KAAK,SAAS,yCAAyC,MAAM;AAC3D,OAAK,4EAA4E,OAAO,EAAE,MAAM,QAAQ,MAAM;
|
|
4
|
+
"sourcesContent": ["import { expect, test, type Page, type Request as PlaywrightRequest } from '@playwright/test'\nimport { login } from '@open-mercato/core/helpers/integration/auth'\n\n/**\n * TC-TELEMETRY-002: browser RUM in a real browser.\n *\n * TC-TELEMETRY-001 drives the proxy route with `request.post` \u2014 a useful API contract test that\n * never opens a page. This spec covers what only a browser can: that the SDK actually boots, that\n * the `__omOriginalFetch` patch dance leaves both fetch paths working (a recursion here hangs every\n * API call in the backoffice), that batches reach the proxy, and \u2014 the mirror image \u2014 that a\n * default environment ships none of it.\n *\n * Browser RUM is normally an environment-wide env switch, which a spec cannot flip for one page.\n * The integration environment therefore sets `OM_TEST_BROWSER_TELEMETRY_MODE=opt-in` on top of\n * `OM_TEST_MODE=1`, and only then does the server honour an `om_test_browser_telemetry=on` cookie.\n * Both variables are absent in production. Same escape hatch the auth rate limiter already uses.\n *\n * No collector is configured here, so the proxy answers 204 (accept-and-drop) rather than 202 \u2014\n * which is why the span assertions read the batch the *browser posted* instead of anything a sink\n * received. That is the stronger assertion anyway: it is the browser's output under test.\n */\n\nconst TRACES_PATH = '/api/telemetry/browser-traces'\nconst OPT_IN_COOKIE = 'om_test_browser_telemetry'\n\ntype IngestRecord = { status: number; body: string }\n\n/** Records every proxy call the page makes, with the payload the exporter actually produced. */\nfunction recordIngest(page: Page): IngestRecord[] {\n const records: IngestRecord[] = []\n page.on('response', async (response) => {\n if (!response.url().includes(TRACES_PATH)) return\n const request: PlaywrightRequest = response.request()\n if (request.method() !== 'POST') return\n records.push({ status: response.status(), body: request.postData() ?? '' })\n })\n return records\n}\n\n/**\n * Records the same-origin API GETs the backoffice itself made and that answered 200. Replaying a\n * real one beats naming an endpoint here: the set of routes is not this spec's business, and a\n * path that quietly stops existing would fail the test for the wrong reason.\n */\nfunction recordApiGets(page: Page): string[] {\n const urls: string[] = []\n page.on('response', (response) => {\n const request = response.request()\n if (request.method() !== 'GET') return\n if (response.status() !== 200) return\n try {\n const { pathname } = new URL(response.url())\n if (!pathname.startsWith('/api/') || pathname.startsWith(TRACES_PATH)) return\n urls.push(response.url())\n } catch {\n // A non-absolute or exotic URL is simply not a candidate for replay.\n }\n })\n return urls\n}\n\n/** The web SDK registers itself on this global symbol; its presence is the \"SDK booted\" signal. */\nasync function otelApiIsRegistered(page: Page): Promise<boolean> {\n return page.evaluate(() =>\n Object.getOwnPropertySymbols(globalThis).some((symbol) => symbol.toString().includes('opentelemetry.js.api')),\n )\n}\n\ntest.describe('TC-TELEMETRY-002: browser RUM runtime', () => {\n test('exports document-load and fetch spans without breaking either fetch path', async ({ page, baseURL }) => {\n // The server ignores the opt-in cookie unless the app runs with this variable; lanes that\n // start the app from their own env (the standalone app) do not set it, so there is no SDK to boot.\n test.skip(\n process.env.OM_TEST_BROWSER_TELEMETRY_MODE !== 'opt-in',\n 'OM_TEST_BROWSER_TELEMETRY_MODE=opt-in is not configured for this lane.',\n )\n const origin = new URL(baseURL ?? 'http://localhost:3000').origin\n await page.context().addCookies([{ name: OPT_IN_COOKIE, value: 'on', url: origin }])\n const ingest = recordIngest(page)\n const apiGets = recordApiGets(page)\n\n await login(page, 'admin')\n await page.goto('/backend')\n await expect.poll(() => otelApiIsRegistered(page), {\n message: 'the web SDK must boot once the server hands the layout a non-null config',\n timeout: 15_000,\n }).toBe(true)\n\n // The patch dance, asserted where it actually runs. `@open-mercato/ui`'s api utils stash the\n // pristine fetch on `__omOriginalFetch` and put a wrapper on `window.fetch`; the SDK has to\n // instrument the stash rather than the wrapper, or direct `apiFetch` callers (DataTable,\n // CrudForm, react-query) produce no spans at all.\n const patchState = await page.evaluate(() => {\n const w = window as Window & { __omOriginalFetch?: typeof window.fetch }\n return {\n hasStash: typeof w.__omOriginalFetch === 'function',\n // Identical would mean the wrapper was handed back to itself \u2014 an infinite recursion on\n // the first request through `apiFetch`.\n stashIsWrapper: w.__omOriginalFetch === window.fetch,\n }\n })\n expect(patchState.hasStash, '@open-mercato/ui installs the fetch wrapper on every backoffice page').toBe(true)\n expect(patchState.stashIsWrapper, 'instrumenting the wrapper instead of the native fetch recurses forever').toBe(false)\n\n // A DataTable page drives `apiFetch` (which reads the instrumented stash) end to end. Waiting\n // on the API call it makes, rather than on a DOM role, keeps this assertion about the thing\n // under test: that the swap left `apiFetch` able to reach the API at all.\n await expect.poll(() => apiGets.length, {\n message: 'the backoffice must still reach its own API through the patched fetch',\n timeout: 30_000,\n }).toBeGreaterThan(0)\n await page.goto('/backend/customers/people')\n await expect(page.locator('table, [role=\"grid\"]').first()).toBeVisible({ timeout: 30_000 })\n\n // Replay one of the page's OWN successful GETs down both paths. A hang here IS the recursion\n // bug, hence the tight budget; a non-200 means the swap broke the wrapper's auth handling.\n const replayUrl = apiGets[apiGets.length - 1]\n const replay = await page.evaluate(async (url: string) => {\n const w = window as Window & { __omOriginalFetch?: typeof window.fetch }\n const withTimeout = async (call: Promise<Response>) => {\n const timeout = new Promise<never>((_, reject) => setTimeout(() => reject(new Error('timeout')), 10_000))\n return (await Promise.race([call, timeout])).status\n }\n return {\n viaWindow: await withTimeout(window.fetch(url)),\n viaStash: await withTimeout(w.__omOriginalFetch!(url)),\n }\n }, replayUrl)\n expect(replay.viaWindow, `window.fetch must still reach ${replayUrl} through the framework wrapper`).toBe(200)\n expect(replay.viaStash, `the instrumented native fetch must still reach ${replayUrl}`).toBe(200)\n\n // Exports are batched on a 3s delay, and a flush is forced when the page is hidden.\n await page.evaluate(() => {\n Object.defineProperty(document, 'visibilityState', { value: 'hidden', configurable: true })\n document.dispatchEvent(new Event('visibilitychange'))\n })\n await expect.poll(() => ingest.length, {\n message: 'the exporter must post its batches to the same-origin proxy',\n timeout: 20_000,\n }).toBeGreaterThan(0)\n\n // Every answer must be one the OTLP exporter does NOT retry (429/502/503/504 would storm).\n for (const record of ingest) {\n expect([202, 204], `unexpected ingest status ${record.status}`).toContain(record.status)\n }\n\n const spanNames = ingest.flatMap((record) => {\n try {\n const payload = JSON.parse(record.body) as {\n resourceSpans?: Array<{\n resource?: { attributes?: Array<{ key: string; value?: { stringValue?: string } }> }\n scopeSpans?: Array<{ spans?: Array<{ name: string }> }>\n }>\n }\n for (const resourceSpan of payload.resourceSpans ?? []) {\n const serviceName = resourceSpan.resource?.attributes?.find((a) => a.key === 'service.name')\n expect(\n serviceName?.value?.stringValue,\n 'browser spans must carry their own service name so they sort apart from server spans',\n ).toMatch(/-browser$/)\n }\n return (payload.resourceSpans ?? []).flatMap((r) => (r.scopeSpans ?? []).flatMap((s) => s.spans ?? [])).map((s) => s.name)\n } catch {\n return []\n }\n })\n expect(spanNames, 'document-load instrumentation must produce the navigation span').toContain('documentLoad')\n expect(spanNames, 'document-load instrumentation must produce the document fetch span').toContain('documentFetch')\n })\n\n test('ships nothing at all without the opt-in \u2014 the default path must cost a disabled app zero bytes', async ({ page }) => {\n // No cookie: this is the shipped default, and the mirror image of the case above. It is the\n // assertion that protects the \"zero client-bundle cost while off\" promise from regressing.\n const ingest = recordIngest(page)\n\n await login(page, 'admin')\n await page.goto('/backend')\n await page.goto('/backend/customers/people')\n await expect(page.locator('table, [role=\"grid\"]').first()).toBeVisible({ timeout: 30_000 })\n await page.waitForTimeout(5_000)\n\n expect(await otelApiIsRegistered(page), 'the web SDK chunk must never be requested while RUM is off').toBe(false)\n expect(ingest, 'a disabled app must not post a single batch').toHaveLength(0)\n })\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/telemetry",
|
|
3
|
-
"version": "0.10.1-develop.
|
|
3
|
+
"version": "0.10.1-develop.7833.1.d6cbf0e3ab",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Vendor-neutral spans, metrics, and error reporting backed by a pluggable OTLP provider. Off by default; the telemetry runtime is loaded only when explicitly enabled.",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
41
|
+
"@open-mercato/shared": "0.10.1-develop.7833.1.d6cbf0e3ab"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"react": ">=18.0.0"
|
|
@@ -68,6 +68,12 @@ async function otelApiIsRegistered(page: Page): Promise<boolean> {
|
|
|
68
68
|
|
|
69
69
|
test.describe('TC-TELEMETRY-002: browser RUM runtime', () => {
|
|
70
70
|
test('exports document-load and fetch spans without breaking either fetch path', async ({ page, baseURL }) => {
|
|
71
|
+
// The server ignores the opt-in cookie unless the app runs with this variable; lanes that
|
|
72
|
+
// start the app from their own env (the standalone app) do not set it, so there is no SDK to boot.
|
|
73
|
+
test.skip(
|
|
74
|
+
process.env.OM_TEST_BROWSER_TELEMETRY_MODE !== 'opt-in',
|
|
75
|
+
'OM_TEST_BROWSER_TELEMETRY_MODE=opt-in is not configured for this lane.',
|
|
76
|
+
)
|
|
71
77
|
const origin = new URL(baseURL ?? 'http://localhost:3000').origin
|
|
72
78
|
await page.context().addCookies([{ name: OPT_IN_COOKIE, value: 'on', url: origin }])
|
|
73
79
|
const ingest = recordIngest(page)
|