diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 3556a27bb82..a8b434ff6c6 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -3,6 +3,10 @@ _Released 02/14/2023 (PENDING)_ +**Features:** + +- Added the "Open in IDE" feature for failed tests reported from the Debug page. Addressed in [#25691](https://github.com/cypress-io/cypress/pull/25691). + **Misc:** - Improved the layout of the Debug Page on smaller viewports when there is a pending run. Addresses [#25664](https://github.com/cypress-io/cypress/issues/25664). diff --git a/packages/app/cypress/e2e/debug.cy.ts b/packages/app/cypress/e2e/debug.cy.ts index 1ebf16833cd..b39c4ca19dd 100644 --- a/packages/app/cypress/e2e/debug.cy.ts +++ b/packages/app/cypress/e2e/debug.cy.ts @@ -1,3 +1,4 @@ +import type { OpenFileInIdeQuery } from '../../src/generated/graphql-test' import RelevantRunsDataSource_RunsByCommitShas from '../fixtures/gql-RelevantRunsDataSource_RunsByCommitShas.json' Cypress.on('window:before:load', (win) => { @@ -31,27 +32,27 @@ describe('App - Debug Page', () => { it('all tests passed', () => { // This mocks all the responses so we can get deterministic // results to test the debug page. - cy.intercept('POST', '/__cypress/graphql/query-Debug', { + cy.intercept('query-Debug', { fixture: 'debug-Passing/gql-Debug.json', }) - cy.intercept('POST', '/__cypress/graphql/query-CloudViewerAndProject_RequiredData', { + cy.intercept('query-CloudViewerAndProject_RequiredData', { fixture: 'debug-Passing/gql-CloudViewerAndProject_RequiredData.json', }) - cy.intercept('POST', '/__cypress/graphql/query-MainAppQuery', { + cy.intercept('query-MainAppQuery', { fixture: 'debug-Passing/gql-MainAppQuery.json', }) - cy.intercept('POST', '/__cypress/graphql/query-SideBarNavigationContainer', { + cy.intercept('query-SideBarNavigationContainer', { fixture: 'debug-Passing/gql-SideBarNavigationContainer', }) - cy.intercept('POST', '/__cypress/graphql/query-HeaderBar_HeaderBarQuery', { + cy.intercept('query-HeaderBar_HeaderBarQuery', { fixture: 'debug-Passing/gql-HeaderBar_HeaderBarQuery', }) - cy.intercept('POST', '/__cypress/graphql/query-SpecsPageContainer', { + cy.intercept('query-SpecsPageContainer', { fixture: 'debug-Passing/gql-SpecsPageContainer', }) @@ -86,30 +87,44 @@ describe('App - Debug Page', () => { }) it('shows information about a failed spec', () => { - cy.intercept('POST', '/__cypress/graphql/query-Debug', { + cy.intercept('query-Debug', { fixture: 'debug-Failing/gql-Debug.json', }) - cy.intercept('POST', '/__cypress/graphql/query-CloudViewerAndProject_RequiredData', { + cy.intercept('query-CloudViewerAndProject_RequiredData', { fixture: 'debug-Failing/gql-CloudViewerAndProject_RequiredData.json', }) - cy.intercept('POST', '/__cypress/graphql/query-MainAppQuery', { + cy.intercept('query-MainAppQuery', { fixture: 'debug-Failing/gql-MainAppQuery.json', }) - cy.intercept('POST', '/__cypress/graphql/query-SideBarNavigationContainer', { + cy.intercept('query-SideBarNavigationContainer', { fixture: 'debug-Failing/gql-SideBarNavigationContainer', }) - cy.intercept('POST', '/__cypress/graphql/query-HeaderBar_HeaderBarQuery', { + cy.intercept('query-HeaderBar_HeaderBarQuery', { fixture: 'debug-Failing/gql-HeaderBar_HeaderBarQuery', }) - cy.intercept('POST', '/__cypress/graphql/query-SpecsPageContainer', { + cy.intercept('query-SpecsPageContainer', { fixture: 'debug-Failing/gql-SpecsPageContainer', }) + cy.intercept('query-OpenFileInIDE', (req) => { + req.on('response', (res) => { + const gqlData = res.body.data as OpenFileInIdeQuery + + gqlData.localSettings.preferences.preferredEditorBinary = 'code' + }) + }) + + cy.intercept('mutation-OpenFileInIDE_Mutation').as('openFileInIDE') + + cy.withCtx((ctx, { sinon }) => { + sinon.stub(ctx.actions.file, 'openFile') + }) + cy.visitApp() cy.findByTestId('sidebar-link-debug-page').click() @@ -147,5 +162,11 @@ describe('App - Debug Page', () => { cy.findByTestId('test-row').contains('InfoPanel') cy.findByTestId('test-row').contains('renders') cy.findByTestId('run-failures').should('exist').should('have.attr', 'href', '#/specs/runner?file=src/components/InfoPanel/InfoPanel.cy.ts&mode=debug') + + cy.findByLabelText('Open in IDE').click() + cy.wait('@openFileInIDE') + cy.withCtx((ctx) => { + expect(ctx.actions.file.openFile).to.have.been.calledWith('src/components/InfoPanel/InfoPanel.cy.ts', 1, 1) + }) }) }) diff --git a/packages/app/src/debug/DebugSpec.cy.tsx b/packages/app/src/debug/DebugSpec.cy.tsx index 0d1aa4481d7..b22c893ea14 100644 --- a/packages/app/src/debug/DebugSpec.cy.tsx +++ b/packages/app/src/debug/DebugSpec.cy.tsx @@ -530,3 +530,52 @@ describe('Run Failures button', () => { .and('not.have.attr', 'aria-disabled') }) }) + +describe('Open in IDE', () => { + const spec = { + id: '8879798756s88d', + path: 'cypress/tests/', + fileName: 'auth', + fileExtension: '.spec.ts', + fullPath: 'cypress/tests/auth.spec.ts', + testsPassed: resultCounts(22, 22), + testsFailed: resultCounts(2, 2), + testsPending: resultCounts(1, 1), + specDuration: { + min: 143000, + max: 143000, + }, + } + + const renderDebugSpec = ({ foundLocally } = { foundLocally: true }) => cy.mount(() => +
+ +
) + + it('shows openInIDE if file is found locally', () => { + renderDebugSpec() + + cy.findByLabelText(defaultMessages.debugPage.openFile.openInIDE).as('openInIDE').realHover() + cy.findByTestId('open-in-ide-tooltip').should('be.visible').and('contain', defaultMessages.debugPage.openFile.openInIDE) + cy.percySnapshot() + + cy.get('@openInIDE').click() + + cy.findByLabelText('External editor preferences').should('be.visible') + cy.findByLabelText('Close').click() + }) + + it('shows disabled openInIDE if file is not found locally', () => { + renderDebugSpec({ foundLocally: false }) + + cy.findByLabelText(defaultMessages.debugPage.openFile.notFoundLocally).as('openInIDE').realHover() + cy.findByTestId('open-in-ide-disabled-tooltip').should('be.visible').and('contain', defaultMessages.debugPage.openFile.notFoundLocally) + cy.percySnapshot() + }) +}) diff --git a/packages/app/src/debug/DebugSpec.vue b/packages/app/src/debug/DebugSpec.vue index 34c6cb3490a..e18b299596b 100644 --- a/packages/app/src/debug/DebugSpec.vue +++ b/packages/app/src/debug/DebugSpec.vue @@ -16,13 +16,72 @@ class="flex w-full grid px-18px gap-y-8px items-center" >
- - + + + + + + + + + +
import { computed, unref } from 'vue' -import { IconActionRefresh, IconDocumentText } from '@cypress-design/vue-icon' +import { IconActionRefresh, IconDocumentText, IconDocumentMinus } from '@cypress-design/vue-icon' import type { SpecDataAggregate, CloudRunInstance } from '@packages/data-context/src/gen/graphcache-config.gen' import DebugFailedTest from './DebugFailedTest.vue' import StatsMetaData from './StatsMetadata.vue' @@ -156,6 +215,7 @@ import { useI18n } from '@cy/i18n' import { useDurationFormat } from '../composables/useDurationFormat' import { posixify } from '../paths' import type { StatsMetadata_GroupsFragment, TestingTypeEnum } from '../generated/graphql' +import OpenFileInIDE from '@cy/gql-components/OpenFileInIDE.vue' export interface Spec { id: string diff --git a/packages/frontend-shared/src/locales/en-US.json b/packages/frontend-shared/src/locales/en-US.json index d088613b58f..73a759357e2 100644 --- a/packages/frontend-shared/src/locales/en-US.json +++ b/packages/frontend-shared/src/locales/en-US.json @@ -650,6 +650,10 @@ } }, "debugPage": { + "openFile": { + "openInIDE": "Open in IDE", + "notFoundLocally": "Opening in IDE is disabled because the spec is not found in this project" + }, "limit": { "title": "Cypress renders up to 100 failed test results", "message": "This run has {n} failed tests | This run has {n} failed test | This run has {n} failed tests",