Skip to content

Commit 282e6c0

Browse files
authored
fix: login/utm param bug fixes and test coverage (#23787)
1 parent 7c6c231 commit 282e6c0

19 files changed

+153
-33
lines changed

packages/app/cypress/e2e/runs.cy.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,20 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
7575
it('clicking the login button will open the login modal', () => {
7676
cy.visitApp()
7777
moveToRunsPage()
78-
cy.contains('Log In').click()
78+
cy.contains(defaultMessages.runs.connect.buttonUser).click()
79+
cy.withCtx((ctx, o) => {
80+
o.sinon.spy(ctx._apis.authApi, 'logIn')
81+
})
82+
7983
cy.findByRole('dialog', { name: 'Log in to Cypress' }).within(() => {
80-
cy.get('button').contains('Log In')
84+
cy.contains('button', 'Log In').click()
85+
})
86+
87+
cy.withCtx((ctx, o) => {
88+
// validate utmSource
89+
expect((ctx._apis.authApi.logIn as SinonStub).lastCall.args[1]).to.eq('Binary: App')
90+
// validate utmMedium
91+
expect((ctx._apis.authApi.logIn as SinonStub).lastCall.args[2]).to.eq('Runs Tab')
8192
})
8293
})
8394

@@ -239,6 +250,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
239250
moveToRunsPage()
240251

241252
cy.withCtx(async (ctx, options) => {
253+
ctx.coreData.app.browserStatus = 'open'
242254
options.sinon.stub(ctx._apis.electronApi, 'isMainWindowFocused').returns(false)
243255
options.sinon.stub(ctx._apis.authApi, 'logIn').callsFake(async (onMessage) => {
244256
setTimeout(() => {
@@ -272,8 +284,8 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
272284
})
273285

274286
context('Runs - Create Project', () => {
275-
it('when a project is created, injects new projectId into the config file', () => {
276-
cy.remoteGraphQLIntercept(async (obj) => {
287+
it('when a project is created, injects new projectId into the config file, and sends expected UTM params', () => {
288+
cy.remoteGraphQLIntercept((obj) => {
277289
if (obj.operationName === 'SelectCloudProjectModal_CreateCloudProject_cloudProjectCreate') {
278290
obj.result.data!.cloudProjectCreate = {
279291
slug: 'newProjectId',
@@ -290,7 +302,9 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
290302
cy.loginUser()
291303
cy.visitApp()
292304

293-
cy.withCtx(async (ctx) => {
305+
cy.withCtx(async (ctx, o) => {
306+
o.sinon.spy(ctx.cloud, 'executeRemoteGraphQL')
307+
294308
const config = await ctx.project.getConfig()
295309

296310
expect(config.projectId).to.not.equal('newProjectId')
@@ -305,6 +319,12 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
305319
const config = await ctx.project.getConfig()
306320

307321
expect(config.projectId).to.equal('newProjectId')
322+
expect(ctx.cloud.executeRemoteGraphQL).to.have.been.calledWithMatch({
323+
fieldName: 'cloudProjectCreate',
324+
operationVariables: {
325+
medium: 'Runs Tab',
326+
source: 'Binary: App',
327+
} })
308328
})
309329
})
310330

packages/app/cypress/e2e/settings.cy.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,12 @@ describe('App: Settings', () => {
1616
cy.visitApp()
1717
cy.get(SidebarSettingsLinkSelector).click()
1818

19-
cy.get('div[data-cy="app-header-bar"]').should('contain', 'Settings')
19+
cy.contains('[data-cy="app-header-bar"]', 'Settings')
20+
cy.contains('[data-cy="app-header-bar"] button', 'Log In').should('be.visible')
21+
2022
cy.findByText('Device Settings').should('be.visible')
2123
cy.findByText('Project Settings').should('be.visible')
22-
})
23-
24-
it('shows a button to log in if user is not connected', () => {
25-
cy.startAppServer('e2e')
26-
cy.visitApp()
27-
cy.get(SidebarSettingsLinkSelector).click()
28-
cy.findByText('Project Settings').click()
29-
cy.get('button').contains('Log In')
24+
cy.findByText('Dashboard Settings').should('be.visible')
3025
})
3126

3227
describe('Cloud Settings', () => {
@@ -406,7 +401,7 @@ describe('App: Settings', () => {
406401
})
407402

408403
describe('App: Settings without cloud', () => {
409-
it('the projectId section shows a prompt to connect when there is no projectId', () => {
404+
it('the projectId section shows a prompt to log in when there is no projectId, and uses correct UTM params', () => {
410405
cy.scaffoldProject('simple-ct')
411406
cy.openProject('simple-ct')
412407
cy.startAppServer('component')
@@ -415,7 +410,21 @@ describe('App: Settings without cloud', () => {
415410
cy.get(SidebarSettingsLinkSelector).click()
416411
cy.findByText('Dashboard Settings').click()
417412
cy.findByText('Project ID').should('exist')
418-
cy.contains('button', 'Log in to the Cypress Dashboard').should('be.visible')
413+
cy.withCtx((ctx, o) => {
414+
o.sinon.spy(ctx._apis.authApi, 'logIn')
415+
})
416+
417+
cy.contains('button', 'Log in to the Cypress Dashboard').click()
418+
cy.findByRole('dialog', { name: 'Log in to Cypress' }).within(() => {
419+
cy.contains('button', 'Log In').click()
420+
})
421+
422+
cy.withCtx((ctx, o) => {
423+
// validate utmSource
424+
expect((ctx._apis.authApi.logIn as SinonStub).lastCall.args[1]).to.eq('Binary: App')
425+
// validate utmMedium
426+
expect((ctx._apis.authApi.logIn as SinonStub).lastCall.args[2]).to.eq('Settings Tab')
427+
})
419428
})
420429

421430
it('have returned browsers', () => {

packages/app/cypress/e2e/specs_list_latest_runs.cy.ts

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,37 @@ function averageDurationSelector (specFileName: string) {
1717
return `${specRowSelector(specFileName)} [data-cy="average-duration"]`
1818
}
1919

20+
function makeTestingCloudLink (status: string) {
21+
return `https://google.com?utm_medium=Specs+Latest+Runs+Dots&utm_campaign=${status.toUpperCase()}&utm_source=Binary%3A+App`
22+
}
23+
24+
function assertCorrectRunsLink (specFileName: string, status: string) {
25+
// we avoid the full `cy.validateExternalLink` here because that command
26+
// clicks the link, which focuses the link causing tooltips to appear,
27+
// which produces problems elsewhere testing tooltip behavior
28+
cy.findByRole('link', { name: specFileName })
29+
.should('have.attr', 'href', makeTestingCloudLink(status))
30+
.should('have.attr', 'data-cy', 'external') // to confirm the ExternalLink component is used
31+
}
32+
33+
function validateTooltip (status: string) {
34+
cy.validateExternalLink({
35+
// TODO: (#23778) This name is so long because the entire tooltip is wrapped in a link,
36+
// we can make this more accessible by having the name of the link describe the destination
37+
// (which is currently not described) and keeping the other content separate.
38+
name: `accounts_new.spec.js ${status} 4 months ago 2:23 - 2:39 skipped pending passed failed`,
39+
// the main thing about testing this link is that is gets composed with the expected UTM params
40+
href: makeTestingCloudLink(status),
41+
})
42+
.should('contain.text', 'accounts_new.spec.js')
43+
.and('contain.text', '4 months ago')
44+
.and('contain.text', '2:23 - 2:39')
45+
.and('contain.text', 'skipped 0')
46+
.and('contain.text', 'pending 1-2')
47+
.and('contain.text', `passed 22-23`)
48+
.and('contain.text', 'failed 1-2')
49+
}
50+
2051
function specShouldShow (specFileName: string, runDotsClasses: string[], latestRunStatus: CloudRunStatus|'PLACEHOLDER') {
2152
const latestStatusSpinning = latestRunStatus === 'RUNNING'
2253

@@ -31,10 +62,11 @@ function specShouldShow (specFileName: string, runDotsClasses: string[], latestR
3162
.should(`${latestStatusSpinning ? '' : 'not.'}have.class`, 'animate-spin')
3263
.and('have.attr', 'data-cy-run-status', latestRunStatus)
3364

34-
// TODO: add link verification
35-
// if (latestRunStatus !== 'PLACEHOLDER') {
36-
// cy.get(`${specRowSelector(specFileName)} [data-cy="run-status-dots"]`).validateExternalLink('https://google.com')
37-
// }
65+
if (runDotsClasses?.length) {
66+
assertCorrectRunsLink(`${specFileName} test results`, latestRunStatus)
67+
} else {
68+
cy.findByRole('link', { name: `${specFileName} test results` }).should('not.exist')
69+
}
3870
}
3971

4072
function simulateRunData () {
@@ -330,7 +362,9 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
330362
specShouldShow('accounts_new.spec.js', ['gray-300', 'gray-300', 'jade-400'], 'RUNNING')
331363
cy.get(dotSelector('accounts_new.spec.js', 'latest')).trigger('mouseenter')
332364
cy.get('.v-popper__popper--shown').should('exist')
333-
// TODO: verify the contents of the tooltip
365+
366+
validateTooltip('Running')
367+
334368
cy.get(dotSelector('accounts_new.spec.js', 'latest')).trigger('mouseleave')
335369
cy.get(averageDurationSelector('accounts_new.spec.js')).contains('2:03')
336370
})
@@ -601,7 +635,8 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
601635
specShouldShow('accounts_list.spec.js', ['orange-400', 'gray-300', 'red-400'], 'PASSED')
602636
cy.get(dotSelector('accounts_new.spec.js', 'latest')).trigger('mouseenter')
603637
cy.get('.v-popper__popper--shown').should('exist')
604-
// TODO: verify the contents of the tooltip
638+
639+
validateTooltip('Passed')
605640
cy.get(dotSelector('accounts_new.spec.js', 'latest')).trigger('mouseleave')
606641
cy.get(averageDurationSelector('accounts_list.spec.js')).contains('0:12')
607642

@@ -611,7 +646,8 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
611646
specShouldShow('accounts_list.spec.js', ['orange-400', 'gray-300', 'red-400'], 'PASSED')
612647
cy.get(dotSelector('accounts_new.spec.js', 'latest')).trigger('mouseenter')
613648
cy.get('.v-popper__popper--shown').should('exist')
614-
// TODO: verify the contents of the tooltip
649+
650+
validateTooltip('Passed')
615651
cy.get(dotSelector('accounts_new.spec.js', 'latest')).trigger('mouseleave')
616652
cy.get(averageDurationSelector('accounts_list.spec.js')).contains('0:12')
617653
})

packages/app/cypress/e2e/top-nav.cy.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ describe('App Top Nav Workflows', () => {
407407

408408
const mockLogInActionsForUser = (user) => {
409409
cy.withCtx(async (ctx, options) => {
410+
ctx.coreData.app.browserStatus = 'open'
410411
options.sinon.stub(ctx._apis.electronApi, 'isMainWindowFocused').returns(false)
411412
options.sinon.stub(ctx._apis.authApi, 'logIn').callsFake(async (onMessage) => {
412413
setTimeout(() => {
@@ -455,6 +456,13 @@ describe('App Top Nav Workflows', () => {
455456

456457
mockLogInActionsForUser(mockUser)
457458
logIn({ expectedNextStepText: 'Connect project', displayName: mockUser.name })
459+
cy.withCtx((ctx, o) => {
460+
// validate utmSource
461+
expect((ctx._apis.authApi.logIn as SinonStub).lastCall.args[1]).to.eq('Binary: App')
462+
// validate utmMedium
463+
expect((ctx._apis.authApi.logIn as SinonStub).lastCall.args[2]).to.eq('Nav')
464+
})
465+
458466
cy.findByRole('dialog', { name: 'Create project' }).should('be.visible')
459467
})
460468
})

packages/app/src/layouts/default.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,13 @@
4949
<component :is="Component" />
5050
</transition>
5151
</router-view>
52+
<!-- "Nav" is the correct UTM medium below because
53+
this is only opened by event emitted from the header bar-->
5254
<CloudConnectModals
5355
v-if="showConnectDialog && cloudModalQuery.data.value"
5456
:show="showConnectDialog"
5557
:gql="cloudModalQuery.data.value"
58+
utm-medium="Nav"
5659
@cancel="showConnectDialog = false"
5760
@success="showConnectDialog = false"
5861
/>

packages/app/src/runs/CloudConnectButton.cy.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ describe('<CloudConnectButton />', () => {
99
result.cloudViewer = null
1010
},
1111
render (gqlVal) {
12-
return <div class="h-screen"><CloudConnectButton gql={gqlVal} /></div>
12+
return <div class="h-screen"><CloudConnectButton utmMedium="testing" gql={gqlVal} /></div>
1313
},
1414
})
1515

@@ -56,7 +56,7 @@ describe('<CloudConnectButton />', () => {
5656
result.cloudViewer = cloudViewer
5757
},
5858
render (gqlVal) {
59-
return <div class="h-screen"><CloudConnectButton gql={gqlVal} /></div>
59+
return <div class="h-screen"><CloudConnectButton utmMedium="testing" gql={gqlVal} /></div>
6060
},
6161
})
6262

@@ -69,7 +69,7 @@ describe('<CloudConnectButton />', () => {
6969
result.cloudViewer = cloudViewer
7070
},
7171
render (gqlVal) {
72-
return <div class="h-screen"><CloudConnectButton gql={gqlVal} /></div>
72+
return <div class="h-screen"><CloudConnectButton utmMedium="testing" gql={gqlVal} /></div>
7373
},
7474
})
7575

packages/app/src/runs/CloudConnectButton.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@
1010
<LoginModal
1111
v-model="isLoginOpen"
1212
:gql="props.gql"
13-
utm-medium="Runs Tab"
13+
:utm-medium="props.utmMedium"
1414
:show-connect-button-after-login="!cloudProjectId"
1515
@connect-project="isProjectConnectOpen = true"
1616
/>
1717
<CloudConnectModals
1818
v-if="isProjectConnectOpen"
1919
:show="isProjectConnectOpen"
2020
:gql="props.gql"
21+
:utm-medium="props.utmMedium"
2122
@cancel="isProjectConnectOpen = false"
2223
@success="isProjectConnectOpen = false; emit('success')"
2324
/>
@@ -54,6 +55,7 @@ const emit = defineEmits<{
5455
const props = defineProps<{
5556
gql: CloudConnectButtonFragment
5657
class?: string
58+
utmMedium: string
5759
}>()
5860
5961
const isLoginOpen = ref(false)

packages/app/src/runs/RunsConnect.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<CloudConnectButton
2121
:gql="props.gql"
2222
class="mx-auto mt-40px"
23+
utm-medium="Runs Tab"
2324
@success="emit('success')"
2425
/>
2526
</div>

packages/app/src/runs/RunsErrorRenderer.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
v-if="showConnectDialog"
6363
:show="showConnectDialog"
6464
:gql="props.gql"
65+
utm-medium="Runs Tab"
6566
@cancel="showConnectDialog = false"
6667
@success="showConnectDialog = false"
6768
/>

packages/app/src/runs/modals/CloudConnectModals.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe('<CloudConnectModals />', () => {
3838
},
3939
render (gql) {
4040
return (<div class="h-screen">
41-
<CloudConnectModals gql={gql}/>
41+
<CloudConnectModals utmMedium="testing" gql={gql}/>
4242
</div>)
4343
},
4444
})

0 commit comments

Comments
 (0)