diff --git a/.vscode/cspell.json b/.vscode/cspell.json
index 5649488074b9..17e85156931c 100644
--- a/.vscode/cspell.json
+++ b/.vscode/cspell.json
@@ -37,6 +37,7 @@
"topnav",
"unconfigured",
"unplugin",
+ "unref",
"unrunnable",
"unstaged",
"urql",
@@ -48,4 +49,4 @@
],
"ignoreWords": [],
"import": []
-}
\ No newline at end of file
+}
diff --git a/graphql-codegen.yml b/graphql-codegen.yml
index 496238eb8d1d..e2b11c51214b 100644
--- a/graphql-codegen.yml
+++ b/graphql-codegen.yml
@@ -18,7 +18,7 @@ vueOperations: &vueOperations
- 'typescript-operations'
- 'typed-document-node':
# Intentionally specified under typed-document-node rather than top level config,
- # becuase we don't want it flattening the types for the operations
+ # because we don't want it flattening the types for the operations
flattenGeneratedTypes: true
vueTesting: &vueTesting
@@ -122,9 +122,10 @@ generates:
'./packages/app/src/generated/graphql-test.ts':
documents:
- './packages/app/src/**/*.{vue,ts,tsx,js,jsx}'
- - './packages/frontend-shared/src/**/*.{vue,ts,tsx,js,jsx}'
+ - './packages/frontend-shared/src/gql-components/**/*.{vue,ts,tsx,js,jsx}'
<<: *vueTesting
'./packages/frontend-shared/src/generated/graphql-test.ts':
- documents: './packages/frontend-shared/src/gql-components/**/*.{vue,ts,tsx,js,jsx}'
+ documents:
+ - './packages/frontend-shared/src/gql-components/**/*.{vue,ts,tsx,js,jsx}'
<<: *vueTesting
diff --git a/packages/app/cypress/e2e/runs.cy.ts b/packages/app/cypress/e2e/runs.cy.ts
index c03d84a13489..263a65218663 100644
--- a/packages/app/cypress/e2e/runs.cy.ts
+++ b/packages/app/cypress/e2e/runs.cy.ts
@@ -116,6 +116,10 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
obj.result.data.cloudViewer.organizations.nodes = []
}
+ if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) {
+ obj.result.data.cloudViewer.firstOrganization.nodes = []
+ }
+
return obj.result
})
@@ -150,8 +154,14 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
cy.startAppServer('component')
cy.remoteGraphQLIntercept(async (obj) => {
- if (obj?.result?.data?.cloudViewer?.organizations?.nodes) {
- obj.result.data.cloudViewer.organizations.nodes = []
+ if ((obj.operationName !== 'CreateCloudOrgModal_CloudOrganizationsCheck_refreshOrganizations_cloudViewer')) {
+ if (obj.result.data?.cloudViewer?.organizations?.nodes) {
+ obj.result.data.cloudViewer.organizations.nodes = []
+ }
+
+ if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) {
+ obj.result.data.cloudViewer.firstOrganization.nodes = []
+ }
}
return obj.result
@@ -181,7 +191,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
cy.startAppServer('component')
cy.remoteGraphQLIntercept(async (obj, testState) => {
- if (obj.operationName === 'CloudConnectModals_RefreshCloudViewer_refreshCloudViewer_cloudViewer') {
+ if (obj.operationName === 'LoginConnectModals_LoginConnectModalsQuery_cloudViewer') {
testState.refetchCalled = true
}
@@ -312,7 +322,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
moveToRunsPage()
cy.findByText(defaultMessages.runs.connect.buttonProject).click()
- cy.get('button').contains(defaultMessages.runs.connect.modal.selectProject.createProject).click()
+ cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click()
cy.findByText(defaultMessages.runs.connectSuccessAlert.title).should('be.visible')
cy.withCtx(async (ctx) => {
@@ -351,7 +361,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
cy.get('[href="#/runs"]').click()
cy.findByText(defaultMessages.runs.connect.buttonProject).click()
- cy.get('button').contains(defaultMessages.runs.connect.modal.selectProject.createProject).click()
+ cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click()
cy.get('[data-cy="alert"]').within(() => {
cy.contains(defaultMessages.runs.connect.errors.baseError.title)
@@ -386,7 +396,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
cy.get('[href="#/runs"]').click()
cy.findByText(defaultMessages.runs.connect.buttonProject).click()
- cy.get('button').contains(defaultMessages.runs.connect.modal.selectProject.createProject).click()
+ cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click()
cy.get('[data-cy="alert"]').within(() => {
cy.contains(defaultMessages.runs.connect.errors.internalServerError.title)
@@ -417,7 +427,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
}
if (obj.result.data?.cloudViewer?.organizations?.nodes) {
- const projectNodes = obj.result.data?.cloudViewer.organizations.nodes[0].projects.nodes
+ const projectNodes = obj.result.data.cloudViewer.organizations.nodes[0].projects.nodes
projectNodes.push({
__typename: 'CloudProject',
@@ -440,9 +450,10 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
})
it('opens Connect Project modal after clicking Reconnect Project button', () => {
- cy.findByText(defaultMessages.runs.errors.notFound.button).should('be.visible').click()
+ cy.findByText(defaultMessages.runs.errors.notFound.button).click()
+
cy.get('[aria-modal="true"]').should('exist')
- cy.get('[data-cy="selectProject"] button').should('have.text', 'Mock Project')
+ cy.contains('[data-cy="selectProject"] button', 'Mock Project')
cy.get('[data-cy="connect-project"]').click()
cy.get('[data-cy="runs"]', { timeout: 7500 })
})
@@ -510,12 +521,13 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
it('updates the button text when the request access button is clicked', () => {
cy.remoteGraphQLIntercept(async (obj, testState) => {
- if (obj.operationName === 'Runs_currentProject_cloudProject_cloudProjectBySlug') {
+ if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) {
const proj = obj!.result!.data!.cloudProjectBySlug
proj.__typename = 'CloudProjectUnauthorized'
proj.message = 'Cloud Project Unauthorized'
proj.hasRequestedAccess = false
+
testState.project = proj
} else if (obj.operationName === 'RunsErrorRenderer_RequestAccess_cloudProjectRequestAccess') {
obj!.result!.data!.cloudProjectRequestAccess = {
@@ -696,7 +708,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
cy.loginUser()
cy.remoteGraphQLIntercept((obj) => {
- if (obj.operationName === 'Runs_currentProject_cloudProject_cloudProjectBySlug') {
+ if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) {
cloudData = obj.result
obj.result = {}
@@ -714,7 +726,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
// cy.percySnapshot() // TODO: restore when Percy CSS is fixed. See https://github.com/cypress-io/cypress/issues/23435
cy.remoteGraphQLIntercept((obj) => {
- if (obj.operationName === 'Runs_currentProject_cloudProject_cloudProjectBySlug') {
+ if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) {
return cloudData
}
@@ -732,10 +744,6 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
cy.startAppServer('component')
})
- afterEach(() => {
- cy.goOnline()
- })
-
it('shows alert warning if runs have been returned already', () => {
cy.loginUser()
cy.visitApp()
@@ -773,11 +781,15 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
cy.openProject('component-tests', ['--config-file', 'cypressWithoutProjectId.config.js'])
cy.startAppServer('component')
- cy.remoteGraphQLIntercept(async (obj) => {
+ cy.remoteGraphQLIntercept((obj) => {
if (obj.result.data?.cloudViewer?.organizations?.nodes) {
obj.result.data.cloudViewer.organizations.nodes = []
}
+ if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) {
+ obj.result.data.cloudViewer.firstOrganization.nodes = []
+ }
+
return obj.result
})
@@ -845,7 +857,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
cy.openProject('component-tests')
cy.startAppServer('component')
cy.loginUser()
- cy.remoteGraphQLIntercept((obj, testState) => {
+ cy.remoteGraphQLIntercept((obj) => {
if (obj.result.data?.cloudProjectBySlug?.runs?.nodes.length) {
obj.result.data.cloudProjectBySlug.runs.nodes.map((run) => {
run.status = 'RUNNING'
diff --git a/packages/app/cypress/e2e/specs_list_latest_runs.cy.ts b/packages/app/cypress/e2e/specs_list_latest_runs.cy.ts
index 6d99a9b6a384..7a661af52f78 100644
--- a/packages/app/cypress/e2e/specs_list_latest_runs.cy.ts
+++ b/packages/app/cypress/e2e/specs_list_latest_runs.cy.ts
@@ -160,7 +160,7 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
})
context('when no runs are recorded', () => {
- beforeEach(() => {
+ it('shows placeholders for all visible specs', { defaultCommandTimeout: 6000 }, () => {
cy.loginUser()
cy.remoteGraphQLIntercept(async (obj) => {
@@ -181,10 +181,6 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
})
cy.visitApp()
- cy.findByTestId('sidebar-link-specs-page').click()
- })
-
- it('shows placeholders for all visible specs', () => {
allVisibleSpecsShouldBePlaceholders()
})
})
diff --git a/packages/app/cypress/e2e/subscriptions/createCloudOrgModal-subscription.cy.ts b/packages/app/cypress/e2e/subscriptions/createCloudOrgModal-subscription.cy.ts
index 41f607621d12..1d4365597b50 100644
--- a/packages/app/cypress/e2e/subscriptions/createCloudOrgModal-subscription.cy.ts
+++ b/packages/app/cypress/e2e/subscriptions/createCloudOrgModal-subscription.cy.ts
@@ -1,7 +1,7 @@
import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json'
import type { SinonStub } from 'sinon'
-describe('App: Runs', { viewportWidth: 1200 }, () => {
+describe('CreateCloudOrgModalSubscription', { viewportWidth: 1200 }, () => {
beforeEach(() => {
cy.scaffoldProject('component-tests')
cy.openProject('component-tests')
@@ -22,6 +22,10 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
obj.result.data.cloudViewer.organizations.nodes = []
}
+ if (obj.result.data?.cloudViewer?.firstOrganization?.nodes) {
+ obj.result.data.cloudViewer.firstOrganization.nodes = []
+ }
+
return obj.result
})
diff --git a/packages/app/cypress/e2e/top-nav.cy.ts b/packages/app/cypress/e2e/top-nav.cy.ts
index 330e5f0796b1..e65a10d484f7 100644
--- a/packages/app/cypress/e2e/top-nav.cy.ts
+++ b/packages/app/cypress/e2e/top-nav.cy.ts
@@ -453,6 +453,17 @@ describe('App Top Nav Workflows', () => {
cy.openProject('component-tests', ['--config-file', 'cypressWithoutProjectId.config.js'])
cy.startAppServer()
cy.visitApp()
+ cy.remoteGraphQLIntercept(async (obj) => {
+ if (obj.result.data?.cloudViewer) {
+ obj.result.data.cloudViewer.organizations = {
+ __typename: 'CloudOrganizationConnection',
+ id: 'test',
+ nodes: [{ __typename: 'CloudOrganization', id: '987' }],
+ }
+ }
+
+ return obj.result
+ })
mockLogInActionsForUser(mockUser)
logIn({ expectedNextStepText: 'Connect project', displayName: mockUser.name })
@@ -494,6 +505,24 @@ describe('App Top Nav Workflows', () => {
cy.findByTestId('app-header-bar').findByTestId('user-avatar-title').should('be.visible')
})
+ it('if the project has no runs, shows "record your first run" prompt after clicking', () => {
+ cy.remoteGraphQLIntercept((obj) => {
+ if (obj.result?.data?.cloudProjectBySlug?.runs?.nodes?.length) {
+ obj.result.data.cloudProjectBySlug.runs.nodes = []
+ }
+
+ return obj.result
+ })
+
+ mockLogInActionsForUser(mockUserNoName)
+
+ logIn({ expectedNextStepText: 'Continue', displayName: mockUserNoName.email })
+
+ cy.contains('[data-cy=standard-modal] h2', defaultMessages.specPage.banners.record.title).should('be.visible')
+ cy.contains('[data-cy=standard-modal]', defaultMessages.specPage.banners.record.content).should('be.visible')
+ cy.contains('button', 'Copy').should('be.visible')
+ })
+
it('shows correct error when browser cannot launch', () => {
cy.withCtx((ctx, o) => {
o.sinon.stub(ctx._apis.authApi, 'logIn').callsFake(async (onMessage) => {
diff --git a/packages/app/src/App.vue b/packages/app/src/App.vue
index d76b3823321d..c0e8a339e7fe 100644
--- a/packages/app/src/App.vue
+++ b/packages/app/src/App.vue
@@ -4,13 +4,31 @@
:is="Component"
/>
+
+
+
+
+
+
+
+