Skip to content

Commit

Permalink
feat: Re-introduce Run All specs for End to End under experimentalRun…
Browse files Browse the repository at this point in the history
…AllSpecs flag (#24745)

* feat: re-enable Run All Specs (#24683)

* fix: disable query in run-mode

* feat: Run-all-specs-ui (#24738)

* wip run-all-specs-ui

* added component tests for specs-list, inline-specs-list, and run-all-specs

* updated tests for specslist, inlinespeclist, and runAllSpecs

* simplify test

* make prop with default value optional

* enable run all specs

* use named slot for clarify

* use Record type

* remove un-necessary dynamic component

* use group and hover to inline css

* fix tests

* fix tests and use snapshot for style testing

Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com>

* fix typescript

* fix test

* build binary for run all specs

* fix: tweaks for Run All Specs (#24746)

* update ui [skip ci]

* change types to reflect run all specs is e2e only

* chore: separate run all specs components

* fix typescript error

* fix: run-all UI tweaks and keyboard nav (#24757)

Closes undefined

* fix UI test

* move listener and use i18n [skip ci] (#24761)

* fix: run-all-specs duplicate file-matching (#24763)

* chore: limit linting [skip ci]

* do not get separator until app is loaded

* fix test

Co-authored-by: Zachary Williams <ZachJW34@gmail.com>
Co-authored-by: amehta265 <65267668+amehta265@users.noreply.github.com>
  • Loading branch information
3 people committed Nov 22, 2022
1 parent b9d053e commit 4bbd78e
Show file tree
Hide file tree
Showing 48 changed files with 847 additions and 164 deletions.
10 changes: 5 additions & 5 deletions .circleci/config.yml
Expand Up @@ -27,7 +27,7 @@ mainBuildFilters: &mainBuildFilters
branches:
only:
- develop
- 'ryanm/fix/typescript-issue'
- 'feature/run-all-specs'

# usually we don't build Mac app - it takes a long time
# but sometimes we want to really confirm we are doing the right thing
Expand All @@ -36,7 +36,7 @@ macWorkflowFilters: &darwin-workflow-filters
when:
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ 'ryanm/fix/typescript-issue', << pipeline.git.branch >> ]
- equal: [ 'feature/run-all-specs', << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>
Expand All @@ -45,7 +45,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
when:
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ 'ryanm/fix/typescript-issue', << pipeline.git.branch >> ]
- equal: [ 'feature/run-all-specs', << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>
Expand All @@ -63,7 +63,7 @@ windowsWorkflowFilters: &windows-workflow-filters
when:
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ 'ryanm/fix/typescript-issue', << pipeline.git.branch >> ]
- equal: [ 'feature/run-all-specs', << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>
Expand Down Expand Up @@ -130,7 +130,7 @@ commands:
- run:
name: Check current branch to persist artifacts
command: |
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "ryanm/fix/typescript-issue" ]]; then
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "feature/run-all-specs" ]]; then
echo "Not uploading artifacts or posting install comment for this branch."
circleci-agent step halt
fi
Expand Down
20 changes: 14 additions & 6 deletions cli/types/cypress.d.ts
Expand Up @@ -660,7 +660,7 @@ declare namespace Cypress {
* Whether or not to persist the session across all specs in the run.
* @default {false}
*/
cacheAcrossSpecs?: boolean,
cacheAcrossSpecs?: boolean
/**
* Function to run immediately after the session is created and `setup` function runs or
* after a session is restored and the page is cleared. If this returns `false`, throws an
Expand Down Expand Up @@ -2983,7 +2983,7 @@ declare namespace Cypress {
* Override default config options for E2E Testing runner.
* @default {}
*/
e2e: Omit<CoreConfigOptions, 'indexHtmlFile'>
e2e: EndToEndConfigOptions

/**
* An array of objects defining the certificates
Expand All @@ -2998,6 +2998,14 @@ declare namespace Cypress {
indexHtmlFile: string
}

interface EndToEndConfigOptions extends Omit<CoreConfigOptions, 'indexHtmlFile'> {
/**
* Enables the "Run All Specs" UI feature, allowing the execution of multiple specs sequentially.
* @default false
*/
experimentalRunAllSpecs?: boolean
}

/**
* Options appended to config object on runtime.
*/
Expand Down Expand Up @@ -3121,7 +3129,7 @@ declare namespace Cypress {
type DevServerFn<ComponentDevServerOpts = any> = (cypressDevServerConfig: DevServerConfig, devServerConfig: ComponentDevServerOpts) => ResolvedDevServerConfig | Promise<ResolvedDevServerConfig>

type ConfigHandler<T> = T
| (() => T | Promise<T>)
| (() => T | Promise<T>)

type DevServerConfigOptions = {
bundler: 'webpack'
Expand All @@ -3132,9 +3140,9 @@ declare namespace Cypress {
framework: 'react' | 'vue' | 'svelte'
viteConfig?: ConfigHandler<Omit<Exclude<PickConfigOpt<'viteConfig'>, undefined>, 'base' | 'root'>>
} | {
bundler: 'webpack',
framework: 'angular',
webpackConfig?: ConfigHandler<PickConfigOpt<'webpackConfig'>>,
bundler: 'webpack'
framework: 'angular'
webpackConfig?: ConfigHandler<PickConfigOpt<'webpackConfig'>>
options?: {
projectConfig: AngularDevServerProjectConfig
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -42,7 +42,7 @@
"get-next-version": "node scripts/get-next-version.js",
"postinstall": "node ./scripts/run-postInstall.js",
"jscodeshift": "jscodeshift -t ./node_modules/js-codemod/transforms/arrow-function-arguments.js",
"lint": "lerna run lint --no-bail",
"lint": "lerna run lint --no-bail --concurrency 2",
"lint-changed": "lint-changed",
"prepare-release-artifacts": "node ./scripts/prepare-release-artifacts.js",
"npm-release": "node scripts/npm-release.js",
Expand Down
1 change: 1 addition & 0 deletions packages/app/cypress.config.ts
Expand Up @@ -24,6 +24,7 @@ export default defineConfig({
},
},
'e2e': {
experimentalRunAllSpecs: true,
experimentalStudio: true,
baseUrl: 'http://localhost:5555',
supportFile: 'cypress/e2e/support/e2eSupport.ts',
Expand Down
3 changes: 3 additions & 0 deletions packages/app/cypress/component/support/index.ts
Expand Up @@ -26,8 +26,10 @@ import { createPinia } from '../../../src/store'
import { setActivePinia } from 'pinia'
import type { Pinia } from 'pinia'
import 'cypress-real-events/support'
import 'cypress-plugin-tab'

import { installCustomPercyCommand } from '@packages/frontend-shared/cypress/support/customPercyCommand'
import { tabUntil } from '@packages/frontend-shared/cypress/support/tab-until'

let pinia: Pinia

Expand All @@ -50,3 +52,4 @@ registerMountFn({ plugins: [() => createRouter(), () => pinia] })
installCustomPercyCommand()

Cypress.on('uncaught:exception', (err) => !err.message.includes('ResizeObserver loop limit exceeded'))
Cypress.Commands.add('tabUntil', tabUntil)
116 changes: 116 additions & 0 deletions packages/app/cypress/e2e/run-all-specs.cy.ts
@@ -0,0 +1,116 @@
import { RUN_ALL_SPECS_KEY } from '@packages/types/src'

describe('run-all-specs', () => {
const ALL_SPECS = {
spec1: { relative: 'cypress/e2e/folder-a/spec-a.cy.js', name: 'runs folder-a/spec-a' },
spec2: { relative: 'cypress/e2e/folder-a/spec-b.cy.js', name: 'runs folder-a/spec-b' },
spec3: { relative: 'cypress/e2e/folder-b/spec-a.cy.js', name: 'runs folder-b/spec-a' },
spec4: { relative: 'cypress/e2e/folder-b/spec-b.cy.js', name: 'runs folder-b/spec-b' },
}

const clickRunAllSpecs = (directory: string) => {
const command = cy.get('[data-cy=spec-item-directory]').contains(directory)

return command.realHover().then(() => {
cy.get(`[data-cy="run-all-specs-for-${directory}"]`).click({ force: true })
})
}

it('can run all specs with filter and live-reloading', () => {
cy.scaffoldProject('run-all-specs')
cy.openProject('run-all-specs')
cy.startAppServer()
cy.visitApp()

// Spawns new browser so we need to stub this
cy.withCtx((ctx, { sinon }) => {
sinon.stub(ctx.actions.project, 'launchProject').resolves()
})

// Verify "Run All Specs" with sub-directory
const subDirectorySpecs = [ALL_SPECS.spec1, ALL_SPECS.spec2]

cy.get('[data-cy=sidebar-link-specs-page]').click()

clickRunAllSpecs('folder-a')

cy.waitForSpecToFinish({ passCount: 2 })

cy.withCtx((ctx, { specs }) => {
expect(ctx.project.runAllSpecs).to.include.members(specs.map((spec) => spec.relative))
}, { specs: subDirectorySpecs })

for (const spec of subDirectorySpecs) {
cy.get('.runnable-title').contains(spec.name)
}

// Verify "Run All Specs" with filter
const filteredSpecs = [ALL_SPECS.spec1, ALL_SPECS.spec3]

cy.get('[data-cy=sidebar-link-specs-page]').click()

cy.findByLabelText('Search specs').clear().type('spec-a')
cy.get('[data-cy=spec-list-file]').contains('spec-b').should('not.exist')

clickRunAllSpecs('cypress/e2e')

cy.waitForSpecToFinish({ passCount: 2 })

cy.withCtx((ctx, { specs }) => {
expect(ctx.project.runAllSpecs).to.include.members(specs.map((spec) => spec.relative))
}, { specs: filteredSpecs })

for (const spec of filteredSpecs) {
cy.get('.runnable-title').contains(spec.name)
}

// Verify "Run All Specs" with filter + folder
const filteredWithSubDirectorySpecs = [ALL_SPECS.spec1]

cy.get('[data-cy=sidebar-link-specs-page]').click()

cy.findByLabelText('Search specs').clear().type('spec-a')
cy.get('[data-cy=spec-list-file]').contains('spec-b').should('not.exist')

clickRunAllSpecs('folder-a')

cy.waitForSpecToFinish({ passCount: 1 })

cy.withCtx((ctx, { specs }) => {
expect(ctx.project.runAllSpecs).to.include.members(specs.map((spec) => spec.relative))
}, { specs: filteredWithSubDirectorySpecs })

for (const spec of filteredWithSubDirectorySpecs) {
cy.get('.runnable-title').contains(spec.name)
}

// Verify "Run All Specs" live-reload
cy.get('[data-cy=sidebar-link-specs-page]').click()
cy.findByLabelText('Search specs').clear()
cy.get('[data-cy=spec-list-file]').should('have.length', 4)

clickRunAllSpecs('cypress/e2e')

cy.withCtx((ctx, { specs, runAllSpecsKey }) => {
expect(ctx.actions.project.launchProject).to.have.been.calledWith('e2e', undefined, runAllSpecsKey)
expect(ctx.project.runAllSpecs).to.include.members(specs.map((spec) => spec.relative))
}, { specs: Object.values(ALL_SPECS), runAllSpecsKey: RUN_ALL_SPECS_KEY })

cy.waitForSpecToFinish({ passCount: 4 })

for (const spec of Object.values(ALL_SPECS)) {
cy.get('.runnable-title').contains(spec.name)
}

cy.withCtx(async (ctx, { spec }) => {
const originalContent = await ctx.actions.file.readFileInProject(spec.relative)
const newContent = originalContent.replace('expect(true)', 'expect(false)')

expect(newContent).not.eq(originalContent)

await ctx.actions.file.writeFileInProject(spec.relative, newContent)
}, { spec: ALL_SPECS.spec1 })

cy.waitForSpecToFinish({ passCount: 3, failCount: 1 })
})
})
69 changes: 69 additions & 0 deletions packages/app/src/composables/useRunAllSpecs.ts
@@ -0,0 +1,69 @@
import { RUN_ALL_SPECS_KEY } from '@packages/types/src'
import { gql, useMutation, useQuery } from '@urql/vue'
import { computed, ComputedRef } from 'vue'
import { useRouter } from 'vue-router'
import { RunAllSpecsDocument, RunAllSpecs_ConfigDocument } from '../generated/graphql'
import { getSeparator, SpecTreeNode, UseCollapsibleTreeNode } from '../specs/tree/useCollapsibleTree'

type ResolvedConfig = { value: any, from: 'string', field: string }[]

gql`
query RunAllSpecs_Config {
currentProject {
id
config
currentTestingType
}
}
`

gql`
mutation RunAllSpecs ($specPath: String!, $runAllSpecs: [String!]!) {
setRunAllSpecs(runAllSpecs: $runAllSpecs)
launchOpenProject(specPath: $specPath) {
id
}
}
`

const isRunMode = window.__CYPRESS_MODE__ === 'run' && window.top === window

export function useRunAllSpecs (list: ComputedRef<{tree: UseCollapsibleTreeNode<SpecTreeNode>[]}>) {
const separator = getSeparator()
const router = useRouter()
const query = useQuery({ query: RunAllSpecs_ConfigDocument, pause: isRunMode })
const setRunAllSpecsMutation = useMutation(RunAllSpecsDocument)

return {
runAllSpecs: async (runAllSpecs: string[]) => {
await setRunAllSpecsMutation.executeMutation({ runAllSpecs, specPath: RUN_ALL_SPECS_KEY })

// Won't execute unless we are testing since the browser gets killed. In testing,
// we can stub `launchProject` to verify the functionality is working
router.push({ path: '/specs/runner', query: { file: RUN_ALL_SPECS_KEY } })
},
isRunAllSpecsAllowed: computed(() => {
const isE2E = query.data.value?.currentProject?.currentTestingType === 'e2e'

const config: ResolvedConfig = query.data.value?.currentProject?.config || []
const hasExperiment = config.find(({ field, value }) => field === 'experimentalRunAllSpecs' && value === true)

return Boolean(isE2E && hasExperiment)
}),
directoryChildren: computed(() => {
return list.value.tree.reduce<Record<string, string[]>>((acc, node) => {
if (!node.isLeaf) {
acc[node.id] = []
} else {
Object.keys(acc).forEach((dir) => {
if (node.id.startsWith(dir) && node.id.replace(dir, '').startsWith(separator)) {
acc[dir].push(node.id)
}
})
}

return acc
}, {})
}),
}
}
6 changes: 5 additions & 1 deletion packages/app/src/runner/unifiedRunner.ts
Expand Up @@ -2,7 +2,7 @@ import { Ref, onMounted, ref, watch, watchEffect, onBeforeUnmount, readonly } fr
import { getAutIframeModel, UnifiedRunnerAPI } from '../runner'
import { useSpecStore } from '../store'
import { useSelectorPlaygroundStore } from '../store/selector-playground-store'
import type { SpecFile } from '@packages/types/src'
import { RUN_ALL_SPECS, RUN_ALL_SPECS_KEY, SpecFile } from '@packages/types/src'
import { useRoute } from 'vue-router'
import { getPathForPlatform } from '../paths'

Expand Down Expand Up @@ -35,6 +35,10 @@ export function useUnifiedRunner () {
return
}

if (queryFile === RUN_ALL_SPECS_KEY) {
return specStore.setActiveSpec(RUN_ALL_SPECS)
}

const activeSpecInSpecsList = specs.value.find((x) => x.relative === queryFile)

if (activeSpecInSpecsList && specStore.activeSpec?.relative !== activeSpecInSpecsList.relative) {
Expand Down
35 changes: 20 additions & 15 deletions packages/app/src/specs/DirectoryItem.vue
@@ -1,19 +1,24 @@
<template>
<div
:title="name"
class="flex text-sm py-4px items-center"
>
<i-cy-chevron-down-small_x16
class="mr-8px icon-dark-gray-700 group-hocus:(icon-dark-indigo-300) group-hover:children:(transition-all ease-in-out) "
:class="{'transform rotate-270': !expanded}"
/>
<i-cy-folder_x16 class="h-16px mr-8px w-16px group-hocus:icon-light-indigo-300 group-hocus:icon-dark-indigo-400" />
<HighlightedText
:text="name"
:indexes="indexes"
class="text-gray-400 group-focus:text-indigo-300"
highlight-classes="font-bold text-white"
/>
<div class="flex justify-between">
<div
:title="name"
class="flex text-sm py-4px items-center"
>
<i-cy-chevron-down-small_x16
class="mr-8px icon-dark-gray-700 group-hocus:(icon-dark-indigo-300) group-hover:children:(transition-all ease-in-out) "
:class="{'transform rotate-270': !expanded}"
/>
<i-cy-folder_x16 class="h-16px mr-8px w-16px group-hocus:icon-light-indigo-300 group-hocus:icon-dark-indigo-400" />
<HighlightedText
:text="name"
:indexes="indexes"
class="text-gray-400 group-focus:text-indigo-300"
highlight-classes="font-bold text-white"
/>
</div>
<div class="mr-16px">
<slot name="run-all-specs" />
</div>
</div>
</template>

Expand Down

5 comments on commit 4bbd78e

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 4bbd78e Nov 22, 2022

Choose a reason for hiding this comment

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

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/11.2.0/linux-x64/develop-4bbd78e22e99ae72e909a45c8ff5e8c3fd7d61ef/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 4bbd78e Nov 22, 2022

Choose a reason for hiding this comment

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

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/11.2.0/darwin-x64/develop-4bbd78e22e99ae72e909a45c8ff5e8c3fd7d61ef/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 4bbd78e Nov 22, 2022

Choose a reason for hiding this comment

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

Circle has built the darwin arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/11.2.0/darwin-arm64/develop-4bbd78e22e99ae72e909a45c8ff5e8c3fd7d61ef/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 4bbd78e Nov 22, 2022

Choose a reason for hiding this comment

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

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/11.2.0/win32-x64/develop-4bbd78e22e99ae72e909a45c8ff5e8c3fd7d61ef/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 4bbd78e Nov 22, 2022

Choose a reason for hiding this comment

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

Circle has built the linux arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/11.2.0/linux-arm64/develop-4bbd78e22e99ae72e909a45c8ff5e8c3fd7d61ef/cypress.tgz

Please sign in to comment.