mirror of
https://github.com/atom/atom.git
synced 2026-04-06 03:02:13 -04:00
Merge pull request #5404 from atom/mb-integration-test
Add integration test to cover browser-side code
This commit is contained in:
@@ -222,7 +222,7 @@ module.exports = (grunt) ->
|
||||
grunt.registerTask('test', ['shell:kill-atom', 'run-specs'])
|
||||
grunt.registerTask('docs', ['markdown:guides', 'build-docs'])
|
||||
|
||||
ciTasks = ['output-disk-space', 'download-atom-shell', 'build']
|
||||
ciTasks = ['output-disk-space', 'download-atom-shell', 'download-atom-shell-chromedriver', 'build']
|
||||
ciTasks.push('dump-symbols') if process.platform isnt 'win32'
|
||||
ciTasks.push('set-version', 'check-licenses', 'lint')
|
||||
ciTasks.push('mkdeb') if process.platform is 'linux'
|
||||
@@ -232,6 +232,6 @@ module.exports = (grunt) ->
|
||||
ciTasks.push('publish-build')
|
||||
grunt.registerTask('ci', ciTasks)
|
||||
|
||||
defaultTasks = ['download-atom-shell', 'build', 'set-version']
|
||||
defaultTasks = ['download-atom-shell', 'download-atom-shell-chromedriver', 'build', 'set-version']
|
||||
defaultTasks.push 'install' unless process.platform is 'linux'
|
||||
grunt.registerTask('default', defaultTasks)
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"grunt-contrib-csslint": "~0.1.2",
|
||||
"grunt-contrib-less": "~0.8.0",
|
||||
"grunt-cson": "0.14.0",
|
||||
"grunt-download-atom-shell": "~0.11.0",
|
||||
"grunt-download-atom-shell": "~0.12.0",
|
||||
"grunt-lesslint": "0.13.0",
|
||||
"grunt-peg": "~1.1.0",
|
||||
"grunt-shell": "~0.3.1",
|
||||
@@ -35,6 +35,7 @@
|
||||
"temp": "~0.8.1",
|
||||
"underscore-plus": "1.x",
|
||||
"unzip": "~0.1.9",
|
||||
"vm-compatibility-layer": "~0.1.0"
|
||||
"vm-compatibility-layer": "~0.1.0",
|
||||
"webdriverio": "^2.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,15 +85,27 @@ module.exports = (grunt) ->
|
||||
appPath = getAppPath()
|
||||
resourcePath = process.cwd()
|
||||
coreSpecsPath = path.resolve('spec')
|
||||
chromedriverPath = path.join(resourcePath, "atom-shell", "chromedriver")
|
||||
|
||||
if process.platform in ['darwin', 'linux']
|
||||
options =
|
||||
cmd: appPath
|
||||
args: ['--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}"]
|
||||
opts:
|
||||
env: _.extend({}, process.env,
|
||||
ATOM_INTEGRATION_TESTS_ENABLED: true
|
||||
PATH: [process.env.path, chromedriverPath].join(":")
|
||||
)
|
||||
|
||||
else if process.platform is 'win32'
|
||||
options =
|
||||
cmd: process.env.comspec
|
||||
args: ['/c', appPath, '--test', "--resource-path=#{resourcePath}", "--spec-directory=#{coreSpecsPath}", "--log-file=ci.log"]
|
||||
opts:
|
||||
env: _.extend({}, process.env,
|
||||
ATOM_INTEGRATION_TESTS_ENABLED: true
|
||||
PATH: [process.env.path, chromedriverPath].join(";")
|
||||
)
|
||||
|
||||
spawn options, (error, results, code) ->
|
||||
if process.platform is 'win32'
|
||||
|
||||
5
spec/integration/fixtures/atom-home/config.cson
Normal file
5
spec/integration/fixtures/atom-home/config.cson
Normal file
@@ -0,0 +1,5 @@
|
||||
"*":
|
||||
welcome:
|
||||
showOnStartup: false
|
||||
"exception-reporting":
|
||||
userId: "7c0a3c52-795c-5e20-5323-64efcf91f212"
|
||||
43
spec/integration/helpers/atom-launcher.sh
Executable file
43
spec/integration/helpers/atom-launcher.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script wraps the `Atom` binary, allowing the `chromedriver` server to
|
||||
# execute it with positional arguments and environment variables. `chromedriver`
|
||||
# only allows 'switches' to be specified when starting a browser, not positional
|
||||
# arguments, so this script accepts the following special switches:
|
||||
#
|
||||
# * `atom-path`: The path to the `Atom` binary.
|
||||
# * `atom-arg`: A positional argument to pass to Atom. This flag can be specified
|
||||
# multiple times.
|
||||
# * `atom-env`: A key=value environment variable to set for Atom. This flag can
|
||||
# be specified multiple times.
|
||||
#
|
||||
# Any other switches will be passed through to `Atom`.
|
||||
|
||||
atom_path=""
|
||||
atom_switches=()
|
||||
atom_args=()
|
||||
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--atom-path=*)
|
||||
atom_path="${arg#*=}"
|
||||
;;
|
||||
|
||||
--atom-arg=*)
|
||||
atom_args+=(${arg#*=})
|
||||
;;
|
||||
|
||||
--atom-env=*)
|
||||
export ${arg#*=}
|
||||
;;
|
||||
|
||||
*)
|
||||
atom_switches+=($arg)
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "Launching Atom" >&2
|
||||
echo ${atom_path} ${atom_args[@]} ${atom_switches[@]} >&2
|
||||
|
||||
exec ${atom_path} ${atom_args[@]} ${atom_switches[@]}
|
||||
87
spec/integration/helpers/start-atom.coffee
Normal file
87
spec/integration/helpers/start-atom.coffee
Normal file
@@ -0,0 +1,87 @@
|
||||
path = require "path"
|
||||
temp = require("temp").track()
|
||||
remote = require "remote"
|
||||
{map, extend} = require "underscore-plus"
|
||||
{spawn, spawnSync} = require "child_process"
|
||||
webdriverio = require "../../../build/node_modules/webdriverio"
|
||||
async = require "async"
|
||||
|
||||
AtomPath = remote.process.argv[0]
|
||||
AtomLauncherPath = path.join(__dirname, "..", "helpers", "atom-launcher.sh")
|
||||
SocketPath = path.join(temp.mkdirSync("socket-dir"), "atom.sock")
|
||||
ChromedriverPort = 9515
|
||||
|
||||
module.exports =
|
||||
driverTest: (fn) ->
|
||||
chromedriver = spawn("chromedriver", [
|
||||
"--verbose",
|
||||
"--port=#{ChromedriverPort}",
|
||||
"--url-base=/wd/hub"
|
||||
])
|
||||
|
||||
logs = []
|
||||
errorCode = null
|
||||
chromedriver.on "exit", (code, signal) ->
|
||||
errorCode = code unless signal?
|
||||
chromedriver.stderr.on "data", (log) ->
|
||||
logs.push(log.toString())
|
||||
chromedriver.stderr.on "close", ->
|
||||
if errorCode?
|
||||
jasmine.getEnv().currentSpec.fail "Chromedriver exited. code: #{errorCode}. Logs: #{logs.join("\n")}"
|
||||
|
||||
waitsFor "webdriver steps to complete", (done) ->
|
||||
fn()
|
||||
.catch((error) -> jasmine.getEnv().currentSpec.fail(err.message))
|
||||
.end()
|
||||
.call(done)
|
||||
, 30000
|
||||
|
||||
runs -> chromedriver.kill()
|
||||
|
||||
# Start Atom using chromedriver.
|
||||
startAtom: (args, env={}) ->
|
||||
webdriverio.remote(
|
||||
host: 'localhost'
|
||||
port: ChromedriverPort
|
||||
desiredCapabilities:
|
||||
browserName: "atom"
|
||||
chromeOptions:
|
||||
binary: AtomLauncherPath
|
||||
args: [
|
||||
"atom-path=#{AtomPath}"
|
||||
"dev"
|
||||
"safe"
|
||||
"user-data-dir=#{temp.mkdirSync('integration-spec-')}"
|
||||
"socket-path=#{SocketPath}"
|
||||
]
|
||||
.concat(map args, (arg) -> "atom-arg=#{arg}")
|
||||
.concat(map env, (value, key) -> "atom-env=#{key}=#{value}"))
|
||||
.init()
|
||||
.addCommand "waitForCondition", (conditionFn, timeout, cb) ->
|
||||
timedOut = succeeded = false
|
||||
pollingInterval = Math.min(timeout, 100)
|
||||
|
||||
setTimeout((-> timedOut = true), timeout)
|
||||
|
||||
async.until(
|
||||
(-> succeeded or timedOut),
|
||||
((next) =>
|
||||
setTimeout(=>
|
||||
conditionFn.call(this).then(
|
||||
((result) ->
|
||||
succeeded = result
|
||||
next()),
|
||||
((err) -> next(err))
|
||||
)
|
||||
, pollingInterval)),
|
||||
((err) -> cb(err, succeeded))
|
||||
)
|
||||
|
||||
# Once one `Atom` window is open, subsequent invocations of `Atom` will exit
|
||||
# immediately.
|
||||
startAnotherAtom: (args, env={}) ->
|
||||
spawnSync(AtomPath, args.concat([
|
||||
"--dev"
|
||||
"--safe"
|
||||
"--socket-path=#{SocketPath}"
|
||||
]), env: extend({}, process.env, env))
|
||||
67
spec/integration/startup-spec.coffee
Normal file
67
spec/integration/startup-spec.coffee
Normal file
@@ -0,0 +1,67 @@
|
||||
# These tests are excluded by default. To run them from the command line:
|
||||
#
|
||||
# ATOM_INTEGRATION_TESTS_ENABLED=true apm test
|
||||
return unless process.env.ATOM_INTEGRATION_TESTS_ENABLED
|
||||
|
||||
fs = require "fs"
|
||||
path = require "path"
|
||||
temp = require("temp").track()
|
||||
AtomHome = path.join(__dirname, "fixtures", "atom-home")
|
||||
{startAtom, startAnotherAtom, driverTest} = require("./helpers/start-atom")
|
||||
|
||||
describe "Starting Atom", ->
|
||||
beforeEach ->
|
||||
jasmine.useRealClock()
|
||||
|
||||
describe "opening paths via commmand-line arguments", ->
|
||||
[tempDirPath, tempFilePath] = []
|
||||
|
||||
beforeEach ->
|
||||
tempDirPath = temp.mkdirSync("empty-dir")
|
||||
tempFilePath = path.join(tempDirPath, "an-existing-file")
|
||||
fs.writeFileSync(tempFilePath, "This was already here.")
|
||||
|
||||
it "reuses existing windows when directories are reopened", ->
|
||||
driverTest ->
|
||||
|
||||
# Opening a new file creates one window with one empty text editor.
|
||||
startAtom([path.join(tempDirPath, "new-file")], ATOM_HOME: AtomHome)
|
||||
.waitForExist("atom-text-editor", 5000)
|
||||
.then((exists) -> expect(exists).toBe true)
|
||||
.windowHandles()
|
||||
.then(({value}) -> expect(value.length).toBe 1)
|
||||
.execute(-> atom.workspace.getActivePane().getItems().length)
|
||||
.then(({value}) -> expect(value).toBe 1)
|
||||
|
||||
# Typing in the editor changes its text.
|
||||
.execute(-> atom.workspace.getActiveTextEditor().getText())
|
||||
.then(({value}) -> expect(value).toBe "")
|
||||
.click("atom-text-editor")
|
||||
.keys("Hello!")
|
||||
.execute(-> atom.workspace.getActiveTextEditor().getText())
|
||||
.then(({value}) -> expect(value).toBe "Hello!")
|
||||
|
||||
# Opening an existing file in the same directory reuses the window and
|
||||
# adds a new tab for the file.
|
||||
.call(-> startAnotherAtom([tempFilePath], ATOM_HOME: AtomHome))
|
||||
.waitForCondition(
|
||||
(-> @execute((-> atom.workspace.getActivePane().getItems().length)).then ({value}) -> value is 2),
|
||||
5000)
|
||||
.then((result) -> expect(result).toBe(true))
|
||||
.execute(-> atom.workspace.getActiveTextEditor().getText())
|
||||
.then(({value}) -> expect(value).toBe "This was already here.")
|
||||
|
||||
# Opening a different directory creates a second window with no
|
||||
# tabs open.
|
||||
.call(-> startAnotherAtom([temp.mkdirSync("another-empty-dir")], ATOM_HOME: AtomHome))
|
||||
.waitForCondition(
|
||||
(-> @windowHandles().then(({value}) -> value.length is 2)),
|
||||
5000)
|
||||
.then((result) -> expect(result).toBe(true))
|
||||
.windowHandles()
|
||||
.then(({value}) ->
|
||||
@window(value[1])
|
||||
.waitForExist("atom-workspace", 5000)
|
||||
.then((exists) -> expect(exists).toBe true)
|
||||
.execute(-> atom.workspace.getActivePane().getItems().length)
|
||||
.then(({value}) -> expect(value).toBe 0))
|
||||
@@ -305,13 +305,13 @@ window.waitsForPromise = (args...) ->
|
||||
window.waitsFor timeout, (moveOn) ->
|
||||
promise = fn()
|
||||
if shouldReject
|
||||
promise.catch(moveOn)
|
||||
(promise.catch ? promise.thenCatch).call(promise, moveOn)
|
||||
promise.then ->
|
||||
jasmine.getEnv().currentSpec.fail("Expected promise to be rejected, but it was resolved")
|
||||
moveOn()
|
||||
else
|
||||
promise.then(moveOn)
|
||||
promise.catch (error) ->
|
||||
(promise.catch ? promise.thenCatch).call promise, (error) ->
|
||||
jasmine.getEnv().currentSpec.fail("Expected promise to be resolved, but it was rejected with #{jasmine.pp(error)}")
|
||||
moveOn()
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ url = require 'url'
|
||||
{EventEmitter} = require 'events'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
socketPath =
|
||||
DefaultSocketPath =
|
||||
if process.platform is 'win32'
|
||||
'\\\\.\\pipe\\atom-sock'
|
||||
else
|
||||
@@ -31,17 +31,20 @@ class AtomApplication
|
||||
|
||||
# Public: The entry point into the Atom application.
|
||||
@open: (options) ->
|
||||
options.socketPath ?= DefaultSocketPath
|
||||
|
||||
createAtomApplication = -> new AtomApplication(options)
|
||||
|
||||
# FIXME: Sometimes when socketPath doesn't exist, net.connect would strangely
|
||||
# take a few seconds to trigger 'error' event, it could be a bug of node
|
||||
# or atom-shell, before it's fixed we check the existence of socketPath to
|
||||
# speedup startup.
|
||||
if (process.platform isnt 'win32' and not fs.existsSync socketPath) or options.test
|
||||
if (process.platform isnt 'win32' and not fs.existsSync options.socketPath) or options.test
|
||||
createAtomApplication()
|
||||
return
|
||||
|
||||
client = net.connect {path: socketPath}, ->
|
||||
|
||||
client = net.connect {path: options.socketPath}, ->
|
||||
client.write JSON.stringify(options), ->
|
||||
client.end()
|
||||
app.terminate()
|
||||
@@ -57,7 +60,7 @@ class AtomApplication
|
||||
exit: (status) -> app.exit(status)
|
||||
|
||||
constructor: (options) ->
|
||||
{@resourcePath, @version, @devMode, @safeMode} = options
|
||||
{@resourcePath, @version, @devMode, @safeMode, @socketPath} = options
|
||||
|
||||
# Normalize to make sure drive letter case is consistent on Windows
|
||||
@resourcePath = path.normalize(@resourcePath) if @resourcePath
|
||||
@@ -119,15 +122,15 @@ class AtomApplication
|
||||
connection.on 'data', (data) =>
|
||||
@openWithOptions(JSON.parse(data))
|
||||
|
||||
server.listen socketPath
|
||||
server.listen @socketPath
|
||||
server.on 'error', (error) -> console.error 'Application server failed', error
|
||||
|
||||
deleteSocketFile: ->
|
||||
return if process.platform is 'win32'
|
||||
|
||||
if fs.existsSync(socketPath)
|
||||
if fs.existsSync(@socketPath)
|
||||
try
|
||||
fs.unlinkSync(socketPath)
|
||||
fs.unlinkSync(@socketPath)
|
||||
catch error
|
||||
# Ignore ENOENT errors in case the file was deleted between the exists
|
||||
# check and the call to unlink sync. This occurred occasionally on CI
|
||||
|
||||
@@ -117,6 +117,7 @@ parseCommandLine = ->
|
||||
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
|
||||
options.alias('v', 'version').boolean('v').describe('v', 'Print the version.')
|
||||
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
|
||||
options.string('socket-path')
|
||||
args = options.argv
|
||||
|
||||
if args.help
|
||||
@@ -137,6 +138,7 @@ parseCommandLine = ->
|
||||
newWindow = args['new-window']
|
||||
pidToKillWhenClosed = args['pid'] if args['wait']
|
||||
logFile = args['log-file']
|
||||
socketPath = args['socket-path']
|
||||
|
||||
if args['resource-path']
|
||||
devMode = true
|
||||
@@ -161,6 +163,6 @@ parseCommandLine = ->
|
||||
# explicitly pass it by command line, see http://git.io/YC8_Ew.
|
||||
process.env.PATH = args['path-environment'] if args['path-environment']
|
||||
|
||||
{resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile}
|
||||
{resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile, socketPath}
|
||||
|
||||
start()
|
||||
|
||||
Reference in New Issue
Block a user