Skip to content

Commit a4bec4a

Browse files
feat: switch browser runner (#19048)
Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com>
1 parent 0c9d45a commit a4bec4a

File tree

7 files changed

+88
-32
lines changed

7 files changed

+88
-32
lines changed

packages/app/src/runner/SpecRunnerHeader.spec.tsx

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import SpecRunnerHeader from './SpecRunnerHeader.vue'
2-
import { useAutStore } from '../store'
2+
import { useAutStore, useSpecStore } from '../store'
33
import { SpecRunnerHeaderFragment, SpecRunnerHeaderFragmentDoc } from '../generated/graphql-test'
44
import { createEventManager, createTestAutIframe } from '../../cypress/e2e/support/ctSupport'
55

@@ -96,10 +96,15 @@ describe('SpecRunnerHeader', () => {
9696
cy.get('[data-cy="aut-url"]').should('not.exist')
9797
})
9898

99-
it('shows current browser and viewport', () => {
100-
const autStore = useAutStore()
99+
it('shows current browser and possible browsers', () => {
100+
const specStore = useSpecStore()
101+
102+
specStore.setActiveSpec({
103+
relative: 'packages/app/src/runner/SpecRunnerHeader.spec.tsx',
104+
absolute: '/Users/zachjw/work/cypress/packages/app/src/runner/SpecRunnerHeader.spec.tsx',
105+
name: 'SpecRunnerHeader.spec.tsx',
106+
})
101107

102-
autStore.updateDimensions(555, 777)
103108
cy.mountFragment(SpecRunnerHeaderFragmentDoc, {
104109
onResult: (ctx) => {
105110
ctx.currentBrowser = ctx.browsers?.find((x) => x.displayName === 'Chrome') ?? null
@@ -109,6 +114,8 @@ describe('SpecRunnerHeader', () => {
109114
},
110115
})
111116

112-
cy.get('[data-cy="select-browser"]').contains('Chrome 555x777')
117+
cy.get('[data-cy="select-browser"]').click()
118+
cy.findByRole('listbox').within(() =>
119+
['Chrome', 'Electron', 'Firefox'].forEach((browser) => cy.findAllByText(browser)))
113120
})
114121
})

packages/app/src/runner/SpecRunnerHeader.vue

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
data-cy="aut-url"
2929
>
3030
<div
31-
class="rounded-md flex shadow-md mx-2 url px-4"
31+
class="flex px-4 mx-2 rounded-md shadow-md url"
3232
:class="{
3333
'bg-yellow-50': autStore.isLoadingUrl,
3434
'bg-white': !autStore.isLoadingUrl,
@@ -43,10 +43,11 @@
4343
</div>
4444

4545
<Select
46-
v-model="browser"
46+
:model-value="browser"
4747
data-cy="select-browser"
4848
:options="browsers"
49-
item-value="name"
49+
item-value="displayName"
50+
@update:model-value="changeBrowser"
5051
/>
5152
</div>
5253

@@ -63,13 +64,13 @@
6364
</template>
6465

6566
<script lang="ts" setup>
66-
import { computed } from 'vue'
67-
import { useAutStore } from '../store'
67+
import { computed, ref } from 'vue'
68+
import { useAutStore, useSpecStore } from '../store'
6869
import Select from '@packages/frontend-shared/src/components/Select.vue'
69-
import { gql } from '@urql/vue'
70+
import { gql, useMutation } from '@urql/vue'
7071
import IconCrosshairsGPS from '~icons/mdi/crosshairs-gps'
7172
import Icon from '@packages/frontend-shared/src/components/Icon.vue'
72-
import type { SpecRunnerHeaderFragment } from '../generated/graphql'
73+
import { SpecRunnerHeaderFragment, SpecRunnerHeader_SetBrowserDocument, SpecRunnerHeader_BrowserFragment } from '../generated/graphql'
7374
import SelectorPlayground from './selector-playground/SelectorPlayground.vue'
7475
import { useSelectorPlaygroundStore } from '../store/selector-playground-store'
7576
import type { EventManager } from './event-manager'
@@ -86,12 +87,28 @@ fragment SpecRunnerHeader on CurrentProject {
8687
}
8788
browsers {
8889
id
89-
name
90-
displayName
90+
...SpecRunnerHeader_Browser
9191
}
9292
}
9393
`
9494
95+
gql`
96+
fragment SpecRunnerHeader_Browser on Browser {
97+
id
98+
name
99+
displayName
100+
}
101+
`
102+
103+
gql`
104+
mutation SpecRunnerHeader_SetBrowser($browserId: ID!, $specPath: String!) {
105+
launchpadSetBrowser(id: $browserId)
106+
launchOpenProject(specPath: $specPath)
107+
}
108+
`
109+
110+
const setBrowser = useMutation(SpecRunnerHeader_SetBrowserDocument)
111+
95112
const props = defineProps<{
96113
gql: SpecRunnerHeaderFragment
97114
eventManager: EventManager
@@ -114,20 +131,21 @@ const togglePlayground = () => {
114131
}
115132
}
116133
117-
const browser = computed(() => {
118-
if (!props.gql.currentBrowser) {
119-
return
120-
}
134+
const specStore = useSpecStore()
135+
136+
// Have to spread gql props since binding it to v-model causes error when testing
137+
const browser = ref({ ...props.gql.currentBrowser })
138+
const browsers = computed(() => props.gql.browsers?.slice().map((browser) => ({ ...browser })) ?? [])
121139
122-
const dimensions = `${autStore.viewportDimensions.width}x${autStore.viewportDimensions.height}`
140+
function changeBrowser (browser: SpecRunnerHeader_BrowserFragment) {
141+
const activeSpec = specStore.activeSpec
123142
124-
return {
125-
id: props.gql.currentBrowser.id,
126-
name: `${props.gql.currentBrowser.displayName} ${dimensions}`,
143+
if (props.gql.currentBrowser?.id === browser.id || !activeSpec) {
144+
return
127145
}
128-
})
129146
130-
const browsers = computed(() => props.gql.browsers?.slice() ?? [])
147+
setBrowser.executeMutation({ browserId: browser.id, specPath: activeSpec.absolute })
148+
}
131149
132150
const autStore = useAutStore()
133151

packages/data-context/src/actions/ProjectActions.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ export class ProjectActions {
219219
}
220220
}
221221

222-
async launchProject (testingType: TestingTypeEnum | null, options: LaunchOpts) {
222+
async launchProject (testingType: TestingTypeEnum | null, options: LaunchOpts, specPath?: string | null) {
223223
if (!this.ctx.currentProject) {
224224
return null
225225
}
@@ -230,6 +230,12 @@ export class ProjectActions {
230230
return null
231231
}
232232

233+
let activeSpec: FoundSpec | undefined
234+
235+
if (specPath) {
236+
activeSpec = await this.ctx.project.getCurrentSpecByAbsolute(this.ctx.currentProject.projectRoot, specPath)
237+
}
238+
233239
// Ensure that we have loaded browsers to choose from
234240
if (this.ctx.appData.refreshingBrowsers) {
235241
await this.ctx.appData.refreshingBrowsers
@@ -241,7 +247,9 @@ export class ProjectActions {
241247
return null
242248
}
243249

244-
const spec: Cypress.Spec = {
250+
// launchProject expects a spec when opening browser for url navigation.
251+
// We give it an empty spec if none is passed so as to land on home page
252+
const emptySpec: Cypress.Spec = {
245253
name: '',
246254
absolute: '',
247255
relative: '',
@@ -250,7 +258,7 @@ export class ProjectActions {
250258

251259
this.ctx.appData.currentTestingType = testingType
252260

253-
return this.api.launchProject(browser, spec, options)
261+
return this.api.launchProject(browser, activeSpec ?? emptySpec, options)
254262
}
255263

256264
async launchProjectWithoutElectron () {

packages/data-context/src/sources/ProjectDataSource.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ export class ProjectDataSource {
4646
return specs.filter((spec) => spec.specType === specType)
4747
}
4848

49+
async getCurrentSpecByAbsolute (projectRoot: string, absolute: string) {
50+
// TODO: should cache current specs so we don't need to
51+
// call findSpecs each time we ask for the current spec.
52+
const specs = await this.findSpecs(projectRoot, null)
53+
54+
return specs.find((x) => x.absolute === absolute)
55+
}
56+
4957
async getCurrentSpecById (projectRoot: string, base64Id: string) {
5058
// TODO: should cache current specs so we don't need to
5159
// call findSpecs each time we ask for the current spec.

packages/graphql/schemas/schema.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ type Mutation {
484484
internal_clearProjectPreferencesCache(projectTitle: String!): Boolean
485485

486486
"""Launches project from open_project global singleton"""
487-
launchOpenProject: Boolean
487+
launchOpenProject(specPath: String): Boolean
488488

489489
"""Sets the active browser"""
490490
launchpadSetBrowser(

packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,11 @@ export const mutation = mutationType({
201201

202202
t.liveMutation('launchOpenProject', {
203203
description: 'Launches project from open_project global singleton',
204+
args: {
205+
specPath: stringArg(),
206+
},
204207
resolve: async (_, args, ctx) => {
205-
await ctx.actions.project.launchProject(ctx.wizardData.chosenTestingType, {})
208+
await ctx.actions.project.launchProject(ctx.wizardData.chosenTestingType, {}, args.specPath)
206209
},
207210
})
208211

packages/server/lib/project_utils.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,20 @@ import { makeLegacyDataContext } from './makeDataContext'
1010
const debug = Debug('cypress:server:project_utils')
1111

1212
const multipleForwardSlashesRe = /[^:\/\/](\/{2,})/g
13+
const multipleForwardSlashesReplacer = (match: string) => match.replace('//', '/')
1314
const backSlashesRe = /\\/g
1415

1516
const normalizeSpecUrl = (browserUrl: string, specUrl: string) => {
1617
if (process.env.LAUNCHPAD) {
1718
return browserUrl
1819
}
1920

20-
const replacer = (match: string) => match.replace('//', '/')
21-
2221
return [
2322
browserUrl,
2423
'#/tests',
2524
escapeFilenameInUrl(specUrl),
2625
].join('/')
27-
.replace(multipleForwardSlashesRe, replacer)
26+
.replace(multipleForwardSlashesRe, multipleForwardSlashesReplacer)
2827
}
2928

3029
const getPrefixedPathToSpec = ({
@@ -87,6 +86,19 @@ export const getSpecUrl = ({
8786
specType ??= 'integration'
8887
browserUrl ??= ''
8988

89+
// App routes to spec with convention {browserUrl}#/runner?file={relativeSpecPath}
90+
if (process.env.LAUNCHPAD) {
91+
if (!absoluteSpecPath) {
92+
return browserUrl
93+
}
94+
95+
const relativeSpecPath = path.relative(projectRoot, path.resolve(projectRoot, absoluteSpecPath))
96+
.replace(backSlashesRe, '/')
97+
98+
return `${browserUrl}/#/runner?file=${relativeSpecPath}`
99+
.replace(multipleForwardSlashesRe, multipleForwardSlashesReplacer)
100+
}
101+
90102
debug('get spec url: %s for spec type %s', absoluteSpecPath, specType)
91103

92104
// if we don't have a absoluteSpecPath or its __all

0 commit comments

Comments
 (0)