diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md
index 8fc5ec96fa00..899a0ead4532 100644
--- a/cli/CHANGELOG.md
+++ b/cli/CHANGELOG.md
@@ -7,6 +7,7 @@ _Released 02/14/2023 (PENDING)_
- Fixed an issue with the Cloud project selection modal not showing the correct prompts. Fixes [#25520](https://github.com/cypress-io/cypress/issues/25520).
- Fixed an issue in middleware where error-handling code could itself generate an error and fail to report the original issue. Fixes [#22825](https://github.com/cypress-io/cypress/issues/22825).
+- Fixed an issue that could cause the Debug page to display a different number of specs for in-progress runs than shown in Cypress Cloud. Fixes [#25647](https://github.com/cypress-io/cypress/issues/25647).
**Features:**
diff --git a/packages/app/src/debug/DebugContainer.cy.tsx b/packages/app/src/debug/DebugContainer.cy.tsx
index 0a995496efe8..797b029faad5 100644
--- a/packages/app/src/debug/DebugContainer.cy.tsx
+++ b/packages/app/src/debug/DebugContainer.cy.tsx
@@ -241,6 +241,8 @@ describe('', () => {
result.currentProject.cloudProject.runByNumber = {
...CloudRunStubs.running,
runNumber: 1,
+ completedInstanceCount: 2,
+ totalInstanceCount: 3,
} as typeof test
}
},
@@ -255,6 +257,51 @@ describe('', () => {
})
})
+ it('does not render DebugPendingRunSplash and DebugNewRelevantRunBar at the same time', () => {
+ cy.mountFragment(DebugSpecsFragmentDoc, {
+ variableTypes: DebugSpecVariableTypes,
+ variables: {
+ hasNextRun: false,
+ runNumber: 1,
+ nextRunNumber: -1,
+ },
+ onResult: (result) => {
+ if (result.currentProject?.cloudProject?.__typename === 'CloudProject') {
+ const test = result.currentProject.cloudProject.runByNumber
+
+ // Testing this to confirm we are "making impossible states impossible" in the UI,
+ // and document the expectation in this scenario. For clarity,
+ // we do not expect a 'RUNNING` current and next run at the same time, so
+ // the data below represents an invalid state.
+
+ result.currentProject.cloudProject.runByNumber = {
+ ...CloudRunStubs.running,
+ runNumber: 1,
+ completedInstanceCount: 2,
+ totalInstanceCount: 3,
+ } as typeof test
+
+ result.currentProject.cloudProject.nextRun = {
+ ...CloudRunStubs.running,
+ runNumber: 1,
+ completedInstanceCount: 5,
+ totalInstanceCount: 6,
+ } as typeof test
+ }
+ },
+ render: (gqlVal) => ,
+ })
+
+ cy.findByTestId('debug-header').should('be.visible')
+ cy.findByTestId('debug-pending-splash')
+ .should('be.visible')
+ .within(() => {
+ cy.findByTestId('debug-pending-counts').should('have.text', '0 of 0 specs completed')
+ })
+
+ cy.findByTestId('newer-relevant-run').should('not.exist')
+ })
+
it('renders specs and tests when completed run available', () => {
cy.mountFragment(DebugSpecsFragmentDoc, {
variableTypes: DebugSpecVariableTypes,
diff --git a/packages/app/src/debug/DebugContainer.vue b/packages/app/src/debug/DebugContainer.vue
index 6c5371a005da..01cbacbdc756 100644
--- a/packages/app/src/debug/DebugContainer.vue
+++ b/packages/app/src/debug/DebugContainer.vue
@@ -28,15 +28,15 @@
:gql="run"
:commits-ahead="props.commitsAhead"
/>
-
-
+
+
{
- return specs.map((spec) => spec.groupIds?.length || 0).reduce((acc, curr) => acc += curr, 0)
- }
-
- return {
- totalSpecs: countGroupsForSpec(specs),
- completedSpecs: countGroupsForSpec(specs.filter((spec) => !INCOMPLETE_STATUSES.includes(spec.status || 'UNCLAIMED'))),
- }
- }
-
/**
- * Pulls runs from the current Cypress Cloud account and determines which runs are considered:
- * - "current" the most recent completed run, or if not found, the most recent running run
- * - "next" the most recent running run if a completed run is found
- * @param shas list of Git commit shas to query the Cloud with for matching runs
+ * Pulls the specs that match the relevant run.
+ * @param runs - the current and (optionally) next relevant run
*/
async getRelevantRunSpecs (runs: RelevantRun): Promise {
const projectSlug = await this.ctx.project.projectId()
@@ -147,28 +133,40 @@ export class RelevantRunSpecsDataSource {
}
}
+ function isValidNumber (value: unknown): value is number {
+ return Number.isFinite(value)
+ }
+
if (cloudProject?.__typename === 'CloudProject') {
const runSpecsToReturn: RunSpecReturn = {
runSpecs: {},
statuses: {},
}
- if (cloudProject.current && cloudProject.current.runNumber && cloudProject.current.status) {
- runSpecsToReturn.runSpecs.current = {
- ...this.#calculateSpecMetadata(cloudProject.current.specs || []),
- runNumber: cloudProject.current.runNumber,
+ const { current, next } = cloudProject
+
+ const formatCloudRunInfo = (cloudRunDetails: Partial) => {
+ const { runNumber, totalInstanceCount, completedInstanceCount } = cloudRunDetails
+
+ if (runNumber && isValidNumber(totalInstanceCount) && isValidNumber(completedInstanceCount)) {
+ return {
+ totalSpecs: totalInstanceCount,
+ completedSpecs: completedInstanceCount,
+ runNumber,
+ }
}
- runSpecsToReturn.statuses.current = cloudProject.current.status
+ return undefined
}
- if (cloudProject.next && cloudProject.next.runNumber && cloudProject.next.status) {
- runSpecsToReturn.runSpecs.next = {
- ...this.#calculateSpecMetadata(cloudProject.next.specs || []),
- runNumber: cloudProject.next.runNumber,
- }
+ if (current && current.status) {
+ runSpecsToReturn.runSpecs.current = formatCloudRunInfo(current)
+ runSpecsToReturn.statuses.current = current.status
+ }
- runSpecsToReturn.statuses.next = cloudProject.next.status
+ if (next && next.status) {
+ runSpecsToReturn.runSpecs.next = formatCloudRunInfo(next)
+ runSpecsToReturn.statuses.next = next.status
}
return runSpecsToReturn
@@ -193,6 +191,7 @@ export class RelevantRunSpecsDataSource {
debug(`Spec data is `, specs)
+ const wasWatchingCurrentProject = this.#cached.statuses.current === 'RUNNING'
const specCountsChanged = !isEqual(specs.runSpecs, this.#cached.runSpecs)
const statusesChanged = !isEqual(specs.statuses, this.#cached.statuses)
@@ -208,7 +207,7 @@ export class RelevantRunSpecsDataSource {
debug('Run statuses changed')
const projectSlug = await this.ctx.project.projectId()
- if (projectSlug) {
+ if (projectSlug && wasWatchingCurrentProject) {
debug(`Invalidate cloudProjectBySlug ${projectSlug}`)
await this.ctx.cloud.invalidate('Query', 'cloudProjectBySlug', { slug: projectSlug })
}
diff --git a/packages/data-context/test/unit/sources/RelevantRunSpecsDataSource.spec.ts b/packages/data-context/test/unit/sources/RelevantRunSpecsDataSource.spec.ts
new file mode 100644
index 000000000000..cada9f0e151c
--- /dev/null
+++ b/packages/data-context/test/unit/sources/RelevantRunSpecsDataSource.spec.ts
@@ -0,0 +1,68 @@
+import { expect } from 'chai'
+import sinon from 'sinon'
+
+import { DataContext } from '../../../src'
+import { createTestDataContext } from '../helper'
+import { RelevantRunSpecsDataSource, SPECS_EMPTY_RETURN } from '../../../src/sources'
+import { FAKE_PROJECT_ONE_RUNNING_RUN_ONE_COMPLETED_THREE_SPECS, FAKE_PROJECT_ONE_RUNNING_RUN_ONE_SPEC } from './fixtures/graphqlFixtures'
+
+describe('RelevantRunSpecsDataSource', () => {
+ let ctx: DataContext
+ let dataSource: RelevantRunSpecsDataSource
+
+ beforeEach(() => {
+ ctx = createTestDataContext('open')
+ dataSource = new RelevantRunSpecsDataSource(ctx)
+ sinon.stub(ctx.project, 'projectId').resolves('test123')
+ })
+
+ describe('getRelevantRunSpecs()', () => {
+ it('returns no specs or statuses when no specs found for run', async () => {
+ const result = await dataSource.getRelevantRunSpecs({ current: 11111, next: 22222, commitsAhead: 0 })
+
+ expect(result).to.eql(SPECS_EMPTY_RETURN)
+ })
+
+ it('returns expected specs and statuses when one run is found', async () => {
+ sinon.stub(ctx.cloud, 'executeRemoteGraphQL').resolves(FAKE_PROJECT_ONE_RUNNING_RUN_ONE_SPEC)
+
+ const result = await dataSource.getRelevantRunSpecs({ current: 1, next: null, commitsAhead: 0 })
+
+ expect(result).to.eql({
+ runSpecs: {
+ current: {
+ runNumber: 1,
+ completedSpecs: 1,
+ totalSpecs: 1,
+ },
+ },
+ statuses: { current: 'RUNNING' },
+ })
+ })
+
+ it('returns expected specs and statuses when one run is completed and one is running', async () => {
+ sinon.stub(ctx.cloud, 'executeRemoteGraphQL').resolves(FAKE_PROJECT_ONE_RUNNING_RUN_ONE_COMPLETED_THREE_SPECS)
+
+ const result = await dataSource.getRelevantRunSpecs({ current: 1, next: null, commitsAhead: 0 })
+
+ expect(result).to.eql({
+ runSpecs: {
+ current: {
+ runNumber: 1,
+ completedSpecs: 3,
+ totalSpecs: 3,
+ },
+ next: {
+ runNumber: 2,
+ completedSpecs: 0,
+ totalSpecs: 3,
+ },
+ },
+ statuses: {
+ current: 'PASSED',
+ next: 'RUNNING',
+ },
+ })
+ })
+ })
+})
diff --git a/packages/data-context/test/unit/sources/fixtures/graphqlFixtures.ts b/packages/data-context/test/unit/sources/fixtures/graphqlFixtures.ts
index 6081ba8e5c68..05b49fb5963b 100644
--- a/packages/data-context/test/unit/sources/fixtures/graphqlFixtures.ts
+++ b/packages/data-context/test/unit/sources/fixtures/graphqlFixtures.ts
@@ -49,6 +49,42 @@ export const FAKE_PROJECT_NO_RUNS = { data: { cloudProjectBySlug: { __typename:
export const FAKE_PROJECT_ONE_RUNNING_RUN = { data: { cloudProjectBySlug: { __typename: 'CloudProject', runsByCommitShas: [{ runNumber: 1, status: 'RUNNING', commitInfo: { sha: FAKE_SHAS[0] } }] } } }
-export const FAKE_PROJECT_MULTIPLE_COMPLETED = { data: { cloudProjectBySlug: { __typename: 'CloudProject', runsByCommitShas: [{ runNumber: 4, status: 'FAILED', commitInfo: { sha: FAKE_SHAS[1] } }, { runNumber: 1, status: 'PASSED', commitInfo: { sha: FAKE_SHAS[0] } }] } } }
+export const FAKE_PROJECT_MULTIPLE_COMPLETED = { data: { cloudProjectBySlug: { __typename: 'CloudProject', runsByCommitShas: [
+ { runNumber: 4, status: 'FAILED', commitInfo: { sha: FAKE_SHAS[1] } }, { runNumber: 1, status: 'PASSED', commitInfo: { sha: FAKE_SHAS[0] } },
+] } } }
export const FAKE_PROJECT_MULTIPLE_COMPLETED_PLUS_RUNNING = { data: { cloudProjectBySlug: { __typename: 'CloudProject', runsByCommitShas: [{ runNumber: 5, status: 'RUNNING', commitInfo: { sha: FAKE_SHAS[2] } }, { runNumber: 4, status: 'FAILED', commitInfo: { sha: FAKE_SHAS[1] } }, { runNumber: 1, status: 'PASSED', commitInfo: { sha: FAKE_SHAS[0] } }] } } }
+
+export const FAKE_PROJECT_ONE_RUNNING_RUN_ONE_SPEC = {
+ data: {
+ cloudProjectBySlug: {
+ __typename: 'CloudProject',
+ current: {
+ runNumber: 1,
+ completedInstanceCount: 1,
+ totalInstanceCount: 1,
+ status: 'RUNNING',
+ },
+ },
+ },
+}
+
+export const FAKE_PROJECT_ONE_RUNNING_RUN_ONE_COMPLETED_THREE_SPECS = {
+ data: {
+ cloudProjectBySlug: {
+ __typename: 'CloudProject',
+ current: {
+ runNumber: 1,
+ status: 'PASSED',
+ completedInstanceCount: 3,
+ totalInstanceCount: 3,
+ },
+ next: {
+ runNumber: 2,
+ status: 'RUNNING',
+ completedInstanceCount: 0,
+ totalInstanceCount: 3,
+ },
+ },
+ },
+}
diff --git a/packages/graphql/schemas/cloud.graphql b/packages/graphql/schemas/cloud.graphql
index 0dae909ab716..d3a9d5734d71 100644
--- a/packages/graphql/schemas/cloud.graphql
+++ b/packages/graphql/schemas/cloud.graphql
@@ -347,6 +347,16 @@ type CloudProjectSpec implements Node {
"""
fromBranch: String!
): Float
+
+ """
+ Average duration the spec takes to run within the context of the provided CloudRun ids.
+ """
+ averageDurationForRunIds(
+ """
+ The ids for the CloudRuns to use to derive the average duration. When provided, the fromBranch argument is ignored, as the provided run ids define the search space.
+ """
+ cloudRunIds: [ID!]!
+ ): Float
flakyStatus(
"""
The number of runs to consider when counting flaky runs.
@@ -359,6 +369,16 @@ type CloudProjectSpec implements Node {
fromBranch: String!
): CloudProjectSpecFlakyResult
+ """
+ The flaky metadata for the spec within the context of the provided CloudRun ids.
+ """
+ flakyStatusForRunIds(
+ """
+ The ids for the CloudRuns, ordered from most to least relevant, to use to derive the flaky status.
+ """
+ cloudRunIds: [ID!]!
+ ): CloudProjectSpecFlakyResult
+
"""
Globally unique identifier representing a concrete GraphQL ObjectType
"""
@@ -370,6 +390,16 @@ type CloudProjectSpec implements Node {
fromBranch: String!
): Boolean
+ """
+ Indicator that a spec is considered flaky within the context of the provided CloudRun ids.
+ """
+ isConsideredFlakyForRunIds(
+ """
+ The ids for the CloudRuns to use to derive the flake indicator. When provided, the fromBranch argument is ignored, as the provided run ids define the search space.
+ """
+ cloudRunIds: [ID!]!
+ ): Boolean
+
"""
Current DateTime on the server. Used in connection with CloudLatestRunUpdateSpecData.
"""
@@ -445,6 +475,16 @@ type CloudProjectSpecFlakyStatus {
The last flaky run occurrence, interpreted as "n runs ago" - ex: a value of 5 means a flaky run last occurred 5 runs ago
"""
lastFlaky: Int
+
+ """
+ The associated commit_sha of the CloudRun containing the most recent flaky occurrence of the spec.
+ """
+ lastFlakyRunCommitSha: String
+
+ """
+ The run number of the CloudRun containing the most recent flaky occurrence of the spec.
+ """
+ lastFlakyRunNumber: Int
severity: String
}
@@ -520,6 +560,11 @@ type CloudRun implements Node {
commitInfo: CloudRunCommitInfo
completedAt: DateTime
+ """
+ Number of specs instances that have been completed within a run
+ """
+ completedInstanceCount: Int
+
"""
When the run was created
"""
@@ -588,6 +633,11 @@ type CloudRun implements Node {
"""
totalFlakyTests: Int
+ """
+ Number of instances within a run
+ """
+ totalInstanceCount: Int
+
"""
This is the number of passed tests across all groups in the run
"""
diff --git a/packages/graphql/schemas/schema.graphql b/packages/graphql/schemas/schema.graphql
index f1e6bd0448ee..ae482bce561b 100644
--- a/packages/graphql/schemas/schema.graphql
+++ b/packages/graphql/schemas/schema.graphql
@@ -311,6 +311,16 @@ type CloudProjectSpec implements Node {
"""
fromBranch: String!
): Float
+
+ """
+ Average duration the spec takes to run within the context of the provided CloudRun ids.
+ """
+ averageDurationForRunIds(
+ """
+ The ids for the CloudRuns to use to derive the average duration. When provided, the fromBranch argument is ignored, as the provided run ids define the search space.
+ """
+ cloudRunIds: [ID!]!
+ ): Float
flakyStatus(
"""The number of runs to consider when counting flaky runs."""
flakyRunsWindow: Int!
@@ -321,6 +331,16 @@ type CloudProjectSpec implements Node {
fromBranch: String!
): CloudProjectSpecFlakyResult
+ """
+ The flaky metadata for the spec within the context of the provided CloudRun ids.
+ """
+ flakyStatusForRunIds(
+ """
+ The ids for the CloudRuns, ordered from most to least relevant, to use to derive the flaky status.
+ """
+ cloudRunIds: [ID!]!
+ ): CloudProjectSpecFlakyResult
+
"""Globally unique identifier representing a concrete GraphQL ObjectType"""
id: ID!
isConsideredFlaky(
@@ -328,6 +348,16 @@ type CloudProjectSpec implements Node {
fromBranch: String!
): Boolean
+ """
+ Indicator that a spec is considered flaky within the context of the provided CloudRun ids.
+ """
+ isConsideredFlakyForRunIds(
+ """
+ The ids for the CloudRuns to use to derive the flake indicator. When provided, the fromBranch argument is ignored, as the provided run ids define the search space.
+ """
+ cloudRunIds: [ID!]!
+ ): Boolean
+
"""
Current DateTime on the server. Used in connection with CloudLatestRunUpdateSpecData.
"""
@@ -380,6 +410,16 @@ type CloudProjectSpecFlakyStatus {
The last flaky run occurrence, interpreted as "n runs ago" - ex: a value of 5 means a flaky run last occurred 5 runs ago
"""
lastFlaky: Int
+
+ """
+ The associated commit_sha of the CloudRun containing the most recent flaky occurrence of the spec.
+ """
+ lastFlakyRunCommitSha: String
+
+ """
+ The run number of the CloudRun containing the most recent flaky occurrence of the spec.
+ """
+ lastFlakyRunNumber: Int
severity: String
}
@@ -430,6 +470,9 @@ type CloudRun implements Node {
commitInfo: CloudRunCommitInfo
completedAt: DateTime
+ """Number of specs instances that have been completed within a run"""
+ completedInstanceCount: Int
+
"""When the run was created"""
createdAt: DateTime!
@@ -480,6 +523,9 @@ type CloudRun implements Node {
"""Number of flaky tests, null if flake detection is not enabled"""
totalFlakyTests: Int
+ """Number of instances within a run"""
+ totalInstanceCount: Int
+
"""This is the number of passed tests across all groups in the run"""
totalPassed: Int
diff --git a/packages/graphql/test/stubCloudTypes.ts b/packages/graphql/test/stubCloudTypes.ts
index e27620709741..d770c2a0527c 100644
--- a/packages/graphql/test/stubCloudTypes.ts
+++ b/packages/graphql/test/stubCloudTypes.ts
@@ -179,6 +179,8 @@ export function createCloudRun (config: Partial): Required {
totalRunning: 0,
totalTests: 10,
totalPassed: 10,
+ completedInstanceCount: 10,
+ totalInstanceCount: 10,
totalDuration: 1000 * 60,
totalFlakyTests: 0,
tags: [],
@@ -348,8 +350,12 @@ export function createCloudProjectSpecResult (config: Partial)
flakyStatus: {
__typename: 'CloudProjectSpecFlakyStatus',
severity: 'NONE',
+
},
specRunsForRunIds: [],
+ averageDurationForRunIds: 1234,
+ flakyStatusForRunIds: null,
+ isConsideredFlakyForRunIds: false,
...config,
}