Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions packages/app/src/runner/SpecRunnerHeader.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import SpecRunnerHeader from './SpecRunnerHeader.vue'
import { useAutStore } from '../store'
import { useAutStore, useSpecStore } from '../store'
import { SpecRunnerHeaderFragment, SpecRunnerHeaderFragmentDoc } from '../generated/graphql-test'
import { createEventManager, createTestAutIframe } from '../../cypress/e2e/support/ctSupport'

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

it('shows current browser and viewport', () => {
const autStore = useAutStore()
it('shows current browser and possible browsers', () => {
const specStore = useSpecStore()

specStore.setActiveSpec({
relative: 'packages/app/src/runner/SpecRunnerHeader.spec.tsx',
absolute: '/Users/zachjw/work/cypress/packages/app/src/runner/SpecRunnerHeader.spec.tsx',
name: 'SpecRunnerHeader.spec.tsx',
})

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

cy.get('[data-cy="select-browser"]').contains('Chrome 555x777')
cy.get('[data-cy="select-browser"]').click()
cy.findByRole('listbox').within(() =>
['Chrome', 'Electron', 'Firefox'].forEach((browser) => cy.findAllByText(browser)))
})
})
56 changes: 37 additions & 19 deletions packages/app/src/runner/SpecRunnerHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
data-cy="aut-url"
>
<div
class="rounded-md flex shadow-md mx-2 url px-4"
class="flex px-4 mx-2 rounded-md shadow-md url"
:class="{
'bg-yellow-50': autStore.isLoadingUrl,
'bg-white': !autStore.isLoadingUrl,
Expand All @@ -43,10 +43,11 @@
</div>

<Select
v-model="browser"
:model-value="browser"
data-cy="select-browser"
:options="browsers"
item-value="name"
item-value="displayName"
@update:model-value="changeBrowser"
/>
</div>

Expand All @@ -63,13 +64,13 @@
</template>

