Skip to content

Commit

Permalink
ref(e2e): Fetch received event in the tested app (#4209)
Browse files Browse the repository at this point in the history
  • Loading branch information
krystofwoldrich authored Oct 29, 2024
1 parent 23d9465 commit 4297324
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 59 deletions.
2 changes: 2 additions & 0 deletions dev-packages/e2e-tests/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
*.log
*.app
*.apk

react-native-versions
13 changes: 9 additions & 4 deletions dev-packages/e2e-tests/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,12 @@ if (actions.includes('create')) {
env: Object.assign(env, { YARN_ENABLE_IMMUTABLE_INSTALLS: false }),
});

console.log(`done`);

console.log(`done2`);
execSync(`yarn add [email protected]`, {
stdio: 'inherit',
cwd: appDir,
// yarn v3 run immutable install by default in CI
env: Object.assign(env, { YARN_ENABLE_IMMUTABLE_INSTALLS: false }),
});

// Patch the app
execSync(`patch --verbose --strip=0 --force --ignore-whitespace --fuzz 4 < ${patchScriptsDir}/rn.patch`, {
Expand All @@ -138,9 +141,11 @@ if (actions.includes('create')) {
cwd: `${appDir}/ios`,
env: env,
});
console.log(`done3`);

if (fs.existsSync(`${appDir}/Gemfile`)) {
// TMP Fix for https://github.com/CocoaPods/Xcodeproj/issues/989
fs.appendFileSync(`${appDir}/Gemfile`, "gem 'xcodeproj', '< 1.26.0'\n");

execSync(`bundle install`, { stdio: 'inherit', cwd: appDir, env: env });
execSync('bundle exec pod install --repo-update', { stdio: 'inherit', cwd: `${appDir}/ios`, env: env });
} else {
Expand Down
4 changes: 4 additions & 0 deletions dev-packages/e2e-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@
"jest": "^29.7.0",
"react": "18.3.1",
"react-native": "0.75.4",
"react-native-launch-arguments": "^4.0.2",
"typescript": "4.9.5",
"webdriverio": "^8.27.0"
},
"dependencies": {
"minimist": "1.2.8",
"semver": "7.6.3",
"xcode": "3.0.1"
},
"peerDependencies": {
"react-native-launch-arguments": "^4.0.2"
}
}
115 changes: 76 additions & 39 deletions dev-packages/e2e-tests/src/EndToEndTests.tsx
Original file line number Diff line number Diff line change
@@ -1,64 +1,101 @@
/* eslint-disable import/no-unresolved, @typescript-eslint/no-unsafe-member-access */
import * as Sentry from '@sentry/react-native';
import * as React from 'react';
import { Text, View } from 'react-native';
import { LaunchArguments } from "react-native-launch-arguments";

import { getTestProps } from './utils/getTestProps';
import { fetchEvent } from './utils/fetchEvent';

const getSentryAuthToken = ():
| { token: string }
| { error: string } => {
const { sentryAuthToken } = LaunchArguments.value<{
sentryAuthToken: unknown;
}>();

if (typeof sentryAuthToken !== 'string') {
return { error: 'Sentry Auth Token is required' };
}

if (sentryAuthToken.length === 0) {
return { error: 'Sentry Auth Token must not be empty' };
}

return { token: sentryAuthToken };
};

