diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 4a2e0e2735..c9e8729ef0 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -430,7 +430,6 @@ jobs: disable-animations: true disable-spellchecker: true target: 'aosp_atd' - channel: canary # Necessary for ATDs emulator-options: > -no-window -no-snapshot-save diff --git a/test/e2e/test/e2e.test.ts b/test/e2e/test/e2e.test.ts index 5b61c3f1a5..9464ec134a 100644 --- a/test/e2e/test/e2e.test.ts +++ b/test/e2e/test/e2e.test.ts @@ -1,9 +1,10 @@ /* eslint-disable import/no-unresolved */ import path from 'path'; +import { Platform } from 'react-native'; import type { RemoteOptions } from 'webdriverio'; import { remote } from 'webdriverio'; -import { fetchEvent } from './utils/fetchEvent'; +import { fetchEvent, fetchReplay, fetchReplaySegmentVideo } from './utils/sentryApi'; import { waitForTruthyResult } from './utils/waitFor'; const DRIVER_NOT_INITIALIZED = 'Driver not initialized'; @@ -131,6 +132,33 @@ describe('End to end tests for common events', () => { expect(sentryEvent.eventID).toMatch(eventId); }); + // Currently doesn't work on Android because capturing screenshots doesn't work on emulator in GH Actions. + if (Platform.OS !== 'android') { + test('captureErrorReplay', async () => { + const element = await getElement('captureException'); + await element.click(); + + const eventId = await waitForEventId(); + const sentryEvent = await fetchEvent(eventId); + expect(sentryEvent.eventID).toMatch(eventId); + + expect(sentryEvent.contexts).toBeDefined(); + const replay = sentryEvent.contexts!['replay'] as any; + expect(replay).toBeDefined(); + expect(replay.replay_id.length).toBe(32); + + const replayInfo = await fetchReplay(replay.replay_id); + expect(replayInfo).toBeDefined(); + expect(replayInfo.data.duration).toBeGreaterThan(0); + expect(replayInfo.data.count_segments).toBeGreaterThan(0); + + const video = await fetchReplaySegmentVideo(replay.replay_id, 0); + expect(video).toBeDefined(); + expect(video.size).toBeGreaterThan(1000); + expect(await video.slice(4, 12).text()).toMatch('ftypmp42'); + }); + } + test('unhandledPromiseRejection', async () => { const element = await getElement('unhandledPromiseRejection'); await element.click(); diff --git a/test/e2e/test/utils/fetchEvent.ts b/test/e2e/test/utils/fetchEvent.ts deleted file mode 100644 index 0c1a67be25..0000000000 --- a/test/e2e/test/utils/fetchEvent.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Event } from '@sentry/types'; - -const domain = 'sentry.io'; -const eventEndpoint = '/api/0/projects/sentry-sdks/sentry-react-native/events/'; - -interface ApiEvent extends Event { - /** - * The event returned from the API uses eventID - */ - eventID: string; -} - -const RETRY_COUNT = 600; -const RETRY_INTERVAL = 1000; - -const fetchEvent = async (eventId: string): Promise => { - const url = `https://${domain}${eventEndpoint}${eventId}/`; - - expect(process.env.SENTRY_AUTH_TOKEN).toBeDefined(); - expect(process.env.SENTRY_AUTH_TOKEN?.length).toBeGreaterThan(0); - - const request = () => - fetch(url, { - headers: { - Authorization: `Bearer ${process.env.SENTRY_AUTH_TOKEN}`, - 'Content-Type': 'application/json', - }, - method: 'GET', - }); - - let retries = 0; - const retryer: (json: any) => Promise = (jsonResponse: any) => - new Promise((resolve, reject) => { - if (jsonResponse.detail === 'Event not found') { - if (retries < RETRY_COUNT) { - setTimeout(() => { - retries++; - // eslint-disable-next-line no-console - console.log(`Retrying api request. Retry number: ${retries}`); - resolve( - request() - .then(res => res.json()) - .then(retryer), - ); - }, RETRY_INTERVAL); - } else { - reject(new Error('Could not fetch event within retry limit.')); - } - } else { - resolve(jsonResponse); - } - }); - - const json: ApiEvent = (await request() - // tslint:disable-next-line: no-unsafe-any - .then(res => res.json()) - .then(retryer)) as ApiEvent; - - return json; -}; - -export { fetchEvent }; diff --git a/test/e2e/test/utils/sentryApi.ts b/test/e2e/test/utils/sentryApi.ts new file mode 100644 index 0000000000..6a4e66b0aa --- /dev/null +++ b/test/e2e/test/utils/sentryApi.ts @@ -0,0 +1,68 @@ +import type { Event } from '@sentry/types'; + +const baseUrl = 'https://sentry.io/api/0/projects/sentry-sdks/sentry-react-native'; + +interface ApiEvent extends Event { + /** + * The event returned from the API uses eventID + */ + eventID: string; +} + +const RETRY_COUNT = 600; +const RETRY_INTERVAL = 1000; + +const fetchFromSentry = async (url: string): Promise => { + expect(process.env.SENTRY_AUTH_TOKEN).toBeDefined(); + expect(process.env.SENTRY_AUTH_TOKEN?.length).toBeGreaterThan(0); + + const request = () => + fetch(url, { + headers: { + Authorization: `Bearer ${process.env.SENTRY_AUTH_TOKEN}`, + 'Content-Type': 'application/json', + }, + method: 'GET', + }); + + let retries = 0; + const retrier = (response: Response): Promise => + new Promise((resolve, reject) => { + if (response.status === 200) { + resolve(response); + } else if (response.status === 403) { + reject(new Error(`Could not fetch ${url}: ${response.statusText}`)); + } else if (retries < RETRY_COUNT) { + setTimeout(() => { + retries++; + // eslint-disable-next-line no-console + console.log( + `API request (${url}) failed with: ${response.statusText}. Retrying. Retry number: ${retries}/${RETRY_COUNT}`, + ); + resolve(request().then(retrier)); + }, RETRY_INTERVAL); + } else { + reject(new Error(`Could not fetch ${url} within retry limit.`)); + } + }); + + return request().then(retrier); +}; + +const fetchEvent = async (eventId: string): Promise => { + const response = await fetchFromSentry(`${baseUrl}/events/${eventId}/`); + const json = await response.json(); + return json as ApiEvent; +}; + +const fetchReplay = async (replayId: string): Promise => { + const response = await fetchFromSentry(`${baseUrl}/replays/${replayId}/`); + return response.json(); +}; + +const fetchReplaySegmentVideo = async (replayId: string, segment: number): Promise => { + const response = await fetchFromSentry(`${baseUrl}/replays/${replayId}/videos/${segment}/`); + return response.blob(); +}; + +export { fetchEvent, fetchReplay, fetchReplaySegmentVideo }; diff --git a/test/react-native/rn.patch.app.js b/test/react-native/rn.patch.app.js index c32274bfa9..bc48614534 100755 --- a/test/react-native/rn.patch.app.js +++ b/test/react-native/rn.patch.app.js @@ -26,6 +26,16 @@ Sentry.init({ release: '${SENTRY_RELEASE}', dist: '${SENTRY_DIST}', dsn: 'https://1df17bd4e543fdb31351dee1768bb679@o447951.ingest.sentry.io/5428561', + debug: true, + _experiments: { + replaysOnErrorSampleRate: 1.0, + }, + integrations: [ + Sentry.mobileReplayIntegration({ + maskAllText: true, + maskAllImages: true, + }), + ], }); `; const e2eComponentPatch = '';