<script lang="ts" setup>
import { computed } from 'vue'
import { useAutStore } from '../store'
import { computed, ref } from 'vue'
import { useAutStore, useSpecStore } from '../store'
import Select from '@packages/frontend-shared/src/components/Select.vue'
import { gql } from '@urql/vue'
import { gql, useMutation } from '@urql/vue'
import IconCrosshairsGPS from '~icons/mdi/crosshairs-gps'
import Icon from '@packages/frontend-shared/src/components/Icon.vue'
import type { SpecRunnerHeaderFragment } from '../generated/graphql'
import { SpecRunnerHeaderFragment, SpecRunnerHeader_SetBrowserDocument, SpecRunnerHeader_BrowserFragment } from '../generated/graphql'
import SelectorPlayground from './selector-playground/SelectorPlayground.vue'
import { useSelectorPlaygroundStore } from '../store/selector-playground-store'
import type { EventManager } from './event-manager'
Expand All @@ -86,12 +87,28 @@ fragment SpecRunnerHeader on CurrentProject {
}
browsers {
id
name
displayName
...SpecRunnerHeader_Browser
}
}
`

gql`
fragment SpecRunnerHeader_Browser on Browser {
id
name
displayName
}
`

gql`
mutation SpecRunnerHeader_SetBrowser($browserId: ID!, $specPath: String!) {
launchpadSetBrowser(id: $browserId)
launchOpenProject(specPath: $specPath)
}
`

const setBrowser = useMutation(SpecRunnerHeader_SetBrowserDocument)

const props = defineProps<{
gql: SpecRunnerHeaderFragment
eventManager: EventManager
Expand All @@ -114,20 +131,21 @@ const togglePlayground = () => {
}
}

const browser = computed(() => {
if (!props.gql.currentBrowser) {
return
}
const specStore = useSpecStore()

// Have to spread gql props since binding it to v-model causes error when testing
const browser = ref({ ...props.gql.currentBrowser })
const browsers = computed(() => props.gql.browsers?.slice().map((browser) => ({ ...browser })) ?? [])

const dimensions = `${autStore.viewportDimensions.width}x${autStore.viewportDimensions.height}`
function changeBrowser (browser: SpecRunnerHeader_BrowserFragment) {
const activeSpec = specStore.activeSpec

return {
id: props.gql.currentBrowser.id,
name: `${props.gql.currentBrowser.displayName} ${dimensions}`,
if (props.gql.currentBrowser?.id === browser.id || !activeSpec) {
return
}
})

const browsers = computed(() => props.gql.browsers?.slice() ?? [])
setBrowser.executeMutation({ browserId: browser.id, specPath: activeSpec.absolute })
}

const autStore = useAutStore()

Expand Down
14 changes: 11 additions & 3 deletions packages/data-context/src/actions/ProjectActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export class ProjectActions {
}
}

async launchProject (testingType: TestingTypeEnum | null, options: LaunchOpts) {
async launchProject (testingType: TestingTypeEnum | null, options: LaunchOpts, specPath?: string | null) {
if (!this.ctx.currentProject) {
return null
}
Expand All @@ -230,6 +230,12 @@ export class ProjectActions {
return null
}

let activeSpec: FoundSpec | undefined

if (specPath) {
activeSpec = await this.ctx.project.getCurrentSpecByAbsolute(this.ctx.currentProject.projectRoot, specPath)
}

// Ensure that we have loaded browsers to choose from
if (this.ctx.appData.refreshingBrowsers) {
await this.ctx.appData.refreshingBrowsers
Expand All @@ -241,7 +247,9 @@ export class ProjectActions {
return null
}

const spec: Cypress.Spec = {
// launchProject expects a spec when opening browser for url navigation.
// We give it an empty spec if none is passed so as to land on home page
const emptySpec: Cypress.Spec = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we still need this? You could try removing it and see what happens - I don't know if this is necessary now we launch into unified (no need for a fake spec url to visit).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still need this. It could be removed but it would require refactoring a chunk of server code that is outside the scope of this PR.

name: '',
absolute: '',
relative: '',
Expand All @@ -250,7 +258,7 @@ export class ProjectActions {

this.ctx.appData.currentTestingType = testingType

return this.api.launchProject(browser, spec, options)
return this.api.launchProject(browser, activeSpec ?? emptySpec, options)
}

async launchProjectWithoutElectron () {
Expand Down
8 changes: 8 additions & 0 deletions packages/data-context/src/sources/ProjectDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ export class ProjectDataSource {
return specs.filter((spec) => spec.specType === specType)
}

async getCurrentSpecByAbsolute (projectRoot: string, absolute: string) {
// TODO: should cache current specs so we don't need to
// call findSpecs each time we ask for the current spec.
const specs = await this.findSpecs(projectRoot, null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just use this.ctx.currentProject.specs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can do!


return specs.find((x) => x.absolute === absolute)
}

async getCurrentSpecById (projectRoot: string, base64Id: string) {
// TODO: should cache current specs so we don't need to
// call findSpecs each time we ask for the current spec.
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql/schemas/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ type Mutation {
internal_clearProjectPreferencesCache(projectTitle: String!): Boolean

"""Launches project from open_project global singleton"""
launchOpenProject: Boolean
launchOpenProject(specPath: String): Boolean

"""Sets the active browser"""
launchpadSetBrowser(
Expand Down
5 changes: 4 additions & 1 deletion packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,11 @@ export const mutation = mutationType({

t.liveMutation('launchOpenProject', {
description: 'Launches project from open_project global singleton',
args: {
specPath: stringArg(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this nonNull(stringArg())?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, since this mutation is used in other places that don't require routing to a specific spec.

},
resolve: async (_, args, ctx) => {
await ctx.actions.project.launchProject(ctx.wizardData.chosenTestingType, {})
await ctx.actions.project.launchProject(ctx.wizardData.chosenTestingType, {}, args.specPath)
},
})

Expand Down
18 changes: 15 additions & 3 deletions packages/server/lib/project_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,20 @@ import { makeLegacyDataContext } from './makeDataContext'
const debug = Debug('cypress:server:project_utils')

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

const normalizeSpecUrl = (browserUrl: string, specUrl: string) => {
if (process.env.LAUNCHPAD) {
return browserUrl
}

const replacer = (match: string) => match.replace('//', '/')

return [
browserUrl,
'#/tests',
escapeFilenameInUrl(specUrl),
].join('/')
.replace(multipleForwardSlashesRe, replacer)
.replace(multipleForwardSlashesRe, multipleForwardSlashesReplacer)
}

const getPrefixedPathToSpec = ({
Expand Down Expand Up @@ -87,6 +86,19 @@ export const getSpecUrl = ({
specType ??= 'integration'
browserUrl ??= ''

// App routes to spec with convention {browserUrl}#/runner?file={relativeSpecPath}
if (process.env.LAUNCHPAD) {
if (!absoluteSpecPath) {
return browserUrl
}

const relativeSpecPath = path.relative(projectRoot, path.resolve(projectRoot, absoluteSpecPath))
.replace(backSlashesRe, '/')

return `${browserUrl}/#/runner?file=${relativeSpecPath}`
.replace(multipleForwardSlashesRe, multipleForwardSlashesReplacer)
}

debug('get spec url: %s for spec type %s', absoluteSpecPath, specType)

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