export { getTestProps };
/**
* This screen is for internal end-to-end testing purposes only. Do not use.
* Not visible through the UI (no button to load it).
*/
// Deprecated in https://github.com/DefinitelyTyped/DefinitelyTyped/commit/f1b25591890978a92c610ce575ea2ba2bbde6a89
// eslint-disable-next-line deprecation/deprecation
const EndToEndTestsScreen = (): JSX.Element => {
const [eventId, setEventId] = React.useState<string | null | undefined>();
const [error, setError] = React.useState<string>('No error');

async function assertEventReceived(eventId: string | undefined) {
if (!eventId) {
setError('Event ID is required');
return;
}

const value = getSentryAuthToken();
if ('error' in value) {
setError(value.error);
return;
}

await fetchEvent(eventId, value.token);

setEventId(eventId);
}

// !!! WARNING: This is only for testing purposes.
// We only do this to render the eventId onto the UI for end to end tests.
React.useEffect(() => {
const client: Sentry.ReactNativeClient | undefined = Sentry.getClient();

if (!client) {
setError('Client is not initialized');
return;
}

// WARNING: This is only for testing purposes.
// We only do this to render the eventId onto the UI for end to end tests.
client.getOptions().beforeSend = (e) => {
setEventId(e.event_id || null);
assertEventReceived(e.event_id);
return e;
};
}, []);

const testCases = [
{
id: 'captureMessage',
name: 'Capture Message',
action: () => Sentry.captureMessage('React Native Test Message'),
},
{
id: 'captureException',
name: 'Capture Exception',
action: () => Sentry.captureException(new Error('captureException test')),
},
{
id: 'unhandledPromiseRejection',
name: 'Unhandled Promise Rejection',
action: async () => await Promise.reject(new Error('Unhandled Promise Rejection')),
},
{
id: 'close',
name: 'Close',
action: async () => await Sentry.close(),
},
];

return (
<View>
<Text>{error}</Text>
<Text {...getTestProps('eventId')}>{eventId}</Text>
<Text {...getTestProps('clearEventId')} onPress={() => setEventId('')}>
Clear Event Id
</Text>
<Text
{...getTestProps('captureMessage')}
onPress={() => {
Sentry.captureMessage('React Native Test Message');
}}>
captureMessage
</Text>
<Text
{...getTestProps('captureException')}
onPress={() => {
Sentry.captureException(new Error('captureException test'));
}}>
captureException
</Text>
<Text
onPress={async () => {
await Promise.reject(new Error('Unhandled Promise Rejection'));
}}
{...getTestProps('unhandledPromiseRejection')}>
Unhandled Promise Rejection
</Text>
<Text
{...getTestProps('close')}
onPress={async () => {
await Sentry.close();
}}>
close
</Text>
{testCases.map((testCase) => (
<Text key={testCase.id} {...getTestProps(testCase.id)} onPress={testCase.action}>
{testCase.name}
</Text>
))}
</View>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,13 @@ interface ApiEvent extends Event {
const RETRY_COUNT = 600;
const RETRY_INTERVAL = 1000;

const fetchEvent = async (eventId: string): Promise<ApiEvent> => {
const fetchEvent = async (eventId: string, authToken: string): Promise<ApiEvent> => {
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}`,
Authorization: `Bearer ${authToken}`,
'Content-Type': 'application/json',
},
method: 'GET',
Expand All @@ -47,6 +44,7 @@ const fetchEvent = async (eventId: string): Promise<ApiEvent> => {
reject(new Error('Could not fetch event within retry limit.'));
}
} else {
console.log('Fetched event', jsonResponse.detail);
resolve(jsonResponse);
}
});
Expand Down
16 changes: 6 additions & 10 deletions dev-packages/e2e-tests/test/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import path from 'path';
import type { RemoteOptions } from 'webdriverio';
import { remote } from 'webdriverio';

import { fetchEvent } from './utils/fetchEvent';
import { waitForTruthyResult } from './utils/waitFor';
import { expect, jest, beforeAll, afterAll, beforeEach, afterEach, describe, test } from '@jest/globals';

const DRIVER_NOT_INITIALIZED = 'Driver not initialized';

Expand Down Expand Up @@ -58,6 +58,7 @@ beforeAll(async () => {
platformName: 'Android',
'appium:automationName': 'UIAutomator2',
'appium:app': process.env.APPIUM_APP,
'appium:optionalIntentArguments': `--es sentryAuthToken '${process.env.SENTRY_AUTH_TOKEN}'`,
};
} else {
conf.capabilities = {
Expand All @@ -68,6 +69,7 @@ beforeAll(async () => {
'appium:derivedDataPath': path.resolve(process.env.APPIUM_DERIVED_DATA || ''),
'appium:showXcodeLog': true,
'appium:usePrebuiltWDA': true,
'appium:processArguments': { args: ['-sentryAuthToken', `${process.env.SENTRY_AUTH_TOKEN}`] },
};
}

Expand Down Expand Up @@ -117,27 +119,21 @@ describe('End to end tests for common events', () => {
const element = await getElement('captureMessage');
await element.click();

const eventId = await waitForEventId();
const sentryEvent = await fetchEvent(eventId);
expect(sentryEvent.eventID).toMatch(eventId);
await waitForEventId();
});

test('captureException', async () => {
const element = await getElement('captureException');
await element.click();

const eventId = await waitForEventId();
const sentryEvent = await fetchEvent(eventId);
expect(sentryEvent.eventID).toMatch(eventId);
await waitForEventId();
});

test('unhandledPromiseRejection', async () => {
const element = await getElement('unhandledPromiseRejection');
await element.click();

const eventId = await waitForEventId();
const sentryEvent = await fetchEvent(eventId);
expect(sentryEvent.eventID).toMatch(eventId);
await waitForEventId();
});

test('close', async () => {
Expand Down
1 change: 0 additions & 1 deletion test/react-native/versions/0.65.3
Submodule 0.65.3 deleted from 2d3361
13 changes: 13 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21142,6 +21142,16 @@ __metadata:
languageName: node
linkType: hard

"react-native-launch-arguments@npm:^4.0.2":
version: 4.0.2
resolution: "react-native-launch-arguments@npm:4.0.2"
peerDependencies:
react: ">=16.8.1"
react-native: ">=0.60.0-rc.0 <1.0.x"
checksum: 5a73cfa27c603a743939c7fbc98fef3eb790dfc1043ecfe60855d30f238c41251ba1a47775cb660b37ef86f96961b5643f6ff48480f0d60efb0235488d43ff05
languageName: node
linkType: hard

"react-native-macos@npm:0.73.34":
version: 0.73.34
resolution: "react-native-macos@npm:0.73.34"
Expand Down Expand Up @@ -22685,10 +22695,13 @@ __metadata:
minimist: 1.2.8
react: 18.3.1
react-native: 0.75.4
react-native-launch-arguments: ^4.0.2
semver: 7.6.3
typescript: 4.9.5
webdriverio: ^8.27.0
xcode: 3.0.1
peerDependencies:
react-native-launch-arguments: ^4.0.2
languageName: unknown
linkType: soft

Expand Down

0 comments on commit 4297324

Please sign in to comment.