start-server-and-test
Advanced tools
Comparing version
{ | ||
"name": "start-server-and-test", | ||
"description": "Starts server, waits for URL, then runs test command; when the tests end, shuts down server", | ||
"version": "1.12.0", | ||
"version": "1.15.5", | ||
"author": "Gleb Bahmutov <gleb.bahmutov@gmail.com>", | ||
@@ -51,3 +51,3 @@ "bugs": "https://github.com/bahmutov/start-server-and-test/issues", | ||
"publishConfig": { | ||
"registry": "http://registry.npmjs.org/" | ||
"registry": "https://registry.npmjs.org/" | ||
}, | ||
@@ -77,2 +77,4 @@ "repository": { | ||
"start-fail": "node test/server-fail.js", | ||
"start-304": "node test/server-304.js", | ||
"start-403": "node test/server-403.js", | ||
"start-cross-env": "cross-env FOO=bar node test/server.js", | ||
@@ -83,2 +85,3 @@ "test2": "curl http://127.0.0.1:9000", | ||
"message": "echo Hi there 👋", | ||
"message2": "echo Hi there 2 👋", | ||
"demo": "node src/bin/start.js http://127.0.0.1:9000 message", | ||
@@ -95,7 +98,18 @@ "demo2": "node src/bin/start.js start http://127.0.0.1:9000 test2", | ||
"demo11": "node src/bin/start.js http-get://127.0.0.1:9000", | ||
"demo12": "node src/bin/start.js start-304 9000 test2", | ||
"demo-expect-403": "node src/bin/start.js --expect 403 start-403 9000 'echo Waited'", | ||
"demo-interval": "WAIT_ON_INTERVAL=1000 node src/bin/start.js start http://127.0.0.1:9000 test2", | ||
"demo-timeout": "WAIT_ON_TIMEOUT=10000 node src/bin/start.js start http://127.0.0.1:9000 test2", | ||
"demo-cross-env": "node src/bin/start.js start-cross-env 9000", | ||
"demo-commands": "node src/bin/start.js 'node test/server.js --port 8800' 8800 'node test/client --port 8800'", | ||
"demo-multiple": "node src/bin/start.js 'node test/server --port 6000' 6000 'node test/server --port 6010' 6010 'curl http://127.0.0.1:6000 && curl http://127.0.0.1:6010'" | ||
"demo-multiple": "node src/bin/start.js 'node test/server --port 6000' 6000 'node test/server --port 6010' 6010 'curl http://127.0.0.1:6000 && curl http://127.0.0.1:6010'", | ||
"demo-multiple-test-commands": "node src/bin/start.js 9000 'npm run message && npm run message2'", | ||
"demo-json-server": "WAIT_ON_TIMEOUT=10000 DEBUG=start-server-and-test node src/bin/start.js 'json-server test/data.json' localhost:3000 'echo json-server working'", | ||
"demo-ip6": "WAIT_ON_TIMEOUT=10000 DEBUG=start-server-and-test node src/bin/start.js 'node test/ip6.mjs' localhost:8000 'echo server with ::1 working'", | ||
"demo-zero": "WAIT_ON_TIMEOUT=10000 node src/bin/start.js 'node test/zero.mjs' 8000 'echo server with 0.0.0.0 working'", | ||
"demo-zero-127": "WAIT_ON_TIMEOUT=10000 node src/bin/start.js 'node test/zero.mjs' http://127.0.0.1:8000 'echo server with 0.0.0.0 working'", | ||
"demo-zero-explicit": "node src/bin/start.js 'node test/zero.mjs' http://0.0.0.0:8000 'echo server with 0.0.0.0 working'" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^18.14.1", | ||
"ban-sensitive-files": "1.9.7", | ||
@@ -109,2 +123,3 @@ "chai": "4.2.0", | ||
"got": "9.6.0", | ||
"json-server": "^0.17.1", | ||
"license-checker": "24.1.0", | ||
@@ -120,13 +135,13 @@ "minimist": "1.2.5", | ||
"snap-shot-it": "6.3.5", | ||
"standard": "13.1.0", | ||
"travis-deploy-once": "5.0.11" | ||
"standard": "13.1.0" | ||
}, | ||
"dependencies": { | ||
"arg": "^5.0.2", | ||
"bluebird": "3.7.2", | ||
"check-more-types": "2.24.0", | ||
"debug": "4.3.1", | ||
"execa": "3.4.0", | ||
"debug": "4.3.4", | ||
"execa": "5.1.1", | ||
"lazy-ass": "1.6.0", | ||
"ps-tree": "1.2.0", | ||
"wait-on": "5.2.1" | ||
"wait-on": "7.0.1" | ||
}, | ||
@@ -133,0 +148,0 @@ "release": { |
143
README.md
@@ -83,5 +83,6 @@ # start-server-and-test | ||
``` | ||
start-server-and-test start http://localhost:8080 test | ||
server-test start http://localhost:8080 test | ||
server-test http://localhost:8080 test | ||
start-server-and-test start http://127.0.0.1:8080 test | ||
server-test start http://127.0.0.1:8080 test | ||
server-test http://127.0.0.1:8080 test | ||
server-test 127.0.0.1:8080 test | ||
start-test :8080 test | ||
@@ -92,2 +93,4 @@ start-test 8080 test | ||
**Tip:** I highly recommend you specify the full url instead of the port, see the `localhost vs 0.0.0.0 vs 127.0.0.1` section later in this README. | ||
### Options | ||
@@ -107,3 +110,3 @@ | ||
You can also shorten local url to just port, the code below is equivalent to checking `http://localhost:8080`. | ||
You can also shorten local url to just port, the code below is equivalent to checking `http://127.0.0.1:8080`. | ||
@@ -158,2 +161,29 @@ ```json | ||
If you want to start the server, wait for it to respond, and then run multiple test commands (and stop the server after they finish), you should be able to use `&&` to separate the test commands: | ||
```json | ||
{ | ||
"scripts": { | ||
"start": "npm start", | ||
"test:unit": "mocha test.js", | ||
"test:e2e": "mocha e2e.js", | ||
"ci": "start-test 9000 'npm run test:unit && npm run test:e2e'" | ||
} | ||
} | ||
``` | ||
The above script `ci` after the `127.0.0.1:9000` responds executes the `npm run test:unit` command. Then when it finishes it runs `npm run test:e2e`. If the first or second command fails, the `ci` script fails. Of course, your mileage on Windows might vary. | ||
#### expected | ||
The server might respond, but require authorization, returning an error HTTP code by default. You can still know that the server is responding by using `--expect` argument (or its alias `--expected`): | ||
``` | ||
$ start-test --expect 403 start :9000 test:e2e | ||
``` | ||
See `demo-expect-403` NPM script. | ||
Default expected value is 200. | ||
## `npx` and `yarn` | ||
@@ -178,3 +208,3 @@ | ||
starting server using command "http-server -c-1 ." | ||
and when url "http://localhost:8080" is responding | ||
and when url "http://127.0.0.1:8080" is responding | ||
running tests using command "cypress run" | ||
@@ -192,3 +222,3 @@ Starting up http-server, serving . | ||
starting server using command "http-server -c-1 ." | ||
and when url "http://localhost:8080" is responding | ||
and when url "http://127.0.0.1:8080" is responding | ||
running tests using command "cypress run" | ||
@@ -199,2 +229,18 @@ Starting up http-server, serving . | ||
## localhost vs 0.0.0.0 vs 127.0.0.1 | ||
The latest versions of Node and some web servers listen on host `0.0.0.0` which _no longer means localhost_. Thus if you specify _just the port number_, like `:3000`, this package will try `http://127.0.0.1:3000` to ping the server. A good practice is to specify the full URL you would like to ping. | ||
``` | ||
# same as "http://127.0.0.1:3000" | ||
start-server start 3000 test | ||
# better | ||
start-server start http://127.0.0.1:3000 test | ||
# or | ||
start-server start http://0.0.0.0:3000 test | ||
# of course, if your server is listening on localhost | ||
# you can still set the URL | ||
start-server start http://localhost:3000 test | ||
``` | ||
## Note for yarn users | ||
@@ -214,12 +260,41 @@ | ||
If you are using [webpack-dev-server](https://www.npmjs.com/package/webpack-dev-server) (directly or via `angular/cli` or other boilerplates) then please use the following URL form to check | ||
Also applies to **Vite** users! | ||
If you are using [webpack-dev-server](https://www.npmjs.com/package/webpack-dev-server) (directly or via `angular/cli` or other boilerplates) then the server does not respond to HEAD requests from `start-server-and-test`. You can check if the server responds to the HEAD requests by starting the server and pinging it from another terminal using `curl` | ||
``` | ||
# from the first terminal start the server | ||
$ npm start | ||
# from the second terminal call the server with HEAD request | ||
$ curl --head http://localhost:3000 | ||
``` | ||
If the server responds with 404, then it does not handle the HEAD requests. You have two solutions: | ||
### Use HTTP GET requests | ||
You can force the `start-server-and-test` to ping the server using GET requests using the `http-get://` prefix: | ||
``` | ||
start-server-and-test http-get://localhost:8080 | ||
``` | ||
This is because under the hood this module uses [wait-on](https://github.com/jeffbski/wait-on) to ping the server. Wait-on uses `HEAD` by default, but `webpack-dev-server` does not respond to `HEAD` only to `GET` requests. Thus you need to use `http-get://` URL format to force `wait-on` to use `GET` probe. | ||
### Ping a specific resource | ||
You can even wait on the bundle JavaScript url instead of the page url, see discussion in this [issue #4](https://github.com/bahmutov/start-server-and-test/issues/4) | ||
As an alternative to using GET method to request the root page, you can try pinging a specific resource, see the discussion in the [issue #4](https://github.com/bahmutov/start-server-and-test/issues/4). | ||
``` | ||
# maybe the server responds to HEAD requests to the HTML page | ||
start-server-and-test http://localhost:3000/index.html | ||
# or maybe the server responds to HEAD requests to JS resource | ||
start-server-and-test http://localhost:8080/app.js | ||
``` | ||
### Explanation | ||
You can watch the explanation in the video [Debug a Problem in start-server-and-test](https://youtu.be/rxyZOxYCsAk). | ||
Under the hood this module uses [wait-on](https://github.com/jeffbski/wait-on) to ping the server. Wait-on uses `HEAD` by default, but `webpack-dev-server` does not respond to `HEAD` only to `GET` requests. Thus you need to use `http-get://` URL format to force `wait-on` to use `GET` probe or ask for a particular resource. | ||
### Debugging | ||
@@ -229,5 +304,14 @@ | ||
``` | ||
$ DEBUG=start-server-and-test npm run test | ||
start-server-and-test parsing CLI arguments: [ 'dev', '3000', 'subtask' ] +0ms | ||
start-server-and-test parsed args: { services: [ { start: 'npm run dev', url: [Array] } ], test: 'npm run subtask' } | ||
... | ||
making HTTP(S) head request to url:http://127.0.0.1:3000 ... | ||
HTTP(S) error for http://127.0.0.1:3000 Error: Request failed with status code 404 | ||
``` | ||
### Disable HTTPS certificate checks | ||
To see disable HTTPS checks for `wait-on`, run with environment variable `START_SERVER_AND_TEST_INSECURE=1`. | ||
To disable HTTPS checks for `wait-on`, run with environment variable `START_SERVER_AND_TEST_INSECURE=1`. | ||
@@ -238,2 +322,6 @@ ### Timeout | ||
### Interval | ||
This utility will check for a server response every two seconds (default). Setting an environment variable `WAIT_ON_INTERVAL=600000` (milliseconds) sets the interval for example to 10 minutes. | ||
### Starting two servers | ||
@@ -262,2 +350,14 @@ | ||
## Note for Apollo Server users | ||
When passing a simple GET request to Apollo Server it will respond with a 405 error. To get around this problem you need to pass a valid GraphQL query into the query parameter. Passing in a basic schema introspection query will work to determine the presence of an Apollo Server. You can configure your npm script like so: | ||
```json | ||
{ | ||
"scripts": { | ||
"ci": "start-server-and-test start 'http-get://localhost:4000/graphql?query={ __schema { queryType { name } } }' test" | ||
} | ||
} | ||
``` | ||
### Small print | ||
@@ -278,25 +378,4 @@ | ||
Copyright (c) 2017 Gleb Bahmutov <gleb.bahmutov@gmail.com> | ||
See [LICENSE](./LICENSE) | ||
Permission is hereby granted, free of charge, to any person | ||
obtaining a copy of this software and associated documentation | ||
files (the "Software"), to deal in the Software without | ||
restriction, including without limitation the rights to use, | ||
copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the | ||
Software is furnished to do so, subject to the following | ||
conditions: | ||
The above copyright notice and this permission notice shall be | ||
included in all copies or substantial portions of the Software. | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES | ||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | ||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||
OTHER DEALINGS IN THE SOFTWARE. | ||
[npm-icon]: https://nodei.co/npm/start-server-and-test.svg?downloads=true | ||
@@ -303,0 +382,0 @@ [npm-url]: https://npmjs.org/package/start-server-and-test |
@@ -5,6 +5,9 @@ #!/usr/bin/env node | ||
const args = process.argv.slice(2) | ||
const startAndTest = require('..').startAndTest | ||
const utils = require('../utils') | ||
const namedArguments = utils.getNamedArguments(process.argv.slice(2)) | ||
debug('named arguments: %o', namedArguments) | ||
const args = utils.crossArguments(process.argv.slice(2)) | ||
debug('parsing CLI arguments: %o', args) | ||
@@ -19,7 +22,11 @@ const parsed = utils.getArguments(args) | ||
utils.printArguments({ services, test }) | ||
if (!namedArguments.expect) { | ||
namedArguments.expect = 200 | ||
} | ||
startAndTest({ services, test }).catch(e => { | ||
utils.printArguments({ services, test, namedArguments }) | ||
startAndTest({ services, test, namedArguments }).catch(e => { | ||
console.error(e) | ||
process.exit(1) | ||
}) |
@@ -16,2 +16,4 @@ // @ts-check | ||
const fiveMinutes = 5 * 60 * 1000 | ||
const twoSeconds = 2000 | ||
const waitOnTimeout = process.env.WAIT_ON_TIMEOUT | ||
@@ -21,2 +23,6 @@ ? Number(process.env.WAIT_ON_TIMEOUT) | ||
const waitOnInterval = process.env.WAIT_ON_INTERVAL | ||
? Number(process.env.WAIT_ON_INTERVAL) | ||
: twoSeconds | ||
const isDebug = () => | ||
@@ -27,3 +33,3 @@ process.env.DEBUG && process.env.DEBUG.indexOf('start-server-and-test') !== -1 | ||
function waitAndRun ({ start, url, runFn }) { | ||
function waitAndRun ({ start, url, runFn, namedArguments }) { | ||
la(is.unemptyString(start), 'missing start script name', start) | ||
@@ -36,2 +42,7 @@ la(is.fn(runFn), 'missing test script name', runFn) | ||
) | ||
const isSuccessfulHttpCode = status => | ||
(status >= 200 && status < 300) || status === 304 | ||
const validateStatus = namedArguments.expect | ||
? status => status === namedArguments.expect | ||
: isSuccessfulHttpCode | ||
@@ -81,3 +92,3 @@ debug('starting server with command "%s", verbose mode?', start, isDebug()) | ||
resources: Array.isArray(url) ? url : [url], | ||
interval: 2000, | ||
interval: waitOnInterval, | ||
window: 1000, | ||
@@ -90,3 +101,4 @@ timeout: waitOnTimeout, | ||
Accept: 'text/html, application/json, text/plain, */*' | ||
} | ||
}, | ||
validateStatus | ||
} | ||
@@ -122,3 +134,3 @@ debug('wait-on options %o', options) | ||
*/ | ||
function startAndTest ({ services, test }) { | ||
function startAndTest ({ services, test, namedArguments }) { | ||
if (services.length === 0) { | ||
@@ -128,2 +140,8 @@ throw new Error('Got zero services to start ...') | ||
la( | ||
is.number(namedArguments.expect), | ||
'expected status should be a number', | ||
namedArguments.expect | ||
) | ||
if (services.length === 1) { | ||
@@ -135,2 +153,3 @@ const runTests = runTheTests(test) | ||
url: services[0].url, | ||
namedArguments, | ||
runFn: runTests | ||
@@ -143,5 +162,6 @@ }) | ||
url: services[0].url, | ||
namedArguments, | ||
runFn: () => { | ||
debug('previous service started, now going to the next one') | ||
return startAndTest({ services: services.slice(1), test }) | ||
return startAndTest({ services: services.slice(1), test, namedArguments }) | ||
} | ||
@@ -148,0 +168,0 @@ }) |
109
src/utils.js
@@ -5,4 +5,71 @@ const la = require('lazy-ass') | ||
const { existsSync } = require('fs') | ||
const arg = require('arg') | ||
const debug = require('debug')('start-server-and-test') | ||
const namedArguments = { | ||
'--expect': Number | ||
} | ||
/** | ||
* Returns new array of command line arguments | ||
* where leading and trailing " and ' are indicating | ||
* the beginning and end of an argument. | ||
*/ | ||
const crossArguments = cliArguments => { | ||
const args = arg(namedArguments, { | ||
permissive: true, | ||
argv: cliArguments | ||
}) | ||
debug('initial parsed arguments %o', args) | ||
// all other arguments | ||
const cliArgs = args._ | ||
let concatModeChar = false | ||
const indicationChars = ["'", '"', '`'] | ||
const combinedArgs = [] | ||
for (let i = 0; i < cliArgs.length; i++) { | ||
let arg = cliArgs[i] | ||
if ( | ||
!concatModeChar && | ||
indicationChars.some(char => cliArgs[i].startsWith(char)) | ||
) { | ||
arg = arg.slice(1) | ||
} | ||
if (concatModeChar && cliArgs[i].endsWith(concatModeChar)) { | ||
arg = arg.slice(0, -1) | ||
} | ||
if (concatModeChar && combinedArgs.length) { | ||
combinedArgs[combinedArgs.length - 1] += ' ' + arg | ||
} else { | ||
combinedArgs.push(arg) | ||
} | ||
if ( | ||
!concatModeChar && | ||
indicationChars.some(char => cliArgs[i].startsWith(char)) | ||
) { | ||
concatModeChar = cliArgs[i][0] | ||
} | ||
if (concatModeChar && cliArgs[i].endsWith(concatModeChar)) { | ||
concatModeChar = false | ||
} | ||
} | ||
return combinedArgs | ||
} | ||
const getNamedArguments = cliArgs => { | ||
const args = arg(namedArguments, { | ||
permissive: true, | ||
argv: cliArgs | ||
}) | ||
debug('initial parsed arguments %o', args) | ||
return { | ||
expect: args['--expect'], | ||
// aliases | ||
'--expected': '--expect' | ||
} | ||
} | ||
/** | ||
* Returns parsed command line arguments. | ||
@@ -126,4 +193,13 @@ * If start command is NPM script name defined in the package.json | ||
/** | ||
* Returns the host to ping if the user specified just the port. | ||
* For a long time, the safest bet was "localhost", but now modern | ||
* web servers seem to bind to "0.0.0.0", which means | ||
* the "127.0.0.1" works better | ||
*/ | ||
const getHost = () => '127.0.0.1' | ||
const normalizeUrl = input => { | ||
const str = is.string(input) ? input.split('|') : [input] | ||
const defaultHost = getHost() | ||
@@ -136,3 +212,3 @@ return str.map(s => { | ||
if (is.number(s) && is.port(s)) { | ||
return `http://localhost:${s}` | ||
return `http://${defaultHost}:${s}` | ||
} | ||
@@ -144,8 +220,12 @@ | ||
if (s.startsWith('localhost') || s.startsWith('127.0.0.1') || s.startsWith('0.0.0.0')) { | ||
return `http://${s}` | ||
} | ||
if (is.port(parseInt(s))) { | ||
return `http://localhost:${s}` | ||
return `http://${defaultHost}:${s}` | ||
} | ||
if (s[0] === ':') { | ||
return `http://localhost${s}` | ||
return `http://${defaultHost}${s}` | ||
} | ||
@@ -157,11 +237,26 @@ // for anything else, return original argument | ||
function printArguments ({ services, test }) { | ||
function printArguments ({ services, test, namedArguments }) { | ||
la( | ||
is.number(namedArguments.expect), | ||
'expected status code should be a number', | ||
namedArguments.expect | ||
) | ||
services.forEach((service, k) => { | ||
console.log('%d: starting server using command "%s"', k + 1, service.start) | ||
console.log( | ||
'and when url "%s" is responding with HTTP status code 200', | ||
service.url | ||
'and when url "%s" is responding with HTTP status code %d', | ||
service.url, | ||
namedArguments.expect | ||
) | ||
}) | ||
if (process.env.WAIT_ON_INTERVAL !== undefined) { | ||
console.log('WAIT_ON_INTERVAL is set to', process.env.WAIT_ON_INTERVAL) | ||
} | ||
if (process.env.WAIT_ON_TIMEOUT !== undefined) { | ||
console.log('WAIT_ON_TIMEOUT is set to', process.env.WAIT_ON_TIMEOUT) | ||
} | ||
console.log('running tests using command "%s"', test) | ||
@@ -174,3 +269,5 @@ console.log('') | ||
const UTILS = { | ||
crossArguments, | ||
getArguments, | ||
getNamedArguments, | ||
isPackageScriptName, | ||
@@ -177,0 +274,0 @@ isUrlOrPort, |
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 2 instances in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 2 instances in 1 package
31716
37.69%6
20%400
36.99%379
26.33%8
14.29%21
5%1
Infinity%15
66.67%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated