Skip to content

Commit df89359

Browse files
JoshuaKGoldberggetsantry[bot]claude
authored
feat(test): add it.isKnownFlake for opt-in stress-testing flaky Jest fixes (#111860)
It's hard to determine when a known flaky Jest test is no longer flaky. You basically just have to keep running it repeatedly. But it takes a _long_ time (and bogs up our GHA workflows) to re-run all tests too. This PR adds plumbing that lets us specifically re-run known flaky tests 50x in a single run: 1. I added an opt-in https://github.com/getsentry/sentry/labels/Frontend%3A%20Rerun%20Flaky%20Tests label as seen on this PR 2. When that label is present, the frontend workflow sets a `RERUN_KNOWN_FLAKY_TESTS` process env var 3. Tests defined with the new `it.isKnownFlake` will define the same test 50x with incremented counter names when that var is present [Example CI failure (search `flaky rerun`)](https://github.com/getsentry/sentry/actions/runs/23800263946/job/69358610839?pr=111860): ```plaintext ● EventGroupingInfo › [flaky rerun x50] fetches and renders grouping info for errors › run 2/50 ``` I added it to the following tests that have failed >=2x on `master` over the last month: | File | Test | CI failures (30d) | Ticket | |------|------|-------------------|--------| | `eventReplay/index.spec.tsx` | render replay inline onboarding | 6 | [REPLAY-879](https://linear.app/getsentry/issue/REPLAY-879) | | `stackTrace.spec.tsx` | URL link in tooltip | 5 | [ENG-7192](https://linear.app/getsentry/issue/ENG-7192) | | `resultsSearchQueryBuilder.spec.tsx` | has: dropdown + normal tags (2 tests) | 5 | [ENG-7201](https://linear.app/getsentry/issue/ENG-7201) | | `metricsTab.spec.tsx` | toggle query builder sidebar | 4 | [ENG-7202](https://linear.app/getsentry/issue/ENG-7202) | | `customerDetails.spec.tsx` | disabled without billing.admin | 4 | [ENG-7203](https://linear.app/getsentry/issue/ENG-7203) | | `eventsSearchBar.spec.tsx` | has: dropdown | 3 | [DAIN-1271](https://linear.app/getsentry/issue/DAIN-1271) | | `trace.spec.tsx` | arrowup+shift scroll (was `it.skip`) | 3 | [BROWSE-411](https://linear.app/getsentry/issue/BROWSE-411) | | `allMonitors.spec.tsx` | select all query results | 2 | [ENG-7204](https://linear.app/getsentry/issue/ENG-7204) | | `spansSearchBar.spec.tsx` | onSearch correct query | 2 | [ENG-7205](https://linear.app/getsentry/issue/ENG-7205) | | `react-native/metrics.spec.tsx` | onboarding content | 2 | [ENG-7206](https://linear.app/getsentry/issue/ENG-7206) | | `useReplaysFromIssue.spec.tsx` | fetch replay ids | 2 | [ENG-7207](https://linear.app/getsentry/issue/ENG-7207) | | `spanEvidencePreview.spec.tsx` | error on request fail | 2 | [ENG-7208](https://linear.app/getsentry/issue/ENG-7208) | | `groupingInfoSection.spec.tsx` | render grouping info | 2 | [ENG-7209](https://linear.app/getsentry/issue/ENG-7209) | | `timeSince.spec.tsx` | respects timezone in tooltip | 1 | [ENG-7211](https://linear.app/getsentry/issue/ENG-7211) | | `versionHoverCard.spec.tsx` | renders | 1 | [ENG-7212](https://linear.app/getsentry/issue/ENG-7212) | Made with [Cursor](https://cursor.com) --------- Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4 <noreply@anthropic.com>
1 parent d5c1cfe commit df89359

File tree

17 files changed

+174
-118
lines changed

17 files changed

+174
-118
lines changed

.github/workflows/frontend.yml

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

164167
form-field-registry:

eslint.config.ts

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

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,17 @@ describe('EventReplay', () => {
137137
});
138138
});
139139

140-
it('should render the replay inline onboarding component when replays are enabled and the project supports replay', async () => {
141-
MockUseReplayOnboardingSidebarPanel.mockReturnValue({
142-
activateSidebar: jest.fn(),
143-
});
144-
render(<EventReplay {...defaultProps} />, {organization});
145-
146-
expect(await screen.findByTestId('replay-inline-onboarding')).toBeInTheDocument();
147-
});
140+
it.isKnownFlake(
141+
'should render the replay inline onboarding component when replays are enabled and the project supports replay',
142+
async () => {
143+
MockUseReplayOnboardingSidebarPanel.mockReturnValue({
144+
activateSidebar: jest.fn(),
145+
});
146+
render(<EventReplay {...defaultProps} />, {organization});
147+
148+
expect(await screen.findByTestId('replay-inline-onboarding')).toBeInTheDocument();
149+
}
150+
);
148151

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

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.isKnownFlake('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.isKnownFlake('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/components/timeSince.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ describe('TimeSince', () => {
5858
expect(screen.getByText('10m atrás')).toBeInTheDocument();
5959
});
6060

61-
it('respects timezone in tooltip', async () => {
61+
it.isKnownFlake('respects timezone in tooltip', async () => {
6262
const date = new Date('2024-01-15T12:00:00Z');
6363
render(
6464
<TimezoneProvider timezone="America/New_York">

static/app/components/versionHoverCard.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('VersionHoverCard', () => {
3131
});
3232
});
3333

34-
it('renders', async () => {
34+
it.isKnownFlake('renders', async () => {
3535
render(
3636
<VersionHoverCard
3737
organization={organization}

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.isKnownFlake('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.isKnownFlake('does not show function tags in has: dropdown', async () => {
3131
render(
3232
<EventsSearchBar
3333
onClose={jest.fn()}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ describe('SpansSearchBar', () => {
122122
await screen.findByLabelText('span.op:function');
123123
});
124124

125-
it('calls onSearch with the correct query', async () => {
125+
it.isKnownFlake('calls onSearch with the correct query', async () => {
126126
const onSearch = jest.fn();
127127

128128
renderWithProvider({

0 commit comments

Comments
 (0)