diff --git a/packages/driver/cypress/e2e/commands/sessions/sessions.cy.js b/packages/driver/cypress/e2e/commands/sessions/sessions.cy.js index b9dc24937e4a..b1235fe7823d 100644 --- a/packages/driver/cypress/e2e/commands/sessions/sessions.cy.js +++ b/packages/driver/cypress/e2e/commands/sessions/sessions.cy.js @@ -193,6 +193,7 @@ describe('cy.session', { retries: 0 }, () => { it('groups session logs correctly', () => { expect(logs[0].get()).to.deep.contain({ name: 'session', + state: 'passed', id: sessionGroupId, sessionInfo: { id: 'session-1', @@ -282,6 +283,7 @@ describe('cy.session', { retries: 0 }, () => { it('groups session logs correctly', () => { expect(logs[0].get()).to.deep.contain({ name: 'session', + state: 'passed', id: sessionGroupId, sessionInfo: { id: sessionId, @@ -344,6 +346,7 @@ describe('cy.session', { retries: 0 }, () => { expect(err.message).to.contain('This error occurred while validating the created session') expect(logs[0].get()).to.deep.contain({ name: 'session', + state: 'failed', id: sessionGroupId, sessionInfo: { id: `session-${Cypress.state('test').id}`, @@ -427,6 +430,7 @@ describe('cy.session', { retries: 0 }, () => { it('groups session logs correctly', () => { expect(logs[0].get()).to.contain({ name: 'session', + state: 'passed', id: sessionGroupId, }) @@ -490,6 +494,7 @@ describe('cy.session', { retries: 0 }, () => { it('groups session logs correctly', () => { expect(logs[0].get()).to.contain({ name: 'session', + state: 'passed', id: sessionGroupId, }) @@ -572,6 +577,7 @@ describe('cy.session', { retries: 0 }, () => { it('groups session logs correctly', () => { expect(logs[0].get()).to.contain({ name: 'session', + state: 'warned', id: sessionGroupId, }) @@ -678,6 +684,7 @@ describe('cy.session', { retries: 0 }, () => { expect(logs[0].get()).to.contain({ name: 'session', + state: 'failed', id: sessionGroupId, }) @@ -914,6 +921,7 @@ describe('cy.session', { retries: 0 }, () => { it('groups session logs correctly', () => { expect(logs[0].get()).to.deep.contain({ name: 'session', + state: 'passed', id: sessionGroupId, sessionInfo: { id: 'session-1', @@ -992,6 +1000,7 @@ describe('cy.session', { retries: 0 }, () => { it('groups session logs correctly', () => { expect(logs[0].get()).to.deep.contain({ name: 'session', + state: 'passed', id: sessionGroupId, sessionInfo: { id: sessionId, @@ -1044,6 +1053,7 @@ describe('cy.session', { retries: 0 }, () => { expect(err.message).to.contain('Your `cy.session` **validate** promise rejected with false') expect(logs[0].get()).to.deep.contain({ name: 'session', + state: 'failed', id: sessionGroupId, sessionInfo: { id: `session-${Cypress.state('test').id}`, @@ -1121,6 +1131,7 @@ describe('cy.session', { retries: 0 }, () => { it('groups session logs correctly', () => { expect(logs[0].get()).to.contain({ name: 'session', + state: 'passed', id: sessionGroupId, }) @@ -1178,6 +1189,7 @@ describe('cy.session', { retries: 0 }, () => { it('groups session logs correctly', () => { expect(logs[0].get()).to.contain({ name: 'session', + state: 'passed', id: sessionGroupId, }) @@ -1254,6 +1266,7 @@ describe('cy.session', { retries: 0 }, () => { it('groups session logs correctly', () => { expect(logs[0].get()).to.contain({ name: 'session', + state: 'warned', id: sessionGroupId, }) @@ -1345,6 +1358,7 @@ describe('cy.session', { retries: 0 }, () => { expect(logs[0].get()).to.contain({ name: 'session', + state: 'failed', id: sessionGroupId, }) diff --git a/packages/driver/src/cy/commands/sessions/index.ts b/packages/driver/src/cy/commands/sessions/index.ts index 83e66dfec70a..866110835f20 100644 --- a/packages/driver/src/cy/commands/sessions/index.ts +++ b/packages/driver/src/cy/commands/sessions/index.ts @@ -8,6 +8,7 @@ import SessionsManager from './manager' import { getConsoleProps, navigateAboutBlank, + SESSION_STEPS, statusMap, } from './utils' @@ -39,8 +40,6 @@ export default function (Commands, Cypress, cy) { const sessionsManager = new SessionsManager(Cypress, cy) const sessions = sessionsManager.sessions - type SESSION_STEPS = 'create' | 'restore' | 'recreate' | 'validate' - Cypress.on('run:start', () => { // @ts-ignore Object.values(Cypress.state('activeSessions') || {}).forEach((sessionData: ServerSessionData) => { @@ -149,6 +148,7 @@ export default function (Commands, Cypress, cy) { function setSessionLogStatus (status: string) { _log.set({ + state: statusMap.commandState(status), sessionInfo: { id: session.id, isGlobalSession: session.cacheAcrossSpecs, @@ -232,7 +232,7 @@ export default function (Commands, Cypress, cy) { return sessions.setSessionData(testSession) } - function validateSession (existingSession, step: SESSION_STEPS) { + function validateSession (existingSession, step: keyof typeof SESSION_STEPS) { const isValidSession = true if (!existingSession.validate) { @@ -321,11 +321,7 @@ export default function (Commands, Cypress, cy) { // skip all commands between this command which errored and _commandToRunAfterValidation for (let i = cy.queue.index; i < index; i++) { - const cmd = commands[i] - - if (!cmd.get('restore-within')) { - commands[i].skip() - } + commands[i].skip() } // restore within subject back to the original subject used when @@ -369,7 +365,7 @@ export default function (Commands, Cypress, cy) { } const failValidation = (err) => { - if (step === 'restore') { + if (step === SESSION_STEPS.restore) { enhanceErr(err) // move to recreate session flow @@ -442,13 +438,13 @@ export default function (Commands, Cypress, cy) { .then(() => validateSession(existingSession, step)) .then(async (isValidSession: boolean) => { if (!isValidSession) { - throw new Error('not a valid session :(') + return 'failed' } sessionsManager.registeredSessions.set(existingSession.id, true) await sessions.saveSessionData(existingSession) - setSessionLogStatus(statusMap.complete(step)) + return statusMap.complete(step) }) } @@ -460,19 +456,19 @@ export default function (Commands, Cypress, cy) { */ const restoreSessionWorkflow = (existingSession: SessionData) => { return cy.then(async () => { - setSessionLogStatus('restoring') + setSessionLogStatus(statusMap.inProgress(SESSION_STEPS.restore)) await navigateAboutBlank() await sessions.clearCurrentSessionData() return restoreSession(existingSession) }) - .then(() => validateSession(existingSession, 'restore')) + .then(() => validateSession(existingSession, SESSION_STEPS.restore)) .then((isValidSession: boolean) => { if (!isValidSession) { - return createSessionWorkflow(existingSession, 'recreate') + return createSessionWorkflow(existingSession, SESSION_STEPS.recreate) } - setSessionLogStatus('restored') + return statusMap.complete(SESSION_STEPS.restore) }) } @@ -503,15 +499,15 @@ export default function (Commands, Cypress, cy) { _.extend(session, _.omit(serverStoredSession, 'setup', 'validate')) session.hydrated = true } else { - return createSessionWorkflow(session, 'create') + return createSessionWorkflow(session, SESSION_STEPS.create) } } return restoreSessionWorkflow(session) - }).then(() => { + }).then((status: 'created' | 'restored' | 'recreated' | 'failed') => { return navigateAboutBlank() .then(() => { - _log.set({ state: 'passed' }) + setSessionLogStatus(status) }) }) }) diff --git a/packages/driver/src/cy/commands/sessions/utils.ts b/packages/driver/src/cy/commands/sessions/utils.ts index bac14834bc91..03c78351cdb4 100644 --- a/packages/driver/src/cy/commands/sessions/utils.ts +++ b/packages/driver/src/cy/commands/sessions/utils.ts @@ -208,7 +208,28 @@ function navigateAboutBlank (session: boolean = true) { }) } +const enum SESSION_STEPS { + create = 'create', + restore = 'restore', + recreate = 'recreate', + validate = 'validate', +} + const statusMap = { + commandState: (status: string) => { + switch (status) { + case 'failed': + return 'failed' + case 'recreating': + case 'recreated': + return 'warned' + case 'created': + case 'restored': + return 'passed' + default: + return 'pending' + } + }, inProgress: (step) => { switch (step) { case 'create': @@ -255,5 +276,6 @@ export { getConsoleProps, getPostMessageLocalStorage, navigateAboutBlank, + SESSION_STEPS, statusMap, } diff --git a/packages/reporter/cypress/e2e/commands.cy.ts b/packages/reporter/cypress/e2e/commands.cy.ts index 86ef15d6c461..50da592c0591 100644 --- a/packages/reporter/cypress/e2e/commands.cy.ts +++ b/packages/reporter/cypress/e2e/commands.cy.ts @@ -848,7 +848,7 @@ describe('commands', { viewportHeight: 1000 }, () => { }) it('shows a tooltip', () => { - cy.get('.command-name-within').click() + cy.get('.command-name-within').click('top') cy.get('.cy-tooltip').should('have.text', 'Printed output to your console') }) diff --git a/packages/reporter/src/commands/command.cy.tsx b/packages/reporter/src/commands/command.cy.tsx index aa8867a54163..73aa58a1884d 100644 --- a/packages/reporter/src/commands/command.cy.tsx +++ b/packages/reporter/src/commands/command.cy.tsx @@ -1,31 +1,91 @@ import React from 'react' import Command from './command' import CommandModel from './command-model' +import type { SessionStatus } from '../sessions/utils' +import type { TestState } from '@packages/types' describe('commands', () => { + describe('test states', () => { + it('warned command', () => { + cy.mount( +
+ +
, + ) + + cy.percySnapshot() + }) + }) + describe('sessionPill', () => { - const statusList = [ - 'creating', - 'created', - 'restoring', - 'restored', - 'recreating', - 'recreated', - 'failed', + const statusList: Array<{ + state: TestState + status: SessionStatus + }> = [ + { + state: 'pending', + status: 'creating', + }, + { + state: 'passed', + status: 'created', + }, + { + state: 'pending', + status: 'restoring', + }, + { + state: 'passed', + status: 'restored', + }, + { + state: 'warned', + status: 'recreating', + }, + { + state: 'warned', + status: 'recreated', + }, + { + state: 'failed', + status: 'failed', + }, ] it('session status in command', () => { cy.mount(
- {statusList.map((status, index) => ( + {statusList.map(({ state, status }, index) => ( { {isSessionCommand && ( )} {!model.visible && ( diff --git a/packages/reporter/src/commands/commands.scss b/packages/reporter/src/commands/commands.scss index ecc29616a416..0d124e2c8a2f 100644 --- a/packages/reporter/src/commands/commands.scss +++ b/packages/reporter/src/commands/commands.scss @@ -252,6 +252,30 @@ } } + .command-state-warned { + color: $warn-text; + + &:not(.command-type-system) { + border-left: $warn-border; + } + + .command-number-column, + .command-method, + .command-message { + color: $warn-text; + } + + .command-group { + border-color: $warn-text; + @include nested-command-dashes($warn-text); + + .command-group-block { + border-color: $warn-text; + @include nested-command-dashes($warn-text); + } + } + } + .command-state-failed { color: $err-header-text; @@ -286,6 +310,7 @@ } // Custom Styles for Specific Commands + // NOTE: assert does not support warned state .command-name-assert { .command-method { span { diff --git a/packages/reporter/src/errors/errors.scss b/packages/reporter/src/errors/errors.scss index 1f3f5b2056ba..d18932bbbafe 100644 --- a/packages/reporter/src/errors/errors.scss +++ b/packages/reporter/src/errors/errors.scss @@ -61,10 +61,15 @@ $code-border-radius: 4px; 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 4px); - width: 13px; - min-width: 13px; - } - } + width: 12px; + min-width: 12px; + + &:first-of-type { + width: 13px; + min-width: 13px; + } + } + } .runnable-err-header > .runnable-err-name { padding: 5px 4px 5px 15px; diff --git a/packages/reporter/src/lib/tag.cy.tsx b/packages/reporter/src/lib/tag.cy.tsx index 5a3a1e226583..1390c618b155 100644 --- a/packages/reporter/src/lib/tag.cy.tsx +++ b/packages/reporter/src/lib/tag.cy.tsx @@ -11,6 +11,7 @@ describe('Tag', () => { const statuses = [ 'successful-status', + 'warned-status', 'failed-status', ] diff --git a/packages/reporter/src/lib/tag.scss b/packages/reporter/src/lib/tag.scss index 0951e7f94896..46c7cec4df53 100644 --- a/packages/reporter/src/lib/tag.scss +++ b/packages/reporter/src/lib/tag.scss @@ -37,9 +37,14 @@ color: $jade-300; } + &.warned-status { + background-color: $gray-1000; + border: $gray-900 1px solid; + color: $warn-text; + } + &.failed-status { background-color: $red-500; - font-weight: 500; } &.reporter-tag-has-count { diff --git a/packages/reporter/src/lib/variables.scss b/packages/reporter/src/lib/variables.scss index 5dc085420b5d..a7e778827755 100644 --- a/packages/reporter/src/lib/variables.scss +++ b/packages/reporter/src/lib/variables.scss @@ -104,7 +104,6 @@ $fail: $red-400; $pending: $indigo-400; $pinned: $purple-400; $retried: $orange-400; -$yellow-medium: $orange-800; $link-text: $indigo-600; @@ -118,10 +117,8 @@ $err-text: $red-400; $reporter-section-background: #171926; // not a brand color -$warn-background: $red-1000; -$warn-header-background: $orange-1000; -$warn-header-text: $orange-700; -$warn-text: $orange-600; +$warn-border: 2px solid $orange-300; +$warn-text: $orange-300; $header-height: 64px; $reporter-contents-min-width: 170px; diff --git a/packages/reporter/src/sessions/sessions-model.ts b/packages/reporter/src/sessions/sessions-model.ts index 1e1028704672..aa5a596a902e 100644 --- a/packages/reporter/src/sessions/sessions-model.ts +++ b/packages/reporter/src/sessions/sessions-model.ts @@ -1,5 +1,7 @@ import { observable } from 'mobx' import Instrument, { InstrumentProps } from '../instruments/instrument-model' +import { determineTagType } from './utils' +import type { SessionStatus } from './utils' export interface SessionProps extends InstrumentProps { name: string @@ -8,7 +10,7 @@ export interface SessionProps extends InstrumentProps { sessionInfo: { id: string isGlobalSession: boolean - status: 'creating' | 'created' | 'restored' |'restored' | 'recreating' | 'recreated' | 'failed' + status: SessionStatus } } @@ -16,19 +18,22 @@ export default class Session extends Instrument { @observable name: string @observable status: string @observable isGlobalSession: boolean = false + @observable tagType: string constructor (props: SessionProps) { super(props) - const { sessionInfo: { isGlobalSession, id, status } } = props + const { state, sessionInfo: { isGlobalSession, id, status } } = props this.isGlobalSession = isGlobalSession this.name = id this.status = status + this.tagType = determineTagType(state) } update (props: Partial) { - const { sessionInfo } = props + const { state, sessionInfo } = props this.status = sessionInfo?.status || '' + this.tagType = determineTagType(state || '') } } diff --git a/packages/reporter/src/sessions/sessions.cy.tsx b/packages/reporter/src/sessions/sessions.cy.tsx index 64206563ab50..5079fad1e0ff 100644 --- a/packages/reporter/src/sessions/sessions.cy.tsx +++ b/packages/reporter/src/sessions/sessions.cy.tsx @@ -41,6 +41,20 @@ describe('sessions instrument panel', { viewportWidth: 400 }, () => { }, }) + const warnedSpecSession = new SessionsModel({ + name: 'session', + state: 'warned', + type: 'parent', + testId: '1', + id: 3, + sessionInfo: { + id: 'spec_session_warned', + isGlobalSession: false, + status: 'recreated', + }, + testCurrentRetry: 1, + }) + const failedSpecSession = new SessionsModel({ name: 'session', state: 'failed', @@ -56,7 +70,7 @@ describe('sessions instrument panel', { viewportWidth: 400 }, () => { }) beforeEach(() => { - cy.mount() + cy.mount() cy.get('.sessions-container').should('exist') cy.get('.hook-header > .collapsible-header').as('header') @@ -74,7 +88,7 @@ describe('sessions instrument panel', { viewportWidth: 400 }, () => { cy.get('@header').should('have.attr', 'aria-expanded', 'true') cy.get('.session-item') - .should('have.length', 3) + .should('have.length', 4) .should('be.visible') cy.percySnapshot() @@ -106,11 +120,24 @@ describe('sessions instrument panel', { viewportWidth: 400 }, () => { cy.percySnapshot() }) - it('has failed session item', () => { + it('has warned session item', () => { cy.get('@header').click() cy.get('.session-item') .eq(2) + .within(() => { + cy.contains('spec_session_warned').should('have.class', 'spec-session') + cy.get('.session-status').should('have.class', 'warned-status') + }) + + cy.percySnapshot() + }) + + it('has failed session item', () => { + cy.get('@header').click() + + cy.get('.session-item') + .eq(3) .within(() => { cy.contains('spec_session_failed').should('have.class', 'spec-session') cy.get('.global-session-icon').should('not.exist') @@ -125,7 +152,7 @@ describe('sessions instrument panel', { viewportWidth: 400 }, () => { cy.get('@header').click() - cy.get('.session-item').eq(2).click() + cy.get('.session-item').eq(3).click() cy.get('.cy-tooltip') .should('have.text', 'Printed output to your console') diff --git a/packages/reporter/src/sessions/sessions.tsx b/packages/reporter/src/sessions/sessions.tsx index 86149b5339f2..dd38395afd8e 100644 --- a/packages/reporter/src/sessions/sessions.tsx +++ b/packages/reporter/src/sessions/sessions.tsx @@ -13,7 +13,9 @@ export interface SessionPanelProps { model: Record } -const SessionRow = ({ name, isGlobalSession, id, status, testId }: SessionsModel) => { +const SessionRow = (model: SessionsModel) => { + const { name, isGlobalSession, id, status, testId } = model + const printToConsole = (id) => { events.emit('show:command', testId, id) } @@ -34,7 +36,7 @@ const SessionRow = ({ name, isGlobalSession, id, status, testId }: SessionsModel
diff --git a/packages/reporter/src/sessions/utils.ts b/packages/reporter/src/sessions/utils.ts new file mode 100644 index 000000000000..34fecd367076 --- /dev/null +++ b/packages/reporter/src/sessions/utils.ts @@ -0,0 +1,16 @@ +function determineTagType (state: string) { + switch (state) { + case 'failed': + return 'failed-status' + case 'warned': + return 'warned-status' + default: + return 'successful-status' + } +} + +export type SessionStatus = 'creating' | 'created' | 'restoring' |'restored' | 'recreating' | 'recreated' | 'failed' + +export { + determineTagType, +} diff --git a/packages/types/src/driver.ts b/packages/types/src/driver.ts index 3335a1bb926e..8392302421dc 100644 --- a/packages/types/src/driver.ts +++ b/packages/types/src/driver.ts @@ -43,4 +43,4 @@ export interface CachedTestState { export type Instrument = 'agent' | 'command' | 'route' -export type TestState = 'active' | 'failed' | 'pending' | 'passed' | 'processing' +export type TestState = 'active' | 'failed' | 'pending' | 'passed' | 'processing' | 'warned'