Skip to content

Commit

Permalink
Merge branch '10.0-release' into chore/apply-ux-feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
marktnoonan committed Dec 21, 2021
2 parents 4eb06dc + b441950 commit 3adc825
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 39 deletions.
5 changes: 4 additions & 1 deletion packages/data-context/src/actions/AppActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ export class AppActions {
let browser

try {
browser = (await this.ctx._apis.appApi.ensureAndGetByNameOrPath(browserNameOrPath)) as FoundBrowser | undefined
await this.ctx.coreData.app.refreshingBrowsers
const foundBrowsers = this.ctx.coreData.app.browsers ? [...this.ctx.coreData.app.browsers] : undefined

browser = (await this.ctx._apis.appApi.ensureAndGetByNameOrPath(browserNameOrPath, false, foundBrowsers)) as FoundBrowser | undefined
} catch (err: unknown?) {
this.ctx.debug('Error getting browser by name or path (%s): %s', browserNameOrPath, err?.stack || err)
const message = err.details ? `${err.message}\n\n\`\`\`\n${err.details}\n\`\`\`` : err.message
Expand Down
21 changes: 16 additions & 5 deletions packages/frontend-shared/cypress/e2e/support/e2eSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,17 @@ export interface RemoteGraphQLInterceptPayload {

export type RemoteGraphQLInterceptor = (obj: RemoteGraphQLInterceptPayload) => ExecutionResult | Promise<ExecutionResult>

export interface SetFoundBrowsersOptions {
filter?(browser: Browser): boolean
export interface FindBrowsersOptions {
// Array of FoundBrowser objects that will be used as the mock output
browsers?: FoundBrowser[]
/**
* Function used to filter the standard set of mocked browsers. Ignored if browsers option is provided.
* Ex.
* cy.findBrowsers({
* filter: (browser) => browser.name === 'chrome' // Sets Chrome, Chrome Beta, Canary
* })
*/
filter?(browser: Browser): boolean
}

declare global {
Expand Down Expand Up @@ -100,7 +108,10 @@ declare global {
disableRemoteGraphQLFakes(): void
visitApp(href?: string): Chainable<AUTWindow>
visitLaunchpad(href?: string): Chainable<AUTWindow>
findBrowsers(options?: SetFoundBrowsersOptions): void
/**
* Mocks the system browser retrieval to return the desired browsers
*/
findBrowsers(options?: FindBrowsersOptions): void
}
}
}
Expand Down Expand Up @@ -253,7 +264,7 @@ function loginUser (userShape: Partial<AuthenticatedUserShape> = {}) {
})
}

function findBrowsers (options: SetFoundBrowsersOptions = {}) {
function findBrowsers (options: FindBrowsersOptions = {}) {
let filteredBrowsers = options.browsers

if (!filteredBrowsers) {
Expand Down Expand Up @@ -281,7 +292,7 @@ function findBrowsers (options: SetFoundBrowsersOptions = {}) {
}

cy.withCtx(async (ctx, o) => {
// @ts-ignore sinon is a global in the node process where this is executed
// @ts-ignore sinon is a global in the process where this is executed
sinon.stub(ctx._apis.appApi, 'getBrowsers').resolves(o.browsers)
}, { browsers: filteredBrowsers })
}
Expand Down
34 changes: 17 additions & 17 deletions packages/frontend-shared/src/components/Alert.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import { ref } from 'vue'

const messages = defaultMessages.components.alert

const alertBodySelector = '[data-testid=alert-body]'
const alertHeaderSelector = '[data-testid=alert-header]'
const alertBodySelector = '[data-cy=alert-body]'
const alertHeaderSelector = '[data-cy=alert-header]'

const suffixIconSelector = '[data-testid=alert-suffix-icon]'
const prefixIconSelector = '[data-testid=alert-prefix-icon]'
const suffixIconSelector = '[data-cy=alert-suffix-icon]'
const prefixIconSelector = '[data-cy=alert-prefix-icon]'

// This divider should eventually be tested inside of a visual regression test.
const dividerLineSelector = '[data-testid=alert-body-divider]'
const dividerLineSelector = '[data-cy=alert-body-divider]'
const dismissSelector = `[aria-label=${messages.dismissAriaLabel}]`

const alertTitle = faker.hacker.phrase()
Expand All @@ -31,8 +31,8 @@ const makeDismissibleProps = () => {
return { modelValue, methods }
}

const prefixIcon = () => <CoffeeIcon data-testid="coffee-icon"/>
const suffixIcon = () => <LoadingIcon data-testid="loading-icon" class="animate-spin"/>
const prefixIcon = () => <CoffeeIcon data-cy="coffee-icon"/>
const suffixIcon = () => <LoadingIcon data-cy="loading-icon" class="animate-spin"/>

describe('<Alert />', () => {
describe('classes', () => {
Expand All @@ -49,23 +49,23 @@ describe('<Alert />', () => {

it('renders the prefixIcon slot', () => {
cy.mount(() => <Alert v-slots={{ prefixIcon }} />)
.get('[data-testid=coffee-icon]').should('be.visible')
.get('[data-cy=coffee-icon]').should('be.visible')
})

it('renders the prefixIcon slot even when an icon is passed in', () => {
cy.mount(() => (<Alert
v-slots={{ prefixIcon }}
icon={() => <LoadingIcon data-testid="loading-icon" />}
icon={() => <LoadingIcon data-cy="loading-icon" />}
/>))
.get('[data-testid=coffee-icon]').should('be.visible')
.get('[data-testid=loading-icon]').should('not.exist')
.get('[data-cy=coffee-icon]').should('be.visible')
.get('[data-cy=loading-icon]').should('not.exist')
})
})

describe('suffix', () => {
it('renders the suffixIcon slot', () => {
cy.mount(() => <Alert title="Alert" v-slots={{ suffixIcon }} />)
.get('[data-testid=loading-icon]').should('be.visible')
.get('[data-cy=loading-icon]').should('be.visible')
})
})

Expand All @@ -74,12 +74,12 @@ describe('<Alert />', () => {
cy.mount(() => (
<div class="space-y-2 text-center p-4">
<Alert title="Alert">
<p data-testid="body-content">{ faker.lorem.paragraphs(5) }</p>
<p data-cy="body-content">{ faker.lorem.paragraphs(5) }</p>
</Alert>
</div>
))

cy.get('[data-testid=body-content]').should('be.visible')
cy.get('[data-cy=body-content]').should('be.visible')
})
})

Expand All @@ -90,19 +90,19 @@ describe('<Alert />', () => {
cy.mount(() => (
<div class="space-y-2 text-center p-4">
<Alert title="Alert" dismissible modelValue={modelValue.value} {...methods}>
<p data-testid="body-content">{ faker.lorem.paragraphs(5) }</p>
<p data-cy="body-content">{ faker.lorem.paragraphs(5) }</p>
</Alert>
</div>
))

cy.get('[data-testid=body-content]').should('be.visible')
cy.get('[data-cy=body-content]').should('be.visible')
})

it('cannot be collapsed', () => {
const { modelValue, methods } = makeDismissibleProps()

cy.mount(() => (<Alert title="Alert" dismissible modelValue={modelValue.value} {...methods}>
<p data-testid="body-content">{ faker.lorem.paragraphs(5) }</p>
<p data-cy="body-content">{ faker.lorem.paragraphs(5) }</p>
</Alert>))
.get(alertBodySelector).should('be.visible')
.get(alertHeaderSelector).click()
Expand Down
8 changes: 4 additions & 4 deletions packages/frontend-shared/src/components/Alert.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
>
<template #target="{ open }">
<div
data-testid="alert-header"
data-cy="alert-header"
class="grid grid-cols-1 group"
:class="{
'cursor-pointer': canCollapse,
Expand All @@ -27,7 +27,7 @@
:prefix-icon-class="open ? prefix?.classes + ' rotate-180' : prefix?.classes"
:suffix-icon-aria-label="props.dismissible ? t('components.alert.dismissAriaLabel') : ''"
:suffix-icon="props.dismissible ? DeleteIcon : null"
data-testid="alert"
data-cy="alert"
class="rounded min-w-200px p-16px"
@suffixIconClicked="$emit('update:modelValue', !modelValue)"
>
Expand All @@ -52,7 +52,7 @@
</AlertHeader>
<div
v-if="open"
data-testid="alert-body-divider"
data-cy="alert-body-divider"
class="mx-auto h-1px transform w-[calc(100%-32px)] translate-y-1px"
:class="[classes.dividerClass]"
/>
Expand All @@ -61,7 +61,7 @@
<div
v-if="$slots.default"
class="text-left p-16px"
data-testid="alert-body"
data-cy="alert-body"
>
<slot />
</div>
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend-shared/src/components/AlertHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<component
:is="prefixIcon"
v-if="prefixIcon"
data-testid="alert-prefix-icon"
data-cy="alert-prefix-icon"
class="h-16px w-16px icon-dark-current"
:class="prefixIconClass"
/>
Expand All @@ -27,7 +27,7 @@
>
<button
v-if="suffixIcon"
data-testid="alert-suffix-icon"
data-cy="alert-suffix-icon"
:aria-label="suffixIconAriaLabel"
class="rounded-full flex outline-none h-32px -top-16px -right-8px w-32px hocus:ring-current items-center justify-center absolute hocus:ring-1"
:class="suffixButtonClass"
Expand Down
181 changes: 181 additions & 0 deletions packages/launchpad/cypress/e2e/integration/choose-a-browser.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
describe('Choose a Browser Page', () => {
// Walks through setup pages to get to browser selection
const stepThroughConfigPages = () => {
cy.get('h1').should('contain', 'Configuration Files')
cy.contains('button', 'Continue').click()
cy.get('h1').should('contain', 'Initializing Config...')
cy.contains('button', 'Next Step').click()
}

beforeEach(() => {
cy.scaffoldProject('launchpad')
})

describe('System Browsers Detected', () => {
beforeEach(() => {
cy.findBrowsers({
filter: (browser) => {
return Cypress._.includes(['chrome', 'firefox', 'electron', 'edge'], browser.name) && browser.channel === 'stable'
},
})
})

it('preselects browser that is provided through the command line', () => {
cy.openProject('launchpad', ['--e2e', '--browser', 'edge'])

cy.visitLaunchpad()
stepThroughConfigPages()

cy.get('h1').should('contain', 'Choose a Browser')

cy.findByRole('radio', { name: 'Edge v8', checked: true })
})

it('shows warning when launched with --browser name that cannot be matched to found browsers', () => {
cy.openProject('launchpad', ['--e2e', '--browser', 'doesNotExist'])
cy.visitLaunchpad()

stepThroughConfigPages()

cy.get('h1').should('contain', 'Choose a Browser')
cy.get('[data-cy="alert-header"]').should('contain', 'Warning: Browser Not Found')
cy.get('[data-cy="alert-body"]')
.should('contain', 'The specified browser was not found on your system or is not supported by Cypress: doesNotExist')

cy.get('[data-cy="alert-body"] a').eq(1)
.should('have.attr', 'href')
.and('equal', 'https://on.cypress.io/troubleshooting-launching-browsers')

// Ensure warning can be dismissed
cy.get('[data-cy="alert-suffix-icon"]').click()
cy.get('[data-cy="alert-header"]').should('not.exist')
})

it('shows warning when launched with --browser path option that cannot be matched to found browsers', () => {
cy.openProject('launchpad', ['--e2e', '--browser', '/path/does/not/exist'])

cy.visitLaunchpad()

stepThroughConfigPages()

cy.get('h1').should('contain', 'Choose a Browser')

cy.get('[data-cy="alert-header"]').should('contain', 'Warning: Browser Not Found')
cy.get('[data-cy="alert-body"]')
.should('contain', 'We could not identify a known browser at the path you specified: /path/does/not/exist')
.should('contain', 'spawn /path/does/not/exist ENOENT')

cy.get('[data-cy="alert-body"] a')
.should('have.attr', 'href')
.and('equal', 'https://on.cypress.io/troubleshooting-launching-browsers')

// Ensure warning can be dismissed
cy.get('[data-cy="alert-suffix-icon"]').click()
cy.get('[data-cy="alert-header"]').should('not.exist')
})

it('shows installed browsers with their relevant properties', () => {
cy.openProject('launchpad', ['--e2e'])

cy.visitLaunchpad()

stepThroughConfigPages()

cy.get('h1').should('contain', 'Choose a Browser')

cy.findByRole('radio', { name: 'Chrome v1' })

cy.findByRole('radio', { name: 'Firefox v5' })

cy.findByRole('radio', { name: 'Electron v12' })

cy.findByRole('radio', { name: 'Edge v8' })
})

it('performs mutation to launch selected browser when launch button is pressed', () => {
cy.openProject('launchpad', ['--e2e'])

cy.visitLaunchpad()

stepThroughConfigPages()

cy.get('h1').should('contain', 'Choose a Browser')

cy.contains('button', 'Launch Chrome').as('launchButton')

// Stub out response to prevent browser launch but not break internals
cy.intercept('mutation-OpenBrowser_LaunchProject', {
body: {
data: {
launchOpenProject: true,
setProjectPreferences: {
currentProject: {
id: 'test-id',
title: 'launchpad',
__typename: 'CurrentProject',
},
__typename: 'Query',
},
},
},
}).as('launchProject')

cy.get('@launchButton').click()

cy.wait('@launchProject').then(({ request }) => {
expect(request?.body.variables.browserPath).to.contain('/test/chrome/path')
expect(request?.body.variables.testingType).to.eq('e2e')
})
})

it('performs mutation to change selected browser when browser item is clicked', () => {
cy.openProject('launchpad', ['--e2e'])

cy.visitLaunchpad()
stepThroughConfigPages()

cy.get('h1').should('contain', 'Choose a Browser')

cy.findByRole('radio', { name: 'Chrome v1', checked: true }).as('chromeItem')
cy.findByRole('radio', { name: 'Firefox v5', checked: false }).as('firefoxItem')

cy.contains('button', 'Launch Chrome').should('be.visible')

cy.intercept('mutation-OpenBrowserList_SetBrowser').as('setBrowser')

cy.get('@firefoxItem').then(($input) => {
cy.get(`label[for=${$input.attr('id')}]`).click().then(() => {
cy.wait('@setBrowser').its('request.body.variables.id').should('eq', $input.attr('id'))
})
})

cy.findByRole('radio', { name: 'Chrome v1', checked: false })
cy.findByRole('radio', { name: 'Firefox v5', checked: true })

cy.contains('button', 'Launch Firefox').should('be.visible')
})
})

describe('No System Browsers Detected', () => {
it('shows single electron browser option when no system browsers are detected and process is running in electron', () => {
cy.log('This test simulates the Electron browser injection performed by server.getBrowsers')
cy.findBrowsers({
filter: (browser) => {
return browser.name === 'electron'
},
})

cy.openProject('launchpad', ['--e2e'])

cy.visitLaunchpad()

stepThroughConfigPages()

cy.get('h1').should('contain', 'Choose a Browser')

cy.get('[data-cy="open-browser-list"]').children().should('have.length', 1)

cy.findByRole('radio', { name: 'Electron v12', checked: true })
})
})
})
Loading

0 comments on commit 3adc825

Please sign in to comment.