mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
fix(updater): open background check errors from the status bar (#20270)
* fix(updater): open background check errors from the status bar * docs(updater): describe error disclosure initialization --------- Co-authored-by: m4air <m4air@Mac.localdomain>
This commit is contained in:
@@ -317,8 +317,8 @@ type VisibilityInput = {
|
||||
status: UpdateStatus
|
||||
dismissedVersion: string | null
|
||||
cachedVersion: string | null
|
||||
hasStartedDownload: boolean
|
||||
updateUserInitiatedCycle?: boolean
|
||||
collapsed?: boolean
|
||||
}
|
||||
|
||||
type VisibilityResult = 'hidden' | 'visible'
|
||||
@@ -338,8 +338,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'idle' },
|
||||
dismissedVersion: null,
|
||||
cachedVersion: null,
|
||||
hasStartedDownload: false
|
||||
cachedVersion: null
|
||||
})
|
||||
).toBe('hidden')
|
||||
})
|
||||
@@ -353,8 +352,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'checking' },
|
||||
dismissedVersion: null,
|
||||
cachedVersion: null,
|
||||
hasStartedDownload: false
|
||||
cachedVersion: null
|
||||
})
|
||||
).toBe('hidden')
|
||||
})
|
||||
@@ -364,8 +362,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'checking', userInitiated: true },
|
||||
dismissedVersion: null,
|
||||
cachedVersion: null,
|
||||
hasStartedDownload: false
|
||||
cachedVersion: null
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
@@ -375,8 +372,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'not-available' },
|
||||
dismissedVersion: null,
|
||||
cachedVersion: null,
|
||||
hasStartedDownload: false
|
||||
cachedVersion: null
|
||||
})
|
||||
).toBe('hidden')
|
||||
})
|
||||
@@ -386,8 +382,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'not-available', userInitiated: true },
|
||||
dismissedVersion: null,
|
||||
cachedVersion: null,
|
||||
hasStartedDownload: false
|
||||
cachedVersion: null
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
@@ -397,8 +392,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'available', version: '1.2.0', changelog: null },
|
||||
dismissedVersion: null,
|
||||
cachedVersion: null,
|
||||
hasStartedDownload: false
|
||||
cachedVersion: null
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
@@ -408,8 +402,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'available', version: '1.2.0', changelog: RICH_CHANGELOG },
|
||||
dismissedVersion: null,
|
||||
cachedVersion: null,
|
||||
hasStartedDownload: false
|
||||
cachedVersion: null
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
@@ -419,8 +412,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'available', version: '1.2.0', changelog: null },
|
||||
dismissedVersion: '1.2.0',
|
||||
cachedVersion: '1.2.0',
|
||||
hasStartedDownload: false
|
||||
cachedVersion: '1.2.0'
|
||||
})
|
||||
).toBe('hidden')
|
||||
})
|
||||
@@ -431,7 +423,6 @@ describe('UpdateCard visibility gates', () => {
|
||||
status: { state: 'available', version: '1.2.0', changelog: null },
|
||||
dismissedVersion: '1.2.0',
|
||||
cachedVersion: '1.2.0',
|
||||
hasStartedDownload: false,
|
||||
updateUserInitiatedCycle: true
|
||||
})
|
||||
).toBe('visible')
|
||||
@@ -442,8 +433,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'downloading', percent: 42, version: '1.2.0' },
|
||||
dismissedVersion: '1.2.0',
|
||||
cachedVersion: '1.2.0',
|
||||
hasStartedDownload: true
|
||||
cachedVersion: '1.2.0'
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
@@ -453,19 +443,22 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'downloaded', version: '1.2.0' },
|
||||
dismissedVersion: '1.2.0',
|
||||
cachedVersion: '1.2.0',
|
||||
hasStartedDownload: false
|
||||
cachedVersion: '1.2.0'
|
||||
})
|
||||
).toBe('hidden')
|
||||
})
|
||||
|
||||
it('hides background errors silently', () => {
|
||||
const store = createTestStore()
|
||||
setState(store, { state: 'checking' })
|
||||
setState(store, { state: 'error', message: 'network' })
|
||||
|
||||
expect(
|
||||
computeVisibility({
|
||||
status: { state: 'error', message: 'network' },
|
||||
status: store.getState().updateStatus,
|
||||
collapsed: store.getState().updateCardCollapsed,
|
||||
dismissedVersion: null,
|
||||
cachedVersion: null,
|
||||
hasStartedDownload: false
|
||||
cachedVersion: null
|
||||
})
|
||||
).toBe('hidden')
|
||||
})
|
||||
@@ -475,8 +468,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'error', message: 'network', userInitiated: true },
|
||||
dismissedVersion: null,
|
||||
cachedVersion: null,
|
||||
hasStartedDownload: false
|
||||
cachedVersion: null
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
@@ -486,8 +478,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'error', message: 'ENOSPC' },
|
||||
dismissedVersion: null,
|
||||
cachedVersion: '1.2.0',
|
||||
hasStartedDownload: true
|
||||
cachedVersion: '1.2.0'
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
@@ -497,8 +488,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'error', message: 'ENOSPC' },
|
||||
dismissedVersion: null,
|
||||
cachedVersion: '1.2.0',
|
||||
hasStartedDownload: false
|
||||
cachedVersion: '1.2.0'
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
@@ -517,8 +507,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
}
|
||||
},
|
||||
dismissedVersion: null,
|
||||
cachedVersion: null,
|
||||
hasStartedDownload: false
|
||||
cachedVersion: null
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
@@ -528,8 +517,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'error', message: 'invalid metadata', version: '1.2.0' },
|
||||
dismissedVersion: null,
|
||||
cachedVersion: null,
|
||||
hasStartedDownload: false
|
||||
cachedVersion: null
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
@@ -539,8 +527,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'downloaded', version: '1.2.0' },
|
||||
dismissedVersion: null,
|
||||
cachedVersion: '1.2.0',
|
||||
hasStartedDownload: true
|
||||
cachedVersion: '1.2.0'
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
@@ -550,8 +537,7 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'available', version: '1.3.0', changelog: null },
|
||||
dismissedVersion: '1.2.0',
|
||||
cachedVersion: '1.3.0',
|
||||
hasStartedDownload: false
|
||||
cachedVersion: '1.3.0'
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
@@ -561,19 +547,23 @@ describe('UpdateCard visibility gates', () => {
|
||||
computeVisibility({
|
||||
status: { state: 'error', message: 'fail', userInitiated: true },
|
||||
dismissedVersion: '1.2.0',
|
||||
cachedVersion: '1.2.0',
|
||||
hasStartedDownload: false
|
||||
cachedVersion: '1.2.0'
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
|
||||
it('hides check errors once a new checking cycle cleared the cached version', () => {
|
||||
const store = createTestStore()
|
||||
setState(store, { state: 'available', version: '1.2.0', changelog: null })
|
||||
setState(store, { state: 'checking' })
|
||||
setState(store, { state: 'error', message: 'network timeout' })
|
||||
|
||||
expect(
|
||||
computeVisibility({
|
||||
status: { state: 'error', message: 'network timeout' },
|
||||
status: store.getState().updateStatus,
|
||||
collapsed: store.getState().updateCardCollapsed,
|
||||
dismissedVersion: '1.2.0',
|
||||
cachedVersion: null,
|
||||
hasStartedDownload: false
|
||||
cachedVersion: null
|
||||
})
|
||||
).toBe('hidden')
|
||||
})
|
||||
@@ -649,8 +639,7 @@ describe('full update lifecycle through setUpdateStatus', () => {
|
||||
computeVisibility({
|
||||
status: store.getState().updateStatus,
|
||||
dismissedVersion: store.getState().dismissedUpdateVersion,
|
||||
cachedVersion: '1.3.0',
|
||||
hasStartedDownload: false
|
||||
cachedVersion: '1.3.0'
|
||||
})
|
||||
).toBe('visible')
|
||||
})
|
||||
|
||||
@@ -108,7 +108,6 @@ export function UpdateCard(): React.JSX.Element | null {
|
||||
status,
|
||||
dismissedVersion,
|
||||
cachedVersion,
|
||||
hasStartedDownload: hasStartedDownload.current,
|
||||
updateUserInitiatedCycle,
|
||||
autoDismissed,
|
||||
collapsed
|
||||
|
||||
@@ -4,7 +4,6 @@ export function isUpdateCardVisible({
|
||||
status,
|
||||
dismissedVersion,
|
||||
cachedVersion,
|
||||
hasStartedDownload,
|
||||
updateUserInitiatedCycle,
|
||||
autoDismissed = false,
|
||||
collapsed = false
|
||||
@@ -12,18 +11,11 @@ export function isUpdateCardVisible({
|
||||
status: UpdateStatus
|
||||
dismissedVersion: string | null
|
||||
cachedVersion: string | null
|
||||
hasStartedDownload: boolean
|
||||
updateUserInitiatedCycle: boolean
|
||||
autoDismissed?: boolean
|
||||
collapsed?: boolean
|
||||
}): boolean {
|
||||
const isUserInitiated = 'userInitiated' in status && Boolean(status.userInitiated)
|
||||
const shouldShowDetailedErrorCard =
|
||||
status.state === 'error' &&
|
||||
(hasStartedDownload ||
|
||||
cachedVersion !== null ||
|
||||
status.version !== undefined ||
|
||||
status.recovery?.kind === 'linux-package-install')
|
||||
|
||||
if (status.state === 'checking' && !isUserInitiated) {
|
||||
return false
|
||||
@@ -34,9 +26,6 @@ export function isUpdateCardVisible({
|
||||
if (status.state === 'idle') {
|
||||
return false
|
||||
}
|
||||
if (status.state === 'error' && !shouldShowDetailedErrorCard && !isUserInitiated) {
|
||||
return false
|
||||
}
|
||||
if (cachedVersion && dismissedVersion === cachedVersion && !updateUserInitiatedCycle) {
|
||||
if (status.state !== 'downloading' && status.state !== 'error') {
|
||||
return false
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { UpdateStatus } from '../../../../shared/update-status-types'
|
||||
import { useAppStore } from '../../store'
|
||||
import { UpdateCard } from '../UpdateCard'
|
||||
import { TooltipProvider } from '../ui/tooltip'
|
||||
import { UpdateStatusSegment } from './UpdateStatusSegment'
|
||||
|
||||
const check = vi.fn()
|
||||
const message = 'Could not reach the update server: net::ERR_CONNECTION_REFUSED'
|
||||
const error: UpdateStatus = { state: 'error', message }
|
||||
const actionableErrors: UpdateStatus[] = [
|
||||
{ ...error, userInitiated: true },
|
||||
{ ...error, version: '1.4.200' },
|
||||
{
|
||||
...error,
|
||||
recovery: {
|
||||
kind: 'linux-package-install',
|
||||
packageType: 'deb',
|
||||
reason: 'manual-install-required',
|
||||
version: '1.4.200'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
function setStatus(status: UpdateStatus): void {
|
||||
act(() => useAppStore.getState().setUpdateStatus(status))
|
||||
}
|
||||
|
||||
function renderUpdateControls(): void {
|
||||
render(
|
||||
<TooltipProvider>
|
||||
<UpdateCard />
|
||||
<UpdateStatusSegment compact={false} iconOnly={false} />
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function errorToggle(): HTMLElement {
|
||||
return screen.getByRole('button', { name: 'Update failed. Click to expand.' })
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
useAppStore.setState(useAppStore.getInitialState(), true)
|
||||
check.mockReset().mockResolvedValue(undefined)
|
||||
vi.stubGlobal(
|
||||
'matchMedia',
|
||||
vi.fn().mockReturnValue({
|
||||
matches: true,
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn()
|
||||
})
|
||||
)
|
||||
Object.defineProperty(window, 'api', {
|
||||
configurable: true,
|
||||
value: { updater: { check } }
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
useAppStore.setState(useAppStore.getInitialState(), true)
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('update status disclosure', () => {
|
||||
it('opens background check failure details on the first click and offers a re-check', () => {
|
||||
renderUpdateControls()
|
||||
setStatus({ state: 'checking' })
|
||||
setStatus(error)
|
||||
|
||||
expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull()
|
||||
fireEvent.click(errorToggle())
|
||||
|
||||
expect(screen.getByRole('complementary', { name: 'Update error' })).toBeTruthy()
|
||||
expect(errorToggle().getAttribute('aria-expanded')).toBe('true')
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Show details' }))
|
||||
expect(screen.getByText(message)).toBeTruthy()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Re-check' }))
|
||||
expect(check).toHaveBeenCalledWith({ includePrerelease: false })
|
||||
|
||||
fireEvent.click(errorToggle())
|
||||
expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull()
|
||||
expect(errorToggle().getAttribute('aria-expanded')).toBe('false')
|
||||
})
|
||||
|
||||
it('announces a quiet automatic check failure as collapsed', () => {
|
||||
setStatus(error)
|
||||
renderUpdateControls()
|
||||
|
||||
expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull()
|
||||
expect(errorToggle().getAttribute('aria-expanded')).toBe('false')
|
||||
})
|
||||
|
||||
it('preserves the disclosure choice until the next automatic check', () => {
|
||||
renderUpdateControls()
|
||||
setStatus(error)
|
||||
fireEvent.click(errorToggle())
|
||||
setStatus({ ...error, message: 'Still unavailable' })
|
||||
expect(screen.getByRole('complementary', { name: 'Update error' })).toBeTruthy()
|
||||
|
||||
fireEvent.click(errorToggle())
|
||||
setStatus(error)
|
||||
expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull()
|
||||
fireEvent.click(errorToggle())
|
||||
|
||||
setStatus({ state: 'checking' })
|
||||
setStatus(error)
|
||||
expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull()
|
||||
expect(errorToggle().getAttribute('aria-expanded')).toBe('false')
|
||||
})
|
||||
|
||||
it.each<UpdateStatus>([
|
||||
{ state: 'checking', userInitiated: true },
|
||||
{ state: 'available', version: '1.4.200', changelog: null },
|
||||
{ state: 'downloading', version: '1.4.200', percent: 50 },
|
||||
{ state: 'downloaded', version: '1.4.200' }
|
||||
])('opens an error after $state without requiring a status click', (previousStatus) => {
|
||||
setStatus(previousStatus)
|
||||
setStatus(error)
|
||||
renderUpdateControls()
|
||||
|
||||
expect(screen.getByRole('complementary', { name: 'Update error' })).toBeTruthy()
|
||||
expect(errorToggle().getAttribute('aria-expanded')).toBe('true')
|
||||
})
|
||||
|
||||
it.each(actionableErrors)(
|
||||
'opens an explicit actionable error on initial receipt: %j',
|
||||
(status) => {
|
||||
setStatus(status)
|
||||
renderUpdateControls()
|
||||
|
||||
expect(screen.getByRole('complementary', { name: 'Update error' })).toBeTruthy()
|
||||
expect(
|
||||
screen.getByRole('button', { name: /Click to expand/ }).getAttribute('aria-expanded')
|
||||
).toBe('true')
|
||||
}
|
||||
)
|
||||
|
||||
it.each(actionableErrors)(
|
||||
'opens a newly actionable error and preserves dismissal on repeat: %j',
|
||||
(status) => {
|
||||
renderUpdateControls()
|
||||
setStatus(error)
|
||||
expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull()
|
||||
|
||||
setStatus(status)
|
||||
expect(screen.getByRole('complementary', { name: 'Update error' })).toBeTruthy()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /Click to expand/ }))
|
||||
setStatus({ ...status })
|
||||
expect(screen.queryByRole('complementary', { name: 'Update error' })).toBeNull()
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -182,7 +182,7 @@ export type UISlicePersistence = {
|
||||
/** Dev-only channel override; null follows the running build's own channel. */
|
||||
releaseChannelOverride: ReleaseChannel | null
|
||||
setReleaseChannelOverride: (channel: ReleaseChannel | null) => void
|
||||
// Why: ephemeral, renderer-only — never persisted; resets each session and on every phase transition (see setUpdateStatus).
|
||||
// Ephemeral disclosure state; setUpdateStatus initializes it when the phase or error actionability changes.
|
||||
updateCardCollapsed: boolean
|
||||
setUpdateCardCollapsed: (collapsed: boolean) => void
|
||||
updateReassuranceSeen: boolean
|
||||
|
||||
@@ -8,7 +8,7 @@ export function createUiUpdateActions(set: UISliceSet, get: UISliceGet): Partial
|
||||
return {
|
||||
updateStatus: { state: 'idle' },
|
||||
setUpdateStatus: (status) => {
|
||||
const prevState = get().updateStatus.state
|
||||
const { updateStatus: previousStatus, updateUserInitiatedCycle } = get()
|
||||
const update: Partial<
|
||||
Pick<
|
||||
UISlice,
|
||||
@@ -34,9 +34,22 @@ export function createUiUpdateActions(set: UISliceSet, get: UISliceGet): Partial
|
||||
update.updateChangelog = null
|
||||
}
|
||||
// 'downloading'/'downloaded'/'error': leave updateChangelog untouched to keep the original 'available' content.
|
||||
if (status.state !== prevState) {
|
||||
// Why: re-surface the card on each phase transition so a collapsed `downloading` doesn't bury `downloaded`/`error`.
|
||||
update.updateCardCollapsed = false
|
||||
const errorBecameActionable =
|
||||
status.state === 'error' &&
|
||||
previousStatus.state === 'error' &&
|
||||
((status.userInitiated === true && !previousStatus.userInitiated) ||
|
||||
(status.version !== undefined && previousStatus.version === undefined) ||
|
||||
(status.recovery?.kind === 'linux-package-install' &&
|
||||
previousStatus.recovery?.kind !== 'linux-package-install'))
|
||||
if (status.state !== previousStatus.state || errorBecameActionable) {
|
||||
// Quiet check failures start collapsed so the status bar can still disclose them.
|
||||
update.updateCardCollapsed =
|
||||
status.state === 'error' &&
|
||||
!status.userInitiated &&
|
||||
!updateUserInitiatedCycle &&
|
||||
status.version === undefined &&
|
||||
status.recovery?.kind !== 'linux-package-install' &&
|
||||
!('version' in previousStatus && previousStatus.version !== undefined)
|
||||
}
|
||||
set(update)
|
||||
},
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { expect, test } from './helpers/orca-app'
|
||||
import { waitForSessionReady } from './helpers/store'
|
||||
|
||||
const CHECK_ERROR = 'E2E update check failed: connection refused'
|
||||
|
||||
test.use({ seedTestRepo: false })
|
||||
|
||||
for (const theme of ['dark', 'light'] as const) {
|
||||
test(`automatic update failure opens details from the status bar (${theme})`, async ({
|
||||
orcaPage
|
||||
}, testInfo) => {
|
||||
await waitForSessionReady(orcaPage)
|
||||
await orcaPage.setViewportSize({ width: 1200, height: 800 })
|
||||
await orcaPage.evaluate(async (theme) => {
|
||||
const state = window.__store!.getState()
|
||||
await state.updateSettingsOrThrow({ theme })
|
||||
state.setUpdateStatus({ state: 'checking', userInitiated: false })
|
||||
}, theme)
|
||||
await expect(orcaPage.locator('html')).toHaveClass(theme === 'dark' ? /\bdark\b/ : /\blight\b/)
|
||||
await orcaPage.evaluate((message) => {
|
||||
window.__store!.getState().setUpdateStatus({
|
||||
state: 'error',
|
||||
message,
|
||||
userInitiated: false
|
||||
})
|
||||
}, CHECK_ERROR)
|
||||
|
||||
const statusButton = orcaPage.getByRole('button', {
|
||||
name: 'Update failed. Click to expand.',
|
||||
exact: true
|
||||
})
|
||||
const card = orcaPage.getByRole('complementary', { name: 'Update error', exact: true })
|
||||
await expect(statusButton).toBeVisible()
|
||||
await expect(card).toBeHidden()
|
||||
|
||||
await statusButton.click()
|
||||
// Capture before the assertion so the broken build provides the same visual evidence.
|
||||
const statusClickScreenshot = testInfo.outputPath(
|
||||
`update-error-after-status-click-${theme}.png`
|
||||
)
|
||||
await orcaPage.screenshot({ path: statusClickScreenshot, animations: 'disabled' })
|
||||
await testInfo.attach(`update-error-after-status-click-${theme}`, {
|
||||
path: statusClickScreenshot,
|
||||
contentType: 'image/png'
|
||||
})
|
||||
await expect(card).toBeVisible()
|
||||
await expect(statusButton).toHaveAttribute('aria-expanded', 'true')
|
||||
await expect(card.getByRole('heading', { name: 'Update Check Failed' })).toBeVisible()
|
||||
await expect(card.getByRole('button', { name: 'Re-check', exact: true })).toBeVisible()
|
||||
|
||||
await card.getByRole('button', { name: 'Show details', exact: true }).click()
|
||||
await expect(card.getByText(CHECK_ERROR, { exact: true })).toBeVisible()
|
||||
const detailsScreenshot = testInfo.outputPath(`update-error-expanded-details-${theme}.png`)
|
||||
await orcaPage.screenshot({ path: detailsScreenshot, animations: 'disabled' })
|
||||
await testInfo.attach(`update-error-expanded-details-${theme}`, {
|
||||
path: detailsScreenshot,
|
||||
contentType: 'image/png'
|
||||
})
|
||||
|
||||
await card.getByRole('button', { name: 'Minimize to status bar', exact: true }).click()
|
||||
await expect(card).toBeHidden()
|
||||
await expect(statusButton).toHaveAttribute('aria-expanded', 'false')
|
||||
await statusButton.click()
|
||||
await expect(card).toBeVisible()
|
||||
await expect(statusButton).toHaveAttribute('aria-expanded', 'true')
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user