Skip to content

Commit ac09db1

Browse files
feat(test): Add it.knownFlake for stress-testing flaky Jest fixes
Adds infrastructure for validating flaky test fixes in CI: - `itRepeatsWhenFlaky()`: a test wrapper in tests/js/sentry-test/ that runs a test 50x when the RERUN_KNOWN_FLAKY_TESTS env var is set, otherwise runs once as a normal it() - CI wiring: frontend.yml sets RERUN_KNOWN_FLAKY_TESTS=true when the PR has the "Frontend: Rerun Flaky Tests" label - ESLint: configured jest/no-standalone-expect to recognize itRepeatsWhenFlaky as a test block Wraps all 13 known flaky tests (identified from 30 days of CI failures on master) with itRepeatsWhenFlaky so fixes can be stress-tested: - eventReplay/index.spec.tsx (6 occ) - stackTrace.spec.tsx (5 occ) - resultsSearchQueryBuilder.spec.tsx (5 occ, 2 tests) - metricsTab.spec.tsx (4 occ) - customerDetails.spec.tsx (4 occ) - eventsSearchBar.spec.tsx (3 occ) - trace.spec.tsx (3 occ, previously skipped) - allMonitors.spec.tsx (2 occ) - spansSearchBar.spec.tsx (2 occ) - react-native/metrics.spec.tsx (2 occ) - useReplaysFromIssue.spec.tsx (2 occ) - spanEvidencePreview.spec.tsx (2 occ) - groupingInfoSection.spec.tsx (2 occ) Made-with: Cursor
1 parent 9ba2fea commit ac09db1

File tree

19 files changed

+177
-125
lines changed

19 files changed

+177
-125
lines changed

.github/workflows/frontend.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ jobs:
158158
#
159159
# This quiets up the logs quite a bit.
160160
DEBUG_PRINT_LIMIT: 0
161+
# When the "Frontend: Rerun Flaky Tests" label is on the PR,
162+
# tests wrapped with it.knownFlake() run 50x to validate fixes.
163+
RERUN_KNOWN_FLAKY_TESTS: "${{ contains(github.event.pull_request.labels.*.name, 'Frontend: Rerun Flaky Tests') }}"
161164
run: pnpm run test-ci --forceExit
162165

163166
form-field-registry:

eslint.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,10 @@ export default typescript.config([
826826
'jest/expect-expect': 'off', // Disabled as we have many tests which render as simple validations
827827
'jest/no-conditional-expect': 'off', // TODO(ryan953): Fix violations then delete this line
828828
'jest/no-disabled-tests': 'error', // `recommended` set this to warn, we've upgraded to error
829+
'jest/no-standalone-expect': [
830+
'error',
831+
{additionalTestBlockFunctions: ['it.knownFlake']},
832+
],
829833
},
830834
},
831835
{

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@
264264
"eslint-plugin-no-relative-import-paths": "^1.6.1",
265265
"eslint-plugin-react": "7.37.5",
266266
"eslint-plugin-react-hooks": "6.1.0",
267-
"eslint-plugin-regexp": "^3.0.0",
268267
"eslint-plugin-react-you-might-not-need-an-effect": "0.5.3",
268+
"eslint-plugin-regexp": "^3.0.0",
269269
"eslint-plugin-sentry": "^2.10.0",
270270
"eslint-plugin-testing-library": "^7.16.0",
271271
"eslint-plugin-typescript-sort-keys": "^3.3.0",

pyproject.toml

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -370,32 +370,32 @@ ignore_missing_imports = true
370370
# - python3 -m tools.mypy_helpers.find_easiest_modules
371371
[[tool.mypy.overrides]]
372372
module = [
373-
"sentry.api.endpoints.organization_releases",
374-
"sentry.api.paginator",
375-
"sentry.db.postgres.base",
376-
"sentry.middleware.auth",
377-
"sentry.middleware.ratelimit",
378-
"sentry.net.http",
379-
"sentry.release_health.metrics_sessions_v2",
380-
"sentry.search.events.builder.errors",
381-
"sentry.search.events.builder.metrics",
382-
"sentry.search.events.datasets.filter_aliases",
383-
"sentry.search.events.filter",
384-
"sentry.search.snuba.executors",
385-
"sentry.services.eventstore.models",
386-
"sentry.snuba.metrics.query_builder",
387-
"sentry.testutils.cases",
388-
"tests.sentry.api.helpers.test_group_index",
389-
"tests.sentry.issues.test_utils",
373+
"sentry.api.endpoints.organization_releases",
374+
"sentry.api.paginator",
375+
"sentry.db.postgres.base",
376+
"sentry.middleware.auth",
377+
"sentry.middleware.ratelimit",
378+
"sentry.net.http",
379+
"sentry.release_health.metrics_sessions_v2",
380+
"sentry.search.events.builder.errors",
381+
"sentry.search.events.builder.metrics",
382+
"sentry.search.events.datasets.filter_aliases",
383+
"sentry.search.events.filter",
384+
"sentry.search.snuba.executors",
385+
"sentry.services.eventstore.models",
386+
"sentry.snuba.metrics.query_builder",
387+
"sentry.testutils.cases",
388+
"tests.sentry.api.helpers.test_group_index",
389+
"tests.sentry.issues.test_utils",
390390
]
391391
disable_error_code = [
392-
"arg-type",
393-
"assignment",
394-
"attr-defined",
395-
"call-overload",
396-
"misc",
397-
"override",
398-
"union-attr",
392+
"arg-type",
393+
"assignment",
394+
"attr-defined",
395+
"call-overload",
396+
"misc",
397+
"override",
398+
"union-attr",
399399
]
400400
# end: sentry modules with typing issues
401401

static/app/components/events/eventReplay/index.spec.tsx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -132,20 +132,23 @@ describe('EventReplay', () => {
132132
});
133133
});
134134

