From 869ce3aeafa6d6a3334dfab7659c4cc1ddfd3635 Mon Sep 17 00:00:00 2001 From: Denis Sheikherev Date: Thu, 15 Oct 2026 00:16:25 +0700 Subject: [PATCH 1/4] feat(exceptions)!: ExceptionOptions takes only a domain Neither 7.x SDK's logException takes labels or can leave the video out of one report any more (bugsee-cocoa#233, bugsee-android#256). JS ExceptionOptions is {domain?}; labels and includeVideo are unknown keys and throw TypeError, like any other. A report is labelled in setReportHandler. e2e: the labels cases (exceptions case 7, [API-13d]) are gone, [API-11d] checks that includeVideo is refused and files nothing, and the scenarios stop passing labels. Playground: the includeVideo and labels actions and their known issues are gone; the shop and the boundary demo use a domain. Co-Authored-By: Claude Opus 5.5 --- examples/bare/e2e/api-arguments.test.ts | 34 +++++++---------- examples/bare/e2e/exceptions.test.ts | 19 ++-------- examples/bare/scenarios/api-args.ts | 9 +++-- examples/bare/scenarios/api.tsx | 4 +- examples/bare/scenarios/exceptions.tsx | 2 - examples/bare/scenarios/staging.ts | 2 +- examples/playground/README.md | 2 - examples/playground/__tests__/errors.test.tsx | 2 +- .../playground/__tests__/shop.flow.test.ts | 7 +--- .../__tests__/shop.screens.test.tsx | 5 +-- .../playground/src/screens/ErrorsScreen.tsx | 32 +--------------- .../playground/src/screens/shop/Products.tsx | 2 +- examples/playground/src/shop/flow.ts | 5 +-- examples/playground/src/ui/knownIssues.ts | 10 ----- packages/react-native/src/NativeBugsee.ts | 2 +- .../__tests__/ErrorBoundary.test.tsx | 2 +- .../src/exceptions/__tests__/options.test.ts | 37 ++++--------------- .../src/exceptions/__tests__/report.test.ts | 4 +- .../react-native/src/exceptions/options.ts | 34 +++-------------- 19 files changed, 52 insertions(+), 162 deletions(-) diff --git a/examples/bare/e2e/api-arguments.test.ts b/examples/bare/e2e/api-arguments.test.ts index 7e55eaf2..3248c40c 100644 --- a/examples/bare/e2e/api-arguments.test.ts +++ b/examples/bare/e2e/api-arguments.test.ts @@ -151,7 +151,7 @@ describeDevice(`overloads and arguments on ${TARGET_NAME}`, () => { if (ON_SIMULATOR) { return; } - const result = await runFor(log!, 'api-exc-values', 'exc-values done', 5); + const result = await runFor(log!, 'api-exc-values', 'exc-values done', 3); run = result.run; done = result.line; errors = result.bundles.filter(b => b.request.type === 'error'); @@ -170,21 +170,22 @@ describeDevice(`overloads and arguments on ${TARGET_NAME}`, () => { report('payload reasons', reasons); expect(reasons.filter(r => r === `api-exc string ${nonce}`)).toHaveLength(1); expect(reasons.filter(r => r === 'Non-Error thrown: object')).toHaveLength(2); - // The three values plus the two includeVideo errors. - expect(errors).toHaveLength(5); + // The three values; the includeVideo calls file nothing ([API-11d]). + expect(errors).toHaveLength(3); }); - // iOS ignores includeVideo too: https://github.com/bugsee/bugsee-cocoa/issues/226 - itReports('[API-11d] includeVideo is accepted and changes nothing', () => { + // includeVideo is not an option: neither 7.x SDK can leave the video out of one + // report (https://github.com/bugsee/bugsee-cocoa/issues/226, bugsee-android#247). + itReports('[API-11d] includeVideo is refused and files nothing', () => { const nonce = run.scenario.nonce; const video = jsonAfter>(done.text, 'video'); - expect(video.on!.ok && video.off!.ok).toBe(true); - const on = errors.find(b => payloadReason(b) === `api-exc video-on ${nonce}`); - const off = errors.find(b => payloadReason(b) === `api-exc video-off ${nonce}`); - expect(on).toBeDefined(); - expect(off).toBeDefined(); - report('includeVideo files', { on: types(on!), off: types(off!) }); - expect(types(on!)).toEqual(types(off!)); + report('includeVideo settled', video); + for (const call of [video.on!, video.off!]) { + expect(call.ok).toBe(false); + expect(call.error?.name).toBe('TypeError'); + } + const reasons = [`api-exc video-on ${nonce}`, `api-exc video-off ${nonce}`]; + expect(errors.filter(b => reasons.includes(payloadReason(b)))).toHaveLength(0); }); }); @@ -201,7 +202,7 @@ describeDevice(`overloads and arguments on ${TARGET_NAME}`, () => { report('fallback', fallback.text.trim()); if (!ON_SIMULATOR) { bundles = (await awaitBundles(1, 45_000)).filter(b => b.request.type === 'error'); - report('boundary errors', bundles.map(b => ({ reason: reasonOf(b).slice(0, 160), labels: b.request.labels, crash: crashOf(b) === undefined ? '-' : 'yes' }))); + report('boundary errors', bundles.map(b => ({ reason: reasonOf(b).slice(0, 160), crash: crashOf(b) === undefined ? '-' : 'yes' }))); } await stopApp(); }); @@ -226,13 +227,6 @@ describeDevice(`overloads and arguments on ${TARGET_NAME}`, () => { expect((crash.exceptionLoggingOptions as { exceptionDomain?: unknown }).exceptionDomain).toBe(`api-domain-${run.scenario.nonce}`); } }); - - /** Android 7.3.0 ignores logException labels (R10, exceptions.test.ts case 7): the same path. - * https://github.com/bugsee/bugsee-android/issues/247 */ - (ON_ANDROID ? it.failing : itReports)('[API-13d] the options prop: labels reach the report', () => { - expect(bundles).toHaveLength(1); - expect(bundles[0]!.request.labels).toEqual(expect.arrayContaining([`api-label-${run.scenario.nonce}`])); - }); }); it('[API-15b-d] notify() carries severity, fields and urgent into the relay record; a sixth argument throws', async () => { diff --git a/examples/bare/e2e/exceptions.test.ts b/examples/bare/e2e/exceptions.test.ts index e255e12f..5eb50342 100644 --- a/examples/bare/e2e/exceptions.test.ts +++ b/examples/bare/e2e/exceptions.test.ts @@ -28,11 +28,12 @@ * BUGSEE_E2E exc boundary-onError / boundary-fallback * BUGSEE_E2E exc app-handler fatal= * - * Case 7 is `it.failing` on Android for a documented gap (labels ignored, - * still in 7.3.1; https://github.com/bugsee/bugsee-android/issues/247). Do not weaken it. Case 10 was `it.failing` on 7.3.0, which + * Case 7 (labels on a handled exception) is gone: neither SDK's logException + * takes labels any more (bugsee-android#256, bugsee-cocoa#233); a report is + * labelled in setReportHandler. Case 10 was `it.failing` on 7.3.0, which * also filed an error report for an unhandled exception; 7.3.1 files the * crash only (bugsee-android d390b5203, #192), so it is a plain `it`. - * On iOS both are plain `it` (labels apply; Debug files no second report). + * On iOS it is a plain `it` (Debug files no second report). * * Case 12's red-box line must stay on Android. Gated release R2/R3 (controller * ruling): R13's second Bugsee crash was not retained on SDK 7.3.0; logcat @@ -400,13 +401,6 @@ describeDevice(`JS exceptions on ${TARGET_NAME}`, () => { handledPayload.signature, ); }); - - // Known SDK bug (R10): https://github.com/bugsee/bugsee-android/issues/247 - itAndroid.failing('labels reach the report', () => { - const labels = handled.request.labels; - report('case 7 actual labels', labels); - expect(labels).toEqual(expect.arrayContaining([`lbl-${nonce}`])); - }); }); describeDebug('exc-rejection', () => { @@ -968,11 +962,6 @@ describeDevice(`JS exceptions on ${TARGET_NAME}`, () => { ); }); - itIosDevice('labels reach the report', () => { - const labels = handled.request.labels; - report('case 7 iOS labels', labels); - expect(labels).toEqual(expect.arrayContaining([`lbl-${nonce}`])); - }); }); describeIosDeviceDebug('exc-rejection (iPhone)', () => { diff --git a/examples/bare/scenarios/api-args.ts b/examples/bare/scenarios/api-args.ts index f7d123dd..47baca83 100644 --- a/examples/bare/scenarios/api-args.ts +++ b/examples/bare/scenarios/api-args.ts @@ -6,8 +6,8 @@ * api-token before launch(): launch('') / ' ' / 42 must reject * without starting anything; then the app launches. * api-subs remove() on onLifecycleEvent and onStatusChange. - * api-exc-values logException(string | plain object | null) and - * {includeVideo: true | false}. + * api-exc-values logException(string | plain object | null), and + * {includeVideo: true | false}, which must be refused. * api-notify notify() with severity, fields and urgent, and bare. * api-spans every SpanStatus through finish(status), startChildSpan, * setStatus then finish(), a dead handle, startSpan with no @@ -137,9 +137,10 @@ async function exceptionValues(nonce: string): Promise { null: await settled(() => Bugsee.logException(null)), }; await delay(1_500); + // Not an option any more: each call throws TypeError and files nothing. const video = { - on: await settled(() => Bugsee.logException(new Error(`api-exc video-on ${nonce}`), { includeVideo: true })), - off: await settled(() => Bugsee.logException(new Error(`api-exc video-off ${nonce}`), { includeVideo: false })), + on: await settled(() => Bugsee.logException(new Error(`api-exc video-on ${nonce}`), { includeVideo: true } as never)), + off: await settled(() => Bugsee.logException(new Error(`api-exc video-off ${nonce}`), { includeVideo: false } as never)), }; mark(`exc-values done nonce=${nonce} results=${JSON.stringify(results)} video=${JSON.stringify(video)}`); } diff --git a/examples/bare/scenarios/api.tsx b/examples/bare/scenarios/api.tsx index fcdccf5f..2cef7a8b 100644 --- a/examples/bare/scenarios/api.tsx +++ b/examples/bare/scenarios/api.tsx @@ -185,13 +185,13 @@ function Thrower({ nonce }: { nonce: string }): never { /** * N-08: an ErrorBoundary whose fallback is a function and whose `options` - * carry a domain and labels, around a child that throws on render. + * carry a domain, around a child that throws on render. */ function BoundaryStage({ nonce }: { nonce: string }) { return ( mark(`boundary onError nonce=${nonce}`)} fallback={({ error, componentStack }) => { mark( diff --git a/examples/bare/scenarios/exceptions.tsx b/examples/bare/scenarios/exceptions.tsx index becacf8e..7db0c77d 100644 --- a/examples/bare/scenarios/exceptions.tsx +++ b/examples/bare/scenarios/exceptions.tsx @@ -174,8 +174,6 @@ export function runExceptionScenario( } catch (e) { Bugsee.logException(e, { domain: `e2e-${nonce}`, - labels: ['e2e', `lbl-${nonce}`], - includeVideo: true, }); } Bugsee.logException({ password: `pw-${nonce}`, message: `obj-${nonce}` }); diff --git a/examples/bare/scenarios/staging.ts b/examples/bare/scenarios/staging.ts index eaba0705..728e32a5 100644 --- a/examples/bare/scenarios/staging.ts +++ b/examples/bare/scenarios/staging.ts @@ -142,7 +142,7 @@ export async function runStagingScenario(scenario: StagingScenario, nonce: strin try { stagingThrowSite(nonce, 'js handled'); } catch (error) { - Bugsee.logException(error, { labels: ['campaign', `nonce-${nonce}`] }); + Bugsee.logException(error); } mark(`handled-sent text="${campaignText(nonce, 'js handled')}" nonce=${nonce}`); return; diff --git a/examples/playground/README.md b/examples/playground/README.md index 33cf5315..bd35d8c3 100644 --- a/examples/playground/README.md +++ b/examples/playground/README.md @@ -200,8 +200,6 @@ The app shows a note next to each affected button. | Platform | Issue | |---|---| | iOS | [A second `createReport` wipes the first](https://github.com/bugsee/bugsee-cocoa/issues/224) | -| iOS | [`logException` `includeVideo` ignored](https://github.com/bugsee/bugsee-cocoa/issues/226) | -| Android | [`logException` labels and `includeVideo` ignored](https://github.com/bugsee/bugsee-android/issues/247) | | iOS | [`upload()` source type shows as unknown](https://github.com/bugsee/bugsee-cocoa/issues/223) | For manual test sessions, see [VALIDATION.md](./VALIDATION.md). diff --git a/examples/playground/__tests__/errors.test.tsx b/examples/playground/__tests__/errors.test.tsx index 748cada3..2f8e85e1 100644 --- a/examples/playground/__tests__/errors.test.tsx +++ b/examples/playground/__tests__/errors.test.tsx @@ -9,7 +9,7 @@ test('actions have valid call targets and unique ids', () => { errorsActions.forEach((a) => expect(() => callTarget(a.call)).not.toThrow()); expect(new Set(errorsActions.map((a) => a.id)).size).toBe(errorsActions.length); }); -test('renders labels and the boundary demo', async () => { +test('renders the actions and the boundary demo', async () => { await render(); expect(screen.getByText('Log handled string')).toBeTruthy(); expect(screen.getByText('Throw inside ErrorBoundary')).toBeTruthy(); diff --git a/examples/playground/__tests__/shop.flow.test.ts b/examples/playground/__tests__/shop.flow.test.ts index 6c1953fc..33b400f2 100644 --- a/examples/playground/__tests__/shop.flow.test.ts +++ b/examples/playground/__tests__/shop.flow.test.ts @@ -48,13 +48,10 @@ test('filter returns events without a body unchanged', () => { expect(SHOP_NETWORK_FILTER(n)).toBe(n); }); -test('pay(declined) logs a handled exception with labels and domain', async () => { +test('pay(declined) logs a handled exception with a domain', async () => { startCheckoutFlow(); expect(await pay('declined', { total: 5 })).toBe('declined'); - expect(bugsee.logException).toHaveBeenCalledWith(expect.any(Error), { - labels: ['checkout', 'payment'], - domain: 'payment', - }); + expect(bugsee.logException).toHaveBeenCalledWith(expect.any(Error), { domain: 'payment' }); expect(global.fetch).toHaveBeenCalledWith(expect.stringContaining('/status/402')); }); diff --git a/examples/playground/__tests__/shop.screens.test.tsx b/examples/playground/__tests__/shop.screens.test.tsx index ba0798b0..43855829 100644 --- a/examples/playground/__tests__/shop.screens.test.tsx +++ b/examples/playground/__tests__/shop.screens.test.tsx @@ -74,10 +74,7 @@ test('checkout declined logs the handled exception', async () => { await render(); await fireEvent.press(screen.getByText('Declined')); await fireEvent.press(screen.getByText('Pay')); - await waitFor(() => expect(bugsee.logException).toHaveBeenCalledWith(expect.any(Error), { - labels: ['checkout', 'payment'], - domain: 'payment', - })); + await waitFor(() => expect(bugsee.logException).toHaveBeenCalledWith(expect.any(Error), { domain: 'payment' })); expect(alert).toHaveBeenCalledWith('Checkout', 'Payment declined'); }); diff --git a/examples/playground/src/screens/ErrorsScreen.tsx b/examples/playground/src/screens/ErrorsScreen.tsx index 264ac28c..2f3cc1ef 100644 --- a/examples/playground/src/screens/ErrorsScreen.tsx +++ b/examples/playground/src/screens/ErrorsScreen.tsx @@ -5,7 +5,6 @@ import { NATIVE_UNAVAILABLE, PlaygroundNative } from '../../modules/playground-n import { ActionButton } from '../ui/ActionButton'; import type { Action } from '../ui/action'; import { afterTap } from '../ui/afterTap'; -import { KnownIssue } from '../ui/KnownIssue'; import { Screen } from '../ui/Screen'; import { Section } from '../ui/Section'; @@ -56,14 +55,6 @@ export const errorsActions: Action[] = [ expect: 'A handled error report whose message is the string', run: () => Bugsee.logException('a plain string'), }, - { - id: 'handled-labels', - label: 'Log handled error with labels', - call: "Bugsee.logException(new Error('labelled'), { labels: ['playground', 'handled'] })", - knownIssue: 'android-247', - expect: 'Labels playground, handled on the error', - run: () => Bugsee.logException(new Error('labelled'), { labels: ['playground', 'handled'] }), - }, { id: 'handled-domain', label: 'Log handled error with domain', @@ -71,24 +62,6 @@ export const errorsActions: Action[] = [ expect: 'Domain com.bugsee.playground on the error', run: () => Bugsee.logException(new Error('domain'), { domain: 'com.bugsee.playground' }), }, - { - id: 'handled-video-ios', - label: 'Log handled error with video', - call: "Bugsee.logException(new Error('video'), { includeVideo: true })", - platforms: ['ios'], - knownIssue: 'cocoa-226', - expect: 'A handled error report that includes video', - run: () => Bugsee.logException(new Error('video'), { includeVideo: true }), - }, - { - id: 'handled-video-android', - label: 'Log handled error with video', - call: "Bugsee.logException(new Error('video'), { includeVideo: true })", - platforms: ['android'], - knownIssue: 'android-247', - expect: 'A handled error report that includes video', - run: () => Bugsee.logException(new Error('video'), { includeVideo: true }), - }, { id: 'unhandled-api', @@ -152,9 +125,9 @@ function BoundaryDemo() { const [armed, setArmed] = useState(false); return ( <> - Expect: The fallback renders; a handled error with label boundary is reported + Expect: The fallback renders; a handled error with domain boundary is reported ( <> Fallback: {error instanceof Error ? error.message : String(error)} @@ -171,7 +144,6 @@ function BoundaryDemo() {