mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-26 10:59:23 -06:00
fix: show base-url warning in launchpad (#20490)
Co-authored-by: ElevateBart <ledouxb@gmail.com> Co-authored-by: Barthélémy Ledoux <bart@cypress.io>
This commit is contained in:
@@ -10,6 +10,7 @@ import type { DataContext } from '..'
|
||||
import { codeGenerator, SpecOptions } from '../codegen'
|
||||
import templates from '../codegen/templates'
|
||||
import { insertValuesInConfigFile } from '../util'
|
||||
import { getError } from '@packages/errors'
|
||||
|
||||
export interface ProjectApiShape {
|
||||
/**
|
||||
@@ -34,6 +35,7 @@ export interface ProjectApiShape {
|
||||
getDevServer (): {
|
||||
updateSpecs: (specs: FoundSpec[]) => void
|
||||
}
|
||||
isListening: (url: string) => Promise<void>
|
||||
}
|
||||
|
||||
type SetSpecsFoundBySpecPattern = {
|
||||
@@ -511,4 +513,20 @@ export class ProjectActions {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async pingBaseUrl () {
|
||||
const baseUrl = (await this.ctx.project.getConfig())?.baseUrl
|
||||
|
||||
// Should never happen
|
||||
if (!baseUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
this.ctx.update((d) => {
|
||||
d.warnings = d.warnings.filter((w) => w.cypressError.type !== 'CANNOT_CONNECT_BASE_URL_WARNING')
|
||||
})
|
||||
|
||||
return this.api.isListening(baseUrl)
|
||||
.catch(() => this.ctx.onWarning(getError('CANNOT_CONNECT_BASE_URL_WARNING', baseUrl)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ export const e2eProjectDirs = [
|
||||
'busted-support-file',
|
||||
'chrome-browser-preferences',
|
||||
'component-tests',
|
||||
'config-with-base-url-warning',
|
||||
'config-with-custom-file-js',
|
||||
'config-with-custom-file-ts',
|
||||
'config-with-import-error',
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 2.07143L12.2929 1.36432C12.5789 1.07832 13.009 0.992768 13.3827 1.14755C13.7564 1.30233 14 1.66697 14 2.07143H13ZM13 4.64286H14C14 5.19514 13.5523 5.64286 13 5.64286V4.64286ZM10.4286 4.64286V5.64286C10.0241 5.64286 9.65947 5.39922 9.50469 5.02554C9.34991 4.65187 9.43547 4.22175 9.72146 3.93575L10.4286 4.64286ZM11.5998 8.96396C11.817 8.45615 12.4047 8.22053 12.9125 8.43768C13.4203 8.65483 13.6559 9.24253 13.4387 9.75033L11.5998 8.96396ZM14 2.07143V4.64286H12V2.07143H14ZM7 2C4.23858 2 2 4.23858 2 7H0C0 3.13401 3.13401 0 7 0V2ZM11.5998 5.03604C10.8357 3.24906 9.06255 2 7 2V0C9.89132 0 12.371 1.75275 13.4387 4.24967L11.5998 5.03604ZM13 5.64286H12.5193V3.64286H13V5.64286ZM12.5193 5.64286H10.4286V3.64286H12.5193V5.64286ZM2 7C2 8.38099 2.55855 9.62962 3.46447 10.5355L2.05025 11.9497C0.784597 10.6841 0 8.93272 0 7H2ZM3.46447 10.5355C4.37038 11.4415 5.61901 12 7 12V14C5.06728 14 3.31591 13.2154 2.05025 11.9497L3.46447 10.5355ZM7 12C9.06255 12 10.8357 10.7509 11.5998 8.96396L13.4387 9.75033C12.371 12.2472 9.89132 14 7 14V12ZM9.72146 3.93575L12.2929 1.36432L13.7071 2.77854L11.1357 5.34996L9.72146 3.93575Z" fill="#1B1E2E" class="icon-dark"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -4,6 +4,7 @@ import faker from 'faker'
|
||||
import Alert from './Alert.vue'
|
||||
import { defaultMessages } from '../locales/i18n'
|
||||
import { ref } from 'vue'
|
||||
import ErrorOutlineIcon from '~icons/cy/status-errored-outline_x16.svg'
|
||||
|
||||
const messages = defaultMessages.components.alert
|
||||
|
||||
@@ -240,7 +241,12 @@ describe('playground', () => {
|
||||
<button class="bg-white rounded ml-2 px-2">Focusable</button>
|
||||
</Alert>
|
||||
<Alert status="info" collapsible title="An info alert">Just letting you know what's up.</Alert>
|
||||
<Alert status="warning">Nothing good is happening here!</Alert>
|
||||
<Alert
|
||||
status="warning"
|
||||
icon={ErrorOutlineIcon}
|
||||
icon-classes="icon-dark-orange-400 w-16px h-16px"
|
||||
>
|
||||
Nothing good is happening here!</Alert>
|
||||
<Alert icon={CoffeeIcon}
|
||||
dismissible
|
||||
status="error"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
lazy
|
||||
:initially-open="initiallyOpen"
|
||||
:disable="!canCollapse"
|
||||
class="overflow-hidden rounded-t rounded-b outline-none group"
|
||||
class="rounded-t rounded-b outline-none overflow-hidden group"
|
||||
:class="[
|
||||
classes.headerClass,
|
||||
{[`hocus-default border-1 border-transparent rounded ${classes.ring}`]: canCollapse}]"
|
||||
@@ -137,7 +137,7 @@ const alertStyles: Record<AlertStatus, AlertClasses> = {
|
||||
ring: 'hocus:(ring-info-200 border-info-300)',
|
||||
},
|
||||
warning: {
|
||||
headerClass: 'text-warning-500 bg-warning-100',
|
||||
headerClass: 'text-warning-600 bg-warning-100',
|
||||
suffixIconClass: 'icon-dark-warning-500',
|
||||
suffixButtonClass: 'text-warning-500',
|
||||
bodyClass: 'bg-warning-50',
|
||||
|
||||
@@ -654,5 +654,8 @@
|
||||
"invalidSelector": "Invalid",
|
||||
"selectorMethodsLabel": "Selector Methods"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
"retry": "Try again"
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
:dismissible="dismissible"
|
||||
status="warning"
|
||||
data-cy="warning-alert"
|
||||
header-class="text-warning-600"
|
||||
icon-classes="icon-dark-orange-400 h-16px w-16px"
|
||||
:title="title"
|
||||
:icon="ErrorOutlineIcon"
|
||||
>
|
||||
@@ -12,6 +12,17 @@
|
||||
ref="markdownTarget"
|
||||
v-html="markdown"
|
||||
/>
|
||||
<Button
|
||||
v-if="retryable"
|
||||
class="bg-indigo-500 text-white"
|
||||
size="md"
|
||||
variant="outline"
|
||||
:prefix-icon="RefreshIcon"
|
||||
prefix-icon-class="icon-dark-white"
|
||||
@click="emits('retry')"
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
</Alert>
|
||||
</template>
|
||||
|
||||
@@ -19,11 +30,14 @@
|
||||
import ErrorOutlineIcon from '~icons/cy/status-errored-outline_x16.svg'
|
||||
import { useMarkdown } from '@packages/frontend-shared/src/composables/useMarkdown'
|
||||
import Alert from '@cy/components/Alert.vue'
|
||||
import Button from '@cy/components/Button.vue'
|
||||
import RefreshIcon from '~icons/cy/refresh_x16'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useVModels } from '@vueuse/core'
|
||||
|
||||
const emits = defineEmits<{
|
||||
(eventName: 'update:modelValue', value: boolean): void
|
||||
(eventName: 'retry'): void
|
||||
}>()
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
@@ -32,10 +46,12 @@ const props = withDefaults(defineProps<{
|
||||
details?: string | null
|
||||
modelValue?: boolean
|
||||
dismissible?: boolean
|
||||
retryable?: boolean
|
||||
}>(), {
|
||||
modelValue: true,
|
||||
details: undefined,
|
||||
dismissible: true,
|
||||
retryable: false,
|
||||
})
|
||||
|
||||
const { modelValue: show } = useVModels(props, emits)
|
||||
|
||||
@@ -996,6 +996,9 @@ type Mutation {
|
||||
"""Open a path in the local file explorer"""
|
||||
openInFinder(path: String!): Boolean
|
||||
|
||||
"""Ping configured Base URL"""
|
||||
pingBaseUrl: Query
|
||||
|
||||
"""show the launchpad windows"""
|
||||
reconfigureProject: Boolean!
|
||||
|
||||
|
||||
@@ -649,5 +649,15 @@ export const mutation = mutationType({
|
||||
return {}
|
||||
},
|
||||
})
|
||||
|
||||
t.field('pingBaseUrl', {
|
||||
type: Query,
|
||||
description: 'Ping configured Base URL',
|
||||
resolve: async (source, args, ctx) => {
|
||||
await ctx.actions.project.pingBaseUrl()
|
||||
|
||||
return {}
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
17
packages/launchpad/cypress/e2e/base-url.cy.ts
Normal file
17
packages/launchpad/cypress/e2e/base-url.cy.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
describe('baseUrl', () => {
|
||||
it('should show baseUrl warning if Cypress cannot connect to provided baseUrl', () => {
|
||||
cy.scaffoldProject('config-with-base-url-warning')
|
||||
cy.openProject('config-with-base-url-warning')
|
||||
cy.visitLaunchpad()
|
||||
|
||||
cy.get('[data-cy-testingtype="e2e"]').click()
|
||||
cy.get('[data-cy="alert"]').contains('Warning: Cannot Connect Base Url Warning')
|
||||
|
||||
cy.withCtx((ctx) => {
|
||||
ctx._apis.projectApi.isListening = sinon.stub().resolves(null)
|
||||
})
|
||||
|
||||
cy.contains('button', 'Retry').click()
|
||||
cy.get('[data-cy="alert"]').should('not.exist')
|
||||
})
|
||||
})
|
||||
@@ -4,16 +4,19 @@
|
||||
:key="warning.key"
|
||||
:title="warning.title ?? 'Warning'"
|
||||
:message="warning.errorMessage"
|
||||
:retryable="warning.retryable"
|
||||
dismissible
|
||||
@update:modelValue="dismiss(warning.key)"
|
||||
@retry="retry(warning)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { gql } from '@urql/core'
|
||||
import type { WarningListFragment } from '../generated/graphql'
|
||||
import { ErrorTypeEnum, WarningListFragment, WarningList_PingBaseUrlDocument } from '../generated/graphql'
|
||||
import Warning from '@packages/frontend-shared/src/warning/Warning.vue'
|
||||
import { useMutation } from '@urql/vue'
|
||||
|
||||
gql`
|
||||
fragment WarningContent on ErrorWrapper {
|
||||
@@ -40,14 +43,34 @@ mutation WarningList_removeWarning {
|
||||
}
|
||||
`
|
||||
|
||||
gql`
|
||||
mutation WarningList_pingBaseUrl {
|
||||
pingBaseUrl {
|
||||
warnings {
|
||||
...WarningContent
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const props = defineProps<{
|
||||
gql: WarningListFragment
|
||||
}>()
|
||||
|
||||
const pingBaseUrlMutation = useMutation(WarningList_PingBaseUrlDocument)
|
||||
|
||||
type Retryables = Partial<Record<ErrorTypeEnum, () => void>>
|
||||
|
||||
const retryables: Retryables = {
|
||||
'CANNOT_CONNECT_BASE_URL_WARNING': () => {
|
||||
pingBaseUrlMutation.executeMutation({})
|
||||
},
|
||||
}
|
||||
|
||||
const dismissed = ref({})
|
||||
const warnings = computed(() => {
|
||||
return props.gql.warnings
|
||||
.map((w) => ({ ...w, key: `${w.errorType}${w.errorMessage}` }))
|
||||
.map((w) => ({ ...w, key: `${w.errorType}${w.errorMessage}`, ...(w.errorType in retryables ? { retryable: true } : {}) }))
|
||||
.filter((warning) => {
|
||||
const hasBeenDismissed = dismissed.value[warning.key]
|
||||
|
||||
@@ -60,4 +83,8 @@ const dismiss = (key) => {
|
||||
// However, we still intend to keep the "warnings" dismissal so that the client updates immediately before the server responds.
|
||||
dismissed.value[key] = true
|
||||
}
|
||||
|
||||
const retry = (warning: typeof warnings.value[number]) => {
|
||||
retryables[warning.errorType]?.()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { DataContext, getCtx, clearCtx, setCtx } from '@packages/data-context'
|
||||
import electron, { OpenDialogOptions, SaveDialogOptions, BrowserWindow } from 'electron'
|
||||
import pkg from '@packages/root'
|
||||
import configUtils from '@packages/config'
|
||||
import { isListening } from './util/ensure-url'
|
||||
|
||||
import type {
|
||||
AllModeOptions,
|
||||
@@ -152,6 +153,7 @@ export function makeDataContext (options: MakeDataContextOptions): DataContext {
|
||||
getDevServer () {
|
||||
return devServer
|
||||
},
|
||||
isListening,
|
||||
},
|
||||
electronApi: {
|
||||
openExternal (url: string) {
|
||||
|
||||
@@ -101,7 +101,7 @@ export class ProjectBase<TServer extends Server> extends EE {
|
||||
report: false,
|
||||
onFocusTests () {},
|
||||
onError () {},
|
||||
onWarning () {},
|
||||
onWarning: this.ctx.onWarning,
|
||||
...options,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
e2e: {
|
||||
supportFile: false,
|
||||
baseUrl: 'http://localhost:9999',
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user