= ({ isOpen: isOpenAsProp = false,
style={headerStyle}
tabIndex={-1}
>
- {!hideExpander && }
+ {!hideExpander && headerClass === 'hook-header' && }
+ {!hideExpander && headerClass !== 'hook-header' && }
- {header}
+ {HeaderComponent ? : header}
diff --git a/packages/reporter/src/commands/command.cy.tsx b/packages/reporter/src/commands/command.cy.tsx
index 0958fc74be0b..dcc9771bf883 100644
--- a/packages/reporter/src/commands/command.cy.tsx
+++ b/packages/reporter/src/commands/command.cy.tsx
@@ -71,6 +71,10 @@ describe('commands', () => {
state: 'failed',
status: 'failed',
},
+ {
+ state: 'passed',
+ status: 'created',
+ },
]
it('session status in command', () => {
@@ -103,6 +107,8 @@ describe('commands', () => {
,
)
+ cy.get('.command-name-session').last().click()
+
cy.percySnapshot()
})
})
diff --git a/packages/reporter/src/commands/command.tsx b/packages/reporter/src/commands/command.tsx
index 83d813126a53..819102f60fce 100644
--- a/packages/reporter/src/commands/command.tsx
+++ b/packages/reporter/src/commands/command.tsx
@@ -55,13 +55,13 @@ export const formattedMessage = (message: string, name?: string) => {
if (name === 'assert' && assertionArray) {
const expectedActualArray = () => {
- // get the expected and actual values of assertions
+ // get the expected and actual values of assertions
const splitTrim = message.split(assertionRegex).filter(Boolean).map((s) => s.trim())
// replace outside double asterisks with strong tags
return splitTrim.map((s) => {
- // we want to escape HTML chars so that they display
- // correctly in the command log: -> <p>
+ // we want to escape HTML chars so that they display
+ // correctly in the command log:
-> <p>
const HTMLEscapedString = mdOnlyHTML.renderInline(s)
return HTMLEscapedString.replace(asterisksRegex, `$1 `)
@@ -192,8 +192,8 @@ const Interceptions: React.FC = observer(({ interceptions, wentToOr
const interceptsTitle = (
- {wentToOrigin ? '' : <>This request did not go to origin because the response was stubbed. >}
- This request matched:
+ {wentToOrigin ? '' : <>This request did not go to origin because the response was stubbed. >}
+ This request matched:
{interceptions?.map(({ command, alias, type }, i) => (
@@ -331,7 +331,7 @@ const CommandDetails: React.FC = observer(({ model, groupId
{model.event && model.type !== 'system' ? `(${displayName(model)})` : displayName(model)}
- {!!groupId && model.type === 'system' && model.state === 'failed' && }
+ {!!groupId && model.type === 'system' && model.state === 'failed' && }
{model.referencesAlias ?
:
@@ -509,13 +509,13 @@ const Command: React.FC = observer(({ model, aliasesWithDuplicates
diff --git a/packages/reporter/src/commands/commands.scss b/packages/reporter/src/commands/commands.scss
index 290869c55e0f..aeaf97212443 100644
--- a/packages/reporter/src/commands/commands.scss
+++ b/packages/reporter/src/commands/commands.scss
@@ -7,9 +7,11 @@
.reporter {
// rendered within ../hooks/hooks.tsx
.commands-container {
- background-color: $reporter-section-background;
min-width: $reporter-contents-min-width;
padding: 0;
+ border: 1px solid $gray-900;
+ margin-top: 6px;
+ border-radius: 4px;
&:empty {
display: none;
@@ -23,7 +25,7 @@
.command-is-studio {
cursor: auto;
-
+
&.command-type-parent .commands-controls .studio-command-remove {
display: block;
padding-left: 5px;
@@ -63,11 +65,11 @@
.command-wrapper {
border-left: 2px solid $reporter-section-background;
- background-color: $reporter-section-background;
+ border-radius: 4px;
color: $gray-500;
display: flex;
min-height: 28px;
- padding-right: 2px;
+ align-items: center;
&.command-is-interactive:hover {
background-color: $gray-900;
@@ -95,12 +97,12 @@
.alias-container {
margin-left: 0;
white-space: nowrap;
-
+
> * {
display: inline-block;
margin-left: 2px;
}
-
+
> *:first-child {
margin-left: 0;
}
@@ -109,14 +111,13 @@
.command-number-column {
@include gutter-alignment;
-
- color: #5a5f7a;
+ color: $gray-500;
}
// when no children, add padding to act as the .command-expander-column's width
// to prevent adding another element to the page
.command-number-column + span.command-pin-target {
- margin-left: $gutter-margin;
+ margin-left: 24px;
}
.command-pin-target.command-group {
@@ -131,20 +132,19 @@
}
.command-group-no-children {
- padding-left: 15px;
+ padding-left: 15px;
}
.command-wrapper-text-group {
padding-left: 15px;
width: 100%;
-
}
.command-wrapper-text-group-parent {
padding-left: 5px;
}
- .nested-group-expander {
+ .nested-group-expander {
.command-expander {
position: relative;
margin-left: -16px !important; // Adjust this value to center the caret on the border
@@ -181,7 +181,7 @@
}
.fa-circle.command-message-indicator-bad {
- color: $red-500
+ color: $red-500;
}
.fa-circle.command-message-indicator-pending {
@@ -207,12 +207,13 @@
&.command-is-interactive:hover {
border-left: 2px solid $gray-900;
+ border-radius: 0 4px 4px 0;
}
&:not(.command-is-event) .command-number {
color: $gray-700;
}
-
+
&:not(.command-is-event, .command-type-system) .command-method {
color: $gray-200;
}
@@ -222,8 +223,10 @@
}
}
+ // pending is running in this case
.command-state-pending {
border-left: 2px solid $indigo-800;
+ border-radius: 0 4px 4px 0;
background-color: $gray-900;
cursor: default;
color: $indigo-200;
@@ -235,7 +238,7 @@
.fa-circle {
line-height: 18px;
display: inline-block;
-
+
.icon-light {
stroke: $gray-800;
}
@@ -271,6 +274,7 @@
&:not(.command-type-system) {
border-left: $warn-border;
+ border-radius: 0 4px 4px 0;
}
.command-number-column,
@@ -295,6 +299,7 @@
&:not(.command-type-system) {
border-left: $err-border;
+ border-radius: 0 4px 4px 0;
background-color: $err-header-background;
&.command-is-interactive:hover {
@@ -308,10 +313,6 @@
color: $err-header-text;
}
- .failed-indicator {
- vertical-align: middle;
- }
-
.command-group {
border-color: $err-header-text;
@include nested-command-dashes($err-header-text);
@@ -434,7 +435,7 @@
padding-top: 4px;
svg {
- color: rgba($gray-600, .25);
+ color: rgba($gray-600, 0.25);
color: $gray-600;
vertical-align: text-top;
}
@@ -453,7 +454,7 @@
display: inline-block;
margin-left: 2px;
}
-
+
> *:first-child {
margin-left: 0;
}
@@ -467,8 +468,6 @@
color: $pinned;
font-size: 12px;
line-height: 1;
- margin-top: -1px;
- margin-left: 12px;
outline: none;
text-align: right;
width: 15px;
@@ -500,16 +499,12 @@
.command-expander-column {
@extend %command-expander-base;
- padding: 4px 5px 4px 11px;
- width: 25px;
-
- .command-expander {
- margin-top: 5px;
- }
+ height: 28px;
+ width: 24px;
+ justify-content: center;
+ align-items: center;
}
-
-
.command-expander-column-group {
@extend %command-expander-base;
@include group-indent-width;
@@ -526,6 +521,7 @@
.command-is-pinned {
background: $indigo-1000;
border-left: 2px solid $pinned;
+ border-radius: 0 4px 4px 0;
&,
&:hover {
@@ -535,6 +531,7 @@
&:hover {
background: $indigo-900;
border-left: 2px solid $pinned;
+ border-radius: 0 4px 4px 0;
}
}
@@ -546,6 +543,6 @@
box-shadow: inset 0 1px 1px rgba($white, 0.05);
min-height: 28px;
padding: 9px;
+ margin: 8px 0;
}
}
-
diff --git a/packages/reporter/src/errors/errors.scss b/packages/reporter/src/errors/errors.scss
index c9bd40049ddf..37df09fe60b3 100644
--- a/packages/reporter/src/errors/errors.scss
+++ b/packages/reporter/src/errors/errors.scss
@@ -18,7 +18,9 @@ $code-border-radius: 4px;
}
}
- p, ul, ol {
+ p,
+ ul,
+ ol {
font-size: 1.1em;
}
@@ -43,7 +45,7 @@ $code-border-radius: 4px;
}
ul li {
- list-style: disc
+ list-style: disc;
}
ol li {
@@ -59,7 +61,7 @@ $code-border-radius: 4px;
.err-group-block {
@include group-indent-width;
-
+
border-left: 1px dotted $err-header-text;
border-image-slice: 0 0 0 1;
border-image-source: repeating-linear-gradient(0deg, transparent, $err-header-text, $err-header-text 2px);
@@ -68,9 +70,8 @@ $code-border-radius: 4px;
width: 16px;
min-width: 16px;
}
- }
- }
-
+ }
+ }
.runnable-err-content {
padding: 0 12px 0 0;
@@ -80,7 +81,13 @@ $code-border-radius: 4px;
.runnable-err-content {
width: 100%;
overflow: scroll;
- padding: 0 18px;
+
+
+ .is-open {
+ > .runnable-err-stack-expander .err-collapsible-indicator {
+ transform: rotate(90deg);
+ }
+ }
}
.studio-err-wrapper {
@@ -114,14 +121,13 @@ $code-border-radius: 4px;
&.runnable-err-icon-group {
width: auto;
}
-
+
svg {
color: $red-400;
- align-self: center
+ align-self: center;
}
}
-
.runnable-err-name {
@include command-info-padding;
@@ -145,7 +151,7 @@ $code-border-radius: 4px;
font-family: $font-system;
font-size: 14px;
font-weight: 400;
- padding: 10px 0;
+ padding: 8px 14px;
code {
background-color: rgba($black, 0.2);
@@ -164,9 +170,9 @@ $code-border-radius: 4px;
.runnable-err-stack-expander {
align-items: center;
- border-top: 1px dashed rgba($red-400, 0.1);
+ border-top: 1px solid #4B364C40;
display: flex;
- padding: 10px 0;
+ padding: 16px 14px;
flex-wrap: wrap-reverse;
.collapsible-header {
flex-grow: 1;
@@ -184,10 +190,8 @@ $code-border-radius: 4px;
.collapsible-header-text {
color: $red-100;
}
- .collapsible-indicator {
- .icon-dark {
- stroke: $red-200;
- }
+ .err-collapsible-indicator path {
+ stroke: $red-200;
}
}
@@ -201,13 +205,9 @@ $code-border-radius: 4px;
color: $red-300;
font-size: 14px;
font-weight: 500;
- }
-
- .collapsible-indicator {
- line-height: 18px;
- .icon-dark {
- stroke: $red-400;
- }
+ display: inline-flex;
+ gap: 2px;
+ align-items: center;
}
}
}
@@ -264,7 +264,7 @@ $code-border-radius: 4px;
// ensure empty lines still take up vertical space
&:empty:before {
- content: ' ';
+ content: " ";
}
}
}
@@ -299,4 +299,3 @@ $code-border-radius: 4px;
}
}
}
-
diff --git a/packages/reporter/src/errors/test-error.tsx b/packages/reporter/src/errors/test-error.tsx
index a4cbb6d1224e..57acf85d2dfa 100644
--- a/packages/reporter/src/errors/test-error.tsx
+++ b/packages/reporter/src/errors/test-error.tsx
@@ -16,6 +16,7 @@ import { formattedMessage } from '../commands/command'
import WarningIcon from '@packages/frontend-shared/src/assets/icons/warning_x8.svg'
import TerminalIcon from '@packages/frontend-shared/src/assets/icons/technology-terminal_x16.svg'
+import { IconChevronRightMedium } from '@cypress-design/react-icon'
interface DocsUrlProps {
url: string | string[]
@@ -70,6 +71,12 @@ const TestError: React.FC
= ({ err, groupLevel = 0, testId, comm
}
}
+ const _header =
+ <>
+
+ Stack trace
+ >
+
return (
@@ -90,25 +97,26 @@ const TestError: React.FC = ({ err, groupLevel = 0, testId, comm
{codeFrame &&
}
{err.stack &&
-
- events.emit('show:error', { err, groupLevel, testId, commandId }))}
- role='button'
- tabIndex={0}
- >
-
Print to console
-
-
- }
- contentClass='runnable-err-stack-trace'
- >
-
-
+
+ events.emit('show:error', { err, groupLevel, testId, commandId }))}
+ role='button'
+ tabIndex={0}
+ >
+
Print to console
+
+
+ }
+ contentClass='runnable-err-stack-trace'
+ >
+
+
}
diff --git a/packages/reporter/src/header/DebugDismiss.scss b/packages/reporter/src/header/DebugDismiss.scss
index 237763b33db5..f83efe2be234 100644
--- a/packages/reporter/src/header/DebugDismiss.scss
+++ b/packages/reporter/src/header/DebugDismiss.scss
@@ -1,15 +1,16 @@
.debug-dismiss {
- display: flex !important;
+ display: flex;
align-items: center;
- font-size: 12px !important;
- font-weight: 600 !important;
- line-height: 16px !important;
- color: #9AA2FC !important;
- border: solid 1px #9AA2FC !important;
- border-radius: 16px !important;
+ font-size: 12px;
+ font-weight: 600;
+ line-height: 16px;
+ color: $indigo-300;
+ border: solid 1px $indigo-300;
+ border-radius: 16px;
gap: 4px;
+ padding: 4px 8px;
.delete-icon path {
- fill: #9AA2FC
+ fill: $indigo-300;
}
}
\ No newline at end of file
diff --git a/packages/reporter/src/header/controls.tsx b/packages/reporter/src/header/controls.tsx
index 64736b8b48f6..e9aea6420e1a 100755
--- a/packages/reporter/src/header/controls.tsx
+++ b/packages/reporter/src/header/controls.tsx
@@ -10,10 +10,11 @@ import type { AppState } from '../lib/app-state'
import ChevronDownIcon from '@packages/frontend-shared/src/assets/icons/chevron-down-small_x16.svg'
import ChevronUpIcon from '@packages/frontend-shared/src/assets/icons/chevron-up-small_x16.svg'
-import NextIcon from '@packages/frontend-shared/src/assets/icons/action-next_x16.svg'
-import PlayIcon from '@packages/frontend-shared/src/assets/icons/action-play_x16.svg'
-import RestartIcon from '@packages/frontend-shared/src/assets/icons/action-restart_x16.svg'
-import StopIcon from '@packages/frontend-shared/src/assets/icons/action-stop_x16.svg'
+import { IconActionNext, IconActionPlayLarge, IconActionRestart, IconActionStopCircle } from '@cypress-design/react-icon'
+
+const iconStrokeColor = 'gray-500'
+
+const iconFillColor = 'gray-900'
const ifThen = (condition: boolean, component: React.ReactNode) => (
condition ? component : null
@@ -32,7 +33,7 @@ const Controls: React.FC = observer(({ events = defaultEvents, appState }
}
return (
-
+
Open Testing Preferences} className='cy-tooltip'>
= observer(({ events = defaultEvents, appState }
)}
- {ifThen(appState.isPaused, (
-
Resume C } className='cy-tooltip'>
-
-
-
-
- ))}
- {ifThen(appState.isRunning && !appState.isPaused, (
-
Stop Running S } className='cy-tooltip' visible={appState.studioActive ? false : null}>
-
-
-
-
- ))}
- {ifThen(!appState.isRunning, (
-
Run All Tests R } className='cy-tooltip'>
-
- {appState.studioActive ? (
-
- ) : (
-
- )}
-
-
- ))}
- {ifThen(!!appState.nextCommandName, (
-
Next [N]: {appState.nextCommandName}} className='cy-tooltip'>
-
-
-
-
- ))}
+
+ {ifThen(appState.isPaused, (
+ Resume C } className='cy-tooltip'>
+
+
+
+
+ ))}
+ {ifThen(appState.isRunning && !appState.isPaused, (
+ Stop Running S } className='cy-tooltip' visible={appState.studioActive ? false : null}>
+
+
+
+
+ ))}
+ {ifThen(!appState.isRunning, (
+ Run All Tests R } className='cy-tooltip'>
+
+ {appState.studioActive ? (
+
+ ) : (
+
+ )}
+
+
+ ))}
+ {ifThen(!!appState.nextCommandName, (
+ Next [N]: {appState.nextCommandName}} className='cy-tooltip'>
+
+
+
+
+ ))}
+
)
})
diff --git a/packages/reporter/src/header/header.scss b/packages/reporter/src/header/header.scss
index 82d1417cc763..8bedf1b63791 100644
--- a/packages/reporter/src/header/header.scss
+++ b/packages/reporter/src/header/header.scss
@@ -1,87 +1,62 @@
$color-transition: color 150ms ease-out;
.reporter {
- header {
+ .spec-container {
background-color: $gray-1100;
display: flex;
flex-shrink: 0;
flex-wrap: wrap;
- gap: 8px;
+ gap: 13px;
font-family: $font-system;
min-height: $header-height;
outline: 0;
overflow: hidden;
- padding: 20px 16px;
+ padding: 16px;
width: 100%;
z-index: 1;
+ align-items: center;
.spacer {
flex-grow: 2;
}
- button {
- background-color: transparent;
- border-color: transparent;
- border-radius: 0;
- display: inline-block;
- font-weight: 300;
- line-height: 26px;
- outline: 0;
- padding: 0 8px;
- text-align: center;
+ // TODO: check the impact of removing this
+ // button {
+ // background-color: transparent;
+ // border-color: transparent;
+ // border-radius: 0;
+ // display: inline-block;
+ // font-weight: 300;
+ // line-height: 26px;
+ // outline: 0;
+ // padding: 0 8px;
+ // text-align: center;
- &:hover {
- background-color: $gray-900;
- }
+ // &:hover {
+ // background-color: $gray-900;
+ // }
- &[disabled],
- &[disabled]:hover,
- &[disabled]:active {
- background: none;
- box-shadow: none;
- color: $gray-500;
- }
- }
+ // &[disabled],
+ // &[disabled]:hover,
+ // &[disabled]:active {
+ // background: none;
+ // box-shadow: none;
+ // color: $gray-500;
+ // }
+ // }
}
- .toggle-specs-wrapper {
- display: flex;
- height: 24px;
-
- button {
- color: $gray-700;
- font-size: 16px;
- font-weight: 300;
- padding-left: 0;
- padding-right: 8px;
- transition: $color-transition;
- width: auto !important;
-
- &:focus,
- &:hover {
- background-color: initial;
-
- svg {
- color: $gray-400;
- transition: $color-transition;
- }
-
- .toggle-specs-text {
- color: $gray-400;
- transition: $color-transition;
- }
- }
-
- .toggle-specs-text {
- color: $gray-500;
- transition: $color-transition;
- }
-
- svg {
- margin-right: 8px;
- margin-bottom: -2px;
- }
- }
+ .statsAndControls {
+ display: inline-flex;
+ width: 100%;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 16px;
+ border: 1px solid $gray-900;
+ border-left: none;
+ flex-wrap: wrap;
+ gap: 4px;
+ min-height: 64px;
}
.stats {
@@ -90,29 +65,21 @@ $color-transition: color 150ms ease-out;
border-radius: 4px;
display: flex;
flex-wrap: wrap;
- height: 24px;
+ min-height: 24px;
justify-content: space-between;
- padding: 0 4px;
- min-width: 124px;
li {
display: flex;
- font-size: 12px;
- font-weight: 600;
+ align-items: center;
+ font-size: 14px;
+ font-weight: 700;
list-style-type: none;
- padding: 0 4px;
-
- svg {
- margin-right: 2px;
- }
+ padding: 2px 8px;
+ gap: 4px;
+ line-height: 20px;
.num {
- color: $white;
- line-height: 12px;
- vertical-align: text-top;
- min-width: 16px;
- display: inline-block;
- text-align: center;
+ color: $gray-400;
&.empty {
color: $gray-800;
@@ -121,19 +88,52 @@ $color-transition: color 150ms ease-out;
}
}
- .controls {
+ @mixin control-container-styles($size) {
+ height: $size;
+
+ .testing-preferences-toggle {
+ height: $size;
+ width: $size;
+ }
+
+ .controls {
+ height: $size;
+
+ span button {
+ width: $size;
+ }
+ }
+ }
+
+ .controls-container-studio {
+ // TODO: change this to 32px for the studio redesign
+ @include control-container-styles(24px);
+ }
+
+ .controls-container {
+ @include control-container-styles(24px);
+ }
+
+ .controls-container,
+ .controls-container-studio {
+ display: inline-flex;
align-items: center;
- border: 1px solid $gray-900;
- border-radius: 4px;
- display: flex;
- flex-wrap: wrap;
justify-content: center;
- height: 24px;
+ gap: 4px;
+ flex-wrap: wrap;
+
+ span {
+ height: 100%;
+ }
.testing-preferences-toggle {
- border-left: none;
- color: $gray-700;
- margin-left: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ color: $gray-500;
+ border: 1px solid $gray-900;
+ border-radius: 4px;
&.open {
background-color: $gray-900;
@@ -141,20 +141,133 @@ $color-transition: color 150ms ease-out;
}
}
- span {
- height: 100%;
+ .controls {
+ align-items: center;
+ border: 1px solid $gray-900;
+ border-radius: 4px;
+ display: flex;
+ justify-content: center;
+
+ span {
+ button {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100%;
+ color: $gray-400;
+ padding: 0px;
+ border-left: 1px solid $gray-900;
+ margin-left: -1px;
+ }
+
+ &:first-child {
+ button {
+ border-left: none;
+ }
+ }
+
+ &:last-child {
+ button {
+ border-right: none;
+ }
+ }
+ }
+ }
+ }
+
+ .runnable-header {
+ @include inner-header;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 0;
+ position: unset;
+ line-height: 20px;
+ min-width: 0;
+ align-items: center;
+ flex: 1;
- button {
+ .runnable-header-file-name {
+ display: inline-flex;
+ align-items: center;
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ position: relative;
+ background: $gray-1100;
+
+ .open-in-ide-button {
+ opacity: 0;
+ position: absolute;
+ top: 0;
+ right: 0;
+ gap: 4px;
+ color: $gray-300;
+ background: inherit;
display: flex;
- justify-content: center;
align-items: center;
- height: 100%;
- color: $gray-400;
- width: 31px;
- padding: 0px;
- border-left: 1px solid $gray-900;
- margin-left: -1px;
+
+ &:before {
+ content: "";
+ position: absolute;
+ width: 22px;
+ left: -23px;
+ top: 0;
+ height: 100%;
+ opacity: 0.5;
+ background: inherit;
+ }
+
+ &:focus-visible {
+ opacity: 1;
+ }
+ }
+
+ &:hover {
+ .open-in-ide-button {
+ opacity: 1;
+ }
+ }
+ }
+
+ span > span > a > svg {
+ margin-bottom: -2px;
+ margin-right: 8px;
+ }
+
+ a,
+ a:active,
+ a:focus,
+ a:hover {
+ color: $gray-500;
+ font-weight: 300;
+
+ strong {
+ color: $white;
+ font-weight: 500;
}
}
+
+ .duration {
+ border: 1px solid $gray-900;
+ border-radius: 18px;
+ color: $gray-400;
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 20px;
+ padding: 0 8px;
+ font-variant-numeric: tabular-nums;
+ }
+ }
+
+ .toggle-specs-wrapper {
+ .toggle-specs-button {
+ padding: 0;
+ width: 32px;
+ justify-content: center;
+ color: $gray-500;
+ }
}
}
diff --git a/packages/reporter/src/header/header.tsx b/packages/reporter/src/header/header.tsx
index 678d347f21de..7d97924865a9 100644
--- a/packages/reporter/src/header/header.tsx
+++ b/packages/reporter/src/header/header.tsx
@@ -2,50 +2,56 @@ import { observer } from 'mobx-react'
import React from 'react'
// @ts-ignore
import Tooltip from '@cypress/react-tooltip'
-
-import MenuExpandRightIcon from '@packages/frontend-shared/src/assets/icons/menu-expand-right_x16.svg'
-
+import Button from '@cypress-design/react-button'
import defaultEvents, { Events } from '../lib/events'
import type { AppState } from '../lib/app-state'
import { action } from 'mobx'
-
-import Controls from './controls'
-import Stats from './stats'
import type { StatsStore } from './stats-store'
-import { DebugDismiss } from './DebugDismiss'
import type { RunnablesStore } from '../runnables/runnables-store'
+import RunnableHeader from '../runnables/runnable-header'
+import MenuExpandRightIcon from '@packages/frontend-shared/src/assets/icons/menu-expand-right_x16.svg'
+import Stats from './stats'
+import Controls from './controls'
export interface ReporterHeaderProps {
appState: AppState
events?: Events
statsStore: StatsStore
runnablesStore: RunnablesStore
+ spec?: Cypress.Cypress['spec']
}
-const Header: React.FC
= observer(({ appState, events = defaultEvents, statsStore, runnablesStore }: ReporterHeaderProps) => (
-
- {appState.isSpecsListOpen ? 'Collapse' : 'Expand'} Specs List F } wrapperClassName='toggle-specs-wrapper' className='cy-tooltip'>
- {
- action('toggle:spec:list', () => {
- appState.toggleSpecList()
- events.emit('save:state')
- })()
- }
- }>
-
-
- Specs
-
-
-
- {runnablesStore.testFilter && runnablesStore.totalTests > 0 && }
-
-
+const Header: React.FC = observer(({ appState, events = defaultEvents, statsStore, runnablesStore, spec }: ReporterHeaderProps) => {
+ return
+
+
{appState.isSpecsListOpen ? 'Collapse' : 'Expand'} Specs List F } wrapperClassName='toggle-specs-wrapper' className='cy-tooltip'>
+
+ {
+ action('toggle:spec:list', () => {
+ appState.toggleSpecList()
+ events.emit('save:state')
+ })()
+ }
+ }>
+
+
+
+
+ {spec &&
}
+
+
+
+
+
-))
+})
Header.displayName = 'Header'
diff --git a/packages/reporter/src/header/stats.tsx b/packages/reporter/src/header/stats.tsx
index 0273fd5cf593..baed8414007c 100644
--- a/packages/reporter/src/header/stats.tsx
+++ b/packages/reporter/src/header/stats.tsx
@@ -3,10 +3,7 @@ import { observer } from 'mobx-react'
import React from 'react'
import type { StatsStore } from './stats-store'
-
-import FailedIcon from '@packages/frontend-shared/src/assets/icons/status-failed_x12.svg'
-import PassedIcon from '@packages/frontend-shared/src/assets/icons/status-passed_x12.svg'
-import PendingIcon from '@packages/frontend-shared/src/assets/icons/status-pending_x12.svg'
+import { IconStatusFailedSimple, IconStatusPassedSimple, IconStatusSkippedOutline } from '@cypress-design/react-icon'
const count = (num: number) => num > 0 ? num : '--'
@@ -17,17 +14,17 @@ interface Props {
const Stats: React.FC = observer(({ stats }: Props) => (
-
+
Passed:
{count(stats.numPassed)}
-
+
Failed:
{count(stats.numFailed)}
-
+
Pending:
{count(stats.numPending)}
diff --git a/packages/reporter/src/hooks/hooks.scss b/packages/reporter/src/hooks/hooks.scss
index 84b011bcaf0f..5b72e0070b05 100644
--- a/packages/reporter/src/hooks/hooks.scss
+++ b/packages/reporter/src/hooks/hooks.scss
@@ -1,13 +1,5 @@
.reporter {
.hooks-container {
- .hook-item {
- margin-bottom: 5px;
-
- &:last-of-type {
- margin-bottom: 0;
- }
- }
-
.hook-header {
font-family: $font-system;
display: flex;
@@ -25,11 +17,12 @@
.collapsible-header {
text-transform: uppercase;
color: $gray-400;
- display: inline-block;
+ display: inline-flex;
+ align-items: center;
flex-grow: 1;
- font-size: 11px;
+ font-size: 12px;
cursor: pointer;
- padding: 4px 0;
+ padding: 6px 0 6px 2px;
&:focus {
outline: none;
@@ -38,6 +31,11 @@
> .collapsible-header-inner:focus {
outline: 0;
}
+
+ .collapsible-header-inner {
+ display: flex;
+ align-items: center;
+ }
}
.hook-failed-message {
diff --git a/packages/reporter/src/instruments/instruments.scss b/packages/reporter/src/instruments/instruments.scss
index bf222b365f6f..a0404d8303e5 100644
--- a/packages/reporter/src/instruments/instruments.scss
+++ b/packages/reporter/src/instruments/instruments.scss
@@ -1,5 +1,7 @@
.reporter {
.instruments-container {
+ margin: 0 8px;
+
.instrument-content {
background-color: $reporter-section-background;
border-left: 1px solid $reporter-section-background;
@@ -10,6 +12,10 @@
padding: 0 2px 0 12px;
}
+ > .hooks-container > .hook-item > .collapsible {
+ margin-bottom: 4px;
+ }
+
.instrument-content h3,
h2,
h1:first-child {
@@ -21,7 +27,7 @@
td {
font-family: $monospace;
- font-size: 11px;
+ font-size: 12px;
}
}
diff --git a/packages/reporter/src/lib/base.scss b/packages/reporter/src/lib/base.scss
index 04dcdb629809..e83685b17f55 100644
--- a/packages/reporter/src/lib/base.scss
+++ b/packages/reporter/src/lib/base.scss
@@ -56,23 +56,24 @@ body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input
font-style: italic;
}
- button {
- background: $black;
- border: none;
- border-radius: 2px;
- color: $gray-200;
- cursor: pointer;
- display: inline-block;
- font-size: 14px;
- font-weight: 500;
- line-height: 1.4;
- padding: 10px 16px;
- text-align: center;
- touch-action: manipulation;
- vertical-align: middle;
- white-space: nowrap;
- user-select: none;
- }
+ // TODO: check the impact of removing this
+ // button {
+ // background: $black;
+ // border: none;
+ // border-radius: 2px;
+ // color: $gray-200;
+ // cursor: pointer;
+ // display: inline-block;
+ // font-size: 14px;
+ // font-weight: 500;
+ // line-height: 1.4;
+ // padding: 10px 16px;
+ // text-align: center;
+ // touch-action: manipulation;
+ // vertical-align: middle;
+ // white-space: nowrap;
+ // user-select: none;
+ // }
h1, h2, h3, h4, h5, h6 {
font-weight: 300;
diff --git a/packages/reporter/src/lib/mixins.scss b/packages/reporter/src/lib/mixins.scss
index a8f51eb474f8..a6a80ca6470a 100644
--- a/packages/reporter/src/lib/mixins.scss
+++ b/packages/reporter/src/lib/mixins.scss
@@ -1,7 +1,8 @@
@mixin inner-header {
background: $gray-1100;
display: block;
- font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue",
+ sans-serif;
font-size: 14px;
line-height: 24px;
overflow-wrap: break-word;
@@ -14,7 +15,7 @@
&:before,
&:after {
background-color: $gray-900;
- content: '';
+ content: "";
left: 16px;
position: absolute;
width: calc(100% - 32px);
@@ -32,12 +33,13 @@
@mixin gutter-alignment {
flex-shrink: 0;
- min-height: 1px; // because some numbers are empty
- max-height: 28px; // because some numbers are empty
+ height: 28px; // because some numbers are empty
padding-top: 4px;
padding-bottom: 4px;
- text-align: right;
width: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
@mixin command-info-padding {
@@ -50,4 +52,4 @@
width: 18px;
min-width: 18px;
max-width: 18px;
-}
\ No newline at end of file
+}
diff --git a/packages/reporter/src/lib/state-icon.tsx b/packages/reporter/src/lib/state-icon.tsx
index 1da90dafa3fa..927e6d9bb436 100644
--- a/packages/reporter/src/lib/state-icon.tsx
+++ b/packages/reporter/src/lib/state-icon.tsx
@@ -3,30 +3,29 @@ import { observer } from 'mobx-react'
import React from 'react'
import type { TestState } from '@packages/types'
-import FailedIcon from '@packages/frontend-shared/src/assets/icons/status-failed_x12.svg'
-import PassedIcon from '@packages/frontend-shared/src/assets/icons/status-passed_x12.svg'
-import PendingIcon from '@packages/frontend-shared/src/assets/icons/status-pending_x12.svg'
-import ProcessingIcon from '@packages/frontend-shared/src/assets/icons/status-processing_x12.svg'
-import RunningIcon from '@packages/frontend-shared/src/assets/icons/status-running_x12.svg'
import WandIcon from '@packages/frontend-shared/src/assets/icons/object-magic-wand-dark-mode_x16.svg'
+import { IconStatusFailedSimple, IconStatusPassedSimple, IconStatusQueuedOutline, IconStatusQueuedSimple, IconStatusRunningOutline, IconStatusRunningSimple, IconStatusSkippedOutline, IconStatusSkippedSimple } from '@cypress-design/react-icon'
-interface Props extends React.HTMLProps {
+interface Props extends React.SVGProps {
state: TestState
isStudio?: boolean
+ iconSize?: '8' | '12' | '16'
}
const StateIcon: React.FC = observer((props: Props) => {
- const { state, isStudio, ...rest } = props
+ const { state, isStudio, ref, iconSize, ...rest } = props
if (state === 'active') {
return (
-
+ iconSize === '8' ?
+ :
+
)
}
if (state === 'failed') {
return (
-
+
)
}
@@ -38,24 +37,39 @@ const StateIcon: React.FC = observer((props: Props) => {
}
return (
-
+
)
}
+ // pending is really skipped
if (state === 'pending') {
return (
-
+ iconSize === '8' ?
+ :
+
)
}
+ // processing is really queued
if (state === 'processing') {
return (
-
+ iconSize === '8' ?
+ :
+
)
}
+ // TODO mabel i need to double check if it's this icon or the queued one
return (
-
+ iconSize === '8' ?
+ :
+
)
})
diff --git a/packages/reporter/src/lib/util.ts b/packages/reporter/src/lib/util.ts
index dbef553a2b60..226db711e508 100644
--- a/packages/reporter/src/lib/util.ts
+++ b/packages/reporter/src/lib/util.ts
@@ -1,12 +1,5 @@
import type { KeyboardEvent } from 'react'
-const INDENT_BASE = 5
-const INDENT_AMOUNT = 15
-
-function indent (level: number) {
- return INDENT_BASE + level * INDENT_AMOUNT
-}
-
// Returns a keyboard handler that invokes the provided function when either enter or space is pressed
const onEnterOrSpace = (f: (() => void)) => {
return (e: KeyboardEvent) => {
@@ -74,6 +67,5 @@ const getFilenameParts = (spec: string): [string, string] => {
export {
formatDuration,
getFilenameParts,
- indent,
onEnterOrSpace,
}
diff --git a/packages/reporter/src/main.tsx b/packages/reporter/src/main.tsx
index fd48db362bb2..4758477bc1a2 100644
--- a/packages/reporter/src/main.tsx
+++ b/packages/reporter/src/main.tsx
@@ -47,12 +47,12 @@ export interface BaseReporterProps {
runnerStore: MobxRunnerStore
}
-export interface SingleReporterProps extends BaseReporterProps{
+export interface SingleReporterProps extends BaseReporterProps {
runMode?: 'single'
}
// In React Class components (now deprecated), we used to use appState as a default prop. Now since defaultProps are not supported in functional components, we can use ES6 default params to accomplish the same thing
-const Reporter: React.FC = observer(({ appState = appStateDefault, runner, className, error, runMode = 'single', studioEnabled, autoScrollingEnabled, isSpecsListOpen, resetStatsOnSpecChange, renderReporterHeader = (props: ReporterHeaderProps) => , runnerStore }) => {
+const Reporter: React.FC = observer(({ appState = appStateDefault, runner, className, error, runMode = 'single', studioEnabled, autoScrollingEnabled, isSpecsListOpen, resetStatsOnSpecChange, renderReporterHeader = (props: ReporterHeaderProps) => , runnerStore }) => {
const previousSpecRunId = usePrevious(runnerStore.specRunId)
const [isMounted, setIsMounted] = useState(false)
const [isInitialized, setIsInitialized] = useState(false)
@@ -101,7 +101,7 @@ const Reporter: React.FC = observer(({ appState = appStateD
runnablesStore.setRunningSpec(runnerStore.spec.relative)
if (
resetStatsOnSpecChange &&
- runnerStore.specRunId !== previousSpecRunId
+ runnerStore.specRunId !== previousSpecRunId
) {
statsStore.reset()
}
@@ -112,7 +112,7 @@ const Reporter: React.FC = observer(({ appState = appStateD
'studio-active': appState.studioActive,
'mounted': isMounted,
})}>
- {renderReporterHeader({ appState, statsStore, runnablesStore })}
+ {renderReporterHeader({ appState, statsStore, runnablesStore, spec: runnerStore.spec })}
{appState?.isPreferencesMenuOpen ? (
) : (
diff --git a/packages/reporter/src/preferences/testing-preferences.scss b/packages/reporter/src/preferences/testing-preferences.scss
index a6ba68c2bd80..67c3ad6219db 100644
--- a/packages/reporter/src/preferences/testing-preferences.scss
+++ b/packages/reporter/src/preferences/testing-preferences.scss
@@ -4,6 +4,10 @@
font-size: 16px;
font-weight: 400;
color: $gray-700;
+
+ &::before {
+ width: 0;
+ }
}
.testing-preference {
@@ -19,4 +23,4 @@
margin-bottom: 8px;
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/reporter/src/runnables/runnable-and-suite.tsx b/packages/reporter/src/runnables/runnable-and-suite.tsx
index ec006923a97e..08a371218622 100644
--- a/packages/reporter/src/runnables/runnable-and-suite.tsx
+++ b/packages/reporter/src/runnables/runnable-and-suite.tsx
@@ -3,17 +3,22 @@ import _ from 'lodash'
import { observer } from 'mobx-react'
import React, { MouseEvent, useCallback } from 'react'
-import { indent } from '../lib/util'
-
import appState, { AppState } from '../lib/app-state'
import events, { Events } from '../lib/events'
import Test from '../test/test'
-import Collapsible from '../collapsible/collapsible'
+import Collapsible, { CollapsibleHeaderComponentProps } from '../collapsible/collapsible'
import type SuiteModel from './suite-model'
import type TestModel from '../test/test-model'
-import { LaunchStudioIcon } from '../components/LaunchStudioIcon'
+import { IconActionAddMedium, IconChevronDownMedium, IconObjectStackFailed, IconObjectStackPassed, IconObjectStackQueued, IconObjectStackRunning, IconObjectStackSkipped, WindiColor } from '@cypress-design/react-icon'
+import Button from '@cypress-design/react-button'
+import { RunnableArray } from './runnables-store'
+
+// should only show connection dots if the current runnable is a test and the next runnable is a test and is not the last runnable
+export const shouldShowConnectionDots = (runnables: RunnableArray, runnable: SuiteModel | TestModel, runnableIndex: number) => {
+ return runnable.type === 'test' && runnableIndex !== runnables.length - 1 && runnables[runnableIndex + 1].type === 'test'
+}
interface SuiteProps {
eventManager?: Events
@@ -22,6 +27,11 @@ interface SuiteProps {
canSaveStudioLogs: boolean
}
+const headerIconDefaultProps = {
+ fillColor: 'gray-900' as WindiColor,
+ strokeColor: 'gray-500' as WindiColor,
+}
+
const Suite: React.FC = observer(({ eventManager = events, model, studioEnabled, canSaveStudioLogs }: SuiteProps) => {
const _launchStudio = useCallback((e: MouseEvent) => {
e.preventDefault()
@@ -30,56 +40,91 @@ const Suite: React.FC = observer(({ eventManager = events, model, st
eventManager.emit('studio:init:suite', model.id)
}, [eventManager, model.id])
- const _header = () => (
- <>
- {model.title}
- {(studioEnabled && !appState.studioActive) && (
-
-
-
- )}
- >
- )
+ const getHeaderIcon = useCallback((isHovered: boolean, isFocused: boolean) => {
+ if (isHovered || isFocused) {
+ return
+ }
+
+ switch (model.state) {
+ case 'active':
+ return
+ case 'passed':
+ return
+ case 'failed':
+ return
+ case 'pending':
+ return
+ case 'processing':
+ return
+ default:
+ return <>>
+ }
+ }, [model.state])
+
+ const HeaderComponent = ({ isHovered, isFocused }: CollapsibleHeaderComponentProps) => {
+ return (
+ <>
+
+ {getHeaderIcon(isHovered, isFocused)}
+
+ {model.title}
+ {(studioEnabled && !appState.studioActive) && (
+
+
+ New Test
+
+ )}
+ >
+ )
+ }
+
+ let runnablesList =
+ {_.map(model.children, (runnable, index) => {
+ return ( )
+ })}
+
return (
-
-
- {_.map(model.children, (runnable) =>
- ( ))}
-
-
+ // we don't want to show the collapsible if there are no tests in the suite
+ model.children && !model.children.some((c) => c.type === 'test') ? runnablesList : (
+
+ {runnablesList}
+
+ )
)
})
Suite.displayName = 'Suite'
export interface RunnableProps {
+ appState?: AppState
model: TestModel | SuiteModel
- appState: AppState
studioEnabled: boolean
canSaveStudioLogs: boolean
+ shouldShowConnectingDots: boolean
}
// NOTE: some of the driver tests dig into the React instance for this component
// in order to mess with its internal state. converting it to a functional
// component breaks that, so it needs to stay a Class-based component or
// else the driver tests need to be refactored to support it being functional
-const Runnable: React.FC = observer(({ appState: appStateProps = appState, model, studioEnabled, canSaveStudioLogs }) => {
- return (
+const Runnable: React.FC = observer(({ appState: appStateProps = appState, model, studioEnabled, canSaveStudioLogs, shouldShowConnectingDots }) => {
+ return (<>
= observer(({ appState: appStateProps =
>
{model.type === 'test'
?
- : }
+ : }
+ {shouldShowConnectingDots &&
}
+ >
)
})
diff --git a/packages/reporter/src/runnables/runnable-header.tsx b/packages/reporter/src/runnables/runnable-header.tsx
index b47eac8a3a78..8002c74f5f67 100644
--- a/packages/reporter/src/runnables/runnable-header.tsx
+++ b/packages/reporter/src/runnables/runnable-header.tsx
@@ -3,16 +3,21 @@ import React, { ReactElement } from 'react'
import type { StatsStore } from '../header/stats-store'
import { formatDuration, getFilenameParts } from '../lib/util'
-import FileNameOpener from '../lib/file-name-opener'
+import { RunnablesStore } from './runnables-store'
+import { DebugDismiss } from '../header/DebugDismiss'
+import Button from '@cypress-design/react-button'
+import events from '../lib/events'
+import { IconWindowCodeEditor } from '@cypress-design/react-icon'
const renderRunnableHeader = (children: ReactElement) => {children}
interface RunnableHeaderProps {
spec: Cypress.Cypress['spec']
statsStore: StatsStore
+ runnablesStore: RunnablesStore
}
-const RunnableHeader: React.FC = observer(({ spec, statsStore }) => {
+const RunnableHeader: React.FC = observer(({ spec, statsStore, runnablesStore }) => {
const relativeSpecPath = spec.relative
if (spec.relative === '__all') {
@@ -46,9 +51,17 @@ const RunnableHeader: React.FC = observer(({ spec, statsSto
relativeFile: relativeSpecPath,
}
+ const openInIDE = () => {
+ return events.emit('open:file:unified', fileDetails)}> Open in IDE
+ }
+
return renderRunnableHeader(
<>
-
+
+ {fileDetails.displayFile || fileDetails.originalFile}{!!fileDetails.line && `:${fileDetails.line}`}{!!fileDetails.column && `:${fileDetails.column}`}
+ {openInIDE()}
+
+ {runnablesStore.testFilter && runnablesStore.totalTests > 0 && }
{Boolean(statsStore.duration) && (
{formatDuration(statsStore.duration)}
)}
diff --git a/packages/reporter/src/runnables/runnable-model.ts b/packages/reporter/src/runnables/runnable-model.ts
index 12e2fb6fb484..f3f98acaeaa8 100644
--- a/packages/reporter/src/runnables/runnable-model.ts
+++ b/packages/reporter/src/runnables/runnable-model.ts
@@ -5,6 +5,7 @@ export interface RunnableProps {
id: string
title?: string
hooks: Array
+ parentTitle?: string
}
export default class Runnable {
@@ -12,6 +13,7 @@ export default class Runnable {
title?: string
level: number
hooks: Array = []
+ parentTitle?: string
constructor (props: RunnableProps, level: number) {
makeObservable(this, {
@@ -19,11 +21,13 @@ export default class Runnable {
title: observable,
level: observable,
hooks: observable,
+ parentTitle: observable,
})
this.id = props.id
this.title = props.title
this.level = level
this.hooks = props.hooks
+ this.parentTitle = props.parentTitle
}
}
diff --git a/packages/reporter/src/runnables/runnables-store.ts b/packages/reporter/src/runnables/runnables-store.ts
index ea04a6ff01d6..ff139c17f31a 100644
--- a/packages/reporter/src/runnables/runnables-store.ts
+++ b/packages/reporter/src/runnables/runnables-store.ts
@@ -126,7 +126,32 @@ export class RunnablesStore {
}
_createSuite (props: SuiteProps, level: number) {
- const suite = new SuiteModel(props, level)
+ // Get parent suite titles by traversing up the queue
+ const parentTitles: string[] = []
+
+ // Find the immediate parent suite by looking for the last suite at a lower level
+ let parentLevel = level - 1
+
+ for (let i = this._runnablesQueue.length - 1; i >= 0; i--) {
+ const runnable = this._runnablesQueue[i]
+
+ if ('type' in runnable && runnable.type === 'suite' && runnable.level === parentLevel && runnable.title) {
+ // Add this parent's title
+ parentTitles.unshift(runnable.title)
+ break
+ }
+ }
+
+ // Combine parent titles with current suite title
+ const hierarchicalTitle = [...parentTitles, props.title].join(' > ')
+
+ // Create new props with the hierarchical title
+ const suiteProps = {
+ ...props,
+ title: hierarchicalTitle,
+ }
+
+ const suite = new SuiteModel(suiteProps, level)
this._runnablesQueue.push(suite)
suite.children = this._createRunnableChildren(props, ++level)
diff --git a/packages/reporter/src/runnables/runnables.scss b/packages/reporter/src/runnables/runnables.scss
index 784ffe7e8873..365f9632ef2f 100644
--- a/packages/reporter/src/runnables/runnables.scss
+++ b/packages/reporter/src/runnables/runnables.scss
@@ -1,4 +1,18 @@
-.fa { &:not(.fa-spin) { animation: none; } }
+@mixin dotted-line {
+ content: "";
+ position: absolute;
+ left: 16px;
+ top: 75%;
+ height: 25%;
+ border-left: 1px dotted $gray-800;
+ z-index: 1;
+}
+
+.fa {
+ &:not(.fa-spin) {
+ animation: none;
+ }
+}
.reporter {
min-height: 0; // needed for firefox or else scrolling gets funky
@@ -10,14 +24,14 @@
}
.wrap {
- border-bottom: 1px solid $gray-900;
- margin-bottom: 40px;
padding-left: 0;
width: 100%;
}
.runnables {
padding-left: 0;
+ display: flex;
+ flex-direction: column;
}
.no-tests {
@@ -92,40 +106,53 @@
padding-left: 0;
.runnable-wrapper {
+ border-radius: 4px;
border-left: 4px solid transparent;
- padding: 0 0 0 4px;
+ padding: 0;
.collapsible-header {
- &:focus {
+ &:focus-visible {
+ outline: 0;
+
.collapsible-header-inner {
- background-color: $gray-1100;
+ background-color: $gray-900;
+ outline: 0;
cursor: pointer;
+
+ .runnable-title {
+ color: $indigo-300;
+ }
}
- }
- .collapsible-header-inner {
- &:hover {
- background-color: $gray-900;
- cursor: pointer;
+ .header-collapsible-indicator {
+ path {
+ stroke: $indigo-300;
+ }
}
- &:focus {
- outline: 0;
+ .launch-studio-button {
+ border-color: $gray-900;
+ background-color: $gray-900;
}
+ }
+ .collapsible-header-inner {
+ display: inline-flex;
+ align-items: center;
height: 100%;
- padding: 5px 15px 5px 5px;
width: 100%;
+ min-height: 36px;
+
+ &:focus {
+ outline: 0;
+ }
}
}
+ .runnable-controls-studio {
+ opacity: 0.5;
- &:hover {
- .runnable-controls-studio {
- opacity: 0.5;
-
- &:hover {
- opacity: 1;
- }
+ &:hover {
+ opacity: 1;
}
}
}
@@ -135,27 +162,38 @@
visibility: visible !important;
}
- .hooks-container, .runnable-err-wrapper {
+ .hooks-container,
+ .runnable-err-wrapper {
border-color: $gray-500;
}
}
- .runnable-state,.attempt-state {
+ .runnable-state,
+ .attempt-state {
display: inline-block;
line-height: 18px;
margin-right: 5px;
min-width: 12px;
text-align: center;
- font-size: 11px;
+ font-size: 12px;
}
&.suite .collapsible-indicator {
- margin-left: 2px;
+ flex-shrink: 0;
+
.icon-dark {
- stroke: $gray-800;
+ stroke: $gray-400;
+ }
+ .icon-light {
+ fill: $gray-400;
}
}
+ &.runnable-processing > div > .runnable-wrapper,
+ &.runnable-processing > div > .runnable-instruments {
+ border-left: 4px solid $gray-700;
+ }
+
&.runnable-failed > div > .runnable-wrapper,
&.runnable-failed > div > .runnable-instruments {
border-left: 4px solid $fail;
@@ -190,13 +228,172 @@
&.runnable-skipped > div > .runnable-wrapper,
&.runnable-skipped > div > .runnable-instruments {
+ border: 1px solid $gray-950;
border-left: 4px solid $gray-500;
}
+ &.suite.runnable {
+ margin-top: 16px;
+ }
+
+ &.suite.runnable:first-child {
+ margin-top: 4px;
+ }
&.suite > div > .runnable-wrapper {
+ border: 0;
+ margin: 0;
+ border-radius: 0;
+ background-color: $gray-1100;
+
+ .collapsible-header-inner {
+ padding: 8px;
+ background-color: $gray-1100;
+ }
+
.runnable-title {
- color: $gray-50;
- font-size: 13px;
+ color: $gray-400;
+ font-size: 14px;
+ }
+
+ .runnable-and-suite-header-icon {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .icon-dark-secondary-red-400 {
+ :nth-child(4) {
+ stroke: $red-400;
+ }
+ }
+
+ .collapsible-header-inner {
+ .launch-studio-button {
+ color: $gray-300;
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ flex-shrink: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+ opacity: 0;
+ background: inherit;
+
+ &.should-show {
+ opacity: 1;
+
+ &:before {
+ content: "";
+ position: absolute;
+ width: 20px;
+ left: -21px;
+ top: 0;
+ height: 100%;
+ opacity: 0.5;
+ background: inherit;
+ }
+ }
+ }
+ }
+ }
+
+ &.suite > .collapsible {
+ &.is-open > .runnable-wrapper > .collapsible-header > .collapsible-header-inner {
+ position: relative;
+
+ &::before {
+ @include dotted-line;
+ }
+ }
+ }
+
+ &.test {
+ > .collapsible {
+ &.is-open {
+ border-radius: 4px;
+ border: 3px solid #43486159;
+ background-color: $reporter-section-background;
+ }
+
+ .collapsible-header-inner {
+ padding: 8px 8px 8px 4px;
+ }
+ }
+
+ .collapsible {
+ display: flex;
+ flex-direction: column;
+
+ &.is-open {
+ .runnable-wrapper {
+ border-bottom-left-radius: 0;
+
+ .collapsible-header {
+ border: 1px solid $gray-900;
+ }
+
+ .collapsible-header-inner {
+ background-color: $reporter-section-background;
+ }
+ }
+ }
+
+ .runnable-wrapper {
+ background-color: $gray-1000;
+
+ &:hover {
+ .collapsible-header {
+ border-top: 1px solid $gray-900;
+ border-bottom: 1px solid $gray-900;
+ border-right: 1px solid $gray-900;
+ }
+
+ .collapsible-header-inner {
+ background-color: $gray-1100;
+ }
+ }
+
+ .collapsible-header {
+ border: 1px solid $gray-950;
+ border-radius: 4px;
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0;
+
+ &:focus-visible {
+ border-top: 1px solid $gray-800;
+ border-bottom: 1px solid $gray-800;
+ border-right: 1px solid $gray-800;
+
+ .collapsible-header-inner {
+ background-color: $gray-900;
+ }
+ }
+ }
+
+ .collapsible-header-inner {
+ background-color: $gray-1000;
+ width: 100%;
+ }
+ }
+ }
+ .runnable-commands-region {
+ margin: 0 8px;
+
+ .hooks-container {
+ display: flex;
+ gap: 4px;
+ flex-direction: column;
+ width: 100%;
+ }
+ }
+
+ &.runnable-active > .collapsible {
+ > .runnable-wrapper,
+ > .runnable-instruments {
+ border-left: 4px solid $indigo-400;
+ }
}
}
@@ -204,7 +401,8 @@
.studio-controls {
display: flex;
- .studio-save, .studio-copy {
+ .studio-save,
+ .studio-copy {
display: block;
}
}
@@ -232,14 +430,6 @@
.runnable-state-icon {
flex-shrink: 0;
- margin-right: 5px;
- margin-top: 4px;
-
- &.fa-spin {
- .icon-light {
- stroke: $gray-800;
- }
- }
&.wand-icon {
.icon-light {
@@ -250,20 +440,21 @@
}
}
+ .runnable-dotted-line {
+ margin-left: 16px;
+ height: 4px;
+ border-left: 1px dotted $gray-800;
+ }
+
.runnable-instruments {
border-left: 4px solid transparent;
- padding-bottom: 5px;
}
.runnable-title {
+ color: $white;
font-family: $font-system;
- font-size: 12.5px;
- min-width: $reporter-contents-min-width;
+ font-size: 14px;
white-space: pre-line;
-
- &:focus {
- outline: 1px dotted $gray-400;
- }
}
.runnable-wrapper > .collapsible-header {
@@ -271,24 +462,13 @@
position: relative;
display: inline-flex;
width: 100%;
-
- &:focus {
- outline: 1px dotted $gray-400;
- outline-offset: 3px;
- }
}
- .suite > div .runnable-wrapper,
- .test .runnable-wrapper > .collapsible-header {
- text-overflow: ellipsis;
- white-space: nowrap;
- overflow: hidden;
+ .wrap > .runnables {
+ padding: 0 8px;
}
.runnable-controls {
- float: right;
- height: 18px;
-
label {
padding: 2px 4px;
}
@@ -301,62 +481,52 @@
}
.runnable-controls-studio {
- color: $purple-300;
+ display: flex;
+ height: 20px;
+ width: 20px;
+ color: $gray-500;
+ border: 1px solid $gray-900;
+ border-radius: 4px;
opacity: 0;
+ padding-top: 2px;
+ padding-left: 1px;
+
+ svg {
+ flex: auto;
+ }
}
}
- .test.runnable-failed .runnable-controls .runnable-controls-status {
- visibility: visible;
+ // don't show the dotted line on the last test
+ // since sometimes tests are contained within a collapsible with other collapsibles
+ :nth-last-child(1 of .test) {
+ &::before {
+ content: none !important;
+ }
}
- .test .collapsible {
- display: flex;
- flex-direction: column;
-
- .runnable-wrapper .collapsible-header-inner {
- width: 100%;
-
- .collapsible-header-text {
- display: flex;
-
- .runnable-title {
- flex-grow: 1;
- padding-right: 10px;
- }
- }
- }
+ .test.runnable-failed .runnable-controls .runnable-controls-status {
+ visibility: visible;
}
.collapsible-header {
display: flex;
}
- .runnable-header {
- @include inner-header;
-
- span > span > a > svg {
- margin-bottom: -2px;
- margin-right: 8px;
- }
+ .collapsible-header-text {
+ display: flex;
+ width: 100%;
+ gap: 8px;
+ align-items: center;
+ position: relative;
+ background: inherit;
- a, a:active, a:focus, a:hover {
- color: $gray-700;
- font-weight: 300;
- strong {
- font-weight: 500;
- }
+ svg {
+ flex-shrink: 0;
}
- .duration {
- border: 1px solid $gray-900;
- border-radius: 16px;
- color: $gray-600;
- float: right;
- font-size: 12px;
- line-height: 16px;
- padding: 2px 6px;
- font-variant-numeric: tabular-nums;
+ .runnable-title {
+ flex-grow: 1;
}
}
@@ -399,7 +569,8 @@
font-size: 16px;
padding: 4px 10px 2px;
- &:hover, &:focus {
+ &:hover,
+ &:focus {
background-color: $indigo-100;
}
@@ -439,7 +610,7 @@
.runnable-loading {
font-family: $font-system;
-
+
.runnable-loading-animation {
display: flex;
margin: 3.5rem auto 1.5rem;
@@ -456,32 +627,35 @@
}
div:nth-child(1) {
- animation-delay:0.1s;
+ animation-delay: 0.1s;
background: $jade-400;
}
div:nth-child(2) {
- animation-delay:0.2s;
+ animation-delay: 0.2s;
background: $indigo-400;
}
div:nth-child(3) {
- animation-delay:0.3s;
+ animation-delay: 0.3s;
background: $red-400;
}
div:nth-child(4) {
- animation-delay:0.4s;
+ animation-delay: 0.4s;
background: $orange-400;
}
div:nth-child(5) {
- animation-delay:0.5s;
+ animation-delay: 0.5s;
background: $gray-400;
}
@keyframes scaling {
- 0%, 20%, 80%, 100% {
+ 0%,
+ 20%,
+ 80%,
+ 100% {
opacity: 100%;
transform: scale(0.5);
}
diff --git a/packages/reporter/src/runnables/runnables.tsx b/packages/reporter/src/runnables/runnables.tsx
index b343bc96771d..a8fd48652011 100644
--- a/packages/reporter/src/runnables/runnables.tsx
+++ b/packages/reporter/src/runnables/runnables.tsx
@@ -5,10 +5,9 @@ import React, { MouseEvent, useCallback, useEffect, useRef } from 'react'
import events, { Events } from '../lib/events'
import { RunnablesError, RunnablesErrorModel } from './runnable-error'
-import Runnable from './runnable-and-suite'
-import RunnableHeader from './runnable-header'
+import Runnable, { shouldShowConnectionDots } from './runnable-and-suite'
import type { RunnablesStore, RunnableArray } from './runnables-store'
-import statsStore, { StatsStore } from '../header/stats-store'
+import type { StatsStore } from '../header/stats-store'
import type { Scroller, UserScrollCallback } from '../lib/scroller'
import type { AppState } from '../lib/app-state'
import OpenFileInIDE from '../lib/open-file-in-ide'
@@ -48,7 +47,7 @@ const RunnablesEmptyState = ({ spec, studioEnabled, eventManager = events }: Run
No tests found.
Cypress could not detect tests in this file.
- { !isAllSpecs && (
+ {!isAllSpecs && (
<>
= observer(({ runnables, studioEnabled, canSaveStudioLogs }: RunnablesListProps) => (
-
-
- {_.map(runnables, (runnable) =>
- ( ))}
-
-
-))
+const RunnablesList: React.FC = observer(({ runnables, studioEnabled, canSaveStudioLogs }: RunnablesListProps) => {
+ return (
+
+
+ {_.map(runnables, (runnable, index) =>
+ ( ))}
+
+
+ )
+})
RunnablesList.displayName = 'RunnablesList'
@@ -182,7 +184,6 @@ const Runnables: React.FC = observer(({ appState, scroller, erro
return (
-
child.type === 'test')
+
+ return _.map(testChildren, 'state')
}
get hasRetried (): boolean {
return _.some(this.children, (v) => v.hasRetried)
}
- get _anyChildrenFailed () {
- return _.some(this._childStates, (state) => {
+ get _anyTestChildrenRunning () {
+ return _.some(this._testChildStates, (state) => {
+ return state === 'active'
+ })
+ }
+
+ get _anyTestChildrenFailed () {
+ return _.some(this._testChildStates, (state) => {
return state === 'failed'
})
}
- get _allChildrenPassedOrPending () {
- return !this._childStates.length || _.every(this._childStates, (state) => {
+ get _allTestChildrenPassedOrPending () {
+ return !this._testChildStates.length || _.every(this._testChildStates, (state) => {
return state === 'passed' || state === 'pending'
})
}
- get _allChildrenPending () {
- return !!this._childStates.length
- && _.every(this._childStates, (state) => {
+ get _allTestChildrenPending () {
+ return !!this._testChildStates.length
+ && _.every(this._testChildStates, (state) => {
return state === 'pending'
})
}
diff --git a/packages/reporter/src/test/test.tsx b/packages/reporter/src/test/test.tsx
index fb53087d8869..1deb4319980e 100644
--- a/packages/reporter/src/test/test.tsx
+++ b/packages/reporter/src/test/test.tsx
@@ -7,7 +7,6 @@ import cs from 'classnames'
import events, { Events } from '../lib/events'
import appState, { AppState } from '../lib/app-state'
import Collapsible from '../collapsible/collapsible'
-import { indent } from '../lib/util'
import TestModel from './test-model'
import scroller, { Scroller } from '../lib/scroller'
@@ -17,7 +16,6 @@ import { LaunchStudioIcon } from '../components/LaunchStudioIcon'
import CheckIcon from '@packages/frontend-shared/src/assets/icons/checkmark_x16.svg'
import ClipboardIcon from '@packages/frontend-shared/src/assets/icons/general-clipboard_x16.svg'
-import WarningIcon from '@packages/frontend-shared/src/assets/icons/warning_x16.svg'
interface StudioControlsProps {
events?: Events
@@ -137,16 +135,6 @@ const Test: React.FC = observer(({ model, events: eventsProps = event
const _controls = () => {
let controls: Array = []
- if (model.state === 'failed') {
- controls.push(
-
-
-
-
- ,
- )
- }
-
if (studioEnabled && !appStateProps.studioActive) {
controls.push(
= observer(({ model, events: eventsProps = event
containerRef={containerRef}
header={_header()}
headerClass='runnable-wrapper'
- headerStyle={{ paddingLeft: indent(model.level) }}
contentClass='runnable-instruments'
isOpen={model.isOpen}
onOpenStateChangeRequested={(isOpen: boolean) => model.setIsOpen(isOpen)}
hideExpander
>
-
+
_scrollIntoView()} />
- {appStateProps.studioActive && }
+ {appStateProps.studioActive && }
)
diff --git a/system-tests/projects/e2e/cypress/support/util.js b/system-tests/projects/e2e/cypress/support/util.js
index 1ab07814d030..c608d5337b31 100644
--- a/system-tests/projects/e2e/cypress/support/util.js
+++ b/system-tests/projects/e2e/cypress/support/util.js
@@ -37,7 +37,7 @@ export const verify = (title, ctx, options) => {
.contains(`FAIL - ${getTitle(title, ctx)}`)
.closest('.collapsible')
.within(() => {
- cy.contains('View stack trace').click()
+ cy.contains('Stack trace').click()
_.each([].concat(message), (msg) => {
cy.get('.runnable-err-message')
diff --git a/yarn.lock b/yarn.lock
index e2e471d2fc72..e24ca4c2e895 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2653,6 +2653,11 @@
tailwindcss "^3.4.3"
tailwindcss-hocus "^0.0.7"
+"@cypress-design/constants-button@^1.9.0":
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/@cypress-design/constants-button/-/constants-button-1.9.0.tgz#56683bb760b7eaab9857c7f7384c5ac55eafef18"
+ integrity sha512-4mNYfEdrUJUfC4uPwr7P3u0MFsxrffvf5CefbL1HbjwxdHSLT94PhUL70kOukkN1CkM2TwPOTffNZWFLARyQeQ==
+
"@cypress-design/constants-spinner@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@cypress-design/constants-spinner/-/constants-spinner-1.0.1.tgz#33904995ea95c34867905aa09391d5364ffcc129"
@@ -2682,12 +2687,34 @@
tailwindcss-hocus "^0.0.7"
"@cypress-design/icon-registry@^1.0.0", "@cypress-design/icon-registry@^1.18.0", "@cypress-design/icon-registry@^1.5.1":
- version "1.18.0"
- resolved "https://registry.yarnpkg.com/@cypress-design/icon-registry/-/icon-registry-1.18.0.tgz#799c5ac8f8362aebdcf2181119c3205136ca5ab9"
- integrity sha512-4goChP9rWVq7F/+c36JyJ4quvHSyI6gkjJ/IKFqncNwkC3gvVeJ4GQX2mqQJCQ+z0Er+2Mmzcw7JiVo1GpbJlg==
+ version "1.21.0"
+ resolved "https://registry.yarnpkg.com/@cypress-design/icon-registry/-/icon-registry-1.21.0.tgz#6f815fa1bffe40eab9507d9ec26fd9aff4a6ec62"
+ integrity sha512-zgbNhRCFmaby9QXpzGcT0u3ancmvPIq1bkFTSylChjgK2vvv+iQqmSnAZGrg7Zczd9McUUdTiXy0N1lzqbjAvA==
+ dependencies:
+ "@cypress-design/color-constants" "^1.1.0"
+
+"@cypress-design/icon-registry@^1.27.0":
+ version "1.27.0"
+ resolved "https://registry.yarnpkg.com/@cypress-design/icon-registry/-/icon-registry-1.27.0.tgz#a657acc40cc6b43e14ebf1a0e19eb65f43e08a76"
+ integrity sha512-2/jlv/0RsCwwZpovIk8sjXsbqnnNgmpYiaQUl9XUoZ45rurhr2PuOwYk1HMAkrBcv05adzWLfiEtfzb5TwUG1w==
dependencies:
"@cypress-design/color-constants" "^1.1.0"
+"@cypress-design/react-button@^1.10.1":
+ version "1.10.1"
+ resolved "https://registry.yarnpkg.com/@cypress-design/react-button/-/react-button-1.10.1.tgz#31328d00789cf8a59898afdce1b71eecadec9de1"
+ integrity sha512-NFRWZRmYREaElTZj8ioyNu1lzaf0WperEw98EqC+LOYpwZzHtLahR0Ot/1DGOiCZTacrvBrtjo65VQGieXqJfQ==
+ dependencies:
+ clsx "^2.1.1"
+
+"@cypress-design/react-icon@^1.27.0":
+ version "1.27.0"
+ resolved "https://registry.yarnpkg.com/@cypress-design/react-icon/-/react-icon-1.27.0.tgz#e34952329887deb614b78b66d0357b7a4049cbe8"
+ integrity sha512-r8tu7JFwJWwsKSRMJ83ocSb6zfM0Hjnf2DTH5CEhlVxo/QHMOVk85zQSqxVHlX9p11C7jomUeMkoalD6dpKXTg==
+ dependencies:
+ "@cypress-design/icon-registry" "^1.27.0"
+ clsx "^2.1.1"
+
"@cypress-design/vue-button@^1.1.0", "@cypress-design/vue-button@^1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@cypress-design/vue-button/-/vue-button-1.6.0.tgz#e7266dfe11c31628ef3a979fffcf041b141e39c3"
@@ -12620,6 +12647,11 @@ cloneable-readable@^1.0.0:
process-nextick-args "^2.0.0"
readable-stream "^2.3.5"
+clsx@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
+ integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
+
cmd-shim@6.0.3, cmd-shim@^6.0.0:
version "6.0.3"
resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.3.tgz#c491e9656594ba17ac83c4bd931590a9d6e26033"