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
6 changes: 3 additions & 3 deletions cli/__snapshots__/cli_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ exports['shows help for open --foo 1'] = `
-c, --config <config> sets configuration values. separate multiple
values with a comma. overrides any value in
cypress.config.{ts|js}.
-C, --config-file <config-file> path to JSON file where configuration values
are set. defaults to
-C, --config-file <config-file> path to script file where configuration
values are set. defaults to
"cypress.config.{ts|js}". pass "false" to
disable.
-d, --detached [bool] runs Cypress application in detached mode
Expand Down Expand Up @@ -72,7 +72,7 @@ exports['shows help for run --foo 1'] = `
--ci-build-id <id> the unique identifier for a run on your CI provider. typically a "BUILD_ID" env var. this value is automatically detected for most CI providers
--component runs component tests
-c, --config <config> sets configuration values. separate multiple values with a comma. overrides any value in cypress.config.{ts|js}.
-C, --config-file <config-file> path to JSON file where configuration values are set. defaults to "cypress.config.{ts|js}". pass "false" to disable.
-C, --config-file <config-file> path to script file where configuration values are set. defaults to "cypress.config.{ts|js}". pass "false" to disable.
--e2e runs end to end tests
-e, --env <env> sets environment variables. separate multiple values with a comma. overrides any value in cypress.config.{ts|js} or cypress.env.json
--group <name> a named group for recorded runs in the Cypress Dashboard
Expand Down
81 changes: 63 additions & 18 deletions cli/lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const descriptions = {
ciBuildId: 'the unique identifier for a run on your CI provider. typically a "BUILD_ID" env var. this value is automatically detected for most CI providers',
component: 'runs component tests',
config: 'sets configuration values. separate multiple values with a comma. overrides any value in cypress.config.{ts|js}.',
configFile: 'path to JSON file where configuration values are set. defaults to "cypress.config.{ts|js}". pass "false" to disable.',
configFile: 'path to script file where configuration values are set. defaults to "cypress.config.{ts|js}". pass "false" to disable.',
detached: 'runs Cypress application in detached mode',
dev: 'runs cypress in development and bypasses binary check',
e2e: 'runs end to end tests',
Expand Down Expand Up @@ -270,14 +270,32 @@ const addCypressRunCommand = (program) => {
.option('--dev', text('dev'), coerceFalse)
}

const addCypressOpenCommand = (program) => {
return program
.command('open')
.usage('[options]')
.description('Opens Cypress in the interactive GUI.')
.option('-b, --browser <browser-path>', text('browser'))
.option('--component', text('component'))
.option('-c, --config <config>', text('config'))
.option('-C, --config-file <config-file>', text('configFile'))
.option('-d, --detached [bool]', text('detached'), coerceFalse)
.option('--e2e', text('e2e'))
.option('-e, --env <env>', text('env'))
.option('--global', text('global'))
.option('-p, --port <port>', text('port'))
.option('-P, --project <project-path>', text('project'))
.option('--dev', text('dev'), coerceFalse)
}

/**
* Casts known command line options for "cypress run" to their intended type.
* For example if the user passes "--port 5005" the ".port" property should be
* a number 5005 and not a string "5005".
*
* Returns a clone of the original object.
*/
const castCypressRunOptions = (opts) => {
const castCypressOptions = (opts) => {
// only properties that have type "string | false" in our TS definition
// require special handling, because CLI parsing takes care of purely
// boolean arguments
Expand Down Expand Up @@ -325,7 +343,48 @@ module.exports = {

debug('parsed options %o', options)

const casted = castCypressRunOptions(options)
const casted = castCypressOptions(options)

debug('casted options %o', casted)
resolve(casted)
})

debug('parsing args: %o', cliArgs)
program.parse(cliArgs)
})
},

