Skip to content

Commit b62c949

Browse files
authored
chore: Build Flaky Badge and tooltip content (#22873)
1 parent 41a0271 commit b62c949

12 files changed

+351
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import SpecNameDisplay from './SpecNameDisplay.vue'
2+
3+
describe('<SpecNameDisplay />', () => {
4+
it('should display spec name information', () => {
5+
cy.mount(<SpecNameDisplay specFileName="myFileName" specFileExtension=".cy.tsx" />)
6+
7+
cy.findByText('myFileName').should('be.visible')
8+
cy.findByText('.cy.tsx').should('be.visible')
9+
10+
cy.findByTestId('spec-filename').should('have.attr', 'title', 'myFileName.cy.tsx')
11+
12+
cy.percySnapshot()
13+
})
14+
})
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<template>
2+
<div
3+
class="max-w-60 truncate overflow-hidden"
4+
data-cy="spec-filename"
5+
:title="specFileName + specFileExtension"
6+
>
7+
<span class="font-semibold text-gray-800">{{ specFileName }}</span><span class="text-gray-600">{{ specFileExtension }}</span>
8+
</div>
9+
</template>
10+
11+
<script lang="ts" setup>
12+
13+
defineProps<{
14+
specFileName: string
15+
specFileExtension: string
16+
}>()
17+
18+
</script>

packages/app/src/specs/SpecRunSummary.cy.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ function validateTopBorder (color: string): void {
88
}
99

1010
function validateFilename (expected: string): void {
11-
cy.findByTestId('spec-run-filename').should('have.text', expected)
11+
cy.findByTestId('spec-filename').should('have.text', expected)
1212
}
1313

1414
function validateTimeAgo (expected: string): void {

packages/app/src/specs/SpecRunSummary.vue

+5-6
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@
55
:class="highlightColor"
66
data-cy="spec-run-summary"
77
>
8-
<div
9-
class="max-w-60 truncate overflow-hidden"
10-
data-cy="spec-run-filename"
11-
>
12-
<span class="font-semibold text-gray-800">{{ props.specFileNoExtension }}</span><span class="text-gray-600">{{ props.specFileExtension }}</span>
13-
</div>
8+
<SpecNameDisplay
9+
:spec-file-name="props.specFileNoExtension"
10+
:spec-file-extension="props.specFileExtension"
11+
/>
1412
<div class="flex flex-row text-gray-700 text-size-14px gap-2 items-center">
1513
<div
1614
v-if="statusText"
@@ -68,6 +66,7 @@ import { computed } from 'vue'
6866
import type { CloudSpecRun, SpecDataAggregate } from '../../../graphql/src/gen/cloud-source-types.gen'
6967
import ResultCounts, { ResultCountsProps } from '@packages/frontend-shared/src/components/ResultCounts.vue'
7068
import { getTimeAgo, getDurationString } from '@packages/frontend-shared/src/utils/time'
69+
import SpecNameDisplay from './SpecNameDisplay.vue'
7170
7271
const props = defineProps<{
7372
run: CloudSpecRun
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import FlakyBadge from './FlakyBadge.vue'
2+
import { defaultMessages } from '@cy/i18n'
3+
4+
const flakyBadgeTestId = 'flaky-badge'
5+
6+
describe('<FlakyBadge />', () => {
7+
it('should render expected content', () => {
8+
cy.mount(<FlakyBadge />)
9+
10+
cy.findByTestId(flakyBadgeTestId)
11+
.should('have.text', defaultMessages.specPage.flaky.badgeLabel)
12+
.and('be.visible')
13+
14+
cy.percySnapshot()
15+
})
16+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<template>
2+
<Badge
3+
:label="t('specPage.flaky.badgeLabel')"
4+
status="warning"
5+
class="font-bold uppercase"
6+
data-cy="flaky-badge"
7+
/>
8+
</template>
9+
10+
<script setup lang="ts">
11+
import Badge from '@packages/frontend-shared/src/components/Badge.vue'
12+
import { useI18n } from '@cy/i18n'
13+
14+
const { t } = useI18n()
15+
16+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import FlakySpecSummary from './FlakySpecSummary.vue'
2+
3+
describe('<FlakySpecSummary />', () => {
4+
it('low severity', () => {
5+
cy.mount(
6+
<FlakySpecSummary
7+
specName="test"
8+
specExtension=".cy.tsx"
9+
severity="low"
10+
totalFlakyRuns={4}
11+
totalRuns={50}
12+
runsSinceLastFlake={15}
13+
dashboardUrl="#"
14+
/>,
15+
)
16+
17+
cy.percySnapshot()
18+
})
19+
20+
it('medium severity', () => {
21+
cy.mount(
22+
<FlakySpecSummary
23+
specName="test"
24+
specExtension=".cy.tsx"
25+
severity="medium"
26+
totalFlakyRuns={14}
27+
totalRuns={50}
28+
runsSinceLastFlake={5}
29+
dashboardUrl="#"
30+
/>,
31+
)
32+
33+
cy.percySnapshot()
34+
})
35+
36+
it('high severity', () => {
37+
cy.mount(
38+
<FlakySpecSummary
39+
specName="test"
40+
specExtension=".cy.tsx"
41+
severity="high"
42+
totalFlakyRuns={24}
43+
totalRuns={50}
44+
runsSinceLastFlake={2}
45+
dashboardUrl="#"
46+
/>,
47+
)
48+
49+
cy.percySnapshot()
50+
})
51+
52+
it('fallback state', () => {
53+
// Ensure component handles malformed/incomplete data without blowing up
54+
cy.mount(
55+
<FlakySpecSummary
56+
specName="test"
57+
specExtension=".cy.tsx"
58+
severity={'unknown_value' as any}
59+
totalFlakyRuns={null as any}
60+
totalRuns={null as any}
61+
runsSinceLastFlake={null as any}
62+
dashboardUrl={null as any}
63+
/>,
64+
)
65+
66+
cy.percySnapshot()
67+
})
68+
69+
describe('flaky rate percentages', () => {
70+
it('should round up to next integer if less than 99%', () => {
71+
cy.mount(
72+
<FlakySpecSummary
73+
specName="test"
74+
specExtension=".cy.tsx"
75+
severity="high"
76+
totalFlakyRuns={888}
77+
totalRuns={1000}
78+
runsSinceLastFlake={2}
79+
dashboardUrl="#"
80+
/>,
81+
)
82+
83+
cy.findByTestId('flaky-rate').should('have.text', '89% flaky rate')
84+
})
85+
86+
it('should round down if between 99 and 100%', () => {
87+
cy.mount(
88+
<FlakySpecSummary
89+
specName="test"
90+
specExtension=".cy.tsx"
91+
severity="high"
92+
totalFlakyRuns={999}
93+
totalRuns={1000}
94+
runsSinceLastFlake={2}
95+
dashboardUrl="#"
96+
/>,
97+
)
98+
99+
cy.findByTestId('flaky-rate').should('have.text', '99% flaky rate')
100+
})
101+
})
102+
103+
describe('pluralization', () => {
104+
it('should handle zero flaky runs and zero runs since last flake', () => {
105+
cy.mount(
106+
<FlakySpecSummary
107+
specName="test"
108+
specExtension=".cy.tsx"
109+
severity="high"
110+
totalFlakyRuns={0}
111+
totalRuns={1000}
112+
runsSinceLastFlake={0}
113+
dashboardUrl="#"
114+
/>,
115+
)
116+
117+
cy.findByTestId('flaky-runs').should('have.text', '0 flaky runs / 1000 total')
118+
cy.findByTestId('last-flaky').should('have.text', 'Last flaky 0 runs ago')
119+
})
120+
121+
it('should handle 1 flaky run and 1 run since last flake', () => {
122+
cy.mount(
123+
<FlakySpecSummary
124+
specName="test"
125+
specExtension=".cy.tsx"
126+
severity="high"
127+
totalFlakyRuns={1}
128+
totalRuns={1000}
129+
runsSinceLastFlake={1}
130+
dashboardUrl="#"
131+
/>,
132+
)
133+
134+
cy.findByTestId('flaky-runs').should('have.text', '1 flaky run / 1000 total')
135+
cy.findByTestId('last-flaky').should('have.text', 'Last flaky 1 run ago')
136+
})
137+
138+
it('should handle multiple flaky runs and multiple runs since last flake', () => {
139+
cy.mount(
140+
<FlakySpecSummary
141+
specName="test"
142+
specExtension=".cy.tsx"
143+
severity="high"
144+
totalFlakyRuns={2}
145+
totalRuns={1000}
146+
runsSinceLastFlake={2}
147+
dashboardUrl="#"
148+
/>,
149+
)
150+
151+
cy.findByTestId('flaky-runs').should('have.text', '2 flaky runs / 1000 total')
152+
cy.findByTestId('last-flaky').should('have.text', 'Last flaky 2 runs ago')
153+
})
154+
})
155+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<template>
2+
<div
3+
class="border-t-4px min-w-200px w-full max-w-400px grid p-4 gap-4 grid-cols-1 justify-items-center"
4+
:class="severity.accentClass"
5+
data-cy="flaky-spec-summary"
6+
>
7+
<SpecNameDisplay
8+
:spec-file-name="specName"
9+
:spec-file-extension="specExtension"
10+
/>
11+
<div class="flex flex-row w-full text-size-14px justify-center items-center">
12+
<component :is="severity.icon" />
13+
<span
14+
class="font-medium ml-2"
15+
:class="severity.textClass"
16+
>{{ severity?.label }}</span>
17+
<span
18+
class="ml-4"
19+
data-cy="flaky-rate"
20+
>{{ t('specPage.flaky.flakyRate', [flakyRate]) }}</span>
21+
</div>
22+
23+
<div class="w-full grid text-gray-700 text-size-14px gap-2 grid-cols-2 justify-items-center">
24+
<span data-cy="flaky-runs">{{ t('specPage.flaky.flakyRuns', { count: totalFlakyRuns, flakyRuns: totalFlakyRuns, totalRuns }) }}</span>
25+
<span data-cy="last-flaky">{{ t('specPage.flaky.lastFlaky', { count: runsSinceLastFlake, runsSinceLastFlake }) }}</span>
26+
</div>
27+
</div>
28+
</template>
29+
30+
<script setup lang="ts">
31+
32+
import { computed } from 'vue'
33+
import { useI18n } from '@cy/i18n'
34+
import SpecNameDisplay from '../SpecNameDisplay.vue'
35+
import LowRateIcon from '~icons/cy/rate-low_x16'
36+
import MediumRateIcon from '~icons/cy/rate-medium_x16'
37+
import HighRateIcon from '~icons/cy/rate-high_x16'
38+
39+
const { t } = useI18n()
40+
41+
const SEVERITIES = {
42+
'default': {
43+
accentClass: 'border-t-orange-400',
44+
textClass: null,
45+
label: null,
46+
icon: null,
47+
},
48+
'low': {
49+
accentClass: 'border-t-orange-400',
50+
textClass: 'text-orange-400',
51+
label: t('specPage.flaky.severityLow'),
52+
icon: LowRateIcon,
53+
},
54+
'medium': {
55+
accentClass: 'border-t-orange-500',
56+
textClass: 'text-orange-500',
57+
label: t('specPage.flaky.severityMedium'),
58+
icon: MediumRateIcon,
59+
},
60+
'high': {
61+
accentClass: 'border-t-orange-600',
62+
textClass: 'text-orange-600',
63+
label: t('specPage.flaky.severityHigh'),
64+
icon: HighRateIcon,
65+
},
66+
}
67+
68+
const props = defineProps<{
69+
specName: string
70+
specExtension: string
71+
severity: 'low' | 'medium' | 'high'
72+
totalFlakyRuns: number
73+
totalRuns: number
74+
runsSinceLastFlake: number
75+
dashboardUrl: string
76+
}>()
77+
78+
const flakyRate = computed(() => {
79+
if (props.totalFlakyRuns <= 0 || props.totalRuns <= 0) {
80+
return 0
81+
}
82+
83+
const rawRate = (props.totalFlakyRuns / props.totalRuns) * 100
84+
85+
// Only display 100 if rawRate is actually 100 (do not round to 100)
86+
if (rawRate > 99 && rawRate < 100) {
87+
return 99
88+
}
89+
90+
return Math.ceil(rawRate)
91+
})
92+
93+
const severity = computed(() => SEVERITIES[props.severity] || SEVERITIES.default)
94+
95+
</script>
Loading
Loading
Loading

packages/frontend-shared/src/locales/en-US.json

+9
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,15 @@
151151
"linkText": "average spec durations"
152152
}
153153
},
154+
"flaky": {
155+
"badgeLabel": "Flaky",
156+
"severityLow": "Low",
157+
"severityMedium": "Medium",
158+
"severityHigh": "High",
159+
"flakyRate": "{0}% flaky rate",
160+
"flakyRuns": "{flakyRuns} flaky runs / {totalRuns} total | {flakyRuns} flaky run / {totalRuns} total | {flakyRuns} flaky runs / {totalRuns} total",
161+
"lastFlaky": "Last flaky {runsSinceLastFlake} runs ago | Last flaky {runsSinceLastFlake} run ago | Last flaky {runsSinceLastFlake} runs ago"
162+
},
154163
"connectProjectButton": "Connect your project",
155164
"dashboardLoginButton": "Log in to the Dashboard",
156165
"reconnectProjectButton": "Reconnect your project",

0 commit comments

Comments
 (0)