cypress-fail-on-network-error 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +155 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 nils-hoyer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# cypress-fail-on-network-error
|
|
2
|
+
|
|
3
|
+
This Plugin observes network requests through cypress network events. Cypress test will fail when the error conditions are met. For observing `console.error()` please check out [cypress-fail-on-console-error](https://www.npmjs.com/package/cypress-fail-on-console-error).
|
|
4
|
+
|
|
5
|
+
### Installation
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm install cypress-fail-on-network-error --save-dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Usage
|
|
12
|
+
|
|
13
|
+
`cypress/support/e2e.js`
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import failOnNetworkError, { Config, Request } from 'cypress-fail-on-network-error';
|
|
17
|
+
|
|
18
|
+
const config: Config = {
|
|
19
|
+
requests: [
|
|
20
|
+
'simpleUrlToExclude',
|
|
21
|
+
{ url: 'simpleUrlToExclude', method: 'GET', status: 400 },
|
|
22
|
+
{ url: /urlToExclude/, method: 'POST', status: 428 },
|
|
23
|
+
{ status: 430 },
|
|
24
|
+
{ status: { from: 200, to: 399 } },
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
failOnNetworkError(config)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Config
|
|
32
|
+
|
|
33
|
+
| Parameter | Default | <div style="width:300px">Description</div> |
|
|
34
|
+
|--- |--- |--- |
|
|
35
|
+
| `requests` | `[]` | Exclude requests from throwing `AssertionError`. Types `string`, `RegExp`, `Request` are accepted. `string` and `request.url` will be converted to type `RegExp`. [String.match()](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/String/match) will be used for matching. |
|
|
36
|
+
|
|
37
|
+
<br/>
|
|
38
|
+
|
|
39
|
+
### Set config from cypress test
|
|
40
|
+
Use `failOnNetworkError` functions `getConfig()` and `setConfig()` with your own requirements. Detailed example implementation [cypress comands](https://github.com/nils-hoyer/cypress-fail-on-network-error/blob/main/cypress/support/e2e.ts#L14-L64) & [cypress test](https://github.com/nils-hoyer/cypress-fail-on-network-error/blob/main/cypress/e2e/shouldfailOnNetworkError.cy.ts#L1-L25). Note that the config will be resetted to initial config between tests.
|
|
41
|
+
```js
|
|
42
|
+
const { getConfig, setConfig } = failOnNetworkError(config);
|
|
43
|
+
|
|
44
|
+
Cypress.Commands.addAll({
|
|
45
|
+
getConfigRequests: () => {
|
|
46
|
+
return cy.wrap(getConfig().requests);
|
|
47
|
+
},
|
|
48
|
+
setConfigRequests: (requests: (string | Request)[]) => {
|
|
49
|
+
setConfig({ ...getConfig(), requests });
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
describe('example test', () => {
|
|
56
|
+
it('should set exclude requests', () => {
|
|
57
|
+
cy.setConfigRequests(['urlToExclude']);
|
|
58
|
+
cy.visit('url');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Wait for all pending requests to be resolved
|
|
64
|
+
Use `failOnNetworkError` function `waitForRequests()` to wait until all pending requests are resolved. The default timeout is 10000 ms which can be changed by overriding the default value `waitForRequests(5000)`. When reaching the timeout, Cypress test execution will continue without throwing an timeout exception.
|
|
65
|
+
Detailed documenation for [cypress comands](https://github.com/nils-hoyer/cypress-fail-on-network-error/blob/main/cypress/support/e2e.ts#L13-L35) & [cypress test](https://github.com/nils-hoyer/cypress-fail-on-network-error/blob/main/cypress/e2e/shouldWaitForRequests.cy.ts).
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
const { waitForRequests } = failOnNetworkError(config);
|
|
69
|
+
|
|
70
|
+
Cypress.Commands.addAll({
|
|
71
|
+
waitForRequests: () => waitForRequests(),
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
describe('example test', () => {
|
|
77
|
+
it('should wait for requests to be solved', () => {
|
|
78
|
+
cy.visit('url');
|
|
79
|
+
cy.waitForRequests();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
### Contributing
|
|
86
|
+
1. Create an project issue with proper description and expected behaviour
|
|
87
|
+
2. Provide a PR with implementation and tests. Command `npm run verify` have to pass locally
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
/// <reference types="cypress" />
|
|
3
|
+
/// <reference types="cypress" />
|
|
4
|
+
/// <reference types="cypress" />
|
|
5
|
+
export type Config = {
|
|
6
|
+
requests?: (string | Request)[];
|
|
7
|
+
};
|
|
8
|
+
export interface Request {
|
|
9
|
+
url?: string | RegExp;
|
|
10
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
11
|
+
status?: number | Range;
|
|
12
|
+
}
|
|
13
|
+
export type Range = {
|
|
14
|
+
from: number;
|
|
15
|
+
to: number;
|
|
16
|
+
};
|
|
17
|
+
export default function failOnNetworkError(_config?: Config): {
|
|
18
|
+
getConfig: () => Required<Config>;
|
|
19
|
+
setConfig: (_config: Config) => void;
|
|
20
|
+
waitForRequests: (timeout?: number) => Cypress.Chainable<any>;
|
|
21
|
+
};
|
|
22
|
+
export declare const validateConfig: (config: Config) => void;
|
|
23
|
+
export declare const createConfig: (config: Config) => Required<Config>;
|
|
24
|
+
export declare const mapToRequests: (_unknowns: (string | Request)[]) => Request[];
|
|
25
|
+
export declare const findRequest: (requests: RequestSession[], eventRequestId: string) => RequestSession | undefined;
|
|
26
|
+
export declare const isRequestExcluded: (request: RequestSession, config: Required<Config>) => boolean;
|
|
27
|
+
export type RequestSession = {
|
|
28
|
+
requestId: string;
|
|
29
|
+
method: string;
|
|
30
|
+
url: string;
|
|
31
|
+
status?: number;
|
|
32
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import * as chai from 'chai';
|
|
11
|
+
import { AssertionError } from 'chai';
|
|
12
|
+
import typeDetect from 'type-detect';
|
|
13
|
+
export default function failOnNetworkError(_config = {}) {
|
|
14
|
+
let config;
|
|
15
|
+
let originConfig;
|
|
16
|
+
let requests = [];
|
|
17
|
+
const getRequests = () => requests;
|
|
18
|
+
const getConfig = () => config;
|
|
19
|
+
const setConfig = (_config) => {
|
|
20
|
+
validateConfig(_config);
|
|
21
|
+
config = createConfig(_config);
|
|
22
|
+
originConfig = originConfig !== null && originConfig !== void 0 ? originConfig : Object.assign({}, config);
|
|
23
|
+
};
|
|
24
|
+
const waitForRequests = (timeout = 10000) => {
|
|
25
|
+
const requestsDone = () => getRequests().every((request) => request.status !== undefined);
|
|
26
|
+
return cy.wrap(waitUntil(requestsDone, timeout), {
|
|
27
|
+
timeout: timeout + 100,
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
setConfig(_config);
|
|
31
|
+
Cypress.on('request:event', (eventName, event) => {
|
|
32
|
+
const subscribedEvents = [
|
|
33
|
+
'incoming:request',
|
|
34
|
+
'request:error',
|
|
35
|
+
'response:received',
|
|
36
|
+
];
|
|
37
|
+
const unknownResponse = eventName === 'response:received' &&
|
|
38
|
+
findRequest(requests, event.requestId) === undefined;
|
|
39
|
+
if (!subscribedEvents || unknownResponse)
|
|
40
|
+
return;
|
|
41
|
+
if (eventName === 'incoming:request') {
|
|
42
|
+
requests.push({
|
|
43
|
+
requestId: event.requestId,
|
|
44
|
+
method: event.method,
|
|
45
|
+
url: event.url,
|
|
46
|
+
status: undefined,
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
requests = requests.map((request) => {
|
|
51
|
+
if (request.requestId !== event.requestId)
|
|
52
|
+
return request;
|
|
53
|
+
return Object.assign(Object.assign({}, request), { status: event.status });
|
|
54
|
+
});
|
|
55
|
+
if (!isRequestExcluded(findRequest(requests, event.requestId), getConfig())) {
|
|
56
|
+
throw new AssertionError(`cypress-fail-on-network-error:\n${JSON.stringify(requests)}`);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
Cypress.on('test:after:run', () => __awaiter(this, void 0, void 0, function* () {
|
|
60
|
+
setConfig(originConfig);
|
|
61
|
+
requests = [];
|
|
62
|
+
}));
|
|
63
|
+
return {
|
|
64
|
+
getConfig,
|
|
65
|
+
setConfig,
|
|
66
|
+
waitForRequests,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export const validateConfig = (config) => {
|
|
70
|
+
var _a;
|
|
71
|
+
// TODO replace any with Request
|
|
72
|
+
(_a = config.requests) === null || _a === void 0 ? void 0 : _a.forEach((request) => {
|
|
73
|
+
if (typeDetect(request) === 'string')
|
|
74
|
+
return;
|
|
75
|
+
chai.expect(typeDetect(request.url)).to.be.oneOf([
|
|
76
|
+
'string',
|
|
77
|
+
'RegExp',
|
|
78
|
+
'undefined',
|
|
79
|
+
]);
|
|
80
|
+
chai.expect(typeDetect(request.method)).to.be.oneOf([
|
|
81
|
+
'string',
|
|
82
|
+
'undefined',
|
|
83
|
+
]);
|
|
84
|
+
chai.expect(typeDetect(request.status)).to.be.oneOf([
|
|
85
|
+
'Object',
|
|
86
|
+
'number',
|
|
87
|
+
'undefined',
|
|
88
|
+
]);
|
|
89
|
+
if (typeDetect(request.status) === 'Object') {
|
|
90
|
+
chai.expect(typeDetect(request.status.from)).to.equal('number');
|
|
91
|
+
chai.expect(typeDetect(request.status.to)).to.equal('number');
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
//TODO: use Request[] instead of any
|
|
96
|
+
export const createConfig = (config) => {
|
|
97
|
+
return {
|
|
98
|
+
requests: config.requests !== undefined ? mapToRequests(config.requests) : [],
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
export const mapToRequests = (_unknowns) => _unknowns.map((unknown) => {
|
|
102
|
+
if (typeof unknown !== 'string') {
|
|
103
|
+
let status = undefined;
|
|
104
|
+
if (unknown.status !== undefined) {
|
|
105
|
+
status =
|
|
106
|
+
typeDetect(unknown.status) === 'number'
|
|
107
|
+
? { from: unknown.status, to: unknown.status }
|
|
108
|
+
: {
|
|
109
|
+
from: unknown.status.from,
|
|
110
|
+
to: unknown.status.to,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
url: unknown.url,
|
|
115
|
+
method: unknown.method,
|
|
116
|
+
status,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
url: unknown,
|
|
121
|
+
method: undefined,
|
|
122
|
+
status: undefined,
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
export const findRequest = (requests, eventRequestId) => requests.find((request) => request.requestId === eventRequestId);
|
|
126
|
+
export const isRequestExcluded = (request, config) => {
|
|
127
|
+
var _a;
|
|
128
|
+
//TODO: replace any with Request
|
|
129
|
+
return (_a = config.requests) === null || _a === void 0 ? void 0 : _a.some((configRequest) => {
|
|
130
|
+
const urlMatch = configRequest.url
|
|
131
|
+
? new RegExp(configRequest.url).test(request.url)
|
|
132
|
+
: true;
|
|
133
|
+
const statusMatch = configRequest.status
|
|
134
|
+
? configRequest.status.from >= request.status &&
|
|
135
|
+
configRequest.status.to <= request.status
|
|
136
|
+
: true;
|
|
137
|
+
const methodMatch = (configRequest === null || configRequest === void 0 ? void 0 : configRequest.method)
|
|
138
|
+
? (configRequest === null || configRequest === void 0 ? void 0 : configRequest.method) === request.method
|
|
139
|
+
: true;
|
|
140
|
+
return urlMatch && statusMatch && methodMatch;
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
const waitUntil = (predicate, timeout) => {
|
|
144
|
+
const startTime = new Date().getTime();
|
|
145
|
+
const isTimeUp = (startTime, timeout) => new Date().getTime() > startTime + timeout;
|
|
146
|
+
const poll = (resolve) => {
|
|
147
|
+
if (predicate() || isTimeUp(startTime, timeout)) {
|
|
148
|
+
resolve();
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
setTimeout(() => poll(resolve), 500);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
return new Cypress.Promise(poll);
|
|
155
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cypress-fail-on-network-error",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "fail cypress test on network error",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/**/*"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "npm run build:be & npm run build:fe",
|
|
13
|
+
"build:fe": "npx rimraf dist/ && tsc",
|
|
14
|
+
"build:be": "node ./backend/app.cjs",
|
|
15
|
+
"dev": "npm run dev:fe & npm run dev:be",
|
|
16
|
+
"dev:fe": "nodemon --watch './src' --ext 'ts' --exec 'npm run build:fe'",
|
|
17
|
+
"dev:be": "nodemon --watch './backend/app.js' --ext 'js' --exec 'npm run build:be'",
|
|
18
|
+
"prettier": "prettier --write \"**/*\"",
|
|
19
|
+
"prettier:check": "prettier --check \"**/*\"",
|
|
20
|
+
"lint": "tsc --noEmit && tsc -p ./test/tsconfig.json && tsc -p ./cypress/tsconfig.json",
|
|
21
|
+
"test": "npm run test:ut && npm run test:it",
|
|
22
|
+
"test:ut": "ts-mocha -n loader=ts-node/esm test/unitTest.ts",
|
|
23
|
+
"test:it": "ts-mocha -n loader=ts-node/esm test/integrationTest.ts --timeout 60000",
|
|
24
|
+
"verify": "npm run build && npm run lint && npm run prettier:check && npm run test"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/nils-hoyer/cypress-fail-on-network-error.git"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"cypress",
|
|
32
|
+
"testing",
|
|
33
|
+
"network",
|
|
34
|
+
"request",
|
|
35
|
+
"response",
|
|
36
|
+
"error"
|
|
37
|
+
],
|
|
38
|
+
"author": "nils-hoyer",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/nils-hoyer/cypress-fail-on-network-error/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/nils-hoyer/cypress-fail-on-network-error#readme",
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"chai": "^5.0.0",
|
|
46
|
+
"type-detect": "^4.0.8"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/chai": "4.3.11",
|
|
50
|
+
"@types/expect": "24.3.0",
|
|
51
|
+
"@types/mocha": "10.0.6",
|
|
52
|
+
"@types/type-detect": "4.0.3",
|
|
53
|
+
"cors": "2.8.5",
|
|
54
|
+
"cypress": "13.6.2",
|
|
55
|
+
"express": "4.18.1",
|
|
56
|
+
"mocha": "10.2.0",
|
|
57
|
+
"nodemon": "3.0.2",
|
|
58
|
+
"prettier": "3.1.1",
|
|
59
|
+
"prettier-plugin-organize-imports": "3.2.4",
|
|
60
|
+
"rimraf": "5.0.5",
|
|
61
|
+
"ts-mocha": "10.0.0",
|
|
62
|
+
"ts-node": "^10.9.1",
|
|
63
|
+
"typescript": "5.2.2"
|
|
64
|
+
}
|
|
65
|
+
}
|