Skip to content

Commit

Permalink
fixes #1042 enable plugins to return a promise and modify config
Browse files Browse the repository at this point in the history
  • Loading branch information
brian-mann committed Dec 11, 2017
1 parent 44e84d9 commit 0e07d80
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 47 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"plugin:cypress-dev/general"
],
"env": {
"es6": true,
"node": true
}
}
35 changes: 35 additions & 0 deletions packages/server/__snapshots__/plugins_spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,38 @@ Started video recording: /foo/bar/.projects/working-preprocessor/cypress/videos/
(All Done)
`
exports['e2e plugins can modify config from plugins 1'] = `
Started video recording: /foo/bar/.projects/plugin-config/cypress/videos/abc123.mp4
(Tests Starting)
✓ overrides config
✓ overrides env
2 passing
(Tests Finished)
- Tests: 2
- Passes: 2
- Failures: 0
- Pending: 0
- Duration: 10 seconds
- Screenshots: 0
- Video Recorded: true
- Cypress Version: 1.2.3
(Video)
- Started processing: Compressing to 20 CRF
- Finished processing: /foo/bar/.projects/plugin-config/cypress/videos/abc123.mp4 (0 seconds)
(All Done)
`
26 changes: 23 additions & 3 deletions packages/server/lib/config.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ _ = require("lodash")
path = require("path")
Promise = require("bluebird")
fs = require("fs-extra")
deepDiff = require("return-deep-diff")
errors = require("./errors")
scaffold = require("./scaffold")
errors = require("./errors")
Expand Down Expand Up @@ -236,12 +237,31 @@ module.exports = {

return obj

resolvePluginValues: (cfg, overrides = {}) ->
## TODO: for each override go through
updateWithPluginValues: (cfg, overrides = {}) ->
## diff the overrides with cfg
## including nested objects (env)
diffs = deepDiff(cfg, overrides, true)

setResolvedOn = (resolvedObj, obj) ->
_.each obj, (val, key) ->
if _.isObject(val)
## recurse setting overrides
## inside of this nested objected
setResolvedOn(resolvedObj[key], val)
else
## override the resolved value
resolvedObj[key] = {
value: val
from: "plugin"
}

## for each override go through
## and change the resolved values of cfg
## to point to the plugin
setResolvedOn(cfg.resolved, diffs)

_.defaultsDeep(overrides, cfg)
## merge cfg into overrides
_.defaultsDeep(diffs, cfg)

resolveConfigValues: (config, defaults, resolved = {}) ->
## pick out only the keys found in configKeys
Expand Down
4 changes: 2 additions & 2 deletions packages/server/lib/plugins/index.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ module.exports = {

ipc.send("load", config)

ipc.on "loaded", (config, registrations) ->
ipc.on "loaded", (newCfg, registrations) ->
_.each registrations, (registration) ->
log("register plugins process event", registration.event, "with id", registration.callbackId)
register registration.event, (args...) ->
Expand All @@ -56,7 +56,7 @@ module.exports = {
}
ipc.send("execute", registration.event, ids, args)

resolve(config)
resolve(newCfg)

ipc.on "load:error", (type, args...) ->
reject(errors.get(type, args...))
Expand Down
83 changes: 42 additions & 41 deletions packages/server/lib/project.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,22 @@ class Project extends EE
@memoryCheck = setInterval(logMemory, 1000)

@getConfig(options)
.then (cfg) =>
.tap (cfg) =>
process.chdir(@projectRoot)

## TODO: move plugins config up here
## TODO: we currently always scaffold the plugins file
## even when headlessly or else it will cause an error when
## we try to load it and it's not there. We must do this here
## else initialing the plugins will instantly fail.
if cfg.pluginsFile
scaffold.plugins(path.dirname(cfg.pluginsFile), cfg)
.then (cfg) =>
@_initPlugins(cfg, options)
.then (modifiedCfg) ->
## TODO merge in modifiedCfg into cfg
cfg = config.resolvePluginValues(cfg, modifiedCfg)
# if _.isObject(modifiedCfg)
# _.extend(cfg, modifiedCfg)

debug("plugin config yielded", modifiedCfg)

cfg = config.updateWithPluginValues(cfg, modifiedCfg)

return cfg
.then (cfg) =>
@server.open(cfg, @)
Expand Down Expand Up @@ -118,14 +121,18 @@ class Project extends EE
@watchSupportFile(cfg)
@watchPluginsFile(cfg, options)
)
.then =>
@_initPlugins(cfg, options)

# return our project instance
.return(@)

_initPlugins: (config, options) ->
plugins.init(config, {
_initPlugins: (cfg, options) ->
## only init plugins with the
## whitelisted config values to
## prevent tampering with the
## internals and breaking cypress
cfg = config.whitelist(cfg)

plugins.init(cfg, {
onError: (err) ->
browsers.close()
options.onError(err)
Expand Down Expand Up @@ -161,43 +168,43 @@ class Project extends EE
.then ->
process.chdir(localCwd)

watchSupportFile: (config) ->
if supportFile = config.supportFile
watchSupportFile: (cfg) ->
if supportFile = cfg.supportFile
fs.pathExists(supportFile)
.then (found) =>
if not found
errors.throw("SUPPORT_FILE_NOT_FOUND", supportFile)

relativePath = path.relative(config.projectRoot, config.supportFile)
if config.watchForFileChanges isnt false
relativePath = path.relative(cfg.projectRoot, cfg.supportFile)
if cfg.watchForFileChanges isnt false
options = {
onChange: _.bind(@server.onTestFileChange, @server, relativePath)
}
preprocessor.getFile(relativePath, config, options)
preprocessor.getFile(relativePath, cfg, options)
## ignore errors b/c we're just setting up the watching. errors
## are handled by the spec controller
.catch ->
else
Promise.resolve()

watchPluginsFile: (config, options) ->
debug("attempt watch plugins file: #{config.pluginsFile}")
if not config.pluginsFile
watchPluginsFile: (cfg, options) ->
debug("attempt watch plugins file: #{cfg.pluginsFile}")
if not cfg.pluginsFile
return Promise.resolve()

fs.pathExists(config.pluginsFile)
fs.pathExists(cfg.pluginsFile)
.then (found) =>
debug("plugins file found? #{found}")
## ignore if not found. plugins#init will throw the right error
return if not found

debug("watch plugins file")
@watchers.watch(config.pluginsFile, {
@watchers.watch(cfg.pluginsFile, {
onChange: =>
## TODO: completely re-open project instead?
debug("plugins file changed")
## re-init plugins after a change
@_initPlugins(config, options)
@_initPlugins(cfg, options)
.catch (err) ->
options.onError(err)
})
Expand All @@ -221,21 +228,21 @@ class Project extends EE

@watchers.watch(settings.pathToCypressJson(@projectRoot), obj)

watchSettingsAndStartWebsockets: (options = {}, config = {}) ->
watchSettingsAndStartWebsockets: (options = {}, cfg = {}) ->
@watchSettings(options.onSettingsChanged)

## if we've passed down reporter
## then record these via mocha reporter
if config.report
if not Reporter.isValidReporterName(config.reporter, config.projectRoot)
paths = Reporter.getSearchPathsForReporter(config.reporter, config.projectRoot)
errors.throw("INVALID_REPORTER_NAME", config.reporter, paths)
if cfg.report
if not Reporter.isValidReporterName(cfg.reporter, cfg.projectRoot)
paths = Reporter.getSearchPathsForReporter(cfg.reporter, cfg.projectRoot)
errors.throw("INVALID_REPORTER_NAME", cfg.reporter, paths)

reporter = Reporter.create(config.reporter, config.reporterOptions, config.projectRoot)
reporter = Reporter.create(cfg.reporter, cfg.reporterOptions, cfg.projectRoot)

@automation = Automation.create(config.namespace, config.socketIoCookie, config.screenshotsFolder)
@automation = Automation.create(cfg.namespace, cfg.socketIoCookie, cfg.screenshotsFolder)

@server.startWebsockets(@automation, config, {
@server.startWebsockets(@automation, cfg, {
onReloadBrowser: options.onReloadBrowser

onFocusTests: options.onFocusTests
Expand Down Expand Up @@ -377,7 +384,7 @@ class Project extends EE

[browserUrl, "#/tests", specUrl].join("/").replace(multipleForwardSlashesRe, replacer)

scaffold: (config) ->
scaffold: (cfg) ->
debug("scaffolding project %s", @projectRoot)

scaffolds = []
Expand All @@ -392,19 +399,13 @@ class Project extends EE
##
## ensure support dir is created
## and example support file if dir doesnt exist
push(scaffold.support(config.supportFolder, config))

## TODO: we currently always scaffold the plugins file
## even when headlessly or else it will cause an error when
## we try to load it and it's not there
if config.pluginsFile
push(scaffold.plugins(path.dirname(config.pluginsFile), config))
push(scaffold.support(cfg.supportFolder, cfg))

## if we're in headed mode add these other scaffolding
## tasks
if not config.isTextTerminal
push(scaffold.integration(config.integrationFolder, config))
push(scaffold.fixture(config.fixturesFolder, config))
if not cfg.isTextTerminal
push(scaffold.integration(cfg.integrationFolder, cfg))
push(scaffold.fixture(cfg.fixturesFolder, cfg))

Promise.all(scaffolds)

Expand Down
1 change: 1 addition & 0 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"randomstring": "^1.1.5",
"request": "2.79.0",
"request-promise": "4.1.1",
"return-deep-diff": "^0.2.9",
"sanitize-filename": "^1.6.1",
"semver": "^5.3.0",
"send": "^0.14.1",
Expand Down
11 changes: 11 additions & 0 deletions packages/server/test/e2e/plugins_spec.coffee
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
e2e = require("../support/helpers/e2e")
Fixtures = require("../support/helpers/fixtures")

pluginConfig = Fixtures.projectPath("plugin-config")
workingPreprocessor = Fixtures.projectPath("working-preprocessor")
pluginsAsyncError = Fixtures.projectPath("plugins-async-error")

Expand All @@ -22,3 +23,13 @@ describe "e2e plugins", ->
snapshot: true
expectedExitCode: 1
})

it "can modify config from plugins", ->
e2e.exec(@, {
spec: "app_spec.coffee"
env: "foo=foo,bar=bar"
config: "pageLoadTimeout=10000"
project: pluginConfig
snapshot: true
expectedExitCode: 0
})
35 changes: 35 additions & 0 deletions packages/server/test/integration/cypress_spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ describe "lib/cypress", ->
@todosPath = Fixtures.projectPath("todos")
@pristinePath = Fixtures.projectPath("pristine")
@noScaffolding = Fixtures.projectPath("no-scaffolding")
@pluginConfig = Fixtures.projectPath("plugin-config")
@idsPath = Fixtures.projectPath("ids")

## force cypress to call directly into main without
Expand Down Expand Up @@ -630,6 +631,40 @@ describe "lib/cypress", ->

@expectExitWith(0)

it "can override values in plugins", ->
cypress.start([
"--run-project=#{@pluginConfig}", "--config=requestTimeout=1234,videoCompression=false"
"--env=foo=foo,bar=bar"
])
.then =>
cfg = openProject.getProject().cfg

expect(cfg.videoCompression).to.eq(20)
expect(cfg.defaultCommandTimeout).to.eq(500)
expect(cfg.env).to.deep.eq({
foo: "bar"
bar: "bar"
})

expect(cfg.resolved.videoCompression).to.deep.eq({
value: 20
from: "plugin"
})
expect(cfg.resolved.requestTimeout).to.deep.eq({
value: 1234
from: "cli"
})
expect(cfg.resolved.env.foo).to.deep.eq({
value: "bar"
from: "plugin"
})
expect(cfg.resolved.env.bar).to.deep.eq({
value: "bar"
from: "cli"
})

@expectExitWith(0)

describe "--port", ->
beforeEach ->
headless.listenForProjectEnd.resolves({failures: 0})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
it "overrides config", ->
## overrides come from plugins
expect(Cypress.config("defaultCommandTimeout")).to.eq(500)
expect(Cypress.config("videoCompression")).to.eq(20)

## overrides come from CLI
expect(Cypress.config("pageLoadTimeout")).to.eq(10000)

it "overrides env", ->
## overrides come from plugins
expect(Cypress.env("foo")).to.eq("bar")

## overrides come from CLI
expect(Cypress.env("bar")).to.eq("bar")
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = (on, config) => {
return new Promise((resolve) => {
setTimeout(resolve, 100)
})
.then(() => {
config.defaultCommandTimeout = 500
config.videoCompression = 20
config.env.foo = 'bar'

return config
})
}
Loading

0 comments on commit 0e07d80

Please sign in to comment.