/**
* Parses `cypress open` command line option array into an object
* with options that you can feed into cy.openModeSystemTest test calls
* @example
* const options = parseOpenCommand(['cypress', 'open', '--browser', 'chrome'])
* // options is {browser: 'chrome'}
*/
parseOpenCommand (args) {
return new Promise((resolve, reject) => {
if (!Array.isArray(args)) {
return reject(new Error('Expected array of arguments'))
}

// make a copy of the input arguments array
// and add placeholders where "node ..." would usually be
// also remove "cypress" keyword at the start if present
const cliArgs = args[0] === 'cypress' ? [...args.slice(1)] : [...args]

cliArgs.unshift(null, null)

debug('creating program parser')
const program = createProgram()

addCypressOpenCommand(program)
.action((...fnArgs) => {
debug('parsed Cypress open %o', fnArgs)
const options = parseVariableOpts(fnArgs, cliArgs)

debug('parsed options %o', options)

const casted = castCypressOptions(options)

debug('casted options %o', casted)
resolve(casted)
Expand Down Expand Up @@ -387,21 +446,7 @@ module.exports = {
showVersions(args)
})

program
.command('open')
.usage('[options]')
.description('Opens Cypress in the interactive GUI.')
.option('-b, --browser <browser-path>', text('browser'))
.option('--component', text('component'))
.option('-c, --config <config>', text('config'))
.option('-C, --config-file <config-file>', text('configFile'))
.option('-d, --detached [bool]', text('detached'), coerceFalse)
.option('--e2e', text('e2e'))
.option('-e, --env <env>', text('env'))
.option('--global', text('global'))
.option('-p, --port <port>', text('port'))
.option('-P, --project <project-path>', text('project'))
.option('--dev', text('dev'), coerceFalse)
addCypressOpenCommand(program)
.action((opts) => {
debug('opening Cypress')
require('./exec/open')
Expand Down
74 changes: 47 additions & 27 deletions cli/lib/exec/open.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,62 @@ const spawn = require('./spawn')
const verify = require('../tasks/verify')
const { processTestingType } = require('./shared')

module.exports = {
start (options = {}) {
if (!util.isInstalledGlobally() && !options.global && !options.project) {
options.project = process.cwd()
}
/**
* Maps options collected by the CLI
* and forms list of CLI arguments to the server.
*
* Note: there is lightweight validation, with errors
* thrown synchronously.
*
* @returns {string[]} list of CLI arguments
*/
const processOpenOptions = (options = {}) => {
if (!util.isInstalledGlobally() && !options.global && !options.project) {
options.project = process.cwd()
}

const args = []
const args = []

if (options.config) {
args.push('--config', options.config)
}
if (options.config) {
args.push('--config', options.config)
}

if (options.configFile !== undefined) {
args.push('--config-file', options.configFile)
}
if (options.configFile !== undefined) {
args.push('--config-file', options.configFile)
}

if (options.browser) {
args.push('--browser', options.browser)
}
if (options.browser) {
args.push('--browser', options.browser)
}

if (options.env) {
args.push('--env', options.env)
}
if (options.env) {
args.push('--env', options.env)
}

if (options.port) {
args.push('--port', options.port)
}
if (options.port) {
args.push('--port', options.port)
}

if (options.project) {
args.push('--project', options.project)
}
if (options.project) {
args.push('--project', options.project)
}

if (options.global) {
args.push('--global', options.global)
}

args.push(...processTestingType(options))
args.push(...processTestingType(options))

debug('opening from options %j', options)
debug('command line arguments %j', args)
debug('opening from options %j', options)
debug('command line arguments %j', args)

return args
}

module.exports = {
processOpenOptions,
start (options = {}) {
const args = processOpenOptions(options)

function open () {
return spawn.start(args, {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"codegen": "yarn gulp codegen",
"debug": "yarn gulp debug",
"precypress:open": "yarn ensure-deps",
"cypress:open": "cypress open --dev --global",
"cypress:open": "yarn gulp open --dev --global",
"precypress:open:debug": "yarn ensure-deps",
"cypress:open:debug": "node ./scripts/debug.js cypress:open",
"precypress:run": "yarn ensure-deps",
Expand Down Expand Up @@ -69,7 +69,7 @@
"type-check": "node scripts/type_check",
"verify:mocha:results": "node ./scripts/verify_mocha_results",
"prewatch": "yarn ensure-deps",
"watch": "lerna exec yarn watch --parallel --stream",
"watch": "yarn gulp dev:watch",
"prepare": "husky install"
},
"devDependencies": {
Expand Down
3 changes: 1 addition & 2 deletions packages/app/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,9 @@ export default defineConfig({
pluginsFile: 'cypress/e2e/plugins/index.ts',
supportFile: 'cypress/e2e/support/e2eSupport.ts',
async setupNodeEvents (on, config) {
const { monorepoPaths } = require('../../scripts/gulp/monorepoPaths')
const { e2ePluginSetup } = require('@packages/frontend-shared/cypress/e2e/e2ePluginSetup')

return await e2ePluginSetup(monorepoPaths.pkgApp, on, config)
return await e2ePluginSetup(on, config)
},
},
})
13 changes: 0 additions & 13 deletions packages/app/cypress/e2e/integration/files.spec.ts

This file was deleted.

50 changes: 16 additions & 34 deletions packages/app/cypress/e2e/integration/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,35 @@ import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json'
import type { Interception } from '@packages/net-stubbing/lib/external-types'
import type { FoundSpec } from '@packages/types/src'

describe('Index', () => {
describe('App: Index', () => {
beforeEach(() => {
cy.setupE2E('component-tests')
cy.initializeApp()
cy.scaffoldProject('non-existent-spec')
cy.openProject('non-existent-spec')
cy.withCtx(async (ctx, o) => {
await ctx.actions.file.removeFileInProject('cypress/integration/spec.js')
})

cy.startAppServer()
cy.visitApp()
})

context('with no specs', () => {
beforeEach(() => {
cy.visitApp()
cy.withCtx((ctx, o) => {
ctx.actions.file.removeFileInProject('cypress/integration/integration-spec.js')
ctx.actions.file.removeFileInProject('cypress/integration/1-getting-started')
ctx.actions.file.removeFileInProject('cypress/integration/2-advanced-examples')
})
})

it('shows "Create spec" title', () => {
// TODO: we need more e2e tests around this, but it requires changes to how we set up config in our
// gql mock, which would likely conflict with other ongoing changes.
// In the meantime, the Create Spec vs No Specs Found differences are covered in component tests,
// we just can't mock config values in GQL yet.
cy.visitApp()
cy.contains(defaultMessages.createSpec.page.defaultPatternNoSpecs.title).should('be.visible')
})
})

context('scaffold example specs', () => {
let createdSpecs: FoundSpec[]

beforeEach(() => {
cy.visitApp()
cy.withCtx((ctx) => {
ctx.actions.file.removeFileInProject('cypress/integration/integration-spec.js')
})
})

const assertSpecs = () => cy.wrap(createdSpecs).each((spec: FoundSpec) => cy.contains(spec.baseName).scrollIntoView().should('be.visible'))
const assertSpecs = (createdSpecs: FoundSpec[]) => cy.wrap(createdSpecs).each((spec: FoundSpec) => cy.contains(spec.baseName).scrollIntoView().should('be.visible'))

it('should generate example specs', () => {
cy.visitApp()
let createdSpecs: FoundSpec[]

cy.withCtx((ctx) => {
['cypress/integration/1-getting-started', 'cypress/integration/2-advanced-examples'].forEach((file) => ctx.actions.file.removeFileInProject(file))
})
cy.visitApp()

cy.intercept('mutation-ScaffoldGeneratorStepOne_scaffoldIntegration').as('scaffoldIntegration')
cy.contains(defaultMessages.createSpec.e2e.importFromScaffold.header).click()
Expand All @@ -55,17 +40,14 @@ describe('Index', () => {
expect(createdSpecs).lengthOf.above(0)

cy.contains(defaultMessages.createSpec.e2e.importFromScaffold.specsAddedHeader).should('be.visible')
assertSpecs()
assertSpecs(createdSpecs)
})

cy.contains(defaultMessages.createSpec.e2e.importFromScaffold.specsAddedButton).click()
})

// TODO(ZachW): Move test to test above once live spec refresh and e2e requery bug is fixed
it('should show generated example specs', () => {
cy.visitApp()

assertSpecs()
cy.visitApp().then(() => {
assertSpecs(createdSpecs)
})
})
})
})
8 changes: 6 additions & 2 deletions packages/app/cypress/e2e/integration/navigation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json'
import type { Interception } from '@packages/net-stubbing/lib/external-types'

describe('Navigation', () => {
before(() => {
cy.scaffoldProject('todos')
})

it('External links trigger mutation to open in a new browser', () => {
cy.setupE2E('component-tests')
cy.initializeApp()
cy.openProject('todos')
cy.startAppServer()
cy.visitApp()

cy.intercept('mutation-ExternalLink_OpenExternal', { 'data': { 'openExternal': true } }).as('OpenExternal')
Expand Down
Loading