diff --git a/packages/app/cypress/e2e/cypress-in-cypress-e2e.cy.ts b/packages/app/cypress/e2e/cypress-in-cypress-e2e.cy.ts index 14e709847ca9..1396fd34eaac 100644 --- a/packages/app/cypress/e2e/cypress-in-cypress-e2e.cy.ts +++ b/packages/app/cypress/e2e/cypress-in-cypress-e2e.cy.ts @@ -213,7 +213,6 @@ describe('Cypress In Cypress E2E', { viewportWidth: 1500, defaultCommandTimeout: cy.visitApp() cy.specsPageIsVisible() cy.contains('withFailure.spec').click() - cy.contains('[aria-controls=reporter-inline-specs-list]', 'Specs') cy.get('[data-cy="runnable-header"]').should('be.visible') cy.get('body').type('f') diff --git a/packages/app/cypress/e2e/cypress-in-cypress.cy.ts b/packages/app/cypress/e2e/cypress-in-cypress.cy.ts index ea471e346152..a8399597cec7 100644 --- a/packages/app/cypress/e2e/cypress-in-cypress.cy.ts +++ b/packages/app/cypress/e2e/cypress-in-cypress.cy.ts @@ -131,8 +131,7 @@ describe('Cypress in Cypress', { viewportWidth: 1500, defaultCommandTimeout: 100 // validate that the width we set in `withCtx` above is the starting point cy.get(`[data-cy="reporter-panel"]`).invoke('outerWidth').should('eq', 800) - cy.contains('[aria-controls=reporter-inline-specs-list]', 'Specs') - .click({ force: true }) + cy.findByTestId('toggle-specs-button').click({ force: true }) // this tooltip text confirms specs list is open cy.contains('Collapse Specs List') diff --git a/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts b/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts index 8b15e7d3f49c..82d764653e4d 100644 --- a/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts +++ b/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts @@ -53,11 +53,11 @@ describe('hooks', { o.sinon.stub(ctx.actions.file, 'openFile') }) - cy.contains('Open in IDE').invoke('show').click({ force: true }) + cy.get('.open-in-ide-button').invoke('show').click() cy.withCtx((ctx, o) => { - expect(ctx.actions.file.openFile).to.have.been.calledWith(o.sinon.match(new RegExp(`hooks/basic\.cy\.js$`)), o.ideLine, o.ideColumn) - }, { ideLine: 2, ideColumn: Cypress.browser.family === 'firefox' ? 5 : 2 }) + expect(ctx.actions.file.openFile).to.have.been.calledWith(o.sinon.match(new RegExp(`hooks/basic\.cy\.js$`)), 1, 1) + }) }) it('does not display commands from skipped tests', () => { @@ -84,12 +84,22 @@ describe('hooks', { passCount: 1, }) - cy.contains('test wrapper').parents('.collapsible').first().should(($suite) => { + cy.contains('test wrapper > nested suite 1').parents('.collapsible').first().should(($suite) => { expect($suite).not.to.contain('test 1') expect($suite).to.contain('nested suite 1') expect($suite).to.contain('test 2') expect($suite).not.to.contain('nested suite 2') expect($suite).not.to.contain('test 3') + expect($suite).not.to.contain('nested suite 3') + expect($suite).not.to.contain('test 4') + }) + + cy.contains('test wrapper > nested suite 3').parents('.collapsible').first().should(($suite) => { + expect($suite).not.to.contain('test 1') + expect($suite).not.to.contain('nested suite 1') + expect($suite).not.to.contain('test 2') + expect($suite).not.to.contain('nested suite 2') + expect($suite).not.to.contain('test 3') expect($suite).to.contain('nested suite 3') expect($suite).to.contain('test 4') }) diff --git a/packages/app/cypress/e2e/runner/runner.ui.cy.ts b/packages/app/cypress/e2e/runner/runner.ui.cy.ts index be616e889512..5d574b6e50d6 100644 --- a/packages/app/cypress/e2e/runner/runner.ui.cy.ts +++ b/packages/app/cypress/e2e/runner/runner.ui.cy.ts @@ -171,8 +171,9 @@ describe('src/cypress/runner', () => { o.sinon.stub(ctx.actions.file, 'openFile') }) - cy.contains('a', 'simple-cy-assert.runner') - .click() + cy.get('.open-in-ide-button').should('not.be.visible') + cy.get('.runnable-header-file-name').realHover() + cy.get('.open-in-ide-button').should('be.visible').click() cy.withCtx((ctx, o) => { expect(ctx.actions.file.openFile).to.have.been.calledWith(o.sinon.match(new RegExp(`simple-cy-assert\.runner\.cy\.js$`)), 1, 1) diff --git a/packages/app/cypress/e2e/runner/sessions.ui.cy.ts b/packages/app/cypress/e2e/runner/sessions.ui.cy.ts index d4a771e199cc..8aafd2d1f542 100644 --- a/packages/app/cypress/e2e/runner/sessions.ui.cy.ts +++ b/packages/app/cypress/e2e/runner/sessions.ui.cy.ts @@ -386,7 +386,7 @@ describe('runner/cypress sessions.ui.spec', { .within(() => { cy.contains('.command-wrapper', 'Create new session') .should('have.class', 'command-state-failed') - .find('.failed-indicator') + .find('[data-cy="failed-icon-indicator"]') .should('exist') }) }) @@ -465,7 +465,7 @@ describe('runner/cypress sessions.ui.spec', { cy.contains('.command-wrapper', 'Validate session').as('validateSessionGroup') .should('have.class', 'command-state-failed') - .find('.failed-indicator') + .find('[data-cy="failed-icon-indicator"]') .should('exist') }) }) @@ -528,7 +528,7 @@ describe('runner/cypress sessions.ui.spec', { cy.contains('.command-wrapper', 'Validate session').as('validateSessionGroup') .should('have.class', 'command-state-failed') - .find('.failed-indicator') + .find('[data-cy="failed-icon-indicator"]') .should('exist') const restoredMessagePostfix = 'This error occurred while validating the restored session. Because validation failed, we will try to recreate the session.' @@ -545,7 +545,7 @@ describe('runner/cypress sessions.ui.spec', { cy.contains('.command-wrapper', 'Recreate session') .should('have.class', successfullyRecreatedSession ? 'command-state-passed' : 'command-state-failed') - .find('.failed-indicator') + .find('[data-cy="failed-icon-indicator"]') .should(successfullyRecreatedSession ? 'not.exist' : 'exist', 'is-open') }) }) diff --git a/packages/app/cypress/e2e/runner/support/spec-loader.ts b/packages/app/cypress/e2e/runner/support/spec-loader.ts index 651a6a611153..94182aff830e 100644 --- a/packages/app/cypress/e2e/runner/support/spec-loader.ts +++ b/packages/app/cypress/e2e/runner/support/spec-loader.ts @@ -5,9 +5,10 @@ export const shouldHaveTestResults = ({ passCount, failCount, pendingCount }) => failCount = failCount || '--' cy.get('button.restart', { timeout: 30000 }).should('be.visible') // ensure tests are finished running - cy.findByLabelText('Stats', { timeout: 10000 }).within(() => { - cy.get('.passed .num', { timeout: 30000 }).should('have.text', `${passCount}`) - cy.get('.failed .num', { timeout: 30000 }).should('have.text', `${failCount}`) + + cy.get('.stats', { timeout: 10000 }).within(() => { + cy.get('.passed .num', { timeout: 40000 }).should('have.text', `${passCount}`) + cy.get('.failed .num', { timeout: 40000 }).should('have.text', `${failCount}`) if (pendingCount) { cy.get('.pending .num', { timeout: 20000 }).should('have.text', `${pendingCount}`) diff --git a/packages/app/cypress/e2e/runner/support/verify-failures.ts b/packages/app/cypress/e2e/runner/support/verify-failures.ts index 425a84d72b63..a730fafbcb3d 100644 --- a/packages/app/cypress/e2e/runner/support/verify-failures.ts +++ b/packages/app/cypress/e2e/runner/support/verify-failures.ts @@ -71,7 +71,7 @@ const verifyFailure = (options) => { cy.contains('.runnable-title', specTitle).closest('.runnable').as('Root') cy.get('@Root').within(() => { - cy.contains('View stack trace').click() + cy.contains('Stack trace').click() const messageLines = [].concat(message) diff --git a/packages/app/cypress/e2e/sidebar_navigation.cy.ts b/packages/app/cypress/e2e/sidebar_navigation.cy.ts index d6e46fdfeeae..77f0bf6ddb29 100644 --- a/packages/app/cypress/e2e/sidebar_navigation.cy.ts +++ b/packages/app/cypress/e2e/sidebar_navigation.cy.ts @@ -56,7 +56,7 @@ describe('Sidebar Navigation', { viewportWidth: 1280 }, () => { cy.contains('fixture.js').click() - cy.get('.toggle-specs-text').click() + cy.get('.toggle-specs-button').click() cy.findByTestId('reporter-panel').invoke('outerWidth').then(($initialWidth) => { expect($initialWidth).eq(100) @@ -291,7 +291,7 @@ describe('Sidebar Navigation', { viewportWidth: 1280 }, () => { it.skip('resize nav and persist the state after refresh', () => { cy.contains('fixture.js').click() - cy.get('.toggle-specs-text').click() + cy.get('.toggle-specs-button').click() cy.withCtx((ctx, o) => { o.sinon.stub(ctx.actions.localSettings, 'setPreferences').resolves() diff --git a/packages/app/cypress/e2e/specs_list_e2e.cy.ts b/packages/app/cypress/e2e/specs_list_e2e.cy.ts index 58818aed91b7..1dcb1fc42120 100644 --- a/packages/app/cypress/e2e/specs_list_e2e.cy.ts +++ b/packages/app/cypress/e2e/specs_list_e2e.cy.ts @@ -119,7 +119,6 @@ describe('App: Spec List (E2E)', () => { cy.findAllByTestId('spec-item-link').should('have.attr', 'href') cy.findAllByTestId('spec-item-link').contains('dom-content.spec.js').click() - cy.contains('[aria-controls=reporter-inline-specs-list]', 'Specs') cy.findByText('Your tests are loading...').should('not.be.visible') cy.get('[data-cy="runnable-header"]').should('be.visible') cy.get('body').type('f') @@ -133,10 +132,8 @@ describe('App: Spec List (E2E)', () => { cy.findAllByTestId('spec-item-link').contains('accounts_list.spec.js').click() // ensure the tests are loaded - cy.contains('[aria-controls=reporter-inline-specs-list]', 'Specs') cy.findByText('Your tests are loading...').should('not.be.visible') - cy.contains('[aria-controls=reporter-inline-specs-list]', 'Specs') cy.get('[data-cy="runnable-header"]').should('be.visible') // open the inline spec list cy.get('body').type('f') @@ -369,7 +366,7 @@ describe('App: Spec List (E2E)', () => { // A bit of a hack, but our cy-in-cy test needs to wait for the reporter to fully render before expanding the "Search specs" menu. // Otherwise, the click happens before the event is registered, which causes the "Search Specs" menu to not expand. cy.get('[data-cy="runnable-header"]').should('be.visible') - cy.contains('button', 'Specs').click({ force: true }) + cy.findByTestId('toggle-specs-button').click({ force: true }) // wait until specs list is visible cy.findByTestId('specs-list-container').should('be.visible') diff --git a/packages/app/cypress/e2e/studio/helper.ts b/packages/app/cypress/e2e/studio/helper.ts index 6c66f9dbf475..34a9c03e5f71 100644 --- a/packages/app/cypress/e2e/studio/helper.ts +++ b/packages/app/cypress/e2e/studio/helper.ts @@ -30,9 +30,13 @@ export function launchStudio ({ specName = 'spec.cy.js', createNewTest = false, .closest('.runnable-wrapper').as('runnable-wrapper') .realHover() - cy.get('@runnable-wrapper') - .findByTestId('launch-studio') - .click() + if (createNewTest) { + cy.get('@runnable-wrapper').realHover().findByTestId('create-new-test-button').click() + } else { + cy.get('@runnable-wrapper') + .findByTestId('launch-studio') + .click() + } // Studio re-executes spec before waiting for commands - wait for the spec to finish executing. cy.waitForSpecToFinish() diff --git a/packages/driver/cypress/support/utils.ts b/packages/driver/cypress/support/utils.ts index 486f2dd40e4e..08ef205d357b 100644 --- a/packages/driver/cypress/support/utils.ts +++ b/packages/driver/cypress/support/utils.ts @@ -9,7 +9,7 @@ export const getCommandLogWithText = (command, type?) => { cy.$$('.runnable-active .collapsible:not(.is-open) .collapsible-header', top?.document).click() return cy - .$$(`.runnable-active .command-${type}:contains(${command})`, top?.document) + .$$(`.test.runnable-active .command-${type}:contains(${command})`, top?.document) .closest('.command') } diff --git a/packages/frontend-shared/src/assets/icons/status-processing_x12.svg b/packages/frontend-shared/src/assets/icons/status-processing_x12.svg deleted file mode 100644 index 2b33cc2eb4fe..000000000000 --- a/packages/frontend-shared/src/assets/icons/status-processing_x12.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/reporter/README.md b/packages/reporter/README.md index afdcc2b5758a..0b4b294f4abe 100644 --- a/packages/reporter/README.md +++ b/packages/reporter/README.md @@ -16,20 +16,6 @@ The reporter shows the running results of the tests. It includes the following: - commands and assertions with detailed information - any failures/errors -## Building - -### For development - -```bash -yarn workspace @packages/reporter build -``` - -### For production - -```bash -yarn workspace @packages/reporter build-prod -``` - ## Developing To see the reporter render, see [Developing the driver](../driver/README.md#Developing). diff --git a/packages/reporter/cypress/e2e/commands.cy.ts b/packages/reporter/cypress/e2e/commands.cy.ts index ce0267afb0ad..86f5ef0544d0 100644 --- a/packages/reporter/cypress/e2e/commands.cy.ts +++ b/packages/reporter/cypress/e2e/commands.cy.ts @@ -898,7 +898,10 @@ describe('commands', { viewportHeight: 1000 }, () => { }) it('shows a tooltip', () => { - cy.get('.command-name-within').click('top') + cy.get('.command-name-within').within(() => { + cy.contains('within').click() + }) + cy.get('.cy-tooltip').should('have.text', 'Printed output to your console') }) @@ -911,21 +914,32 @@ describe('commands', { viewportHeight: 1000 }, () => { it('prints to console', () => { cy.spy(runner, 'emit') - cy.get('.command-name-within').click('top') + cy.get('.command-name-within').within(() => { + cy.contains('within').click() + }) cy.wrap(runner.emit).should('be.calledWith', 'runner:console:log', 'r3', fakeIdForTest) }) it('shows the snapshot', () => { cy.spy(runner, 'emit') - cy.get('.command-name-within').click('top') + cy.get('.command-name-within').within(() => { + cy.contains('within').click() + }) + cy.wrap(runner.emit).should('be.calledWith', 'runner:show:snapshot', 'r3', fakeIdForTest) }) it('unpins after clicking again, does not re-print to the console', () => { cy.spy(runner, 'emit') - cy.get('.command-name-within').click('top') - cy.get('.command-name-within').click('top') + cy.get('.command-name-within').within(() => { + cy.contains('within').click() + }) + + cy.get('.command-name-within').within(() => { + cy.contains('within').click() + }) + // @ts-ignore cy.wrap(runner.emit.withArgs('runner:console:log')).should('be.calledOnce') }) diff --git a/packages/reporter/cypress/e2e/header.cy.ts b/packages/reporter/cypress/e2e/header.cy.ts index 4e4c8cc1b2d5..3bb1e9be5259 100755 --- a/packages/reporter/cypress/e2e/header.cy.ts +++ b/packages/reporter/cypress/e2e/header.cy.ts @@ -57,7 +57,7 @@ describe('header', () => { }) it('shows \'Tests\' when >= 398px wide', () => { - cy.get('.toggle-specs-wrapper span').should('be.visible') + cy.get('[data-cy=toggle-specs-button]').should('be.visible') }) }) diff --git a/packages/reporter/cypress/e2e/meta_&%.cy.ts b/packages/reporter/cypress/e2e/meta_&%.cy.ts index 9afdea03c851..0f424b0cbc2c 100644 --- a/packages/reporter/cypress/e2e/meta_&%.cy.ts +++ b/packages/reporter/cypress/e2e/meta_&%.cy.ts @@ -3,7 +3,7 @@ describe('special characters', () => { it('displays file name with decoded special characters', () => { cy.wrap(Cypress.$(window.top.document.body)) - .find('.reporter .runnable-header a') - .should('have.text', 'meta_&%.cy.ts') + .find('.reporter .runnable-header') + .contains('meta_&%.cy.ts') }) }) diff --git a/packages/reporter/cypress/e2e/runnables.cy.ts b/packages/reporter/cypress/e2e/runnables.cy.ts index eb108faa239d..966b8cd653d1 100644 --- a/packages/reporter/cypress/e2e/runnables.cy.ts +++ b/packages/reporter/cypress/e2e/runnables.cy.ts @@ -137,8 +137,8 @@ describe('runnables', () => { it('does not display time if no time taken', () => { start() - cy.get('.runnable-header span:first').should('have.text', 'foo.js') - cy.get('.runnable-header span:last').should('not.have.text', '--') + cy.get('.runnable-header .runnable-header-file-name').contains('foo.js') + cy.get('.runnable-header .duration').should('not.exist') }) describe('when there are no tests', () => { @@ -204,11 +204,12 @@ describe('runnables', () => { }) it('contains name of spec and emits when clicked', () => { - const selector = '.runnable-header a' + const selector = '.runnable-header-file-name' cy.stub(runner, 'emit').callThrough() - cy.get(selector).as('spec-title').contains('foo.js') + cy.get(selector).as('spec-title').contains('foo.js').realHover() + cy.get('.open-in-ide-button').click() cy.get(selector).click().then(() => { expect(runner.emit).to.be.calledWith('open:file:unified') }) diff --git a/packages/reporter/cypress/e2e/shortcuts.cy.ts b/packages/reporter/cypress/e2e/shortcuts.cy.ts index 4ada861cb4ff..c31c1bb7c39c 100755 --- a/packages/reporter/cypress/e2e/shortcuts.cy.ts +++ b/packages/reporter/cypress/e2e/shortcuts.cy.ts @@ -82,17 +82,17 @@ describe('shortcuts', function () { cy.get('body').then(() => { expect(runner.emit).not.to.have.been.calledWith('save:state') - cy.contains('button', 'Specs').should('have.attr', 'aria-expanded', 'false') + cy.get('[data-cy=toggle-specs-button]').should('have.attr', 'aria-expanded', 'false') }) cy.get('body').type('f').then(() => { expect(runner.emit).to.have.been.calledWith('save:state') - cy.contains('button', 'Specs').should('have.attr', 'aria-expanded', 'true') + cy.get('[data-cy=toggle-specs-button]').should('have.attr', 'aria-expanded', 'true') }) cy.get('body').type('f').then(() => { expect(runner.emit).to.have.been.calledWith('save:state') - cy.contains('button', 'Specs').should('have.attr', 'aria-expanded', 'false') + cy.get('[data-cy=toggle-specs-button]').should('have.attr', 'aria-expanded', 'false') }) }) @@ -140,9 +140,9 @@ describe('shortcuts', function () { }) it('has shortcut in tooltips', () => { - cy.get('.toggle-specs-wrapper > button').trigger('mouseover') + cy.get('[data-cy=toggle-specs-button]').trigger('mouseover') cy.get('.cy-tooltip').should('have.text', 'Expand Specs List F') - cy.get('.toggle-specs-wrapper > button').trigger('mouseout') + cy.get('[data-cy=toggle-specs-button]').trigger('mouseout') cy.get('button.restart').trigger('mouseover') cy.get('.cy-tooltip').should('have.text', 'Run All Tests R') diff --git a/packages/reporter/cypress/e2e/spec_title.cy.ts b/packages/reporter/cypress/e2e/spec_title.cy.ts index 51f89915d69c..938c39044808 100644 --- a/packages/reporter/cypress/e2e/spec_title.cy.ts +++ b/packages/reporter/cypress/e2e/spec_title.cy.ts @@ -55,19 +55,24 @@ describe('spec title', () => { }) it('displays name without path', () => { - cy.get('.runnable-header').find('a').should('have.text', 'foo.js') + cy.get('.runnable-header-file-name').contains('foo.js') cy.percySnapshot() }) - it('displays tooltip on hover', () => { - cy.get('.runnable-header a').first().trigger('mouseover') - cy.get('.cy-tooltip').first().should('have.text', 'Open in IDE') + it('displays Open in IDE button on spec name hover', () => { + cy.get('.open-in-ide-button').should('have.css', 'opacity', '0') + + cy.get('.runnable-header-file-name').realHover() + cy.get('.open-in-ide-button').should('have.css', 'opacity', '1') + cy.get('.open-in-ide-button').contains('Open in IDE') + + cy.percySnapshot() }) itHandlesFileOpening({ getRunner: () => runner, - selector: '.runnable-header a', + selector: '.open-in-ide-button', file: { file: '/absolute/path/to/foo.js', line: 0, diff --git a/packages/reporter/cypress/e2e/suites.cy.ts b/packages/reporter/cypress/e2e/suites.cy.ts index ba91185ab3e7..4121dcf28705 100644 --- a/packages/reporter/cypress/e2e/suites.cy.ts +++ b/packages/reporter/cypress/e2e/suites.cy.ts @@ -56,6 +56,10 @@ describe('suites', () => { .closest('.runnable') .should('have.class', 'runnable-failed') + cy.contains('suite 1 > nested suite 1') + .closest('.runnable') + .should('have.class', 'runnable-active') + cy.contains('suite 2') .closest('.runnable') .should('have.class', 'runnable-passed') @@ -148,27 +152,17 @@ describe('suites', () => { cy.contains('nested suite 1') .closest('.runnable-wrapper') .realHover() - .find('.runnable-controls-studio') + .get('[data-cy="create-new-test-button"]') .should('be.visible') - .should('have.css', 'opacity', '0.5') - }) - - it('displays studio icon with no transparency and tooltip on hover', () => { - cy.contains('nested suite 1') - .closest('.collapsible-header') - .find('.runnable-controls-studio') - .realHover() - .should('be.visible') - .should('have.css', 'opacity', '1') - - cy.get('.cy-tooltip').contains('Add New Test') }) it('emits studio:init:suite with the suite id when clicked', () => { cy.stub(runner, 'emit') cy.contains('suite 1').parents('.collapsible-header') - .find('.runnable-controls-studio').click() + .realHover().within(() => { + cy.get('[data-cy="create-new-test-button"]').click() + }) cy.wrap(runner.emit).should('be.calledWith', 'studio:init:suite', 'r2') }) diff --git a/packages/reporter/cypress/e2e/test_errors.cy.ts b/packages/reporter/cypress/e2e/test_errors.cy.ts index a9f797e01e39..73770caf5d2d 100644 --- a/packages/reporter/cypress/e2e/test_errors.cy.ts +++ b/packages/reporter/cypress/e2e/test_errors.cy.ts @@ -70,7 +70,7 @@ describe('test errors', () => { it('does not expand or collapse stack trace when clicking', () => { cy.get('.runnable-err-print').click() cy.get('.runnable-err-stack-trace').should('not.exist') - cy.contains('View stack trace').click() + cy.contains('Stack trace').click() cy.get('.runnable-err-stack-trace').should('be.visible') cy.get('.runnable-err-print').click() cy.get('.runnable-err-stack-trace').should('be.visible') @@ -87,13 +87,13 @@ describe('test errors', () => { }) it('opens stack trace on click', () => { - cy.contains('View stack trace').click() + cy.contains('Stack trace').click() cy.get('.runnable-err-stack-trace').should('be.visible') cy.percySnapshot() }) it('pairs down stack line whitespace', () => { - cy.contains('View stack trace').click() + cy.contains('Stack trace').click() cy.get('.runnable-err-stack-trace').within(() => { cy.get('.err-stack-line') @@ -118,7 +118,7 @@ describe('test errors', () => { }) it('does not include message in stack trace', () => { - cy.contains('View stack trace').click() + cy.contains('Stack trace').click() cy.get('.runnable-err-stack-trace') .invoke('text') .should('not.include', 'Some Error') @@ -126,7 +126,7 @@ describe('test errors', () => { }) it('turns files into links', () => { - cy.contains('View stack trace').click() + cy.contains('Stack trace').click() cy.get('.runnable-err-stack-trace .runnable-err-file-path') .should('have.length', 3) @@ -141,34 +141,34 @@ describe('test errors', () => { }) it('does not turn cypress:// files into links', () => { - cy.contains('View stack trace').click() + cy.contains('Stack trace').click() cy.contains('cypress://').find('a').should('not.exist') }) it('does not turn cypress_runner.js files into links', () => { - cy.contains('View stack trace').click() + cy.contains('Stack trace').click() cy.contains('cypress_runner.js').find('a').should('not.exist') }) it('does not turn lines without absoluteFile into links', () => { - cy.contains('View stack trace').click() + cy.contains('Stack trace').click() cy.contains('.err-stack-line', 'http://localhost:1234/me/dev/my/app.js:8:11') .find('a').should('not.exist') }) it('does not turn anything after "From Node.js Internals" into links', () => { - cy.contains('View stack trace').click() + cy.contains('Stack trace').click() cy.contains('events.js').find('a').should('not.exist') cy.contains('node/internals.js').find('a').should('not.exist') }) it('does not collapse test when clicking', () => { - cy.contains('View stack trace').click() + cy.contains('Stack trace').click() cy.get('.command-wrapper').should('be.visible') }) it('displays tooltip on hover', () => { - cy.contains('View stack trace').click() + cy.contains('Stack trace').click() cy.get('.runnable-err-stack-trace a').first().trigger('mouseover') cy.get('.cy-tooltip').first().should('have.text', 'Open in IDE') diff --git a/packages/reporter/cypress/e2e/tests.cy.ts b/packages/reporter/cypress/e2e/tests.cy.ts index b1ba923e33fa..934d781ba17b 100644 --- a/packages/reporter/cypress/e2e/tests.cy.ts +++ b/packages/reporter/cypress/e2e/tests.cy.ts @@ -48,13 +48,25 @@ describe('tests', () => { }) it('includes the state as a class', () => { - cy.contains('suite 1') - .closest('.runnable') - .should('have.class', 'runnable-failed') + cy.get('.suite').first().within((el) => { + cy.wrap(el).contains('suite 1') + cy.get('.test').eq(0).should('have.class', 'runnable-passed') + cy.get('.test').eq(1).should('have.class', 'runnable-failed') + }) - cy.contains('suite 2') - .closest('.runnable') - .should('have.class', 'runnable-passed') + cy.get('.suite').eq(1).within((el) => { + cy.wrap(el).contains('suite 1 > nested suite 1') + cy.get('.test').eq(0).should('have.class', 'runnable-pending') + cy.get('.test').eq(1).should('have.class', 'runnable-active') + }) + + cy.get('.suite').eq(2).within((el) => { + cy.wrap(el).contains('suite 2') + cy.get('.test').eq(0).should('have.class', 'runnable-passed') + cy.get('.test').eq(1).should('have.class', 'runnable-passed') + cy.get('.test').eq(2).should('have.class', 'runnable-passed') + .should('have.class', 'runnable-retried') + }) }) describe('expand and collapse', () => { @@ -356,7 +368,7 @@ describe('studio controls', () => { cy.contains('nested suite 1') .parents('.collapsible').first() .contains('test 1').click() - .parents('.collapsible').first() + .parents('.collapsible').first().scrollIntoView() .find('.studio-controls').as('pendingControls') .should('be.visible') diff --git a/packages/reporter/cypress/e2e/unit/suite_model.cy.ts b/packages/reporter/cypress/e2e/unit/suite_model.cy.ts index 8822c98e1dcd..7bdcb0411e2a 100644 --- a/packages/reporter/cypress/e2e/unit/suite_model.cy.ts +++ b/packages/reporter/cypress/e2e/unit/suite_model.cy.ts @@ -1,10 +1,10 @@ import Suite from '../../../src/runnables/suite-model' import TestModel from '../../../src/test/test-model' -const suiteWithChildren = (children: Array>) => { - const suite = new Suite({ id: '1', title: '', hooks: [] }, 0) +const suiteWithChildren = (children: Array>) => { + const suite = new Suite({ id: '1', title: '', hooks: [], suites: [], tests: [] }, 0) - suite.children = children as Array + suite.children = children.map((child) => ({ type: 'test', ...child })) as Array return suite } @@ -41,22 +41,22 @@ describe('Suite model', () => { expect(suite.state).to.equal('passed') }) - it('is processing when all children are active', () => { + it('is active when all children are active', () => { const suite = suiteWithChildren([{ state: 'active' }, { state: 'active' }]) - expect(suite.state).to.equal('processing') + expect(suite.state).to.equal('active') }) - it('is processing when there are active tests with passing tests', () => { + it('is active when there are active tests with passing tests', () => { const suite = suiteWithChildren([{ state: 'active' }, { state: 'passed' }]) - expect(suite.state).to.equal('processing') + expect(suite.state).to.equal('active') }) - it('is processing when there are active tests with pending tests', () => { + it('is active when there are active tests with pending tests', () => { const suite = suiteWithChildren([{ state: 'active' }, { state: 'pending' }]) - expect(suite.state).to.equal('processing') + expect(suite.state).to.equal('active') }) it('is processing when all children are processing', () => { @@ -77,4 +77,51 @@ describe('Suite model', () => { expect(suite.state).to.equal('processing') }) }) + + describe('nested suites', () => { + it('is passed even when children suites are not', () => { + const suite = suiteWithChildren([{ state: 'passed', type: 'test' }, { state: 'active', type: 'suite' }, { state: 'failed', type: 'suite' }]) + + expect(suite.state).to.equal('passed') + expect(suite.children[0].state).to.equal('passed') + expect(suite.children[1].state).to.equal('active') + expect(suite.children[2].state).to.equal('failed') + }) + + it('is failed even when children suites are not', () => { + const suite = suiteWithChildren([{ state: 'failed' }, { state: 'passed', type: 'suite' }, { state: 'passed', type: 'suite' }]) + + expect(suite.state).to.equal('failed') + expect(suite.children[0].state).to.equal('failed') + expect(suite.children[1].state).to.equal('passed') + expect(suite.children[2].state).to.equal('passed') + }) + + it('is active even when children suites are not', () => { + const suite = suiteWithChildren([{ state: 'active' }, { state: 'processing', type: 'suite' }, { state: 'passed', type: 'suite' }]) + + expect(suite.state).to.equal('active') + expect(suite.children[0].state).to.equal('active') + expect(suite.children[1].state).to.equal('processing') + expect(suite.children[2].state).to.equal('passed') + }) + + it('is processing even when children suites are not', () => { + const suite = suiteWithChildren([{ state: 'processing' }, { state: 'passed', type: 'suite' }, { state: 'pending', type: 'suite' }]) + + expect(suite.state).to.equal('processing') + expect(suite.children[0].state).to.equal('processing') + expect(suite.children[1].state).to.equal('passed') + expect(suite.children[2].state).to.equal('pending') + }) + + it('is pending even when children suites are not', () => { + const suite = suiteWithChildren([{ state: 'pending' }, { state: 'passed', type: 'suite' }, { state: 'failed', type: 'suite' }]) + + expect(suite.state).to.equal('pending') + expect(suite.children[0].state).to.equal('pending') + expect(suite.children[1].state).to.equal('passed') + expect(suite.children[2].state).to.equal('failed') + }) + }) }) diff --git a/packages/reporter/cypress/support/e2e.ts b/packages/reporter/cypress/support/e2e.ts index 2119e0d339c0..be8838067354 100644 --- a/packages/reporter/cypress/support/e2e.ts +++ b/packages/reporter/cypress/support/e2e.ts @@ -4,7 +4,7 @@ import { installCustomPercyCommand } from '@packages/frontend-shared/cypress/sup installCustomPercyCommand({ before () { - cy.get('.toggle-specs-text').should('be.visible') + cy.get('.toggle-specs-button').should('be.visible') }, elementOverrides: { '.command-progress': true, diff --git a/packages/reporter/cypress/support/utils.ts b/packages/reporter/cypress/support/utils.ts index 1ac9af04decf..f4bbd0e9f96f 100644 --- a/packages/reporter/cypress/support/utils.ts +++ b/packages/reporter/cypress/support/utils.ts @@ -22,7 +22,7 @@ export const itHandlesFileOpening = ({ getRunner, selector, file, stackTrace = f cy.stub(getRunner(), 'emit').callThrough() if (stackTrace) { - cy.contains('View stack trace').click() + cy.contains('Stack trace').click() } cy.get(selector).first().click().then(() => { diff --git a/packages/reporter/package.json b/packages/reporter/package.json index 5c62f5e04cbb..e146cb1e9b4e 100644 --- a/packages/reporter/package.json +++ b/packages/reporter/package.json @@ -15,6 +15,9 @@ "watch": "yarn build-for-tests --watch --progress" }, "devDependencies": { + "@cypress-design/constants-button": "^1.9.0", + "@cypress-design/react-button": "^1.10.1", + "@cypress-design/react-icon": "^1.27.0", "@cypress/react-tooltip": "0.5.3", "@fontsource/mulish": "4.3.0", "@fontsource/open-sans": "4.3.0", diff --git a/packages/reporter/src/attempts/attempts.scss b/packages/reporter/src/attempts/attempts.scss index f88e9ee11762..70243995b679 100644 --- a/packages/reporter/src/attempts/attempts.scss +++ b/packages/reporter/src/attempts/attempts.scss @@ -4,37 +4,50 @@ display: none; } - &.has-multiple-attempts .attempt-item > .collapsible > .collapsible-header-wrapper { - display: flex; + &.has-multiple-attempts .attempt-item { + > .collapsible > .collapsible-header-wrapper { + display: flex; + } + + &:not(:first-child) { + > .collapsible .attempt-name:before { + border-left: 1px dotted $gray-800; + content: ""; + left: 12px; + position: absolute; + top: 0; + height: 8px; + z-index: 1; + } + } + > .collapsible .attempt-name { + &:after { + border-left: 1px dotted $gray-800; + content: ""; + left: 12px; + position: absolute; + bottom: 0; + height: 8px; + z-index: 1; + } + } } } - .attempt-item { - margin-bottom: 7px; + .attempt-error-region { + margin: 0 8.5px; + } + .attempt-item { > .collapsible { position: relative; - margin-right: 16px; + .collapsible-header-inner { outline: none; + display: flex; + align-items: center; + width: 100%; } - - &:before { - border-left: 1px solid $gray-900; - content: ''; - left: 9px; - position: absolute; - top: 22px; - height: 15px; - } - - &.is-open:before { - display: none; - } - } - - &:last-child > .collapsible:before { - display: none; } > .is-open .open-close-indicator { @@ -47,76 +60,60 @@ } } - .open-close-indicator { - svg { - margin-right: 3px; + .open-close-indicator { + svg { + margin-right: 3px; - &.collapse-icon { - display: none; - } + &.collapse-icon { + display: none; + } - &.expand-icon { - display: block; + &.expand-icon { + display: block; + } } } - } - .attempt-content { - padding-left: 5px; - } -} + .attempt-content { + display: flex; + margin-bottom: 6px; - .attempt-state-failed { - .attempt-name:after { - color: $fail; + > div { + width: 100%; + } } - } - .attempt-state-passed { - .attempt-name:after { - color: $pass; + + &:not(:last-child) .attempt-name { + border-bottom: 1px solid $gray-900; } } - .attempt-name { display: flex; - justify-content: flex-end; position: relative; width: 100%; - &:before { - border-top: 1px solid $gray-900; - content: ''; - left: 15px; - position: absolute; - right: 0; - top: 13px; - } - - &:after { - color: $gray-600; - content: '•'; - left: 7px; - position: absolute; - top: 4px; + .collapsible-header { + width: 100%; } .attempt-tag { + display: inline-flex; align-items: center; - border: 1px solid $gray-900; - border-radius: 7px; - box-shadow: 0 1px 1px 0 rgba($white, 0.20); - display: flex; - font-size: 11px; - padding: 2px 5px; + font-size: 14px; + gap: 8px; + width: 100%; + padding: 8px 8px 8px 9px; position: relative; - background-color: $black; user-select: none; cursor: pointer; &:hover { background-color: $gray-1100; } + .attempt-tag-text { + flex-grow: 1; + } } .collapsible-more { diff --git a/packages/reporter/src/attempts/attempts.tsx b/packages/reporter/src/attempts/attempts.tsx index 774b0ae6892e..5156e46d4849 100644 --- a/packages/reporter/src/attempts/attempts.tsx +++ b/packages/reporter/src/attempts/attempts.tsx @@ -24,14 +24,16 @@ const NoCommands = () => ( ) -const AttemptHeader = ({ index, state }: {index: number, state: TestState }) => ( +const AttemptHeader = ({ index, state }: { index: number, state: TestState }) => ( + + + Attempt {index + 1} + - Attempt {index + 1} - ) diff --git a/packages/reporter/src/collapsible/collapsible.tsx b/packages/reporter/src/collapsible/collapsible.tsx index 7c2125a13207..956ae3ae67e6 100644 --- a/packages/reporter/src/collapsible/collapsible.tsx +++ b/packages/reporter/src/collapsible/collapsible.tsx @@ -2,12 +2,19 @@ import cs from 'classnames' import React, { CSSProperties, MouseEvent, ReactNode, RefObject, useCallback, useState } from 'react' import { onEnterOrSpace } from '../lib/util' import ChevronIcon from '@packages/frontend-shared/src/assets/icons/chevron-down-small_x8.svg' +import DocumentBlankIcon from '@packages/frontend-shared/src/assets/icons/document-blank_x16.svg' + +export interface CollapsibleHeaderComponentProps { + isHovered: boolean + isFocused: boolean +} interface CollapsibleProps { isOpen?: boolean headerClass?: string headerStyle?: CSSProperties header?: ReactNode + HeaderComponent?: React.FunctionComponent headerExtras?: ReactNode containerRef?: RefObject contentClass?: string @@ -16,8 +23,10 @@ interface CollapsibleProps { onOpenStateChangeRequested?: (isOpen: boolean) => void } -const Collapsible: React.FC = ({ isOpen: isOpenAsProp = false, header, headerClass = '', headerStyle = {}, headerExtras, contentClass = '', hideExpander = false, containerRef = null, onOpenStateChangeRequested, children }) => { +const Collapsible: React.FC = ({ isOpen: isOpenAsProp = false, header, headerClass = '', headerStyle = {}, headerExtras, contentClass = '', hideExpander = false, containerRef = null, onOpenStateChangeRequested, children, HeaderComponent }) => { const [isOpenState, setIsOpenState] = useState(isOpenAsProp) + const [isHovered, setIsHovered] = useState(false) + const [isFocused, setIsFocused] = useState(false) const toggleOpenState = useCallback((e?: MouseEvent) => { e?.stopPropagation() @@ -38,6 +47,14 @@ const Collapsible: React.FC = ({ isOpen: isOpenAsProp = false, className='collapsible-header' onClick={toggleOpenState} onKeyUp={onEnterOrSpace(toggleOpenState)} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + onFocus={() => { + setIsFocused(true) + }} + onBlur={() => { + setIsFocused(false) + }} role='button' tabIndex={0} > @@ -46,9 +63,10 @@ const Collapsible: React.FC = ({ 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'>
    - {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'> - -
    - ))} - {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'> + +
    + ))} + {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'> - -
    -
    - {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'> +
    + +
    +
    + {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) => (
    • -
    • -
    • -
    • 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) && ( + + )} + + ) + } + + 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 + } + 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"