diff --git a/CHANGELOG.md b/CHANGELOG.md index 816dd090ff..286dbd0c9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ Update bugsnag-android to [v6.22.0](https//github.com/bugsnag/bugsnag-android/releases/tag/v6.22.0) [#2656](https://github.com/bugsnag/bugsnag-js/pull/2656) +### Fixed + +(plugin-network-instrumentation) Report HTTP Errors as handled [#2662](https://github.com/bugsnag/bugsnag-js/pull/2662) + ## [8.8.1] - 2026-01-26 ### Fixed diff --git a/packages/plugin-network-instrumentation/network-instrumentation.js b/packages/plugin-network-instrumentation/network-instrumentation.js index f0ac3b6812..530edb9fef 100644 --- a/packages/plugin-network-instrumentation/network-instrumentation.js +++ b/packages/plugin-network-instrumentation/network-instrumentation.js @@ -136,7 +136,7 @@ module.exports = (config = {}, global = window) => { const handledState = { severity: 'error', - unhandled: true, + unhandled: false, severityReason: { type: 'httpError' } } diff --git a/packages/plugin-network-instrumentation/test/network-instrumentation-xhr.test.ts b/packages/plugin-network-instrumentation/test/network-instrumentation-xhr.test.ts index b06796d394..63f28e4e90 100644 --- a/packages/plugin-network-instrumentation/test/network-instrumentation-xhr.test.ts +++ b/packages/plugin-network-instrumentation/test/network-instrumentation-xhr.test.ts @@ -135,7 +135,7 @@ describe('plugin-network-instrumentation', () => { expect(event.exceptions[0].errorMessage).toBe('404: https://api.example.com/users/123') expect(event.context).toBe('POST api.example.com') expect(event.severity).toBe('error') - expect(event.unhandled).toBe(true) + expect(event.unhandled).toBe(false) expect(event.severityReason.type).toBe('httpError') // Verify request metadata diff --git a/packages/plugin-network-instrumentation/test/network-instrumentation.test.ts b/packages/plugin-network-instrumentation/test/network-instrumentation.test.ts index 5d3407aed9..fda8520bfd 100644 --- a/packages/plugin-network-instrumentation/test/network-instrumentation.test.ts +++ b/packages/plugin-network-instrumentation/test/network-instrumentation.test.ts @@ -87,7 +87,7 @@ describe('plugin-network-instrumentation', () => { expect(event.exceptions[0].errorMessage).toBe('404: https://example.com/api/users') expect(event.context).toBe('GET example.com') expect(event.severity).toBe('error') - expect(event.unhandled).toBe(true) + expect(event.unhandled).toBe(false) expect(event.severityReason.type).toBe('httpError') expect(event.request.url).toBe('https://example.com/api/users') expect(event.request.httpMethod).toBe('GET') diff --git a/scripts/generate-react-native-fixture.js b/scripts/generate-react-native-fixture.js index c4b79085c6..a151a271df 100644 --- a/scripts/generate-react-native-fixture.js +++ b/scripts/generate-react-native-fixture.js @@ -40,7 +40,8 @@ const replacementFilesDir = resolve(ROOT_DIR, 'test/react-native/features/fixtur const INTERNAL_DEPENDENCIES = [ '@bugsnag/react-native', - '@bugsnag/request-tracker' + '@bugsnag/request-tracker', + '@bugsnag/plugin-network-instrumentation' ] // make sure we install a compatible versions of peer dependencies diff --git a/test/react-native/features/breadcrumbs.feature b/test/react-native/features/breadcrumbs.feature index 7274fd6a9c..13388a2cc5 100644 --- a/test/react-native/features/breadcrumbs.feature +++ b/test/react-native/features/breadcrumbs.feature @@ -47,3 +47,10 @@ Scenario: Manual breadcrumbs (Native) | ios | NSException | And the exception "message" equals "BreadcrumbsNativeManualScenario" And the event contains a breadcrumb matching the JSON fixture in "features/fixtures/expected_breadcrumbs/NativeManualScenario.json" + +Scenario: Network breadcrumbs (JS) + When I run "NetworkBreadcrumbsJsScenario" + Then I wait to receive an error + And the exception "errorClass" equals "Error" + And the exception "message" equals "NetworkBreadcrumbsJsScenario" + And the event contains a breadcrumb matching the JSON fixture in "features/fixtures/expected_breadcrumbs/NetworkJsScenario.json" \ No newline at end of file diff --git a/test/react-native/features/fixtures/expected_breadcrumbs/NetworkJsScenario.json b/test/react-native/features/fixtures/expected_breadcrumbs/NetworkJsScenario.json new file mode 100644 index 0000000000..79b9c3026e --- /dev/null +++ b/test/react-native/features/fixtures/expected_breadcrumbs/NetworkJsScenario.json @@ -0,0 +1,11 @@ +{ + "type": "request", + "name": "XMLHttpRequest succeeded", + "timestamp": "^\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:[\\d\\.]+Z?$", + "metaData": { + "status": "NUMBER", + "method": "GET", + "url": "^http:\/\/[0-9.:]*\/reflect$", + "duration": "NUMBER" + } +} \ No newline at end of file diff --git a/test/react-native/features/fixtures/expected_http_errors/401.json b/test/react-native/features/fixtures/expected_http_errors/401.json new file mode 100644 index 0000000000..e719a18b29 --- /dev/null +++ b/test/react-native/features/fixtures/expected_http_errors/401.json @@ -0,0 +1,8 @@ +[ + { + "message": "^401: .*\/reflect\/[?]status=401$", + "errorClass": "HTTPError", + "type": "reactnativejs", + "stacktrace": "IGNORE" + } +] diff --git a/test/react-native/features/fixtures/expected_http_errors/500.json b/test/react-native/features/fixtures/expected_http_errors/500.json new file mode 100644 index 0000000000..ccbe396790 --- /dev/null +++ b/test/react-native/features/fixtures/expected_http_errors/500.json @@ -0,0 +1,8 @@ +[ + { + "message": "^500: .*\/reflect\/[?]status=500$", + "errorClass": "HTTPError", + "type": "reactnativejs", + "stacktrace": "IGNORE" + } +] diff --git a/test/react-native/features/fixtures/scenario-launcher/scenarios/core/NetworkBreadcrumbsJsScenario.js b/test/react-native/features/fixtures/scenario-launcher/scenarios/core/NetworkBreadcrumbsJsScenario.js new file mode 100644 index 0000000000..a781356bc7 --- /dev/null +++ b/test/react-native/features/fixtures/scenario-launcher/scenarios/core/NetworkBreadcrumbsJsScenario.js @@ -0,0 +1,15 @@ +import Scenario from './Scenario' +import Bugsnag from '@bugsnag/react-native' + +export class NetworkBreadcrumbsJsScenario extends Scenario { + constructor (nativeConfig, jsConfig, scenarioData) { + super() + this.reflectEndpoint = nativeConfig.endpoints.notify.replace('/notify', '/reflect') + } + + run () { + fetch(this.reflectEndpoint).then(() => { + Bugsnag.notify(new Error('NetworkBreadcrumbsJsScenario')) + }) + } +} diff --git a/test/react-native/features/fixtures/scenario-launcher/scenarios/core/NetworkRequestScenario.js b/test/react-native/features/fixtures/scenario-launcher/scenarios/core/NetworkRequestScenario.js new file mode 100644 index 0000000000..f69b85eb72 --- /dev/null +++ b/test/react-native/features/fixtures/scenario-launcher/scenarios/core/NetworkRequestScenario.js @@ -0,0 +1,27 @@ +import Scenario from './Scenario' +import Bugsnag from '@bugsnag/react-native' +import BugsnagPluginNetworkInstrumentation from '@bugsnag/plugin-network-instrumentation' + +export class NetworkRequestScenario extends Scenario { + constructor (nativeConfig, jsConfig, scenarioData) { + super() + this.reflectEndpoint = nativeConfig.endpoints.notify.replace('/notify', '/reflect') + this.statusCode = scenarioData + + const plugin = new BugsnagPluginNetworkInstrumentation({ + maxRequestSize: 1024, + maxResponseSize: 1024 + }) + + jsConfig.plugins = jsConfig.plugins || [] + jsConfig.plugins.push(plugin) + } + + run () { + const url = new URL(this.reflectEndpoint) + url.searchParams.append('status', this.statusCode) + fetch(url).catch((err) => { + Bugsnag.notify(err) + }) + } +} diff --git a/test/react-native/features/fixtures/scenario-launcher/scenarios/core/index.js b/test/react-native/features/fixtures/scenario-launcher/scenarios/core/index.js index 988e1529a8..1075c4c8c6 100644 --- a/test/react-native/features/fixtures/scenario-launcher/scenarios/core/index.js +++ b/test/react-native/features/fixtures/scenario-launcher/scenarios/core/index.js @@ -35,6 +35,7 @@ export { BreadcrumbsAutomaticErrorScenario } from './BreadcrumbsAutomaticErrorSc export { BreadcrumbsJsManualScenario } from './BreadcrumbsJsManualScenario' export { BreadcrumbsNativeManualScenario } from './BreadcrumbsNativeManualScenario' export { BreadcrumbsNullEnabledBreadcrumbTypesScenario } from './BreadcrumbsNullEnabledBreadcrumbTypesScenario' +export { NetworkBreadcrumbsJsScenario } from './NetworkBreadcrumbsJsScenario' // device.feature export { DeviceJsHandledScenario } from './DeviceJsHandledScenario' @@ -80,3 +81,6 @@ export { ReactNativeErrorBoundaryScenario } from './ReactNativeErrorBoundaryScen // grouping-discriminator.feature export { GroupingDiscriminatorScenario } from './GroupingDiscriminatorScenario' export { GroupingDiscriminatorNativeScenario } from './GroupingDiscriminatorNativeScenario' + +// http_errors.feature +export { NetworkRequestScenario } from './NetworkRequestScenario' diff --git a/test/react-native/features/http_errors.feature b/test/react-native/features/http_errors.feature new file mode 100644 index 0000000000..3f59d9ce5e --- /dev/null +++ b/test/react-native/features/http_errors.feature @@ -0,0 +1,19 @@ +Feature: HTTP Errors + +Scenario Outline: Error is reported for network requests with error status code + When I run "NetworkRequestScenario" with data "" + And I wait to receive an error + Then the error payload field "events.0.context" matches the regex "^GET [0-9.]*:[0-9]{4}$" + And the error payload field "events.0.exceptions" matches the JSON fixture in "features/fixtures/expected_http_errors/.json" + Examples: + | status_code | + | 401 | + | 500 | + +Scenario Outline: Error is not reported for successful network requests + When I run "NetworkRequestScenario" with data "" + Then I should receive no errors + Examples: + | status_code | + | 200 | + | 307 | \ No newline at end of file