135-
it('should render the replay inline onboarding component when replays are enabled and the project supports replay', async () => {
136-
MockUseReplayOnboardingSidebarPanel.mockReturnValue({
137-
activateSidebar: jest.fn(),
138-
});
139-
MockApiClient.addMockResponse({
140-
url: '/organizations/org-slug/prompts-activity/',
141-
body: {data: {dismissed_ts: null}},
142-
});
143-
render(<EventReplay {...defaultProps} />, {organization});
144-
145-
expect(
146-
await screen.findByText('Watch the errors and latency issues your users face')
147-
).toBeInTheDocument();
148-
});
135+
it.knownFlake(
136+
'should render the replay inline onboarding component when replays are enabled and the project supports replay',
137+
async () => {
138+
MockUseReplayOnboardingSidebarPanel.mockReturnValue({
139+
activateSidebar: jest.fn(),
140+
});
141+
MockApiClient.addMockResponse({
142+
url: '/organizations/org-slug/prompts-activity/',
143+
body: {data: {dismissed_ts: null}},
144+
});
145+
render(<EventReplay {...defaultProps} />, {organization});
146+
147+
expect(
148+
await screen.findByText('Watch the errors and latency issues your users face')
149+
).toBeInTheDocument();
150+
}
151+
);
149152

150153
it('should render a replay when there is a replayId from tags', async () => {
151154
MockUseReplayOnboardingSidebarPanel.mockReturnValue({

static/app/components/events/groupingInfo/groupingInfoSection.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('EventGroupingInfo', () => {
4545
});
4646
});
4747

48-
it('fetches and renders grouping info for errors', async () => {
48+
it.knownFlake('fetches and renders grouping info for errors', async () => {
4949
render(<EventGroupingInfoSection {...defaultProps} />);
5050
await userEvent.click(
5151
screen.getByRole('button', {name: 'View Event Grouping Information Section'})

static/app/components/groupPreviewTooltip/spanEvidencePreview.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe('SpanEvidencePreview', () => {
2828
expect(mock).not.toHaveBeenCalled();
2929
});
3030

31-
it('shows error when request fails', async () => {
31+
it.knownFlake('shows error when request fails', async () => {
3232
MockApiClient.addMockResponse({
3333
url: `/organizations/org-slug/issues/group-id/events/recommended/`,
3434
body: {},

static/app/components/stackTrace/stackTrace.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,7 @@ describe('Core StackTrace', () => {
877877
).toBeInTheDocument();
878878
});
879879

880-
it('shows URL link in tooltip when absPath is an http URL', async () => {
880+
it.knownFlake('shows URL link in tooltip when absPath is an http URL', async () => {
881881
jest.useFakeTimers();
882882
const {event, stacktrace} = makeStackTraceData();
883883
const frame = stacktrace.frames[stacktrace.frames.length - 1]!;

static/app/gettingStartedDocs/react-native/metrics.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function renderMockRequests({
2828
}
2929

3030
describe('getting started with react-native', () => {
31-
it('shows React Native metrics onboarding content', async () => {
31+
it.knownFlake('shows React Native metrics onboarding content', async () => {
3232
const organization = OrganizationFixture();
3333
const project = ProjectFixture({platform: 'react-native'});
3434
renderMockRequests({organization, project});

static/app/views/dashboards/widgetBuilder/buildSteps/filterResultsStep/eventsSearchBar.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('EventsSearchBar', () => {
2727
});
2828
});
2929

30-
it('does not show function tags in has: dropdown', async () => {
30+
it.knownFlake('does not show function tags in has: dropdown', async () => {
3131
render(
3232
<EventsSearchBar
3333
onClose={jest.fn()}

0 commit comments

Comments
 (0)