diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 987778181fa5..787c97085730 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -13,6 +13,10 @@ _Released 05/09/2023 (PENDING)_ - Updated the Chromium renderer process crash message to be more terse. Addressed in [#26597](https://github.com/cypress-io/cypress/pull/26597). - Fixed an issue with `CYPRESS_DOWNLOAD_PATH_TEMPLATE` regex to allow multiple replacements. Addresses [#23670](https://github.com/cypress-io/cypress/issues/23670). +**Misc:** + +- Updated styling & content of Cypress Cloud slideshows when not logged in or no runs have been recorded. Addresses [#26181](https://github.com/cypress-io/cypress/issues/26181). + **Dependency Updates:** - Upgraded [`plist`](https://www.npmjs.com/package/plist) from `3.0.5` to `3.0.6` to address [CVE-2022-26260](https://nvd.nist.gov/vuln/detail/CVE-2022-22912#range-8131646) NVD security vulnerability. Addressed in [#26631](https://github.com/cypress-io/cypress/pull/26631). diff --git a/packages/app/src/assets/debug-guide-skeleton-1.png b/packages/app/src/assets/debug-guide-skeleton-1.png deleted file mode 100644 index 2abb50e0bb50..000000000000 Binary files a/packages/app/src/assets/debug-guide-skeleton-1.png and /dev/null differ diff --git a/packages/app/src/assets/debug-guide-skeleton-1.svg b/packages/app/src/assets/debug-guide-skeleton-1.svg new file mode 100644 index 000000000000..8d5c836e53b4 --- /dev/null +++ b/packages/app/src/assets/debug-guide-skeleton-1.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/app/src/assets/debug-guide-skeleton-2.png b/packages/app/src/assets/debug-guide-skeleton-2.png deleted file mode 100644 index 44d36ccbe60b..000000000000 Binary files a/packages/app/src/assets/debug-guide-skeleton-2.png and /dev/null differ diff --git a/packages/app/src/assets/debug-guide-skeleton-2.svg b/packages/app/src/assets/debug-guide-skeleton-2.svg new file mode 100644 index 000000000000..611e8f2ef594 --- /dev/null +++ b/packages/app/src/assets/debug-guide-skeleton-2.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/app/src/assets/debug-guide-skeleton-3.png b/packages/app/src/assets/debug-guide-skeleton-3.png deleted file mode 100644 index 9b7957130f2f..000000000000 Binary files a/packages/app/src/assets/debug-guide-skeleton-3.png and /dev/null differ diff --git a/packages/app/src/assets/debug-guide-skeleton-3.svg b/packages/app/src/assets/debug-guide-skeleton-3.svg new file mode 100644 index 000000000000..83cc61d919ee --- /dev/null +++ b/packages/app/src/assets/debug-guide-skeleton-3.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/app/src/assets/debug-guide-text-1.png b/packages/app/src/assets/debug-guide-text-1.png deleted file mode 100644 index 0c3dbeecffda..000000000000 Binary files a/packages/app/src/assets/debug-guide-text-1.png and /dev/null differ diff --git a/packages/app/src/assets/debug-guide-text-2.png b/packages/app/src/assets/debug-guide-text-2.png deleted file mode 100644 index 4a7dd371a1cd..000000000000 Binary files a/packages/app/src/assets/debug-guide-text-2.png and /dev/null differ diff --git a/packages/app/src/assets/debug-guide-text-3.png b/packages/app/src/assets/debug-guide-text-3.png deleted file mode 100644 index 3a1928737037..000000000000 Binary files a/packages/app/src/assets/debug-guide-text-3.png and /dev/null differ diff --git a/packages/app/src/assets/record-guide.svg b/packages/app/src/assets/record-guide.svg new file mode 100644 index 000000000000..426b5fb4f2aa --- /dev/null +++ b/packages/app/src/assets/record-guide.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/app/src/assets/runs-guide-skeleton-1.svg b/packages/app/src/assets/runs-guide-skeleton-1.svg new file mode 100644 index 000000000000..3ec7a6ad2223 --- /dev/null +++ b/packages/app/src/assets/runs-guide-skeleton-1.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/app/src/assets/runs-guide-skeleton-2.svg b/packages/app/src/assets/runs-guide-skeleton-2.svg new file mode 100644 index 000000000000..566ca8059b33 --- /dev/null +++ b/packages/app/src/assets/runs-guide-skeleton-2.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/app/src/assets/runs-guide-skeleton-3.svg b/packages/app/src/assets/runs-guide-skeleton-3.svg new file mode 100644 index 000000000000..0d439f56cbaa --- /dev/null +++ b/packages/app/src/assets/runs-guide-skeleton-3.svg @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/app/src/components/Slideshow.vue b/packages/app/src/components/Slideshow.vue deleted file mode 100644 index 2d08e039316c..000000000000 --- a/packages/app/src/components/Slideshow.vue +++ /dev/null @@ -1,57 +0,0 @@ - - - diff --git a/packages/app/src/debug/DebugContainer.cy.tsx b/packages/app/src/debug/DebugContainer.cy.tsx index 3b1511300eb0..f2baafb0f0c6 100644 --- a/packages/app/src/debug/DebugContainer.cy.tsx +++ b/packages/app/src/debug/DebugContainer.cy.tsx @@ -4,7 +4,6 @@ import { defaultMessages } from '@cy/i18n' import { useUserProjectStatusStore } from '@packages/frontend-shared/src/store/user-project-status-store' import { specsList } from './utils/DebugMapping' import { CloudRunStubs, createCloudRun } from '@packages/graphql/test/stubCloudTypes' -import { DEBUG_SLIDESHOW } from './utils/constants' import type { CloudRun, CloudSpecRun, CloudTestResult } from '@packages/graphql/src/gen/test-cloud-graphql-types.gen' const DebugSpecVariableTypes = { @@ -29,19 +28,12 @@ describe('', () => { describe('empty states', () => { const validateEmptyState = (expectedMessages: string[]) => { cy.stubMutationResolver(UseCohorts_DetermineCohortDocument, (defineResult) => { - return defineResult({ determineCohort: { __typename: 'Cohort', name: DEBUG_SLIDESHOW.id, cohort: 'A' } }) + return defineResult({ determineCohort: { __typename: 'Cohort', name: 'iatr_debug_slideshow', cohort: 'A' } }) }) cy.mountFragment(DebugSpecsFragmentDoc, { variableTypes: DebugSpecVariableTypes, variables: defaultVariables, - onResult: (res) => { - if (res.currentProject) { - res.currentProject.savedState = { - debugSlideshowComplete: true, - } - } - }, render: (gqlVal) => , }) @@ -55,7 +47,7 @@ describe('', () => { userProjectStatusStore.setHasInitiallyLoaded() - validateEmptyState([defaultMessages.debugPage.emptyStates.connectToCypressCloud, defaultMessages.debugPage.emptyStates.debugDirectlyInCypress, defaultMessages.debugPage.emptyStates.notLoggedInTestMessage]) + validateEmptyState([defaultMessages.debugPage.emptyStates.connectToCypressCloud, defaultMessages.debugPage.emptyStates.notLoggedIn.title, defaultMessages.debugPage.emptyStates.notLoggedIn.description]) cy.findByRole('button', { name: 'Connect to Cypress Cloud' }).should('be.visible') }) @@ -66,7 +58,7 @@ describe('', () => { userProjectStatusStore.setProjectFlag('isProjectConnected', false) userProjectStatusStore.setHasInitiallyLoaded() - validateEmptyState([defaultMessages.debugPage.emptyStates.debugDirectlyInCypress, defaultMessages.debugPage.emptyStates.reviewRerunAndDebug, defaultMessages.debugPage.emptyStates.noProjectTestMessage]) + validateEmptyState([defaultMessages.debugPage.emptyStates.debugDirectlyInCypress, defaultMessages.debugPage.emptyStates.reviewRerunAndDebug]) cy.findByRole('button', { name: 'Connect a Cypress Cloud project' }).should('be.visible') }) @@ -82,7 +74,7 @@ describe('', () => { render: (gqlVal) => , }) - validateEmptyState([defaultMessages.debugPage.emptyStates.recordYourFirstRun, defaultMessages.debugPage.emptyStates.almostThere, defaultMessages.debugPage.emptyStates.noRunsTestMessage]) + validateEmptyState([defaultMessages.debugPage.emptyStates.noRuns.title]) cy.findByDisplayValue('npx cypress run --record --key 2aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa').should('be.visible') }) diff --git a/packages/app/src/debug/DebugContainer.vue b/packages/app/src/debug/DebugContainer.vue index 7b9e722e9dac..94072128c7a7 100644 --- a/packages/app/src/debug/DebugContainer.vue +++ b/packages/app/src/debug/DebugContainer.vue @@ -184,7 +184,6 @@ fragment DebugSpecs on Query { } currentTestingType } - ..._DebugEmptyView } ` diff --git a/packages/app/src/debug/empty/DebugEmptyStates.cy.tsx b/packages/app/src/debug/empty/DebugEmptyStates.cy.tsx index 6dd4c6aa621c..f28aa9b9d7c6 100644 --- a/packages/app/src/debug/empty/DebugEmptyStates.cy.tsx +++ b/packages/app/src/debug/empty/DebugEmptyStates.cy.tsx @@ -5,103 +5,95 @@ import DebugLoading from './DebugLoading.vue' import DebugError from './DebugError.vue' import DebugEmptyView from './DebugEmptyView.vue' import { useUserProjectStatusStore } from '@packages/frontend-shared/src/store/user-project-status-store' -import { DebugEmptyView_RecordEventDocument, DebugEmptyView_SetPreferencesDocument, UseCohorts_DetermineCohortDocument, _DebugEmptyViewFragment, _DebugEmptyViewFragmentDoc } from '../../generated/graphql-test' -import { DEBUG_SLIDESHOW } from '../utils/constants' - -function mountWithGql (component: JSX.Element, gqlOptions?: { debugSlideshowComplete?: boolean, cohort?: 'A' | 'B' }) { - let gql: _DebugEmptyViewFragment - const opts = { debugSlideshowComplete: true, cohort: 'A', ...gqlOptions } - - cy.stubMutationResolver(UseCohorts_DetermineCohortDocument, (defineResult) => { - return defineResult({ determineCohort: { __typename: 'Cohort', name: DEBUG_SLIDESHOW.id, cohort: opts.cohort } }) - }) +import { Promo_PromoSeenDocument, _PromoFragmentDoc } from '../../generated/graphql-test' +import { DEBUG_PROMO_CAMPAIGNS, DEBUG_TAB_MEDIUM } from '../utils/constants' +function mountWithGql (component: JSX.Element) { const recordEvent = cy.stub().as('recordEvent') - const storeSlideshowComplete = cy.stub().as('storeSlideshowComplete') - cy.stubMutationResolver(DebugEmptyView_RecordEventDocument, (defineResult, args) => { + cy.stubMutationResolver(Promo_PromoSeenDocument, (defineResult, args) => { recordEvent(args) return defineResult({ recordEvent: true }) }) - cy.stubMutationResolver(DebugEmptyView_SetPreferencesDocument, (defineResult, args) => { - storeSlideshowComplete(args) - - return defineResult({ - setPreferences: { - __typename: 'Query', - currentProject: { - __typename: 'CurrentProject', - id: gql.currentProject?.id!, - savedState: { - debugSlideshowComplete: true, - }, - }, - }, - }) - }) - - cy.mountFragment(_DebugEmptyViewFragmentDoc, { - onResult: (res) => { - if (res.currentProject) { - res.currentProject.savedState = { - debugSlideshowComplete: opts.debugSlideshowComplete, - } - - gql = res - } - }, + cy.mountFragment(_PromoFragmentDoc, { render: () => { return component }, }) } -describe('Debug page empty states', () => { +describe('Debug page empty states', { defaultCommandTimeout: 250 }, () => { context('empty view', () => { it('renders with slot', () => { const slotVariableStub = cy.stub().as('slot') mountWithGql( - + {{ cta: slotVariableStub, }} , ) - cy.get('@slot').should('be.calledWith', { utmContent: Cypress.sinon.match.string }) + cy.get('@slot').should('be.calledWith', { utmContent: 'Z' }) }) }) context('not logged in', () => { it('renders', () => { - const userProjectStatusStore = useUserProjectStatusStore() + const useUserProjectStatusStore = useUserProjectStatusStore() // We need to set isLoggedIn so that CloudConnectButton shows the correct state - userProjectStatusStore.setUserFlag('isLoggedIn', false) + useUserProjectStatusStore.setUserFlag('isLoggedIn', false) mountWithGql() - cy.findByRole('link', { name: 'Learn more about debugging CI failures in Cypress' }).should('have.attr', 'href', 'https://on.cypress.io/debug-page?utm_source=Binary%3A+Launchpad&utm_medium=Debug+Tab&utm_campaign=Learn+More') - cy.percySnapshot() }) - it('sends record event upon seeing slideshow', () => { - useUserProjectStatusStore().setUserFlag('isLoggedIn', false) - mountWithGql(, { debugSlideshowComplete: false }) - cy.get('@recordEvent').should('have.been.calledWithMatch', { campaign: DEBUG_SLIDESHOW.campaigns.login, messageId: Cypress.sinon.match.string, medium: DEBUG_SLIDESHOW.medium, cohort: Cypress.sinon.match(/A|B/) }) + context('slideshow', () => { + function moveThroughSlideshow (options: { percy?: boolean }) { + for (const step of [1, 2, 3]) { + const { title, description } = cy.i18n.debugPage.emptyStates.slideshow[`step${step}`] + + cy.contains(title) + cy.contains(description) + if (options.percy) { + cy.percySnapshot(`slideshow step ${step}`) + } + + cy.get('[data-cy="promo-action"]:visible').last().click() + } + } + + it('renders slideshow', () => { + useUserProjectStatusStore().setUserFlag('isLoggedIn', false) + mountWithGql() + cy.get('@recordEvent').should('have.been.calledWithMatch', { campaign: DEBUG_PROMO_CAMPAIGNS.login, messageId: Cypress.sinon.match.string, medium: DEBUG_TAB_MEDIUM, cohort: null }) + moveThroughSlideshow({ percy: true }) + + // Can repeat moving through slideshow once complete + // Should not re-record slideshow being seen + cy.get('@recordEvent').should('not.have.been.calledTwice') + moveThroughSlideshow({ }) + }) + + it('sends record event upon seeing slideshow', () => { + useUserProjectStatusStore().setUserFlag('isLoggedIn', false) + mountWithGql() + cy.get('@recordEvent').should('have.been.calledWithMatch', { campaign: DEBUG_PROMO_CAMPAIGNS.login, messageId: Cypress.sinon.match.string, medium: DEBUG_TAB_MEDIUM, cohort: null }) + }) }) }) context('no project', () => { it('renders', () => { - const userProjectStatusStore = useUserProjectStatusStore() + const useUserProjectStatusStore = useUserProjectStatusStore() // We need to set isLoggedIn so that CloudConnectButton shows the correct state - userProjectStatusStore.setUserFlag('isLoggedIn', true) + useUserProjectStatusStore.setUserFlag('isLoggedIn', true) mountWithGql() @@ -113,28 +105,16 @@ describe('Debug page empty states', () => { cy.percySnapshot('responsive') }) - - it('sends record event upon seeing slideshow', () => { - useUserProjectStatusStore().setUserFlag('isLoggedIn', false) - mountWithGql(, { debugSlideshowComplete: false }) - cy.get('@recordEvent').should('have.been.calledWithMatch', { campaign: DEBUG_SLIDESHOW.campaigns.connectProject, messageId: Cypress.sinon.match.string, medium: DEBUG_SLIDESHOW.medium, cohort: Cypress.sinon.match(/A|B/) }) - }) }) context('no runs', () => { it('renders', () => { mountWithGql() - cy.findByRole('link', { name: 'Learn more about recording a run to Cypress Cloud' }).should('have.attr', 'href', 'https://on.cypress.io/cypress-run-record-key?utm_source=Binary%3A+Launchpad&utm_medium=Debug+Tab&utm_campaign=Learn+More') + cy.findByText('Copy the command below to record your first run').should('be.visible') cy.percySnapshot() }) - - it('sends record event upon seeing slideshow', () => { - useUserProjectStatusStore().setUserFlag('isLoggedIn', false) - mountWithGql(, { debugSlideshowComplete: false }) - cy.get('@recordEvent').should('have.been.calledWithMatch', { campaign: DEBUG_SLIDESHOW.campaigns.recordRun, messageId: Cypress.sinon.match.string, medium: DEBUG_SLIDESHOW.medium, cohort: Cypress.sinon.match(/A|B/) }) - }) }) context('loading', () => { @@ -153,57 +133,5 @@ describe('Debug page empty states', () => { cy.percySnapshot() }) - - it('does not render slideshow on error page', () => { - mountWithGql(, { debugSlideshowComplete: false }) - cy.get('@recordEvent').should('not.have.been.called') - cy.findByTestId('debug-default-empty-state').should('be.visible') - cy.findByTestId('debug-slideshow-slide').should('not.exist') - }) - }) - - context('slideshow', () => { - function moveThroughSlideshow (options: { cohort: 'A' | 'B', percy?: boolean }) { - for (const step of [1, 2, 3]) { - const { title, description } = cy.i18n.debugPage.emptyStates.slideshow[`step${step}`] - const imageSrc = `debug-guide-${options.cohort === 'A' ? 'skeleton' : 'text'}-${step}.png` - - cy.findByAltText('Debug tutorial').should('have.attr', 'src').should('include', imageSrc) - cy.contains('h2', title) - cy.contains('p', description) - cy.findByTestId('debug-slideshow-step').contains(`${step}/3`) - cy.contains('button', 'Previous').should(step === 1 ? 'not.exist' : 'be.visible') - if (options.percy) { - cy.percySnapshot(`slideshow step ${step}`) - } - - cy.contains('button', step === 3 ? 'Done' : 'Next').click() - } - - cy.findByTestId('debug-slideshow-slide').should('not.exist') - } - - it('renders slideshow if debugSlideshowComplete = false', () => { - useUserProjectStatusStore().setUserFlag('isLoggedIn', false) - mountWithGql(, { cohort: 'B', debugSlideshowComplete: false }) - cy.get('@recordEvent').should('have.been.calledWithMatch', { campaign: DEBUG_SLIDESHOW.campaigns.recordRun, messageId: Cypress.sinon.match.string, medium: DEBUG_SLIDESHOW.medium, cohort: Cypress.sinon.match(/A|B/) }) - moveThroughSlideshow({ cohort: 'B', percy: true }) - cy.get('@storeSlideshowComplete').should('have.been.called') - - // Can still move through slideshow, does not record events after completion - cy.contains('button', 'Info').click() - cy.get('@recordEvent').should('not.have.been.calledTwice') - moveThroughSlideshow({ cohort: 'B' }) - cy.get('@storeSlideshowComplete').should('not.have.been.calledTwice') - }) - - it('renders default empty state if debugSlideshowComplete = true', () => { - useUserProjectStatusStore().setUserFlag('isLoggedIn', false) - mountWithGql(, { cohort: 'A', debugSlideshowComplete: true }) - cy.findByTestId('debug-default-empty-state') - - cy.contains('button', 'Info').click() - moveThroughSlideshow({ cohort: 'A', percy: true }) - }) }) }) diff --git a/packages/app/src/debug/empty/DebugEmptyView.vue b/packages/app/src/debug/empty/DebugEmptyView.vue index c4ff0601877d..db4624830c27 100644 --- a/packages/app/src/debug/empty/DebugEmptyView.vue +++ b/packages/app/src/debug/empty/DebugEmptyView.vue @@ -25,87 +25,30 @@ - + + diff --git a/packages/app/src/debug/guide/GuideCard1.vue b/packages/app/src/debug/guide/GuideCard1.vue new file mode 100644 index 000000000000..da5382edf9c8 --- /dev/null +++ b/packages/app/src/debug/guide/GuideCard1.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/app/src/debug/guide/GuideCard2.vue b/packages/app/src/debug/guide/GuideCard2.vue new file mode 100644 index 000000000000..9c903bd5d83d --- /dev/null +++ b/packages/app/src/debug/guide/GuideCard2.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/app/src/debug/guide/GuideCard3.vue b/packages/app/src/debug/guide/GuideCard3.vue new file mode 100644 index 000000000000..a9569ce8eb0a --- /dev/null +++ b/packages/app/src/debug/guide/GuideCard3.vue @@ -0,0 +1,34 @@ + + + diff --git a/packages/app/src/debug/guide/TourCard.vue b/packages/app/src/debug/guide/TourCard.vue new file mode 100644 index 000000000000..ee395a196821 --- /dev/null +++ b/packages/app/src/debug/guide/TourCard.vue @@ -0,0 +1,34 @@ + + + diff --git a/packages/app/src/debug/utils/constants.ts b/packages/app/src/debug/utils/constants.ts index eeeb1cb4a920..4487de9b9a11 100644 --- a/packages/app/src/debug/utils/constants.ts +++ b/packages/app/src/debug/utils/constants.ts @@ -1,15 +1,6 @@ -type ValueOf = T[keyof T] - export const DEBUG_TAB_MEDIUM = 'Debug Tab' -export const DEBUG_SLIDESHOW = { - id: 'iatr_debug_slideshow', - campaigns: { - login: 'Debug Login Empty State', - connectProject: 'Debug Connect Project Empty State', - recordRun: 'Debug Record Run Empty State', - }, - medium: DEBUG_TAB_MEDIUM, +export const DEBUG_PROMO_CAMPAIGNS = { + login: 'Debug Login Empty State', + recordRun: 'Debug Record Run Empty State', } as const - -export type DebugSlideshowCampaigns = ValueOf diff --git a/packages/app/src/runs/RunsConnect.vue b/packages/app/src/runs/RunsConnect.vue index 41306e153184..8f11f627dbaf 100644 --- a/packages/app/src/runs/RunsConnect.vue +++ b/packages/app/src/runs/RunsConnect.vue @@ -1,50 +1,50 @@ diff --git a/packages/app/src/runs/RunsContainer.cy.tsx b/packages/app/src/runs/RunsContainer.cy.tsx index 18e62d7b66c9..358c6051179f 100644 --- a/packages/app/src/runs/RunsContainer.cy.tsx +++ b/packages/app/src/runs/RunsContainer.cy.tsx @@ -56,9 +56,6 @@ describe('', { keystrokeDelay: 0 }, () => { const text = defaultMessages.runs.connect cy.contains(text.title).should('be.visible') - cy.contains(text.smartText).should('be.visible') - cy.contains(text.debugText).should('be.visible') - cy.contains(text.chartText).should('be.visible') cy.contains(text.buttonProject).should('be.visible') cy.percySnapshot() }) @@ -75,14 +72,36 @@ describe('', { keystrokeDelay: 0 }, () => { const text = defaultMessages.runs.connect cy.contains(text.title).should('be.visible') - cy.contains(text.smartText).should('be.visible') - cy.contains(text.debugText).should('be.visible') - cy.contains(text.chartText).should('be.visible') cy.contains(text.buttonUser).should('be.visible') cy.percySnapshot() }) }) + context('when the user has no recorded runs', () => { + it('renders instructions and record prompt', () => { + cy.mountFragment(RunsContainerFragmentDoc, { + onResult (gql) { + gql.cloudViewer = cloudViewer + if (gql.currentProject?.cloudProject?.__typename === 'CloudProject') { + gql.currentProject.cloudProject.runs = { + __typename: 'CloudRunConnection', + pageInfo: null as any, + nodes: [], + } + } + }, + render (gqlVal) { + return + }, + }) + + const text = defaultMessages.runs.empty + + cy.contains(text.title).should('be.visible') + cy.percySnapshot() + }) + }) + context('with errors', () => { it('renders connection failed', () => { cy.mountFragment(RunsContainerFragmentDoc, { diff --git a/packages/app/src/runs/RunsEmpty.vue b/packages/app/src/runs/RunsEmpty.vue index 660490d7204c..a5f884f5e2e0 100644 --- a/packages/app/src/runs/RunsEmpty.vue +++ b/packages/app/src/runs/RunsEmpty.vue @@ -1,29 +1,72 @@ - +const guideUrl = computed(() => { + return getUrlWithParams({ + url: 'https://on.cypress.io/recording-project-runs', + params: { + utm_campaign: RUNS_PROMO_CAMPAIGNS.recordRun, + utm_medium: RUNS_TAB_MEDIUM, + }, + }) +}) + diff --git a/packages/app/src/runs/guide/GuideCard1.vue b/packages/app/src/runs/guide/GuideCard1.vue new file mode 100644 index 000000000000..2c887579a83f --- /dev/null +++ b/packages/app/src/runs/guide/GuideCard1.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/app/src/runs/guide/GuideCard2.vue b/packages/app/src/runs/guide/GuideCard2.vue new file mode 100644 index 000000000000..84560e4e0ae3 --- /dev/null +++ b/packages/app/src/runs/guide/GuideCard2.vue @@ -0,0 +1,35 @@ + + + diff --git a/packages/app/src/runs/guide/GuideCard3.vue b/packages/app/src/runs/guide/GuideCard3.vue new file mode 100644 index 000000000000..a456dc920690 --- /dev/null +++ b/packages/app/src/runs/guide/GuideCard3.vue @@ -0,0 +1,34 @@ + + + diff --git a/packages/app/src/runs/guide/TourCard.vue b/packages/app/src/runs/guide/TourCard.vue new file mode 100644 index 000000000000..80301adf4e06 --- /dev/null +++ b/packages/app/src/runs/guide/TourCard.vue @@ -0,0 +1,34 @@ + + + diff --git a/packages/app/src/runs/utils/constants.ts b/packages/app/src/runs/utils/constants.ts new file mode 100644 index 000000000000..5c11ae97ffa2 --- /dev/null +++ b/packages/app/src/runs/utils/constants.ts @@ -0,0 +1,6 @@ +export const RUNS_TAB_MEDIUM = 'Runs Tab' + +export const RUNS_PROMO_CAMPAIGNS = { + login: 'Runs Login Empty State', + recordRun: 'Runs Record Run Empty State', +} as const diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index 07cc6dd6af03..4239db764cf7 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -20,6 +20,7 @@ "devDependencies": { "@antfu/utils": "^0.3.0", "@cypress-design/css": "^0.13.1", + "@cypress-design/vue-icon": "0.22.1", "@graphql-typed-document-node/core": "^3.1.0", "@headlessui/vue": "1.4.0", "@iconify/json": "1.1.368", diff --git a/packages/frontend-shared/src/components/Slideshow.vue b/packages/frontend-shared/src/components/Slideshow.vue new file mode 100644 index 000000000000..b6a34bb04008 --- /dev/null +++ b/packages/frontend-shared/src/components/Slideshow.vue @@ -0,0 +1,42 @@ + + + diff --git a/packages/frontend-shared/src/gql-components/promo/Promo.cy.tsx b/packages/frontend-shared/src/gql-components/promo/Promo.cy.tsx new file mode 100644 index 000000000000..fac398d5d2c5 --- /dev/null +++ b/packages/frontend-shared/src/gql-components/promo/Promo.cy.tsx @@ -0,0 +1,9 @@ +describe('', () => { + it('renders properly on narrow viewport', { viewportWidth: 600 }, () => { + // TODO + }) + + it('renders properly on wide viewport', { viewportWidth: 1200 }, () => { + // TODO + }) +}) diff --git a/packages/frontend-shared/src/gql-components/promo/Promo.vue b/packages/frontend-shared/src/gql-components/promo/Promo.vue new file mode 100644 index 000000000000..ef5efd3f7d1e --- /dev/null +++ b/packages/frontend-shared/src/gql-components/promo/Promo.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/packages/frontend-shared/src/gql-components/promo/PromoAction.cy.tsx b/packages/frontend-shared/src/gql-components/promo/PromoAction.cy.tsx new file mode 100644 index 000000000000..caff43d79fe4 --- /dev/null +++ b/packages/frontend-shared/src/gql-components/promo/PromoAction.cy.tsx @@ -0,0 +1,53 @@ +import PromoAction from './PromoAction.vue' +import { IconChevronRightSmall, IconActionRestart, IconObjectTassel } from '@cypress-design/vue-icon' + +describe('', () => { + it('next', () => { + const action = cy.stub().as('action') + + cy.mount( + , + ) + + cy.get('@action').should('not.have.been.called') + + cy.findByTestId('promo-action').click() + + cy.get('@action').should('have.been.calledOnce') + }) + + it('reset', () => { + const action = cy.stub().as('action') + + cy.mount( + , + ) + + cy.get('@action').should('not.have.been.called') + + cy.findByTestId('promo-action').click() + + cy.get('@action').should('have.been.calledOnce') + }) + + it('tour', () => { + cy.mount( + , + ) + + cy.get('a').should('have.attr', 'href', '#test') + }) +}) diff --git a/packages/frontend-shared/src/gql-components/promo/PromoAction.vue b/packages/frontend-shared/src/gql-components/promo/PromoAction.vue new file mode 100644 index 000000000000..13003369393e --- /dev/null +++ b/packages/frontend-shared/src/gql-components/promo/PromoAction.vue @@ -0,0 +1,80 @@ + + + diff --git a/packages/frontend-shared/src/gql-components/promo/PromoCard.vue b/packages/frontend-shared/src/gql-components/promo/PromoCard.vue new file mode 100644 index 000000000000..c2ce86b90fd9 --- /dev/null +++ b/packages/frontend-shared/src/gql-components/promo/PromoCard.vue @@ -0,0 +1,45 @@ + + + diff --git a/packages/frontend-shared/src/gql-components/promo/PromoHeader.cy.tsx b/packages/frontend-shared/src/gql-components/promo/PromoHeader.cy.tsx new file mode 100644 index 000000000000..8ca36a4a25a4 --- /dev/null +++ b/packages/frontend-shared/src/gql-components/promo/PromoHeader.cy.tsx @@ -0,0 +1,17 @@ +import PromoHeader from './PromoHeader.vue' +import Button from '../../components/Button.vue' + +describe('', () => { + it('renders', () => { + cy.mount( +

Description of this header

, + control: () => , + content: () =>
Test Content
, + }} + />, + ) + }) +}) diff --git a/packages/frontend-shared/src/gql-components/promo/PromoHeader.vue b/packages/frontend-shared/src/gql-components/promo/PromoHeader.vue new file mode 100644 index 000000000000..65fcf7f4932d --- /dev/null +++ b/packages/frontend-shared/src/gql-components/promo/PromoHeader.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/packages/frontend-shared/src/locales/en-US.json b/packages/frontend-shared/src/locales/en-US.json index c03a7d5b8694..0d58de7d8a4b 100644 --- a/packages/frontend-shared/src/locales/en-US.json +++ b/packages/frontend-shared/src/locales/en-US.json @@ -568,11 +568,33 @@ } }, "runs": { + "slideshow": { + "tour": { + "title": "Record your first test run to Cypress Cloud", + "description": "You're almost there! Start recording your test runs to Cypress Cloud by running the command above in your local or CI terminal." + }, + "step1": { + "title": "Monitor test failures in real time", + "description": "Uncover high-risk failed and flaky tests in CI before they become a problem in production." + }, + "step2": { + "title": "Check if failed CI test runs also fail locally", + "description": "Use the Test Runner to run only the tests that failed in your last recorded run." + }, + "step3": { + "title": "Build, test, and ship with confidence 🚀", + "description": "With Cypress Cloud, you can proactively identify, investigate, and resolve failures, and be a quality champion." + }, + "controls": { + "step": "{0} of {1}", + "next": "Next", + "reset": "Reset", + "view": "View Tour" + } + }, "connect": { - "title": "View your recorded runs from Cypress Cloud", - "smartText": "Scale your test runs with built-in Smart Orchestration.", - "debugText": "Debug tests that fail in CI with visual feedback.", - "chartText": "Keep your tests in tip-top shape with powerful analytics.", + "title": "Connect to view your recorded runs", + "description": "Cypress Cloud is built for teams who need to do testing at scale.", "buttonUser": "Connect to Cypress Cloud", "buttonProject": "Connect a Cypress Cloud project", "modal": { @@ -617,7 +639,7 @@ }, "internalServerError": { "title": "Cannot connect to Cypress Cloud", - "description": "The request times out when trying to retrieve the info from Cypress Cloud. Please refresh the page to try again and visit out {0} if this behavior continues.", + "description": "The request times out when trying to retrieve the info from Cypress Cloud. Please refresh the page to try again and visit our {0} if this behavior continues.", "link": "Support Page" } } @@ -628,8 +650,9 @@ "item2": "Please ensure that your {0} file is checked into source control." }, "empty": { - "title": "Record your first run to Cypress Cloud", - "description": "Run the command below in your local development terminal or in CI." + "title": "Copy the command below to record your first run", + "description": "Review the {0} to Cypress Cloud", + "link": "guide on recording runs" }, "results": { "skipped": "skipped", @@ -705,17 +728,26 @@ "debugDirectlyInCypress": "Debug failed CI runs directly in Cypress", "reviewRerunAndDebug": "Review, rerun, and debug failed CI test runs that are recorded to Cypress Cloud – all from within your local Cypress app.", "connectToCypressCloud": "Connect to Cypress Cloud", - "notLoggedInTestMessage": "Connect to Cypress Cloud to locally debug failed CI test runs", + "notLoggedIn": { + "title": "Connect to debug your tests", + "description": "Cypress Cloud is built for teams who need to do testing at scale." + }, "noProjectTestMessage": "Connect a Cypress Cloud project to locally debug failed CI test runs", - "recordYourFirstRun": "Record your first test run to Cypress Cloud", - "almostThere": "You're almost there! Start recording your test runs to Cypress Cloud by running the command below in your local or CI terminal.", - "noRunsTestMessage": "Record your first test run to Cypress Cloud to locally debug failed CI test runs", + "noRuns": { + "title": "Copy the command below to record your first run", + "description": "Review the {0} to Cypress Cloud", + "link": "guide on recording runs" + }, "gitRepositoryNotDetected": "Git repository not detected", "ensureGitSetupCorrectly": "Cypress uses Git to associate runs with your local state. Please ensure that version control is set up correctly.", "learnAboutRecordingSrText": "about recording a run to Cypress Cloud", "learnAboutDebuggingSrText": "about debugging CI failures in Cypress", "learnAboutProjectSetupSrText": "about project setup in Cypress", "slideshow": { + "tour": { + "title": "Record your first test run to Cypress Cloud", + "description": "You're almost there! Start recording your test runs to Cypress Cloud by running the command below in your local or CI terminal." + }, "step1": { "title": "Review how many tests failed during a CI test run", "description": "The Debug page shows the latest completed test run for your current checked out commit." @@ -728,11 +760,11 @@ "title": "Locally debug failed test runs with visual artifacts", "description": "Easily review screenshots, videos and logs from your tests." }, - "imgAlt": "Debug tutorial", "controls": { - "previous": "Previous", + "step": "{0} of {1}", "next": "Next", - "done": "Done" + "reset": "Reset", + "view": "View Tour" } } }, diff --git a/yarn.lock b/yarn.lock index 7182fde53c20..10cf8212f918 100644 --- a/yarn.lock +++ b/yarn.lock @@ -31130,12 +31130,7 @@ ws@^6.1.2, ws@^6.2.1: dependencies: async-limiter "~1.0.0" -ws@^8.0.0, ws@^8.4.2, ws@^8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== - -ws@~8.11.0: +ws@^8.0.0, ws@^8.4.2, ws@^8.5.0, ws@~8.11.0: version "8.11.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==