mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-23 11:16:39 +00:00
Merge pull request #2162 from resin-io/refactor-spectron-tests
test(spectron): Refactor tests, dereference app after use
This commit is contained in:
commit
7f118c9841
136
npm-shrinkwrap.json
generated
136
npm-shrinkwrap.json
generated
@ -402,8 +402,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/arch/-/arch-2.1.0.tgz"
|
"resolved": "https://registry.npmjs.org/arch/-/arch-2.1.0.tgz"
|
||||||
},
|
},
|
||||||
"archiver": {
|
"archiver": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"archiver-utils": {
|
"archiver-utils": {
|
||||||
@ -1935,6 +1935,11 @@
|
|||||||
"version": "1.5.4",
|
"version": "1.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz"
|
"resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz"
|
||||||
},
|
},
|
||||||
|
"combined-stream": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"command-join": {
|
"command-join": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/command-join/-/command-join-2.0.0.tgz"
|
"resolved": "https://registry.npmjs.org/command-join/-/command-join-2.0.0.tgz"
|
||||||
@ -2269,6 +2274,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"delegates": {
|
"delegates": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz"
|
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz"
|
||||||
@ -2623,49 +2633,35 @@
|
|||||||
"resolved": "https://registry.npmjs.org/electron-chromedriver/-/electron-chromedriver-1.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/electron-chromedriver/-/electron-chromedriver-1.7.1.tgz",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"debug": {
|
||||||
|
"version": "2.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"electron-download": {
|
"electron-download": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/electron-download/-/electron-download-4.1.0.tgz",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"dependencies": {
|
|
||||||
"debug": {
|
|
||||||
"version": "2.6.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"fs-extra": {
|
"fs-extra": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-2.1.2.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ms": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"path-exists": {
|
"path-exists": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "5.4.1",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"sumchecker": {
|
"sumchecker": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-2.0.2.tgz",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"dependencies": {
|
|
||||||
"debug": {
|
|
||||||
"version": "2.6.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -6880,6 +6876,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"regenerator-runtime": {
|
||||||
|
"version": "0.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"regenerator-transform": {
|
"regenerator-transform": {
|
||||||
"version": "0.10.1",
|
"version": "0.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz",
|
||||||
@ -7615,8 +7616,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.1.tgz"
|
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.1.tgz"
|
||||||
},
|
},
|
||||||
"spectron": {
|
"spectron": {
|
||||||
"version": "3.7.2",
|
"version": "3.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/spectron/-/spectron-3.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/spectron/-/spectron-3.7.3.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"speedometer": {
|
"speedometer": {
|
||||||
@ -8527,11 +8528,6 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz"
|
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz"
|
||||||
},
|
},
|
||||||
"validator": {
|
|
||||||
"version": "9.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/validator/-/validator-9.1.2.tgz",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"verror": {
|
"verror": {
|
||||||
"version": "1.3.6",
|
"version": "1.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz"
|
"resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz"
|
||||||
@ -8620,18 +8616,18 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"webdriverio": {
|
"webdriverio": {
|
||||||
"version": "4.9.11",
|
"version": "4.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-4.9.11.tgz",
|
"resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-4.12.0.tgz",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "5.5.1",
|
"version": "5.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ansi-escapes": {
|
"ansi-escapes": {
|
||||||
"version": "3.0.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
@ -8640,8 +8636,8 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ansi-styles": {
|
"ansi-styles": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"assert-plus": {
|
"assert-plus": {
|
||||||
@ -8665,13 +8661,13 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"supports-color": {
|
"supports-color": {
|
||||||
"version": "4.5.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8686,11 +8682,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"combined-stream": {
|
|
||||||
"version": "1.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"cryptiles": {
|
"cryptiles": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
|
||||||
@ -8703,11 +8694,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"delayed-stream": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"extend": {
|
"extend": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
|
||||||
@ -8719,8 +8705,8 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"form-data": {
|
"form-data": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"har-schema": {
|
"har-schema": {
|
||||||
@ -8734,8 +8720,8 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"has-flag": {
|
"has-flag": {
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"hawk": {
|
"hawk": {
|
||||||
@ -8744,8 +8730,8 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"hoek": {
|
"hoek": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"http-signature": {
|
"http-signature": {
|
||||||
@ -8764,13 +8750,13 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mime-db": {
|
"mime-db": {
|
||||||
"version": "1.30.0",
|
"version": "1.33.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mime-types": {
|
"mime-types": {
|
||||||
"version": "2.1.17",
|
"version": "2.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"mute-stream": {
|
"mute-stream": {
|
||||||
@ -8798,11 +8784,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"regenerator-runtime": {
|
|
||||||
"version": "0.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"request": {
|
"request": {
|
||||||
"version": "2.83.0",
|
"version": "2.83.0",
|
||||||
"resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
|
"resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
|
||||||
@ -8846,16 +8827,23 @@
|
|||||||
"supports-color": {
|
"supports-color": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.0.1.tgz",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tough-cookie": {
|
"tough-cookie": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"version": "3.1.0",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@
|
|||||||
"pkg": "4.3.0",
|
"pkg": "4.3.0",
|
||||||
"sass-lint": "1.12.1",
|
"sass-lint": "1.12.1",
|
||||||
"simple-progress-webpack-plugin": "1.1.2",
|
"simple-progress-webpack-plugin": "1.1.2",
|
||||||
"spectron": "3.7.2",
|
"spectron": "3.7.3",
|
||||||
"versionist": "2.8.1",
|
"versionist": "2.8.1",
|
||||||
"webpack": "github:jviotti/webpack#symlink-fix"
|
"webpack": "github:jviotti/webpack#symlink-fix"
|
||||||
}
|
}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 resin.io
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const m = require('mochainon')
|
|
||||||
|
|
||||||
module.exports = () => {
|
|
||||||
describe('Browser Window', function () {
|
|
||||||
it('should set a proper title', function () {
|
|
||||||
return this.app.client.getTitle().then((title) => {
|
|
||||||
m.chai.expect(title).to.equal('Etcher')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should open a browser window', function () {
|
|
||||||
return this.app.browserWindow.isVisible().then((isVisible) => {
|
|
||||||
m.chai.expect(isVisible).to.be.true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
const Bluebird = require('bluebird')
|
const Bluebird = require('bluebird')
|
||||||
const spectron = require('spectron')
|
const spectron = require('spectron')
|
||||||
|
const m = require('mochainon')
|
||||||
const EXIT_CODES = require('../../lib/shared/exit-codes')
|
const EXIT_CODES = require('../../lib/shared/exit-codes')
|
||||||
const entrypoint = process.env.ETCHER_SPECTRON_ENTRYPOINT
|
const entrypoint = process.env.ETCHER_SPECTRON_ENTRYPOINT
|
||||||
|
|
||||||
@ -30,22 +31,40 @@ describe('Spectron', function () {
|
|||||||
// Mainly for CI jobs
|
// Mainly for CI jobs
|
||||||
this.timeout(20000)
|
this.timeout(20000)
|
||||||
|
|
||||||
beforeEach(function () {
|
let app = null
|
||||||
this.app = new spectron.Application({
|
|
||||||
|
before('app:start', function () {
|
||||||
|
app = new spectron.Application({
|
||||||
path: entrypoint,
|
path: entrypoint,
|
||||||
args: [ '.' ]
|
args: [ '.' ]
|
||||||
})
|
})
|
||||||
|
|
||||||
return this.app.start()
|
return app.start()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
after('app:stop', function () {
|
||||||
if (this.app && this.app.isRunning()) {
|
if (app && app.isRunning()) {
|
||||||
return this.app.stop()
|
return app.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
return Bluebird.resolve()
|
return Bluebird.resolve()
|
||||||
})
|
})
|
||||||
|
|
||||||
require('./browser-window')()
|
after('app:deref', function () {
|
||||||
|
app = null
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Browser Window', function () {
|
||||||
|
it('should open a browser window', function () {
|
||||||
|
return app.browserWindow.isVisible().then((isVisible) => {
|
||||||
|
m.chai.expect(isVisible).to.be.true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should set a proper title', function () {
|
||||||
|
return app.client.getTitle().then((title) => {
|
||||||
|
m.chai.expect(title).to.equal('Etcher')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user