Skip to content

Commit 8ef9995

Browse files
author
Luca Forstner
authored
test(e2e): Add behaviour test for Errors in standard React E2E tests application (#5909)
1 parent 614a7e3 commit 8ef9995

File tree

14 files changed

+474
-28
lines changed

14 files changed

+474
-28
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,8 @@ jobs:
648648
- name: Run E2E tests
649649
env:
650650
E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION: ${{ env.DEFAULT_NODE_VERSION }}
651+
E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }}
652+
E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }}
651653
run: |
652654
cd packages/e2e-tests
653655
yarn test:e2e

packages/e2e-tests/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
E2E_TEST_AUTH_TOKEN=
2+
E2E_TEST_DSN=

packages/e2e-tests/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.env

packages/e2e-tests/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
"lint": "run-s lint:prettier lint:eslint",
1414
"lint:eslint": "eslint . --cache --cache-location '../../eslintcache/' --format stylish",
1515
"lint:prettier": "prettier --config ../../.prettierrc.json --check .",
16-
"test:e2e": "run-s test:validate-configuration test:test-app-setups test:run",
16+
"test:e2e": "run-s test:validate-configuration test:validate-test-app-setups test:run",
1717
"test:run": "ts-node run.ts",
1818
"test:validate-configuration": "ts-node validate-verdaccio-configuration.ts",
19-
"test:test-app-setups": "ts-node validate-test-app-setups.ts"
19+
"test:validate-test-app-setups": "ts-node validate-test-app-setups.ts"
2020
},
2121
"devDependencies": {
2222
"@types/glob": "8.0.0",

packages/e2e-tests/run.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,27 @@ const PUBLISH_PACKAGES_DOCKER_IMAGE_NAME = 'publish-packages';
1313

1414
const publishScriptNodeVersion = process.env.E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION;
1515

16-
const DEFAULT_BUILD_TIMEOUT_SECONDS = 60;
16+
const DEFAULT_BUILD_TIMEOUT_SECONDS = 60 * 5;
1717
const DEFAULT_TEST_TIMEOUT_SECONDS = 60;
1818

19+
if (!process.env.E2E_TEST_AUTH_TOKEN) {
20+
console.log(
21+
"No auth token configured! Please configure the E2E_TEST_AUTH_TOKEN environment variable with an auth token that has the scope 'project:read'!",
22+
);
23+
}
24+
25+
if (!process.env.E2E_TEST_DSN) {
26+
console.log('No DSN configured! Please configure the E2E_TEST_DSN environment variable with a DSN!');
27+
}
28+
29+
if (!process.env.E2E_TEST_AUTH_TOKEN || !process.env.E2E_TEST_DSN) {
30+
process.exit(1);
31+
}
32+
33+
const envVarsToInject = {
34+
REACT_APP_E2E_TEST_DSN: process.env.E2E_TEST_DSN,
35+
};
36+
1937
// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines
2038
function groupCIOutput(groupTitle: string, fn: () => void): void {
2139
if (process.env.CI) {
@@ -145,13 +163,32 @@ const recipeResults: RecipeResult[] = recipePaths.map(recipePath => {
145163
encoding: 'utf8',
146164
shell: true, // needed so we can pass the build command in as whole without splitting it up into args
147165
timeout: (recipe.buildTimeoutSeconds ?? DEFAULT_BUILD_TIMEOUT_SECONDS) * 1000,
166+
env: {
167+
...process.env,
168+
...envVarsToInject,
169+
},
148170
});
149171

150172
// Prepends some text to the output build command's output so we can distinguish it from logging in this script
151173
console.log(buildCommandProcess.stdout.replace(/^/gm, '[BUILD OUTPUT] '));
152174
console.log(buildCommandProcess.stderr.replace(/^/gm, '[BUILD OUTPUT] '));
153175

154-
if (buildCommandProcess.status !== 0) {
176+
const error: undefined | (Error & { code?: string }) = buildCommandProcess.error;
177+
178+
if (error?.code === 'ETIMEDOUT') {
179+
processShouldExitWithError = true;
180+
181+
printCIErrorMessage(
182+
`Build command in test application "${recipe.testApplicationName}" (${path.dirname(recipePath)}) timed out!`,
183+
);
184+
185+
return {
186+
testApplicationName: recipe.testApplicationName,
187+
testApplicationPath: recipePath,
188+
buildFailed: true,
189+
testResults: [],
190+
};
191+
} else if (buildCommandProcess.status !== 0) {
155192
processShouldExitWithError = true;
156193

157194
printCIErrorMessage(
@@ -177,6 +214,10 @@ const recipeResults: RecipeResult[] = recipePaths.map(recipePath => {
177214
timeout: (test.timeoutSeconds ?? DEFAULT_TEST_TIMEOUT_SECONDS) * 1000,
178215
encoding: 'utf8',
179216
shell: true, // needed so we can pass the test command in as whole without splitting it up into args
217+
env: {
218+
...process.env,
219+
...envVarsToInject,
220+
},
180221
});
181222

182223
// Prepends some text to the output test command's output so we can distinguish it from logging in this script

packages/e2e-tests/test-applications/standard-frontend-react/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@ npm-debug.log*
2222
yarn-debug.log*
2323
yarn-error.log*
2424

25+
/test-results/
26+
/playwright-report/
27+
/playwright/.cache/
28+
2529
!*.d.ts

packages/e2e-tests/test-applications/standard-frontend-react/package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
"web-vitals": "2.1.0"
2121
},
2222
"scripts": {
23-
"start": "react-scripts start",
24-
"build": "react-scripts build"
23+
"build": "react-scripts build",
24+
"start": "serve -s build",
25+
"test": "playwright test"
2526
},
2627
"eslintConfig": {
2728
"extends": [
@@ -40,5 +41,10 @@
4041
"last 1 firefox version",
4142
"last 1 safari version"
4243
]
44+
},
45+
"devDependencies": {
46+
"@playwright/test": "1.26.1",
47+
"axios": "1.1.2",
48+
"serve": "14.0.1"
4349
}
4450
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import type { PlaywrightTestConfig } from '@playwright/test';
2+
import { devices } from '@playwright/test';
3+
4+
/**
5+
* See https://playwright.dev/docs/test-configuration.
6+
*/
7+
const config: PlaywrightTestConfig = {
8+
testDir: './tests',
9+
/* Maximum time one test can run for. */
10+
timeout: 60 * 1000,
11+
expect: {
12+
/**
13+
* Maximum time expect() should wait for the condition to be met.
14+
* For example in `await expect(locator).toHaveText();`
15+
*/
16+
timeout: 5000,
17+
},
18+
/* Run tests in files in parallel */
19+
fullyParallel: true,
20+
/* Fail the build on CI if you accidentally left test.only in the source code. */
21+
forbidOnly: !!process.env.CI,
22+
/* Retry on CI only */
23+
retries: 0,
24+
/* Opt out of parallel tests on CI. */
25+
workers: 1,
26+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
27+
reporter: 'dot',
28+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
29+
use: {
30+
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
31+
actionTimeout: 0,
32+
/* Base URL to use in actions like `await page.goto('/')`. */
33+
// baseURL: 'http://localhost:3000',
34+
35+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
36+
trace: 'on-first-retry',
37+
},
38+
39+
/* Configure projects for major browsers */
40+
projects: [
41+
{
42+
name: 'chromium',
43+
use: {
44+
...devices['Desktop Chrome'],
45+
},
46+
},
47+
// For now we only test Chrome!
48+
// {
49+
// name: 'firefox',
50+
// use: {
51+
// ...devices['Desktop Firefox'],
52+
// },
53+
// },
54+
// {
55+
// name: 'webkit',
56+
// use: {
57+
// ...devices['Desktop Safari'],
58+
// },
59+
// },
60+
],
61+
62+
/* Run your local dev server before starting the tests */
63+
webServer: {
64+
command: 'yarn start',
65+
port: 3000,
66+
},
67+
};
68+
69+
export default config;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
interface Window {
2+
recordedTransactions?: string[];
3+
capturedExceptionId?: string;
4+
}

packages/e2e-tests/test-applications/standard-frontend-react/src/index.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Index from './pages/Index';
1515
import User from './pages/User';
1616

1717
Sentry.init({
18-
dsn: 'https://[email protected]/1337',
18+
dsn: process.env.REACT_APP_E2E_TEST_DSN,
1919
integrations: [
2020
new BrowserTracing({
2121
routingInstrumentation: Sentry.reactRouterV6Instrumentation(
@@ -39,10 +39,10 @@ Sentry.addGlobalEventProcessor(event => {
3939
(event.contexts?.trace?.op === 'pageload' || event.contexts?.trace?.op === 'navigation')
4040
) {
4141
const eventId = event.event_id;
42-
// @ts-ignore
43-
window.recordedTransactions = window.recordedTransactions || [];
44-
// @ts-ignore
45-
window.recordedTransactions.push(eventId);
42+
if (eventId) {
43+
window.recordedTransactions = window.recordedTransactions || [];
44+
window.recordedTransactions.push(eventId);
45+
}
4646
}
4747

4848
return event;

packages/e2e-tests/test-applications/standard-frontend-react/src/pages/Index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ const Index = () => {
1111
id="exception-button"
1212
onClick={() => {
1313
const eventId = Sentry.captureException(new Error('I am an error!'));
14-
// @ts-ignore
1514
window.capturedExceptionId = eventId;
1615
}}
1716
/>
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
{
22
"$schema": "../../test-recipe-schema.json",
33
"testApplicationName": "standard-frontend-react",
4-
"buildCommand": "yarn install && yarn build",
5-
"tests": []
4+
"buildCommand": "yarn install --pure-lockfile && npx playwright install && yarn build",
5+
"tests": [
6+
{
7+
"testName": "Playwright tests",
8+
"testCommand": "yarn test"
9+
}
10+
]
611
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { test, expect } from '@playwright/test';
2+
import axios, { AxiosError } from 'axios';
3+
4+
const SENTRY_TEST_ORG_SLUG = 'sentry-sdks';
5+
const SENTRY_TEST_PROJECT = 'sentry-javascript-e2e-tests';
6+
7+
const EVENT_POLLING_TIMEOUT = 45000;
8+
const EVENT_POLLING_RETRY_INTERVAL = 1000;
9+
10+
const authToken = process.env.E2E_TEST_AUTH_TOKEN;
11+
12+
test('Sends an exception to Sentry', async ({ page }) => {
13+
await page.goto('/');
14+
15+
const exceptionButton = page.locator('id=exception-button');
16+
await exceptionButton.click();
17+
18+
const exceptionIdHandle = await page.waitForFunction(() => window.capturedExceptionId);
19+
const exceptionEventId = await exceptionIdHandle.jsonValue();
20+
21+
let lastErrorResponse: AxiosError | undefined;
22+
23+
const timeout = setTimeout(() => {
24+
if (lastErrorResponse?.response?.status) {
25+
throw new Error(
26+
`Timeout reached while polling event. Last received status code: ${lastErrorResponse.response.status}`,
27+
);
28+
} else {
29+
throw new Error('Timeout reached while polling event.');
30+
}
31+
}, EVENT_POLLING_TIMEOUT);
32+
33+
while (true) {
34+
try {
35+
const response = await axios.get(
36+
`https://sentry.io/api/0/projects/${SENTRY_TEST_ORG_SLUG}/${SENTRY_TEST_PROJECT}/events/${exceptionEventId}/`,
37+
{ headers: { Authorization: `Bearer ${authToken}` } },
38+
);
39+
clearTimeout(timeout);
40+
expect(response?.status).toBe(200);
41+
break;
42+
} catch (e) {
43+
lastErrorResponse = e;
44+
await new Promise(resolve => setTimeout(resolve, EVENT_POLLING_RETRY_INTERVAL));
45+
}
46+
}
47+
});

0 commit comments

Comments
 (0)