Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ _Released 11/4/2025 (PENDING)_
- Have cursor on hover of the AUT URL to show as pointer. Addresses [#32777](https://github.com/cypress-io/cypress/issues/32777). Addressed in [#32782](https://github.com/cypress-io/cypress/pull/32782).
- WebKit now prefers a cookie's fully qualified `domain` when requesting a cookie value via [`cy.getCookie()`](https://docs.cypress.io/api/commands/getcookie). If none are found, the cookie's apex domain will be used as a fallback. Addresses [#29954](https://github.com/cypress-io/cypress/issues/29954), [#29973](https://github.com/cypress-io/cypress/issues/29973) and [#30392](https://github.com/cypress-io/cypress/issues/30392). Addressed in [#32852](https://github.com/cypress-io/cypress/pull/32852).
- The 'Next' tooltip style was updated. Addressed in [#32866](https://github.com/cypress-io/cypress/pull/32866).
- Make test name header sticky in studio mode and in the tests list. Addresses [#32591](https://github.com/cypress-io/cypress/issues/32591). Addressed in [#32840](https://github.com/cypress-io/cypress/pull/32840)

**Dependency Updates:**

Expand Down
6 changes: 6 additions & 0 deletions packages/reporter/src/collapsible/collapsible.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import "../lib/mixins.scss";

.reporter {
.collapsible-indicator {
margin-right: 8px;
Expand All @@ -8,4 +10,8 @@
.is-open > .collapsible-header-wrapper > .collapsible-header > .collapsible-header-inner > .collapsible-indicator {
transform: rotate(0);
}

.test > .is-open > .collapsible-header-wrapper {
@include sticky-header-with-shadow;
}
}
24 changes: 22 additions & 2 deletions packages/reporter/src/collapsible/collapsible.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import cs from 'classnames'
import React, { CSSProperties, MouseEvent, ReactNode, RefObject, useCallback, useState } from 'react'
import React, { CSSProperties, MouseEvent, ReactNode, RefObject, useCallback, useEffect, useRef, useState } from 'react'
import { onEnterOrSpace } from '../lib/util'
import DocumentBlankIcon from '@packages/frontend-shared/src/assets/icons/document-blank_x16.svg'
import { IconChevronDownSmall } from '@cypress-design/react-icon'
Expand All @@ -24,6 +24,8 @@ interface CollapsibleProps {

const Collapsible: React.FC<CollapsibleProps> = ({ isOpen: isOpenAsProp = false, header, headerClass = '', headerStyle = {}, headerExtras, contentClass = '', hideExpander = false, containerRef = null, onOpenStateChangeRequested, children, HeaderComponent }) => {
const [isOpenState, setIsOpenState] = useState(isOpenAsProp)
const headerRef = useRef<HTMLDivElement>(null)
const fixedElementRef = useRef<HTMLDivElement>(null)

const toggleOpenState = useCallback((e?: MouseEvent) => {
e?.stopPropagation()
Expand All @@ -36,9 +38,27 @@ const Collapsible: React.FC<CollapsibleProps> = ({ isOpen: isOpenAsProp = false,

const isOpen = onOpenStateChangeRequested ? isOpenAsProp : isOpenState

const toggleHeaderShadow = (entries) => {
const [entry] = entries

headerRef.current?.classList.toggle('shadow-active', !entry.isIntersecting)
}

useEffect(() => {
if (!fixedElementRef?.current) return

const observer = new IntersectionObserver(toggleHeaderShadow)

observer.observe(fixedElementRef.current)

return () => observer.disconnect()
}, [])

return (
<div className={cs('collapsible', { 'is-open': isOpen })} ref={containerRef}>
<div className={cs('collapsible-header-wrapper', headerClass)}>
{/* This empty div acts as an intersection observer target to toggle the header shadow based on scroll position */}
<div ref={fixedElementRef}/>
<div className={cs('collapsible-header-wrapper', headerClass)} ref={headerRef}>
<div
aria-expanded={isOpen}
className='collapsible-header'
Expand Down
18 changes: 18 additions & 0 deletions packages/reporter/src/lib/mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,22 @@
right: 0;
background: $linear-gradient;
z-index: 0;
}

@mixin sticky-header-with-shadow {
position: sticky;
top: 0;
z-index: 1;
background-color: $gray-1100;

&.shadow-active::after {
content: "";
position: absolute;
top: 101%;
left: 0;
right: 0;
height: 16px;
background: linear-gradient(to bottom, $gray-1100 0%, rgba(22, 24, 39, 0.3) 100%);
pointer-events: none;
}
}
1 change: 0 additions & 1 deletion packages/reporter/src/runnables/runnables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ $dotted-line-left-padding: 19px;
width: 100%;
color: $gray-400;
background-color: $gray-1100;
overflow: auto;
line-height: 18px;

.runnable-wrapper {
Expand Down
3 changes: 3 additions & 0 deletions packages/reporter/src/studio/StudioTest.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import "../lib/mixins.scss";

.studio-single-test-container {
display: flex;
flex-direction: column;
Expand All @@ -15,6 +17,7 @@
font-size: 14px;
line-height: 20px;
flex-shrink: 0;
@include sticky-header-with-shadow;

.state-icon {
height: 32px;
Expand Down
29 changes: 25 additions & 4 deletions packages/reporter/src/studio/StudioTest.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useRef } from 'react'
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import { observer } from 'mobx-react'
import { RunnablesStore } from '../runnables/runnables-store'
import { Duration } from '../duration/duration'
Expand Down Expand Up @@ -65,6 +65,8 @@ export const StudioTest = observer(({ appState, runnablesStore, statsStore }: St
// Single we're in single test mode, the current test is the first test in the runnablesStore._tests
const currentTest = Object.values(runnablesStore._tests)[0]
const tooltipRef = useRef<HTMLUListElement>(null)
const testSectionRef = useRef<HTMLDivElement>(null)
const fixedElementRef = useRef<HTMLDivElement>(null)

const { containerRef, isMounted, scrollIntoView } = useScrollIntoView({
appState,
Expand All @@ -89,10 +91,28 @@ export const StudioTest = observer(({ appState, runnablesStore, statsStore }: St

const testTitle = currentTest ? <span data-cy='studio-single-test-title' className='studio-header__test-title'>{currentTest.title}</span> : null

const toggleHeaderShadow = (entries) => {
const [entry] = entries

testSectionRef.current?.classList.toggle('shadow-active', !entry.isIntersecting)
}

useEffect(() => {
if (!fixedElementRef.current) return

const observer = new IntersectionObserver(toggleHeaderShadow)

observer.observe(fixedElementRef.current)

return () => observer.disconnect()
}, [])

return (
currentTest && (
<div className='studio-single-test-container' >
<div className='studio-header__test-section'>
currentTest && (<>
{/* This empty div acts as an intersection observer target to toggle the header shadow based on scroll position */}
<div ref={fixedElementRef} />
<div className='studio-single-test-container'>
<div className='studio-header__test-section' ref={testSectionRef}>
<div className='studio-header__test-section-left'>

<Tooltip placement='bottom' title={<p>All tests</p>} className='cy-tooltip'>
Expand Down Expand Up @@ -126,6 +146,7 @@ export const StudioTest = observer(({ appState, runnablesStore, statsStore }: St
<Attempts isSingleStudioTest test={currentTest} scrollIntoView={scrollIntoView} />
</div>
</div>
</>
)
)
})
Loading