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:
Zachary Williams
2022-03-10 22:54:35 -06:00
committed by GitHub
parent 037fd911ba
commit 89db0a942f
14 changed files with 119 additions and 7 deletions

View File

@@ -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)))
}
}

View File

@@ -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',

View File

@@ -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

View File

@@ -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"

View File

@@ -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',

View File

@@ -654,5 +654,8 @@
"invalidSelector": "Invalid",
"selectorMethodsLabel": "Selector Methods"
}
},
"warnings": {
"retry": "Try again"
}
}

View File

@@ -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)

View File

@@ -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!

View File

@@ -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 {}
},
})
},
})

View 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')
})
})

View File

@@ -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>

View File

@@ -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) {

View File

@@ -101,7 +101,7 @@ export class ProjectBase<TServer extends Server> extends EE {
report: false,
onFocusTests () {},
onError () {},
onWarning () {},
onWarning: this.ctx.onWarning,
...options,
}

View File

@@ -0,0 +1,6 @@
module.exports = {
e2e: {
supportFile: false,
baseUrl: 'http://localhost:9999',
},
}