From 2d8ce9c0b7403b0044311a4af90e2aca135182f6 Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Wed, 22 Oct 2025 14:24:15 -0500 Subject: [PATCH 01/16] chore: display self healed badge for cyPrompt --- .../src/assets/icons/sparkle_x16.svg | 3 + packages/reporter/cypress/e2e/commands.cy.ts | 82 +++++++++++++++++++ .../reporter/src/attempts/attempt-model.ts | 10 +++ .../reporter/src/commands/command-model.ts | 5 ++ packages/reporter/src/commands/command.tsx | 4 + packages/reporter/src/commands/commands.scss | 8 +- packages/reporter/src/hooks/hook-model.ts | 5 ++ packages/reporter/src/hooks/hooks.scss | 3 + packages/reporter/src/hooks/hooks.tsx | 58 +++++++------ .../reporter/src/lib/selfHealedBadge.scss | 17 ++++ packages/reporter/src/lib/selfHealedBadge.tsx | 13 +++ packages/reporter/src/main-runner.scss | 1 + packages/reporter/src/main.scss | 1 + .../reporter/src/runnables/runnables.scss | 1 + packages/reporter/src/test/test-model.ts | 10 +++ packages/reporter/src/test/test.tsx | 4 + scripts/gulp/monorepoPaths.ts | 1 + 17 files changed, 201 insertions(+), 25 deletions(-) create mode 100644 packages/frontend-shared/src/assets/icons/sparkle_x16.svg create mode 100644 packages/reporter/src/lib/selfHealedBadge.scss create mode 100644 packages/reporter/src/lib/selfHealedBadge.tsx diff --git a/packages/frontend-shared/src/assets/icons/sparkle_x16.svg b/packages/frontend-shared/src/assets/icons/sparkle_x16.svg new file mode 100644 index 00000000000..92c300f3c83 --- /dev/null +++ b/packages/frontend-shared/src/assets/icons/sparkle_x16.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/reporter/cypress/e2e/commands.cy.ts b/packages/reporter/cypress/e2e/commands.cy.ts index 069eee2ac19..c034a887820 100644 --- a/packages/reporter/cypress/e2e/commands.cy.ts +++ b/packages/reporter/cypress/e2e/commands.cy.ts @@ -1142,4 +1142,86 @@ describe('commands', { viewportHeight: 1000 }, () => { cy.contains('GET /api/data/1').should('be.visible') }) }) + + context('self-healed badge', () => { + it('renders the self-healed badge when the command is self-healed and move to top level when is closed', () => { + const nestedGroupId = addCommand(runner, { + name: 'session', + defaultCollapsedState: 'open', + state: 'passed', + type: 'child', + }) + + addCommand(runner, { + name: 'get', + message: 'do something', + state: 'passed', + groupLevel: 1, + group: nestedGroupId, + }) + + const nestedSessionGroupId = addCommand(runner, { + name: 'session', + defaultCollapsedState: 'open', + displayName: 'validate', + type: 'child', + groupLevel: 2, + group: nestedGroupId, + renderProps: { + selfHealed: true, + }, + }) + + addCommand(runner, { + name: 'log', + message: 'inside of group', + state: 'passed', + group: nestedSessionGroupId, + }) + + cy.get('[data-cy="self-healed-badge-command"]').should('exist') + cy.get('[data-cy="self-healed-badge-hook"]').should('not.exist') + cy.get('[data-cy="self-healed-badge-test"]').should('not.exist') + + cy.percySnapshot('initial state') + + cy.get('.command-info').eq(10).within(() => { + cy.get('[data-cy="self-healed-badge-command"]').should('not.exist') + cy.get('[data-cy="self-healed-badge-hook"]').should('not.exist') + cy.get('[data-cy="self-healed-badge-test"]').should('not.exist') + }) + + cy.get('.command-info').eq(12).within(() => { + cy.get('[data-cy="self-healed-badge-command"]').should('exist') + cy.get('[data-cy="self-healed-badge-hook"]').should('not.exist') + cy.get('[data-cy="self-healed-badge-test"]').should('not.exist') + }) + + cy.get('.command-expander').eq(1).click() + + cy.percySnapshot('after clicking command expander') + + cy.get('.command-info').eq(10).within(() => { + cy.get('[data-cy="self-healed-badge-command"]').should('exist') + cy.get('[data-cy="self-healed-badge-hook"]').should('not.exist') + cy.get('[data-cy="self-healed-badge-test"]').should('not.exist') + }) + + cy.get('.collapsible-header-inner').eq(2).click({ force: true }) + + cy.percySnapshot('after clicking collapsible header') + + cy.get('[data-cy="self-healed-badge-command"]').should('not.exist') + cy.get('[data-cy="self-healed-badge-hook"]').should('exist') + cy.get('[data-cy="self-healed-badge-test"]').should('not.exist') + + cy.get('.collapsible-header-inner').first().click() + + cy.get('[data-cy="self-healed-badge-command"]').should('not.exist') + cy.get('[data-cy="self-healed-badge-hook"]').should('not.exist') + cy.get('[data-cy="self-healed-badge-test"]').should('exist') + + cy.percySnapshot() + }) + }) }) diff --git a/packages/reporter/src/attempts/attempt-model.ts b/packages/reporter/src/attempts/attempt-model.ts index c2c9c377841..9a95e05d28f 100644 --- a/packages/reporter/src/attempts/attempt-model.ts +++ b/packages/reporter/src/attempts/attempt-model.ts @@ -166,6 +166,11 @@ export default class Attempt { } log.update(props) + + // Check if this command was auto-healed and propagate to test + if (props.instrument === 'command' && (props as CommandProps).renderProps?.selfHealed) { + this.test.setIsSelfHealed(true) + } } } @@ -317,6 +322,11 @@ export default class Attempt { hook.hookNumber = ++this.hookCount[hook.hookName] } + // Check if this command was self-healed and propagate to test + if (props.renderProps?.selfHealed) { + this.test.setIsSelfHealed(true) + } + return command } diff --git a/packages/reporter/src/commands/command-model.ts b/packages/reporter/src/commands/command-model.ts index 132780db386..ae61fecc934 100644 --- a/packages/reporter/src/commands/command-model.ts +++ b/packages/reporter/src/commands/command-model.ts @@ -21,6 +21,7 @@ export interface RenderProps { }> status?: InterceptStatuses | XHRStatuses wentToOrigin?: boolean + selfHealed?: boolean } export interface CommandProps extends InstrumentProps { @@ -278,4 +279,8 @@ export default class Command extends Instrument { _isPending () { return this.state === 'pending' } + + get isSelfHealed () { + return (!!this.renderProps.selfHealed || (this.hasChildren && !this.isOpen && this.children.some((child) => !!child.renderProps.selfHealed))) + } } diff --git a/packages/reporter/src/commands/command.tsx b/packages/reporter/src/commands/command.tsx index 59c461cee6f..da2ef284c04 100644 --- a/packages/reporter/src/commands/command.tsx +++ b/packages/reporter/src/commands/command.tsx @@ -25,6 +25,7 @@ import HiddenIcon from '@packages/frontend-shared/src/assets/icons/general-eye-c import PinIcon from '@packages/frontend-shared/src/assets/icons/object-pin_x16.svg' import RunningIcon from '@packages/frontend-shared/src/assets/icons/status-running_x16.svg' import { IconTechnologyAngleBrackets } from '@cypress-design/react-icon' +import { SelfHealedBadge } from '../lib/selfHealedBadge' const displayName = (model: CommandModel) => model.displayName || model.name const nameClassName = (name: string) => name.replace(/(\s+)/g, '-') @@ -279,6 +280,9 @@ const Message: React.FC = observer(({ model }: MessageProps) => ( className='command-message-text' dangerouslySetInnerHTML={{ __html: formattedMessage(model.displayMessage, model.name) }} />} + {model.isSelfHealed && ( + + )} )) diff --git a/packages/reporter/src/commands/commands.scss b/packages/reporter/src/commands/commands.scss index b486aab5307..fe47c247080 100644 --- a/packages/reporter/src/commands/commands.scss +++ b/packages/reporter/src/commands/commands.scss @@ -155,7 +155,8 @@ .command-info { @include command-info-padding; - display: -webkit-box; + display: inline-flex; + align-items: center; font-weight: 600; margin-left: 0; overflow: hidden; @@ -166,6 +167,8 @@ .command-aliases, .command-message { overflow: hidden; + display: inline-flex; + align-items: center; .fa-circle { padding: 4px; @@ -547,4 +550,7 @@ padding: 9px; margin: 8px 0; } + + + } diff --git a/packages/reporter/src/hooks/hook-model.ts b/packages/reporter/src/hooks/hook-model.ts index 4537ab0be66..fc608590809 100644 --- a/packages/reporter/src/hooks/hook-model.ts +++ b/packages/reporter/src/hooks/hook-model.ts @@ -41,6 +41,7 @@ export default class Hook implements HookProps { aliasesWithDuplicates: computed, hasFailedCommand: computed, showStudioPrompt: computed, + isSelfHealed: computed, }) this.hookId = props.hookId @@ -90,6 +91,10 @@ export default class Hook implements HookProps { return this.isStudio && !this.hasFailedCommand && (!this.commands.length || (this.commands.length === 1 && this.commands[0].name === 'visit')) } + get isSelfHealed () { + return this.commands.length > 0 && this.commands.some((command) => !!command.renderProps.selfHealed) + } + addCommand (command: CommandModel) { if (!command.event && command.type !== 'system' && !this.isStudio) { command.number = this._currentNumber diff --git a/packages/reporter/src/hooks/hooks.scss b/packages/reporter/src/hooks/hooks.scss index 92c2c2d53e8..fce9e1cf5b2 100644 --- a/packages/reporter/src/hooks/hooks.scss +++ b/packages/reporter/src/hooks/hooks.scss @@ -49,6 +49,7 @@ .collapsible-header-inner { padding: 6px 0 !important; width: 100%; + max-height: 30px; } &:focus { @@ -67,6 +68,8 @@ .hook-name { font-weight: 600; + display: flex; + align-items: center; } .hook-failed-message { diff --git a/packages/reporter/src/hooks/hooks.tsx b/packages/reporter/src/hooks/hooks.tsx index ca071290dee..3815d76cf94 100644 --- a/packages/reporter/src/hooks/hooks.tsx +++ b/packages/reporter/src/hooks/hooks.tsx @@ -1,23 +1,28 @@ import cs from 'classnames' import _ from 'lodash' import { observer } from 'mobx-react' -import React from 'react' +import React, { useState } from 'react' import appState, { AppState } from '../lib/app-state' import Command from '../commands/command' import Collapsible from '../collapsible/collapsible' import type HookModel from './hook-model' import type { HookName } from './hook-model' import { OpenFileInIDEButton } from '../header/OpenFileInIDEButton' +import { SelfHealedBadge } from '../lib/selfHealedBadge' export interface HookHeaderProps { model: HookModel number?: number + isOpen: boolean } -const HookHeader = ({ model, number }: HookHeaderProps) => ( +const HookHeader = ({ model, number, isOpen }: HookHeaderProps) => ( {model.hookName} {number && `(${number})`} {model.failed && (failed)} + {(!isOpen && model.isSelfHealed) && ( + + )} ) @@ -27,28 +32,33 @@ export interface HookProps { scrollIntoView: Function } -const Hook: React.FC = observer(({ model, showNumber, scrollIntoView }: HookProps) => ( -
  • - - - {model.invocationDetails && Cypress.testingType !== 'component' && ( - e.stopPropagation()}> - - - )} - - } - headerClass='hook-header' - isOpen - > -
      - {_.map(model.commands, (command) => )} -
    -
    -
  • -)) +const Hook: React.FC = observer(({ model, showNumber, scrollIntoView }: HookProps) => { + const [isOpen, setIsOpen] = useState(true) + + return ( +
  • + + + {model.invocationDetails && Cypress.testingType !== 'component' && ( + e.stopPropagation()}> + + + )} + + } + headerClass='hook-header' + isOpen={isOpen} + onOpenStateChangeRequested={(isOpen: boolean) => setIsOpen(isOpen)} + > +
      + {_.map(model.commands, (command) => )} +
    +
    +
  • + ) +}) Hook.displayName = 'Hook' diff --git a/packages/reporter/src/lib/selfHealedBadge.scss b/packages/reporter/src/lib/selfHealedBadge.scss new file mode 100644 index 00000000000..15b4114357e --- /dev/null +++ b/packages/reporter/src/lib/selfHealedBadge.scss @@ -0,0 +1,17 @@ +.command-self-healed-badge { + background-color: $gray-1000; + border-radius: 4px; + display: flex; + height: 20px; + border: 1px solid $gray-900; + min-width: 110px; + gap: 4px; + padding: 0 4px; + color: $jade-300; + font-weight: 400; + margin-left: 12px; + align-items: center; + justify-content: center; + white-space: nowrap; + text-transform: none; +} \ No newline at end of file diff --git a/packages/reporter/src/lib/selfHealedBadge.tsx b/packages/reporter/src/lib/selfHealedBadge.tsx new file mode 100644 index 00000000000..faacebac541 --- /dev/null +++ b/packages/reporter/src/lib/selfHealedBadge.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import SparkleIcon from '@packages/frontend-shared/src/assets/icons/sparkle_x16.svg' + +export const SelfHealedBadge = ({ source }: { source: 'command' | 'hook' | 'test' }) => { + return ( +
    + + + Self-healed + +
    + ) +} diff --git a/packages/reporter/src/main-runner.scss b/packages/reporter/src/main-runner.scss index 91761e2bd1f..dd8f97b305b 100644 --- a/packages/reporter/src/main-runner.scss +++ b/packages/reporter/src/main-runner.scss @@ -6,6 +6,7 @@ @import 'lib/switch'; @import 'lib/tag'; @import 'lib/tooltip'; +@import 'lib/selfHealedBadge'; @import '@reach/dialog/styles.css'; // import all other scss files in src except if they are in lib // or their file name is `selector-playground` or `main` diff --git a/packages/reporter/src/main.scss b/packages/reporter/src/main.scss index d4aea12f9af..ddc28fafd7b 100644 --- a/packages/reporter/src/main.scss +++ b/packages/reporter/src/main.scss @@ -12,6 +12,7 @@ @import 'lib/switch'; @import 'lib/tag'; @import 'lib/tooltip'; +@import 'lib/selfHealedBadge'; @import '@reach/dialog/styles.css'; // import all other scss files in src except if they are in lib // or their file name is `selector-playground` or `main` diff --git a/packages/reporter/src/runnables/runnables.scss b/packages/reporter/src/runnables/runnables.scss index cf8dfa3783e..7b28bbcb4c5 100644 --- a/packages/reporter/src/runnables/runnables.scss +++ b/packages/reporter/src/runnables/runnables.scss @@ -438,6 +438,7 @@ $dotted-line-left-padding: 19px; font-size: 14px; display: flex; flex-grow: 1; + // align-items: center; } .runnable-wrapper > .collapsible-header { diff --git a/packages/reporter/src/test/test-model.ts b/packages/reporter/src/test/test-model.ts index 424ca2b373b..1b153bf6a84 100644 --- a/packages/reporter/src/test/test-model.ts +++ b/packages/reporter/src/test/test-model.ts @@ -58,6 +58,7 @@ export default class Test extends Runnable { _isOpen: boolean | null = null isOpenWhenActive: Boolean | null = null _isFinished = false + _isSelfHealed = false constructor (props: TestProps, level: number, private store: RunnablesStore) { super(props, level) @@ -80,6 +81,7 @@ export default class Test extends Runnable { update: action, setIsOpen: action, finish: action, + _isSelfHealed: observable, }) this.invocationDetails = props.invocationDetails @@ -257,4 +259,12 @@ export default class Test extends Runnable { return null } + + setIsSelfHealed (isSelfHealed: boolean) { + this._isSelfHealed = isSelfHealed + } + + get isSelfHealed () { + return this._isSelfHealed + } } diff --git a/packages/reporter/src/test/test.tsx b/packages/reporter/src/test/test.tsx index 116df5db0b8..9b253ee9284 100644 --- a/packages/reporter/src/test/test.tsx +++ b/packages/reporter/src/test/test.tsx @@ -10,6 +10,7 @@ import Attempts from '../attempts/attempts' import StateIcon from '../lib/state-icon' import { LaunchStudioIcon } from '../components/LaunchStudioIcon' import { useScrollIntoView } from '../lib/useScrollIntoView' +import { SelfHealedBadge } from '../lib/selfHealedBadge' interface TestProps { events?: Events @@ -45,6 +46,9 @@ const Test: React.FC = observer(({ model, events: eventsProps = event {model.title} {model.state} + {model.isSelfHealed && !model.isOpen && ( + + )} {_controls()} ) diff --git a/scripts/gulp/monorepoPaths.ts b/scripts/gulp/monorepoPaths.ts index 89d074ff10b..c066625a055 100644 --- a/scripts/gulp/monorepoPaths.ts +++ b/scripts/gulp/monorepoPaths.ts @@ -21,6 +21,7 @@ export const monorepoPaths = { pkgLaunchpad: path.join(__dirname, '../../packages/launchpad'), pkgNetStubbing: path.join(__dirname, '../../packages/net-stubbing'), pkgNetwork: path.join(__dirname, '../../packages/network'), + pkgNetworkTools: path.join(__dirname, '../../packages/network-tools'), pkgPackherdRequire: path.join(__dirname, '../../packages/packherd-require'), pkgProxy: path.join(__dirname, '../../packages/proxy'), pkgReporter: path.join(__dirname, '../../packages/reporter'), From ee83ca06b99ebe6540aedf36373e36c92b183007 Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Thu, 23 Oct 2025 13:20:05 -0500 Subject: [PATCH 02/16] Update with review --- packages/reporter/cypress/e2e/commands.cy.ts | 13 ++----------- packages/reporter/src/hooks/hook-model.ts | 5 ----- packages/reporter/src/hooks/hooks.tsx | 4 ---- packages/reporter/src/lib/selfHealedBadge.scss | 2 ++ packages/reporter/src/lib/selfHealedBadge.tsx | 2 +- packages/reporter/src/test/test.tsx | 2 +- 6 files changed, 6 insertions(+), 22 deletions(-) diff --git a/packages/reporter/cypress/e2e/commands.cy.ts b/packages/reporter/cypress/e2e/commands.cy.ts index c034a887820..ab6976708e8 100644 --- a/packages/reporter/cypress/e2e/commands.cy.ts +++ b/packages/reporter/cypress/e2e/commands.cy.ts @@ -1180,21 +1180,16 @@ describe('commands', { viewportHeight: 1000 }, () => { }) cy.get('[data-cy="self-healed-badge-command"]').should('exist') - cy.get('[data-cy="self-healed-badge-hook"]').should('not.exist') - cy.get('[data-cy="self-healed-badge-test"]').should('not.exist') + cy.get('[data-cy="self-healed-badge-test"]').should('exist') cy.percySnapshot('initial state') cy.get('.command-info').eq(10).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('not.exist') - cy.get('[data-cy="self-healed-badge-hook"]').should('not.exist') - cy.get('[data-cy="self-healed-badge-test"]').should('not.exist') }) cy.get('.command-info').eq(12).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('exist') - cy.get('[data-cy="self-healed-badge-hook"]').should('not.exist') - cy.get('[data-cy="self-healed-badge-test"]').should('not.exist') }) cy.get('.command-expander').eq(1).click() @@ -1203,8 +1198,6 @@ describe('commands', { viewportHeight: 1000 }, () => { cy.get('.command-info').eq(10).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('exist') - cy.get('[data-cy="self-healed-badge-hook"]').should('not.exist') - cy.get('[data-cy="self-healed-badge-test"]').should('not.exist') }) cy.get('.collapsible-header-inner').eq(2).click({ force: true }) @@ -1212,13 +1205,11 @@ describe('commands', { viewportHeight: 1000 }, () => { cy.percySnapshot('after clicking collapsible header') cy.get('[data-cy="self-healed-badge-command"]').should('not.exist') - cy.get('[data-cy="self-healed-badge-hook"]').should('exist') - cy.get('[data-cy="self-healed-badge-test"]').should('not.exist') + cy.get('[data-cy="self-healed-badge-test"]').should('exist') cy.get('.collapsible-header-inner').first().click() cy.get('[data-cy="self-healed-badge-command"]').should('not.exist') - cy.get('[data-cy="self-healed-badge-hook"]').should('not.exist') cy.get('[data-cy="self-healed-badge-test"]').should('exist') cy.percySnapshot() diff --git a/packages/reporter/src/hooks/hook-model.ts b/packages/reporter/src/hooks/hook-model.ts index fc608590809..4537ab0be66 100644 --- a/packages/reporter/src/hooks/hook-model.ts +++ b/packages/reporter/src/hooks/hook-model.ts @@ -41,7 +41,6 @@ export default class Hook implements HookProps { aliasesWithDuplicates: computed, hasFailedCommand: computed, showStudioPrompt: computed, - isSelfHealed: computed, }) this.hookId = props.hookId @@ -91,10 +90,6 @@ export default class Hook implements HookProps { return this.isStudio && !this.hasFailedCommand && (!this.commands.length || (this.commands.length === 1 && this.commands[0].name === 'visit')) } - get isSelfHealed () { - return this.commands.length > 0 && this.commands.some((command) => !!command.renderProps.selfHealed) - } - addCommand (command: CommandModel) { if (!command.event && command.type !== 'system' && !this.isStudio) { command.number = this._currentNumber diff --git a/packages/reporter/src/hooks/hooks.tsx b/packages/reporter/src/hooks/hooks.tsx index 3815d76cf94..4995528c45e 100644 --- a/packages/reporter/src/hooks/hooks.tsx +++ b/packages/reporter/src/hooks/hooks.tsx @@ -8,7 +8,6 @@ import Collapsible from '../collapsible/collapsible' import type HookModel from './hook-model' import type { HookName } from './hook-model' import { OpenFileInIDEButton } from '../header/OpenFileInIDEButton' -import { SelfHealedBadge } from '../lib/selfHealedBadge' export interface HookHeaderProps { model: HookModel @@ -20,9 +19,6 @@ const HookHeader = ({ model, number, isOpen }: HookHeaderProps) => ( {model.hookName} {number && `(${number})`} {model.failed && (failed)} - {(!isOpen && model.isSelfHealed) && ( - - )} ) diff --git a/packages/reporter/src/lib/selfHealedBadge.scss b/packages/reporter/src/lib/selfHealedBadge.scss index 15b4114357e..a0fe0b937c0 100644 --- a/packages/reporter/src/lib/selfHealedBadge.scss +++ b/packages/reporter/src/lib/selfHealedBadge.scss @@ -8,6 +8,8 @@ gap: 4px; padding: 0 4px; color: $jade-300; + font-family: $font-system; + font-size: 14px; font-weight: 400; margin-left: 12px; align-items: center; diff --git a/packages/reporter/src/lib/selfHealedBadge.tsx b/packages/reporter/src/lib/selfHealedBadge.tsx index faacebac541..cd58a31b819 100644 --- a/packages/reporter/src/lib/selfHealedBadge.tsx +++ b/packages/reporter/src/lib/selfHealedBadge.tsx @@ -1,7 +1,7 @@ import React from 'react' import SparkleIcon from '@packages/frontend-shared/src/assets/icons/sparkle_x16.svg' -export const SelfHealedBadge = ({ source }: { source: 'command' | 'hook' | 'test' }) => { +export const SelfHealedBadge = ({ source }: { source: 'command' | 'test' }) => { return (
    diff --git a/packages/reporter/src/test/test.tsx b/packages/reporter/src/test/test.tsx index 9b253ee9284..b63e41630d2 100644 --- a/packages/reporter/src/test/test.tsx +++ b/packages/reporter/src/test/test.tsx @@ -46,7 +46,7 @@ const Test: React.FC = observer(({ model, events: eventsProps = event {model.title} {model.state} - {model.isSelfHealed && !model.isOpen && ( + {model.isSelfHealed && ( )} From 5c6e639249850661b3a34ba9a6379ca14fbacc2f Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Thu, 23 Oct 2025 13:22:38 -0500 Subject: [PATCH 03/16] Revert changes --- packages/reporter/src/hooks/hooks.tsx | 54 ++++++++++++--------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/packages/reporter/src/hooks/hooks.tsx b/packages/reporter/src/hooks/hooks.tsx index 4995528c45e..ca071290dee 100644 --- a/packages/reporter/src/hooks/hooks.tsx +++ b/packages/reporter/src/hooks/hooks.tsx @@ -1,7 +1,7 @@ import cs from 'classnames' import _ from 'lodash' import { observer } from 'mobx-react' -import React, { useState } from 'react' +import React from 'react' import appState, { AppState } from '../lib/app-state' import Command from '../commands/command' import Collapsible from '../collapsible/collapsible' @@ -12,10 +12,9 @@ import { OpenFileInIDEButton } from '../header/OpenFileInIDEButton' export interface HookHeaderProps { model: HookModel number?: number - isOpen: boolean } -const HookHeader = ({ model, number, isOpen }: HookHeaderProps) => ( +const HookHeader = ({ model, number }: HookHeaderProps) => ( {model.hookName} {number && `(${number})`} {model.failed && (failed)} @@ -28,33 +27,28 @@ export interface HookProps { scrollIntoView: Function } -const Hook: React.FC = observer(({ model, showNumber, scrollIntoView }: HookProps) => { - const [isOpen, setIsOpen] = useState(true) - - return ( -
  • - - - {model.invocationDetails && Cypress.testingType !== 'component' && ( - e.stopPropagation()}> - - - )} - - } - headerClass='hook-header' - isOpen={isOpen} - onOpenStateChangeRequested={(isOpen: boolean) => setIsOpen(isOpen)} - > -
      - {_.map(model.commands, (command) => )} -
    -
    -
  • - ) -}) +const Hook: React.FC = observer(({ model, showNumber, scrollIntoView }: HookProps) => ( +
  • + + + {model.invocationDetails && Cypress.testingType !== 'component' && ( + e.stopPropagation()}> + + + )} + + } + headerClass='hook-header' + isOpen + > +
      + {_.map(model.commands, (command) => )} +
    +
    +
  • +)) Hook.displayName = 'Hook' From 6ca95bdd91235c35055b02c35eeabd397289b574 Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Thu, 23 Oct 2025 14:16:43 -0500 Subject: [PATCH 04/16] Cleanup code, add test --- packages/reporter/cypress/e2e/commands.cy.ts | 73 +++++++++++++++++++ packages/reporter/src/commands/command.tsx | 4 +- packages/reporter/src/commands/commands.scss | 10 +-- packages/reporter/src/hooks/hooks.scss | 2 - .../reporter/src/lib/selfHealedBadge.scss | 1 + .../reporter/src/runnables/runnables.scss | 1 - 6 files changed, 81 insertions(+), 10 deletions(-) diff --git a/packages/reporter/cypress/e2e/commands.cy.ts b/packages/reporter/cypress/e2e/commands.cy.ts index ab6976708e8..e40d3bdaee6 100644 --- a/packages/reporter/cypress/e2e/commands.cy.ts +++ b/packages/reporter/cypress/e2e/commands.cy.ts @@ -1214,5 +1214,78 @@ describe('commands', { viewportHeight: 1000 }, () => { cy.percySnapshot() }) + + it('renders the self-healed badge when the command is self-healed and long text and move to top level when is closed', () => { + const nestedGroupId = addCommand(runner, { + name: 'session', + defaultCollapsedState: 'open', + state: 'passed', + type: 'child', + message: 'with long text to show wrapping works as expected and move to top level when is closed and is self-healed and is long text to show wrapping works as expected', + }) + + addCommand(runner, { + name: 'get', + message: 'do something', + state: 'passed', + groupLevel: 1, + group: nestedGroupId, + }) + + const nestedSessionGroupId = addCommand(runner, { + name: 'session', + defaultCollapsedState: 'open', + displayName: 'validate', + type: 'child', + groupLevel: 2, + group: nestedGroupId, + message: 'with long text to show wrapping works as expected and move to top level when is closed and is self-healed and is long text to show wrapping works as expected', + renderProps: { + selfHealed: true, + }, + }) + + addCommand(runner, { + name: 'log', + message: 'inside of group', + state: 'passed', + group: nestedSessionGroupId, + }) + + cy.get('[data-cy="self-healed-badge-command"]').should('exist') + cy.get('[data-cy="self-healed-badge-test"]').should('exist') + + cy.percySnapshot('initial state') + + cy.get('.command-info').eq(10).within(() => { + cy.get('[data-cy="self-healed-badge-command"]').should('not.exist') + }) + + cy.get('.command-info').eq(12).within(() => { + cy.get('[data-cy="self-healed-badge-command"]').should('exist') + }) + + cy.get('.command-expander').eq(1).click() + + cy.percySnapshot('after clicking command expander') + + cy.get('.command-info').eq(10).within(() => { + cy.get('[data-cy="self-healed-badge-command"]').should('exist') + }) + + cy.get('.collapsible-header-inner').eq(2).click({ force: true }) + + cy.percySnapshot('after clicking collapsible header') + + cy.get('[data-cy="self-healed-badge-command"]').should('not.exist') + cy.get('[data-cy="self-healed-badge-test"]').should('exist') + + cy.get('.collapsible-header-inner').first().click() + + cy.get('[data-cy="self-healed-badge-command"]').should('not.exist') + cy.get('[data-cy="self-healed-badge-test"]').should('exist') + + cy.percySnapshot() + }) }) }) diff --git a/packages/reporter/src/commands/command.tsx b/packages/reporter/src/commands/command.tsx index da2ef284c04..32deaf8fce1 100644 --- a/packages/reporter/src/commands/command.tsx +++ b/packages/reporter/src/commands/command.tsx @@ -277,7 +277,7 @@ const Message: React.FC = observer(({ model }: MessageProps) => ( /> )} {!!model.displayMessage && } {model.isSelfHealed && ( @@ -331,7 +331,7 @@ interface CommandProps { const CommandDetails: React.FC = observer(({ model, groupId, aliasesWithDuplicates }) => ( - + {model.event && model.type !== 'system' ? `(${displayName(model)})` : displayName(model)} diff --git a/packages/reporter/src/commands/commands.scss b/packages/reporter/src/commands/commands.scss index 44b73b0aa33..d21cc01eadc 100644 --- a/packages/reporter/src/commands/commands.scss +++ b/packages/reporter/src/commands/commands.scss @@ -157,7 +157,6 @@ @include command-info-padding; display: inline-flex; - align-items: center; font-weight: 600; margin-left: 0; overflow: hidden; @@ -169,7 +168,6 @@ .command-message { overflow: hidden; display: inline-flex; - align-items: center; .fa-circle { padding: 4px; @@ -195,6 +193,11 @@ } } + .command-message-text-self-healed, + .command-method-self-healed { + margin-top: 1px; + } + .command-message-text { overflow: hidden; text-overflow: ellipsis; @@ -557,7 +560,4 @@ padding: 9px; margin: 8px 0; } - - - } diff --git a/packages/reporter/src/hooks/hooks.scss b/packages/reporter/src/hooks/hooks.scss index 844b5fa5238..331e91c24df 100644 --- a/packages/reporter/src/hooks/hooks.scss +++ b/packages/reporter/src/hooks/hooks.scss @@ -53,7 +53,6 @@ .collapsible-header-inner { padding: 6px 0 !important; width: 100%; - max-height: 30px; } &:focus { @@ -73,7 +72,6 @@ .hook-name { font-weight: 600; display: flex; - align-items: center; } .hook-failed-message { diff --git a/packages/reporter/src/lib/selfHealedBadge.scss b/packages/reporter/src/lib/selfHealedBadge.scss index a0fe0b937c0..f607722c46b 100644 --- a/packages/reporter/src/lib/selfHealedBadge.scss +++ b/packages/reporter/src/lib/selfHealedBadge.scss @@ -12,6 +12,7 @@ font-size: 14px; font-weight: 400; margin-left: 12px; + margin-right: 4px; align-items: center; justify-content: center; white-space: nowrap; diff --git a/packages/reporter/src/runnables/runnables.scss b/packages/reporter/src/runnables/runnables.scss index 7b28bbcb4c5..cf8dfa3783e 100644 --- a/packages/reporter/src/runnables/runnables.scss +++ b/packages/reporter/src/runnables/runnables.scss @@ -438,7 +438,6 @@ $dotted-line-left-padding: 19px; font-size: 14px; display: flex; flex-grow: 1; - // align-items: center; } .runnable-wrapper > .collapsible-header { From 0c70478d03953969476b3531ac24f5ce9fb08bd5 Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Thu, 23 Oct 2025 14:23:35 -0500 Subject: [PATCH 05/16] Revert change --- packages/reporter/src/commands/commands.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reporter/src/commands/commands.scss b/packages/reporter/src/commands/commands.scss index d21cc01eadc..330ccdcf912 100644 --- a/packages/reporter/src/commands/commands.scss +++ b/packages/reporter/src/commands/commands.scss @@ -156,7 +156,7 @@ .command-info { @include command-info-padding; - display: inline-flex; + display: -webkit-box; font-weight: 600; margin-left: 0; overflow: hidden; From a7e671ffba8accdab7c9a1218109ae23b9f267d7 Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Thu, 23 Oct 2025 16:21:36 -0500 Subject: [PATCH 06/16] Fix styles --- packages/reporter/src/commands/command.tsx | 14 ++++++++------ packages/reporter/src/commands/commands.scss | 15 ++++++++------- packages/reporter/src/hooks/hooks.scss | 1 - packages/reporter/src/lib/selfHealedBadge.scss | 6 +++++- packages/reporter/src/lib/selfHealedBadge.tsx | 3 ++- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/reporter/src/commands/command.tsx b/packages/reporter/src/commands/command.tsx index 32deaf8fce1..dc806a702c9 100644 --- a/packages/reporter/src/commands/command.tsx +++ b/packages/reporter/src/commands/command.tsx @@ -277,12 +277,9 @@ const Message: React.FC = observer(({ model }: MessageProps) => ( /> )} {!!model.displayMessage && } - {model.isSelfHealed && ( - - )} )) @@ -331,7 +328,7 @@ interface CommandProps { const CommandDetails: React.FC = observer(({ model, groupId, aliasesWithDuplicates }) => ( - + {model.event && model.type !== 'system' ? `(${displayName(model)})` : displayName(model)} @@ -540,7 +537,12 @@ const Command: React.FC = observer(({ model, aliasesWithDuplicates
    )} - +
    + + {model.isSelfHealed && ( + + )} +
    {model.isCyPrompt && model.state === 'passed' && ( diff --git a/packages/reporter/src/commands/commands.scss b/packages/reporter/src/commands/commands.scss index 330ccdcf912..19858c19aae 100644 --- a/packages/reporter/src/commands/commands.scss +++ b/packages/reporter/src/commands/commands.scss @@ -153,6 +153,14 @@ } } + .command-details-container { + display: flex; + align-items: baseline; + margin-left: 0; + overflow: hidden; + width: 100%; + } + .command-info { @include command-info-padding; @@ -160,14 +168,12 @@ font-weight: 600; margin-left: 0; overflow: hidden; - width: 100%; -webkit-line-clamp: 100; -webkit-box-orient: vertical; .command-aliases, .command-message { overflow: hidden; - display: inline-flex; .fa-circle { padding: 4px; @@ -193,11 +199,6 @@ } } - .command-message-text-self-healed, - .command-method-self-healed { - margin-top: 1px; - } - .command-message-text { overflow: hidden; text-overflow: ellipsis; diff --git a/packages/reporter/src/hooks/hooks.scss b/packages/reporter/src/hooks/hooks.scss index 331e91c24df..68de8f4152c 100644 --- a/packages/reporter/src/hooks/hooks.scss +++ b/packages/reporter/src/hooks/hooks.scss @@ -71,7 +71,6 @@ .hook-name { font-weight: 600; - display: flex; } .hook-failed-message { diff --git a/packages/reporter/src/lib/selfHealedBadge.scss b/packages/reporter/src/lib/selfHealedBadge.scss index f607722c46b..8634dbac92d 100644 --- a/packages/reporter/src/lib/selfHealedBadge.scss +++ b/packages/reporter/src/lib/selfHealedBadge.scss @@ -11,10 +11,14 @@ font-family: $font-system; font-size: 14px; font-weight: 400; - margin-left: 12px; + margin-left: 8px; margin-right: 4px; align-items: center; justify-content: center; white-space: nowrap; text-transform: none; +} + +.command-self-healed-badge-command { + margin-top: 4px; } \ No newline at end of file diff --git a/packages/reporter/src/lib/selfHealedBadge.tsx b/packages/reporter/src/lib/selfHealedBadge.tsx index cd58a31b819..23aaf1a2c08 100644 --- a/packages/reporter/src/lib/selfHealedBadge.tsx +++ b/packages/reporter/src/lib/selfHealedBadge.tsx @@ -1,9 +1,10 @@ import React from 'react' import SparkleIcon from '@packages/frontend-shared/src/assets/icons/sparkle_x16.svg' +import cs from 'classnames' export const SelfHealedBadge = ({ source }: { source: 'command' | 'test' }) => { return ( -
    +
    Self-healed From e76a4b146e1457753bc23c17d3a8fe5fbf529ebe Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Thu, 23 Oct 2025 16:23:03 -0500 Subject: [PATCH 07/16] Revert change --- packages/reporter/src/commands/command.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reporter/src/commands/command.tsx b/packages/reporter/src/commands/command.tsx index dc806a702c9..cf6ad1659fc 100644 --- a/packages/reporter/src/commands/command.tsx +++ b/packages/reporter/src/commands/command.tsx @@ -277,7 +277,7 @@ const Message: React.FC = observer(({ model }: MessageProps) => ( /> )} {!!model.displayMessage && } From 81d81f6787e6599253ed022f4c0b9ebeec387573 Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Thu, 23 Oct 2025 16:53:19 -0500 Subject: [PATCH 08/16] Fix Cy test --- packages/reporter/cypress/e2e/commands.cy.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/reporter/cypress/e2e/commands.cy.ts b/packages/reporter/cypress/e2e/commands.cy.ts index e40d3bdaee6..c3f9fdcefdd 100644 --- a/packages/reporter/cypress/e2e/commands.cy.ts +++ b/packages/reporter/cypress/e2e/commands.cy.ts @@ -1184,11 +1184,11 @@ describe('commands', { viewportHeight: 1000 }, () => { cy.percySnapshot('initial state') - cy.get('.command-info').eq(10).within(() => { + cy.get('.command-details-container').eq(10).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('not.exist') }) - cy.get('.command-info').eq(12).within(() => { + cy.get('.command-details-container').eq(12).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('exist') }) @@ -1196,7 +1196,7 @@ describe('commands', { viewportHeight: 1000 }, () => { cy.percySnapshot('after clicking command expander') - cy.get('.command-info').eq(10).within(() => { + cy.get('.command-details-container').eq(10).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('exist') }) @@ -1257,11 +1257,11 @@ describe('commands', { viewportHeight: 1000 }, () => { cy.percySnapshot('initial state') - cy.get('.command-info').eq(10).within(() => { + cy.get('.command-details-container').eq(10).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('not.exist') }) - cy.get('.command-info').eq(12).within(() => { + cy.get('.command-details-container').eq(12).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('exist') }) @@ -1269,7 +1269,7 @@ describe('commands', { viewportHeight: 1000 }, () => { cy.percySnapshot('after clicking command expander') - cy.get('.command-info').eq(10).within(() => { + cy.get('.command-details-container').eq(10).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('exist') }) From f6215f4a57229a6909e1498a2a83b0edaef2e397 Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Fri, 24 Oct 2025 09:47:42 -0500 Subject: [PATCH 09/16] Update with review --- packages/reporter/src/commands/command.tsx | 10 ++++------ packages/reporter/src/commands/commands.scss | 9 +-------- packages/reporter/src/lib/selfHealedBadge.scss | 9 +++++---- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/packages/reporter/src/commands/command.tsx b/packages/reporter/src/commands/command.tsx index cf6ad1659fc..da2ef284c04 100644 --- a/packages/reporter/src/commands/command.tsx +++ b/packages/reporter/src/commands/command.tsx @@ -280,6 +280,9 @@ const Message: React.FC = observer(({ model }: MessageProps) => ( className='command-message-text' dangerouslySetInnerHTML={{ __html: formattedMessage(model.displayMessage, model.name) }} />} + {model.isSelfHealed && ( + + )} )) @@ -537,12 +540,7 @@ const Command: React.FC = observer(({ model, aliasesWithDuplicates
    )} -
    - - {model.isSelfHealed && ( - - )} -
    +
    {model.isCyPrompt && model.state === 'passed' && ( diff --git a/packages/reporter/src/commands/commands.scss b/packages/reporter/src/commands/commands.scss index 19858c19aae..5353c97fa20 100644 --- a/packages/reporter/src/commands/commands.scss +++ b/packages/reporter/src/commands/commands.scss @@ -153,14 +153,6 @@ } } - .command-details-container { - display: flex; - align-items: baseline; - margin-left: 0; - overflow: hidden; - width: 100%; - } - .command-info { @include command-info-padding; @@ -168,6 +160,7 @@ font-weight: 600; margin-left: 0; overflow: hidden; + width: 100%; -webkit-line-clamp: 100; -webkit-box-orient: vertical; diff --git a/packages/reporter/src/lib/selfHealedBadge.scss b/packages/reporter/src/lib/selfHealedBadge.scss index 8634dbac92d..d694089cf97 100644 --- a/packages/reporter/src/lib/selfHealedBadge.scss +++ b/packages/reporter/src/lib/selfHealedBadge.scss @@ -1,12 +1,11 @@ .command-self-healed-badge { background-color: $gray-1000; border-radius: 4px; - display: flex; + display: inline-flex; height: 20px; border: 1px solid $gray-900; - min-width: 110px; gap: 4px; - padding: 0 4px; + padding: 0 8px; color: $jade-300; font-family: $font-system; font-size: 14px; @@ -17,8 +16,10 @@ justify-content: center; white-space: nowrap; text-transform: none; + vertical-align: middle; } .command-self-healed-badge-command { - margin-top: 4px; + height: 16px; + font-size: 12px; } \ No newline at end of file From 33fa4ae3cf18cba66d25e007ca279555f5424d36 Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Fri, 24 Oct 2025 12:29:39 -0500 Subject: [PATCH 10/16] Fix Cy test --- packages/reporter/cypress/e2e/commands.cy.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/reporter/cypress/e2e/commands.cy.ts b/packages/reporter/cypress/e2e/commands.cy.ts index c3f9fdcefdd..a214a172442 100644 --- a/packages/reporter/cypress/e2e/commands.cy.ts +++ b/packages/reporter/cypress/e2e/commands.cy.ts @@ -1184,11 +1184,11 @@ describe('commands', { viewportHeight: 1000 }, () => { cy.percySnapshot('initial state') - cy.get('.command-details-container').eq(10).within(() => { + cy.get('.command-message').eq(10).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('not.exist') }) - cy.get('.command-details-container').eq(12).within(() => { + cy.get('.command-message').eq(12).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('exist') }) @@ -1196,7 +1196,7 @@ describe('commands', { viewportHeight: 1000 }, () => { cy.percySnapshot('after clicking command expander') - cy.get('.command-details-container').eq(10).within(() => { + cy.get('.command-message').eq(10).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('exist') }) @@ -1257,11 +1257,11 @@ describe('commands', { viewportHeight: 1000 }, () => { cy.percySnapshot('initial state') - cy.get('.command-details-container').eq(10).within(() => { + cy.get('.command-message').eq(10).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('not.exist') }) - cy.get('.command-details-container').eq(12).within(() => { + cy.get('.command-message').eq(12).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('exist') }) @@ -1269,7 +1269,7 @@ describe('commands', { viewportHeight: 1000 }, () => { cy.percySnapshot('after clicking command expander') - cy.get('.command-details-container').eq(10).within(() => { + cy.get('.command-message').eq(10).within(() => { cy.get('[data-cy="self-healed-badge-command"]').should('exist') }) From 6e1a261e179d42cfb901225df61fb8ff6f040283 Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Fri, 24 Oct 2025 13:06:02 -0500 Subject: [PATCH 11/16] Update with cursor review --- packages/reporter/src/commands/command-model.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/reporter/src/commands/command-model.ts b/packages/reporter/src/commands/command-model.ts index ae61fecc934..18af7450fd1 100644 --- a/packages/reporter/src/commands/command-model.ts +++ b/packages/reporter/src/commands/command-model.ts @@ -164,6 +164,7 @@ export default class Command extends Instrument { hasChildren: computed, showError: computed, setGroup: action, + isSelfHealed: computed, }) if (props.err) { From e91683873e76c9cc03768a21dd4b67019a469d3b Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Fri, 24 Oct 2025 13:23:04 -0500 Subject: [PATCH 12/16] Update with cursor review --- packages/reporter/src/commands/command-model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reporter/src/commands/command-model.ts b/packages/reporter/src/commands/command-model.ts index 18af7450fd1..2b58e45ca0e 100644 --- a/packages/reporter/src/commands/command-model.ts +++ b/packages/reporter/src/commands/command-model.ts @@ -282,6 +282,6 @@ export default class Command extends Instrument { } get isSelfHealed () { - return (!!this.renderProps.selfHealed || (this.hasChildren && !this.isOpen && this.children.some((child) => !!child.renderProps.selfHealed))) + return (!!this.renderProps.selfHealed || (this.hasChildren && !this.isOpen && this.children.some((child) => child.isSelfHealed))) } } From ce2541a742fc1d8998966de1bab0d8838be99e7c Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Mon, 27 Oct 2025 09:48:47 -0500 Subject: [PATCH 13/16] Update with review --- .../src/assets/icons/sparkle_x16.svg | 3 -- .../cypress/e2e/unit/test_model.cy.ts | 36 +++++++++++++++++++ .../reporter/src/attempts/attempt-model.ts | 10 ------ .../reporter/src/lib/selfHealedBadge.scss | 2 +- packages/reporter/src/lib/selfHealedBadge.tsx | 4 +-- packages/reporter/src/test/test-model.ts | 13 ++++--- 6 files changed, 45 insertions(+), 23 deletions(-) delete mode 100644 packages/frontend-shared/src/assets/icons/sparkle_x16.svg diff --git a/packages/frontend-shared/src/assets/icons/sparkle_x16.svg b/packages/frontend-shared/src/assets/icons/sparkle_x16.svg deleted file mode 100644 index 92c300f3c83..00000000000 --- a/packages/frontend-shared/src/assets/icons/sparkle_x16.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/packages/reporter/cypress/e2e/unit/test_model.cy.ts b/packages/reporter/cypress/e2e/unit/test_model.cy.ts index f520c0b2d52..8423f4f1305 100644 --- a/packages/reporter/cypress/e2e/unit/test_model.cy.ts +++ b/packages/reporter/cypress/e2e/unit/test_model.cy.ts @@ -368,4 +368,40 @@ describe('Test model', () => { expect(test.isOpen).eq(true) }) }) + + context('#isSelfHealed', () => { + it('false by default', () => { + const test = createTest() + + expect(test.isSelfHealed).to.be.false + }) + + it('true when there is a self-healed command', () => { + const test = createTest() + + test.addLog(createCommand({ renderProps: { selfHealed: true } })) + expect(test.isSelfHealed).to.be.true + }) + + it('true when there is a self-healed command in an attempt', () => { + const test = createTest({ + currentRetry: 2, + prevAttempts: [ + { id: 'r3', currentRetry: 0, state: 'failed', hooks: [] }, + { id: 'r3', currentRetry: 1, state: 'failed', hooks: [] }, + ], + }) + + // Add a regular command to the first attempt + test.addLog(createCommand({ testCurrentRetry: 0, renderProps: { selfHealed: false } })) + + // Add a self-healed command to the second attempt + test.addLog(createCommand({ testCurrentRetry: 1, renderProps: { selfHealed: true } })) + + // Add a regular command to the current (third) attempt + test.addLog(createCommand({ testCurrentRetry: 2, renderProps: { selfHealed: false } })) + + expect(test.isSelfHealed).to.be.true + }) + }) }) diff --git a/packages/reporter/src/attempts/attempt-model.ts b/packages/reporter/src/attempts/attempt-model.ts index 9a95e05d28f..c2c9c377841 100644 --- a/packages/reporter/src/attempts/attempt-model.ts +++ b/packages/reporter/src/attempts/attempt-model.ts @@ -166,11 +166,6 @@ export default class Attempt { } log.update(props) - - // Check if this command was auto-healed and propagate to test - if (props.instrument === 'command' && (props as CommandProps).renderProps?.selfHealed) { - this.test.setIsSelfHealed(true) - } } } @@ -322,11 +317,6 @@ export default class Attempt { hook.hookNumber = ++this.hookCount[hook.hookName] } - // Check if this command was self-healed and propagate to test - if (props.renderProps?.selfHealed) { - this.test.setIsSelfHealed(true) - } - return command } diff --git a/packages/reporter/src/lib/selfHealedBadge.scss b/packages/reporter/src/lib/selfHealedBadge.scss index d694089cf97..fc65267c45e 100644 --- a/packages/reporter/src/lib/selfHealedBadge.scss +++ b/packages/reporter/src/lib/selfHealedBadge.scss @@ -5,7 +5,7 @@ height: 20px; border: 1px solid $gray-900; gap: 4px; - padding: 0 8px; + padding: 0 4px; color: $jade-300; font-family: $font-system; font-size: 14px; diff --git a/packages/reporter/src/lib/selfHealedBadge.tsx b/packages/reporter/src/lib/selfHealedBadge.tsx index 23aaf1a2c08..8ac8a0080dc 100644 --- a/packages/reporter/src/lib/selfHealedBadge.tsx +++ b/packages/reporter/src/lib/selfHealedBadge.tsx @@ -1,11 +1,11 @@ import React from 'react' -import SparkleIcon from '@packages/frontend-shared/src/assets/icons/sparkle_x16.svg' +import { IconGeneralSparkleSingleSmall } from '@cypress-design/react-icon' import cs from 'classnames' export const SelfHealedBadge = ({ source }: { source: 'command' | 'test' }) => { return (
    - + Self-healed diff --git a/packages/reporter/src/test/test-model.ts b/packages/reporter/src/test/test-model.ts index 1b153bf6a84..89db85d288c 100644 --- a/packages/reporter/src/test/test-model.ts +++ b/packages/reporter/src/test/test-model.ts @@ -58,7 +58,6 @@ export default class Test extends Runnable { _isOpen: boolean | null = null isOpenWhenActive: Boolean | null = null _isFinished = false - _isSelfHealed = false constructor (props: TestProps, level: number, private store: RunnablesStore) { super(props, level) @@ -77,11 +76,11 @@ export default class Test extends Runnable { hasRetried: computed, isActive: computed, currentRetry: computed, + isSelfHealed: computed, start: action, update: action, setIsOpen: action, finish: action, - _isSelfHealed: observable, }) this.invocationDetails = props.invocationDetails @@ -260,11 +259,11 @@ export default class Test extends Runnable { return null } - setIsSelfHealed (isSelfHealed: boolean) { - this._isSelfHealed = isSelfHealed - } - get isSelfHealed () { - return this._isSelfHealed + // Compute self-healed status from the commands in all attempts + // This ensures the badge is shown correctly even across retries + return _.some(this.attempts, (attempt: Attempt) => { + return _.some(attempt.commands, (command) => command.isSelfHealed) + }) } } From 2623a08ec49f90a5cc4e833b042dc6c3830f8ff6 Mon Sep 17 00:00:00 2001 From: Jennifer Shehane Date: Mon, 27 Oct 2025 16:51:48 -0400 Subject: [PATCH 14/16] add feature changelog entry --- cli/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 734f96fa730..955a4b22e80 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -5,6 +5,7 @@ _Released 10/20/2025 (PENDING)_ **Features:** + - Added a 'Self-healed' badge to the Command Log when `cy.prompt()` steps automatically recover after the element they need is not found in the cache. Addressed in [#32802](https://github.com/cypress-io/cypress/pull/32802). - `cy.prompt()` will now show a warning in the `Get code` modal when there are unsaved changes in `Studio` that will be lost if the user saves the generated code. Addressed in [#32741](https://github.com/cypress-io/cypress/pull/32741). **Bugfixes:** From 554d5051bba668f2af044d0b6133d5a358e6c864 Mon Sep 17 00:00:00 2001 From: Jennifer Shehane Date: Mon, 27 Oct 2025 16:52:28 -0400 Subject: [PATCH 15/16] Update date for release --- cli/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 955a4b22e80..860395e61b4 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -1,7 +1,7 @@ ## 15.6.0 -_Released 10/20/2025 (PENDING)_ +_Released 11/4/2025 (PENDING)_ **Features:** From 2abb6298ea750a9c425ff61cc2c1bdbb1355699d Mon Sep 17 00:00:00 2001 From: estrada9166 Date: Tue, 28 Oct 2025 09:38:41 -0500 Subject: [PATCH 16/16] Update with review --- packages/reporter/src/commands/commands.scss | 1 + packages/reporter/src/lib/selfHealedBadge.scss | 10 +++++++++- packages/reporter/src/lib/selfHealedBadge.tsx | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/reporter/src/commands/commands.scss b/packages/reporter/src/commands/commands.scss index 5353c97fa20..99607cf18b3 100644 --- a/packages/reporter/src/commands/commands.scss +++ b/packages/reporter/src/commands/commands.scss @@ -421,6 +421,7 @@ white-space: initial; word-wrap: inherit; width: 100%; + margin-right: 8px; } // Styles for Uncaught Exception diff --git a/packages/reporter/src/lib/selfHealedBadge.scss b/packages/reporter/src/lib/selfHealedBadge.scss index fc65267c45e..f19849e3024 100644 --- a/packages/reporter/src/lib/selfHealedBadge.scss +++ b/packages/reporter/src/lib/selfHealedBadge.scss @@ -10,16 +10,24 @@ font-family: $font-system; font-size: 14px; font-weight: 400; - margin-left: 8px; margin-right: 4px; align-items: center; justify-content: center; white-space: nowrap; text-transform: none; vertical-align: middle; + + .command-info:hover & { + border-color: $gray-800; + } + } .command-self-healed-badge-command { height: 16px; font-size: 12px; +} + +.command-self-healed-badge-test { + margin-left: 8px; } \ No newline at end of file diff --git a/packages/reporter/src/lib/selfHealedBadge.tsx b/packages/reporter/src/lib/selfHealedBadge.tsx index 8ac8a0080dc..890a90636d6 100644 --- a/packages/reporter/src/lib/selfHealedBadge.tsx +++ b/packages/reporter/src/lib/selfHealedBadge.tsx @@ -4,7 +4,7 @@ import cs from 'classnames' export const SelfHealedBadge = ({ source }: { source: 'command' | 'test' }) => { return ( -
    +
    Self-healed