testit-adapter-cypress 3.7.5
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/README.md +240 -0
- package/dist/browser/commandLog.d.ts +10 -0
- package/dist/browser/commandLog.js +213 -0
- package/dist/browser/events/cypress.d.ts +2 -0
- package/dist/browser/events/cypress.js +38 -0
- package/dist/browser/events/index.d.ts +2 -0
- package/dist/browser/events/index.js +15 -0
- package/dist/browser/events/mocha.d.ts +3 -0
- package/dist/browser/events/mocha.js +80 -0
- package/dist/browser/index.d.ts +1 -0
- package/dist/browser/index.js +14 -0
- package/dist/browser/lifecycle.d.ts +21 -0
- package/dist/browser/lifecycle.js +227 -0
- package/dist/browser/patching.d.ts +5 -0
- package/dist/browser/patching.js +117 -0
- package/dist/browser/runtime.d.ts +31 -0
- package/dist/browser/runtime.js +284 -0
- package/dist/browser/serialize.d.ts +3 -0
- package/dist/browser/serialize.js +57 -0
- package/dist/browser/state.d.ts +28 -0
- package/dist/browser/state.js +80 -0
- package/dist/browser/steps.d.ts +14 -0
- package/dist/browser/steps.js +114 -0
- package/dist/browser/testplan.d.ts +2 -0
- package/dist/browser/testplan.js +55 -0
- package/dist/browser/types.d.ts +35 -0
- package/dist/browser/types.js +30 -0
- package/dist/browser/utils.d.ts +65 -0
- package/dist/browser/utils.js +187 -0
- package/dist/converter.d.ts +43 -0
- package/dist/converter.js +84 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/models/index.d.ts +0 -0
- package/dist/models/index.js +1 -0
- package/dist/models/status.d.ts +5 -0
- package/dist/models/status.js +9 -0
- package/dist/models/types.d.ts +280 -0
- package/dist/models/types.js +2 -0
- package/dist/node-utils.d.ts +15 -0
- package/dist/node-utils.js +93 -0
- package/dist/reporter.d.ts +24 -0
- package/dist/reporter.js +382 -0
- package/dist/utils.d.ts +17 -0
- package/dist/utils.js +34 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Test IT TMS adapters for Cypress
|
|
2
|
+

|
|
3
|
+
|
|
4
|
+
## Getting Started
|
|
5
|
+
|
|
6
|
+
### Installation
|
|
7
|
+
```
|
|
8
|
+
npm install testit-adapter-cypress
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Configuration
|
|
14
|
+
|
|
15
|
+
| Description | File property | Environment variable |
|
|
16
|
+
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------|--------------------------------------------|
|
|
17
|
+
| Location of the TMS instance | url | TMS_URL |
|
|
18
|
+
| API secret key [How to getting API secret key?](https://github.com/testit-tms/.github/tree/main/configuration#privatetoken) | privateToken | TMS_PRIVATE_TOKEN |
|
|
19
|
+
| ID of project in TMS instance [How to getting project ID?](https://github.com/testit-tms/.github/tree/main/configuration#projectid) | projectId | TMS_PROJECT_ID |
|
|
20
|
+
| ID of configuration in TMS instance [How to getting configuration ID?](https://github.com/testit-tms/.github/tree/main/configuration#configurationid) | configurationId | TMS_CONFIGURATION_ID |
|
|
21
|
+
| ID of the created test run in TMS instance.<br/>It's necessary for **adapterMode** 1 | testRunId | TMS_TEST_RUN_ID |
|
|
22
|
+
| Parameter for specifying the name of test run in TMS instance (**It's optional**). If it is not provided, it is created automatically | testRunName | TMS_TEST_RUN_NAME |
|
|
23
|
+
| Adapter mode. Default value - 1. The adapter supports following modes:<br>1 - in this mode, the adapter sends all results to the test run without filtering or [with filtering CLI](#run-with-filter)<br/>2 - in this mode, the adapter creates a new test run and sends results to the new test run | adapterMode | TMS_ADAPTER_MODE |
|
|
24
|
+
| It enables/disables certificate validation (**It's optional**). Default value - true | certValidation | TMS_CERT_VALIDATION |
|
|
25
|
+
| Mode of automatic creation test cases (**It's optional**). Default value - false. The adapter supports following modes:<br/>true - in this mode, the adapter will create a test case linked to the created autotest (not to the updated autotest)<br/>false - in this mode, the adapter will not create a test case | automaticCreationTestCases | TMS_AUTOMATIC_CREATION_TEST_CASES |
|
|
26
|
+
| Mode of automatic updation links to test cases (**It's optional**). Default value - false. The adapter supports following modes:<br/>true - in this mode, the adapter will update links to test cases<br/>false - in this mode, the adapter will not update link to test cases | automaticUpdationLinksToTestCases | TMS_AUTOMATIC_UPDATION_LINKS_TO_TEST_CASES |
|
|
27
|
+
|
|
28
|
+
Add Adapter to Cypress file configuration:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { tmsCypress } from "testit-adapter-cypress/reporter";
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
e2e: {
|
|
35
|
+
setupNodeEvents(on, config) {
|
|
36
|
+
tmsCypress(on, config);
|
|
37
|
+
|
|
38
|
+
return config;
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Add Adapter to `cypress/support/e2e.js`:
|
|
45
|
+
```ts
|
|
46
|
+
import "testit-adapter-cypress";
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### File
|
|
50
|
+
|
|
51
|
+
Create .env config or file config with default name tms.config.json in the root directory of the project
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"url": "URL",
|
|
56
|
+
"privateToken": "USER_PRIVATE_TOKEN",
|
|
57
|
+
"projectId": "PROJECT_ID",
|
|
58
|
+
"configurationId": "CONFIGURATION_ID",
|
|
59
|
+
"testRunId": "TEST_RUN_ID",
|
|
60
|
+
"testRunName": "TEST_RUN_NAME",
|
|
61
|
+
"adapterMode": "ADAPTER_MODE",
|
|
62
|
+
"automaticCreationTestCases": "AUTOMATIC_CREATION_TEST_CASES",
|
|
63
|
+
"automaticUpdationLinksToTestCases": "AUTOMATIC_UPDATION_LINKS_TO_TEST_CASES"
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### Parallel run
|
|
68
|
+
To create and complete TestRun you can use the Test IT CLI (use adapterMode 1 for parallel run):
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
$ export TMS_TOKEN=<YOUR_TOKEN>
|
|
72
|
+
$ testit testrun create
|
|
73
|
+
--url https://tms.testit.software \
|
|
74
|
+
--project-id 5236eb3f-7c05-46f9-a609-dc0278896464 \
|
|
75
|
+
--testrun-name "New test run" \
|
|
76
|
+
--output tmp/output.txt
|
|
77
|
+
|
|
78
|
+
$ export TMS_TEST_RUN_ID=$(cat tmp/output.txt)
|
|
79
|
+
|
|
80
|
+
$ npx cypress run
|
|
81
|
+
|
|
82
|
+
$ testit testrun complete
|
|
83
|
+
--url https://tms.testit.software \
|
|
84
|
+
--testrun-id $(cat tmp/output.txt)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### Run with filter
|
|
88
|
+
It is necessary to use test filtering in cypress. This example uses the "@cypress/grep" plugin.
|
|
89
|
+
To create filter by autotests you can use the Test IT CLI (use adapterMode 1 for run with filter):
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
$ export TMS_TOKEN=<YOUR_TOKEN>
|
|
93
|
+
$ testit autotests_filter
|
|
94
|
+
--url https://tms.testit.software \
|
|
95
|
+
--configuration-id 5236eb3f-7c05-46f9-a609-dc0278896464 \
|
|
96
|
+
--testrun-id 6d4ac4b7-dd67-4805-b879-18da0b89d4a8 \
|
|
97
|
+
--framework cypress \
|
|
98
|
+
--output tmp/filter.txt
|
|
99
|
+
|
|
100
|
+
$ export TMS_TEST_RUN_ID=6d4ac4b7-dd67-4805-b879-18da0b89d4a8
|
|
101
|
+
$ export TMS_ADAPTER_MODE=1
|
|
102
|
+
|
|
103
|
+
$ npx cypress run --expose grep="$(cat tmp/filter.txt)"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### Launch using GitLab repository
|
|
107
|
+
To run your Cypress test's from GitLab to TestIT or in reverse order using "testit-adapter-cypress", you can take this .gitlab-ci.yml file example:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
image: node:latest
|
|
111
|
+
|
|
112
|
+
stages:
|
|
113
|
+
- run
|
|
114
|
+
|
|
115
|
+
first-job:
|
|
116
|
+
stage: run
|
|
117
|
+
script:
|
|
118
|
+
- npm install
|
|
119
|
+
- npx cypress run
|
|
120
|
+
artifacts:
|
|
121
|
+
paths:
|
|
122
|
+
- node_modules/
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
### Methods
|
|
127
|
+
|
|
128
|
+
Methods can be used to specify information about autotest.
|
|
129
|
+
|
|
130
|
+
Description of methods:
|
|
131
|
+
- `tms.addWorkItemIds` - a dynamic method that links autotests with manual tests. Receives the array of manual tests' IDs
|
|
132
|
+
- `tms.addDisplayName` - a dynamic method for adding internal autotest name (used in Test IT)
|
|
133
|
+
- `tms.addTitle` - a dynamic method for adding autotest name specified in the autotest card. If not specified, the name from the displayName method is used
|
|
134
|
+
- `tms.addDescription` - a dynamic method for adding autotest description specified in the autotest card
|
|
135
|
+
- `tms.addLabels` - a dynamic method for adding labels listed in the autotest card
|
|
136
|
+
- `tms.addTags` - a dynamic method for adding tags listed in the autotest card
|
|
137
|
+
- `tms.addLinks` - links in the autotest result
|
|
138
|
+
- `tms.addAttachments` - uploading files in the autotest result
|
|
139
|
+
- `tms.addMessage` - information about autotest in the autotest result
|
|
140
|
+
- `tms.addNameSpace` - a dynamic method for adding directory in the TMS system (default - file's name of test)
|
|
141
|
+
- `tms.addClassName` - a dynamic method for adding subdirectory in the TMS system (default - class's name of test)
|
|
142
|
+
- `tms.addParameter` - a dynamic method for adding parameter in the autotest result
|
|
143
|
+
- `tms.step` - usage in the "with" construct to designation a step in the body of the test
|
|
144
|
+
|
|
145
|
+
### Examples
|
|
146
|
+
|
|
147
|
+
#### Simple test
|
|
148
|
+
```ts
|
|
149
|
+
import { getTestRuntime } from "testit-adapter-cypress/runtime";
|
|
150
|
+
|
|
151
|
+
describe('example to-do app', () => {
|
|
152
|
+
it('displays two todo items by default', () => {
|
|
153
|
+
const tms = getTestRuntime();
|
|
154
|
+
|
|
155
|
+
tms.addWorkItemIds('123', '321');
|
|
156
|
+
tms.addDisplayName('display name');
|
|
157
|
+
tms.addTitle('test title');
|
|
158
|
+
tms.addDescription('Test description');
|
|
159
|
+
tms.addLabels('label1', 'label2');
|
|
160
|
+
tms.addTags('tag1', 'tag2');
|
|
161
|
+
tms.addLinks([
|
|
162
|
+
{
|
|
163
|
+
url: 'https://www.google.com',
|
|
164
|
+
title: 'Google',
|
|
165
|
+
description: 'This is a link to Google',
|
|
166
|
+
type: 'Related',
|
|
167
|
+
},
|
|
168
|
+
]);
|
|
169
|
+
tms.addParameter("name", "value");
|
|
170
|
+
tms.addNameSpace("namespace");
|
|
171
|
+
tms.addClassName("classname");
|
|
172
|
+
|
|
173
|
+
tms.step("step title", () => {
|
|
174
|
+
// ...
|
|
175
|
+
}).then((user) => {
|
|
176
|
+
tms.step("inner step title", () => {
|
|
177
|
+
// ...
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Parameterized test
|
|
185
|
+
|
|
186
|
+
> [!WARNING]
|
|
187
|
+
> When linking a parameterized autotest to a parameterized test case, please consider the problematic points:
|
|
188
|
+
> - In TMS test cases have a table with parameters, but autotests do not. They are not equal entities, so there may be incompatibility in terms of parameters
|
|
189
|
+
> - Running a parameterized test case, TMS expects the results of all related autotests with all the parameters specified in the test case table
|
|
190
|
+
> - In TMS, the parameters are limited to the string type, so the adapter transmits absolutely all the autotest parameters as a string. This implies the following problematic point for the test case table
|
|
191
|
+
> - TMS expects a complete **textual** match of the name and value of the parameters of the test case table with the autotest parameters
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { getTestRuntime } from "testit-adapter-cypress/runtime";
|
|
195
|
+
|
|
196
|
+
describe('example to-do app', () => {
|
|
197
|
+
const tests = [2, 3, "string", false];
|
|
198
|
+
|
|
199
|
+
tests.forEach((value) => {
|
|
200
|
+
it(`3 is ${value}`, () => {
|
|
201
|
+
const tms = getTestRuntime();
|
|
202
|
+
|
|
203
|
+
tms.addParameter("name", value);
|
|
204
|
+
// ...
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### Content types
|
|
211
|
+
```
|
|
212
|
+
TEXT = "text/plain",
|
|
213
|
+
XML = "application/xml",
|
|
214
|
+
HTML = "text/html",
|
|
215
|
+
CSV = "text/csv",
|
|
216
|
+
TSV = "text/tab-separated-values",
|
|
217
|
+
CSS = "text/css",
|
|
218
|
+
URI = "text/uri-list",
|
|
219
|
+
SVG = "image/svg+xml",
|
|
220
|
+
PNG = "image/png",
|
|
221
|
+
JSON = "application/json",
|
|
222
|
+
ZIP = "application/zip",
|
|
223
|
+
WEBM = "video/webm",
|
|
224
|
+
JPEG = "image/jpeg",
|
|
225
|
+
MP4 = "video/mp4",
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
# Contributing
|
|
229
|
+
|
|
230
|
+
You can help to develop the project. Any contributions are **greatly appreciated**.
|
|
231
|
+
|
|
232
|
+
* If you have suggestions for adding or removing projects, feel free to [open an issue](https://github.com/testit-tms/adapters-js/issues/new) to discuss it, or directly create a pull request after you edit the *README.md* file with necessary changes.
|
|
233
|
+
* Please make sure you check your spelling and grammar.
|
|
234
|
+
* Create individual PR for each suggestion.
|
|
235
|
+
* Please also read through the [Code Of Conduct](https://github.com/testit-tms/adapters-js/blob/master/CODE_OF_CONDUCT.md) before posting your first idea as well.
|
|
236
|
+
|
|
237
|
+
# License
|
|
238
|
+
|
|
239
|
+
Distributed under the Apache-2.0 License. See [LICENSE](https://github.com/testit-tms/adapters-js/blob/master/LICENSE.md) for more information.
|
|
240
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CypressLogEntry } from "../models/types.js";
|
|
2
|
+
export declare const shouldCreateStepFromCommandLogEntry: (entry: CypressLogEntry) => boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Checks if the current step represents a cy.screenshot command log entry. If this is the case, associates the name
|
|
5
|
+
* of the screenshot with the step. Later, that will allow converting the step with the attachment into the attachment
|
|
6
|
+
* step.
|
|
7
|
+
*/
|
|
8
|
+
export declare const setupScreenshotAttachmentStep: (originalName: string | undefined, name: string) => void;
|
|
9
|
+
export declare const startCommandLogStep: (entry: CypressLogEntry) => void;
|
|
10
|
+
export declare const stopCommandLogStep: (entryId: string) => void;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.stopCommandLogStep = exports.startCommandLogStep = exports.setupScreenshotAttachmentStep = exports.shouldCreateStepFromCommandLogEntry = void 0;
|
|
7
|
+
const utils_js_1 = require("../utils.js");
|
|
8
|
+
const lifecycle_js_1 = require("./lifecycle.js");
|
|
9
|
+
const serialize_js_1 = __importDefault(require("./serialize.js"));
|
|
10
|
+
const state_js_1 = require("./state.js");
|
|
11
|
+
const steps_js_1 = require("./steps.js");
|
|
12
|
+
const utils_js_2 = require("./utils.js");
|
|
13
|
+
const shouldCreateStepFromCommandLogEntry = (entry) => {
|
|
14
|
+
const { event, instrument } = entry.attributes;
|
|
15
|
+
if (instrument !== "command") {
|
|
16
|
+
// We are interested in the "TEST BODY" panel only for now.
|
|
17
|
+
// Other instruments are logged in separate panels.
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
if (event) {
|
|
21
|
+
// Events are tricky to report as they may span across commands and even leave the test's scope.
|
|
22
|
+
// We ignore them for now.
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (isApiStepErrorLogEntry(entry)) {
|
|
26
|
+
// Cypress don't create a log message for 'cy.then' except when it throws an error.
|
|
27
|
+
// This is in particularly happens when the function passed to 'testit.step' throws. In such a case however,
|
|
28
|
+
// creating an extra step from the log entry is redundant because the error is already included in the report as
|
|
29
|
+
// a part of the step.
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
};
|
|
34
|
+
exports.shouldCreateStepFromCommandLogEntry = shouldCreateStepFromCommandLogEntry;
|
|
35
|
+
/**
|
|
36
|
+
* Checks if the current step represents a cy.screenshot command log entry. If this is the case, associates the name
|
|
37
|
+
* of the screenshot with the step. Later, that will allow converting the step with the attachment into the attachment
|
|
38
|
+
* step.
|
|
39
|
+
*/
|
|
40
|
+
const setupScreenshotAttachmentStep = (originalName, name) => {
|
|
41
|
+
const step = (0, state_js_1.getCurrentStep)();
|
|
42
|
+
if (step && (0, steps_js_1.isLogStep)(step)) {
|
|
43
|
+
const { name: commandName, props: { name: nameFromProps }, } = (0, utils_js_2.resolveConsoleProps)(step.log);
|
|
44
|
+
if (commandName === "screenshot" && nameFromProps === originalName) {
|
|
45
|
+
step.attachmentName = name;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
exports.setupScreenshotAttachmentStep = setupScreenshotAttachmentStep;
|
|
50
|
+
const startCommandLogStep = (entry) => {
|
|
51
|
+
const currentLogEntry = getCurrentLogEntry();
|
|
52
|
+
if (typeof currentLogEntry !== "undefined" && shouldStopCurrentLogStep(currentLogEntry.log, entry)) {
|
|
53
|
+
(0, exports.stopCommandLogStep)(currentLogEntry.log.attributes.id);
|
|
54
|
+
}
|
|
55
|
+
pushLogEntry(entry);
|
|
56
|
+
(0, lifecycle_js_1.reportStepStart)(entry.attributes.id, getCommandLogStepName(entry));
|
|
57
|
+
scheduleCommandLogStepStop(entry);
|
|
58
|
+
};
|
|
59
|
+
exports.startCommandLogStep = startCommandLogStep;
|
|
60
|
+
const stopCommandLogStep = (entryId) => (0, steps_js_1.findAndStopStepWithSubsteps)(({ id }) => id === entryId);
|
|
61
|
+
exports.stopCommandLogStep = stopCommandLogStep;
|
|
62
|
+
const pushLogEntry = (entry) => {
|
|
63
|
+
const id = entry.attributes.id;
|
|
64
|
+
const stepDescriptor = { id, type: "log", log: entry };
|
|
65
|
+
(0, state_js_1.pushStep)(stepDescriptor);
|
|
66
|
+
// Some properties of some Command Log entries are undefined at the time the entry is stopped. An example is the
|
|
67
|
+
// Yielded property of some queries. We defer converting them to Tms step parameters until the test/hook ends.
|
|
68
|
+
(0, state_js_1.setupStepFinalization)(stepDescriptor, (data) => {
|
|
69
|
+
data.parameters = getCommandLogStepParameters(entry);
|
|
70
|
+
if (stepDescriptor.attachmentName) {
|
|
71
|
+
// Rename the step to match the attachment name. Once the names are the same, Tms will render the
|
|
72
|
+
// attachment in the place of the step.
|
|
73
|
+
data.name = stepDescriptor.attachmentName;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Wraps `entry.endGroup` so that the Tms step created for a Command Log group
|
|
79
|
+
* is always stopped when Cypress finishes that group.
|
|
80
|
+
*
|
|
81
|
+
* Behavior:
|
|
82
|
+
* - Retains the original `entry.endGroup` handler.
|
|
83
|
+
* - Replaces it with a wrapper function that:
|
|
84
|
+
* - first calls `stopCommandLogStep(id)`, completing the step in our state;
|
|
85
|
+
* - then calls the original `entry.endGroup` with the same `this` so as not to break Cypress.
|
|
86
|
+
*/
|
|
87
|
+
const overrideEntryEndGroup = (entry, id) => {
|
|
88
|
+
const originalEndGroup = entry.endGroup;
|
|
89
|
+
entry.endGroup = function () {
|
|
90
|
+
(0, exports.stopCommandLogStep)(id);
|
|
91
|
+
return originalEndGroup.call(this);
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Wraps `entry.end` for single Command Log entries to synchronize
|
|
96
|
+
* the lifecycle of a step in Tms with the completion of a log entry in Cypress.
|
|
97
|
+
*
|
|
98
|
+
* Behavior:
|
|
99
|
+
* - Saves the original `entry.end` (without context binding).
|
|
100
|
+
* - Sets a new `entry.end`, which:
|
|
101
|
+
* - first calls `stopCommandLogStep(id)` to stop the step;
|
|
102
|
+
* - then calls the original `end` with the correct `this', while maintaining the standard behavior of Cypress.
|
|
103
|
+
*/
|
|
104
|
+
const overrideEntryEnd = (entry, id) => {
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
106
|
+
const originalEnd = entry.end;
|
|
107
|
+
entry.end = function () {
|
|
108
|
+
(0, exports.stopCommandLogStep)(id);
|
|
109
|
+
return originalEnd.call(this);
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
const scheduleCommandLogStepStop = (entry) => {
|
|
113
|
+
const { groupStart, end, id } = entry.attributes;
|
|
114
|
+
if (end) {
|
|
115
|
+
// Some entries are already completed (this is similar to the idea behind tms.logStep).
|
|
116
|
+
// Cypress won't call entry.end() in such a case, so we need to stop such a step now.
|
|
117
|
+
// Example: cy.log
|
|
118
|
+
(0, exports.stopCommandLogStep)(id);
|
|
119
|
+
}
|
|
120
|
+
else if (groupStart) {
|
|
121
|
+
// A logging group must be stopped be the user via the Cypress.Log.endGroup() call.
|
|
122
|
+
// If the call is missing, the corresponding step will be stopped either at the test's (the hook's) end.
|
|
123
|
+
overrideEntryEndGroup(entry, id);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Regular log entries are finalized by Cypress via the Cypress.Log.end() call. We're hooking into this function
|
|
127
|
+
// to complete the step at the same time.
|
|
128
|
+
overrideEntryEnd(entry, id);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const isApiStepErrorLogEntry = (entry) => {
|
|
132
|
+
const { name } = entry.attributes;
|
|
133
|
+
return name === "then" && Object.is((0, utils_js_2.resolveConsoleProps)(entry).props["Applied To"], steps_js_1.TMS_STEP_CMD_SUBJECT);
|
|
134
|
+
};
|
|
135
|
+
const getCommandLogStepName = (entry) => {
|
|
136
|
+
const { name, message, displayName } = entry.attributes;
|
|
137
|
+
const resolvedName = (displayName ?? name).trim();
|
|
138
|
+
const resolvedMessage = (maybeGetAssertionLogMessage(entry) ??
|
|
139
|
+
maybeGetCucumberLogMessage(entry) ??
|
|
140
|
+
(0, utils_js_2.resolveRenderProps)(entry).message ??
|
|
141
|
+
message).trim();
|
|
142
|
+
const stepName = [resolvedName, resolvedMessage].filter(Boolean).join(" ");
|
|
143
|
+
return stepName;
|
|
144
|
+
};
|
|
145
|
+
const getCommandLogStepParameters = (entry) => getLogProps(entry)
|
|
146
|
+
.map(([k, v]) => ({
|
|
147
|
+
name: k.toString(),
|
|
148
|
+
value: (0, serialize_js_1.default)(v),
|
|
149
|
+
}))
|
|
150
|
+
.filter(getPropValueSetFilter(entry));
|
|
151
|
+
const WELL_KNOWN_CUCUMBER_LOG_NAMES = ["Given", "When", "Then", "And"];
|
|
152
|
+
const maybeGetCucumberLogMessage = (entry) => {
|
|
153
|
+
const { attributes: { name, message }, } = entry;
|
|
154
|
+
if (WELL_KNOWN_CUCUMBER_LOG_NAMES.includes(name.trim()) && message.startsWith("**") && message.endsWith("**")) {
|
|
155
|
+
return message.substring(2, message.length - 2);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const getLogProps = (entry) => {
|
|
159
|
+
const isAssertionWithMessage = !!maybeGetAssertionLogMessage(entry);
|
|
160
|
+
const { props, name } = (0, utils_js_2.resolveConsoleProps)(entry);
|
|
161
|
+
// accessing LocalStorage after the page reload can stick the test runner
|
|
162
|
+
// to avoid the issue, we just need to log the command manually
|
|
163
|
+
// the problem potentially can happen with other storage related commands, like `clearAllLocalStorage`, `clearAllSessionStorage`, `getAllLocalStorage`, `getAllSessionStorage`, `setLocalStorage`, `setSessionStorage`
|
|
164
|
+
// but probably, we don't need to silent them all at this moment
|
|
165
|
+
if (["clearLocalStorage"].includes(name)) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
// For assertion logs, we interpolate the 'Message' property, which contains unformatted assertion description,
|
|
169
|
+
// directly into the step's name.
|
|
170
|
+
// No need to keep the exact same information in the step's parameters.
|
|
171
|
+
const entries = Object.entries(props);
|
|
172
|
+
return entries.filter(([key, value]) => isLogPropIncludedInParameters({ key, value, isAssertionWithMessage }));
|
|
173
|
+
};
|
|
174
|
+
const isLogPropIncludedInParameters = ({ key, value, isAssertionWithMessage, }) => {
|
|
175
|
+
if (!(0, utils_js_1.isDefined)(value)) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
if (isAssertionWithMessage && key === "Message") {
|
|
179
|
+
// The same text is already included into the step's name.
|
|
180
|
+
// No need to duplicate it in parameters.
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
};
|
|
185
|
+
const maybeGetAssertionLogMessage = (entry) => {
|
|
186
|
+
if (isAssertLog(entry)) {
|
|
187
|
+
const message = (0, utils_js_2.resolveConsoleProps)(entry).props.Message;
|
|
188
|
+
if (message && typeof message === "string") {
|
|
189
|
+
return message;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const isAssertLog = ({ attributes: { name } }) => name === "assert";
|
|
194
|
+
const getCurrentLogEntry = () => (0, state_js_1.getStepStack)().findLast(steps_js_1.isLogStep);
|
|
195
|
+
const shouldStopCurrentLogStep = (currentLogEntry, newLogEntry) => {
|
|
196
|
+
const { groupStart: currentEntryIsGroup, type: currentEntryType } = currentLogEntry.attributes;
|
|
197
|
+
const { type: newEntryType } = newLogEntry.attributes;
|
|
198
|
+
return !currentEntryIsGroup && (currentEntryType === "child" || newEntryType !== "child");
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* Returns a predicate that decides whether a step parameter should be included.
|
|
202
|
+
* For "wrap" commands we include all parameters; for others we drop "Yielded" when it's "{}".
|
|
203
|
+
*/
|
|
204
|
+
const getPropValueSetFilter = (entry) => {
|
|
205
|
+
const isWrapCommand = entry.attributes.name === "wrap";
|
|
206
|
+
if (isWrapCommand) {
|
|
207
|
+
return () => true;
|
|
208
|
+
}
|
|
209
|
+
return ({ name, value }) => shouldIncludeParameter(name, value);
|
|
210
|
+
};
|
|
211
|
+
const shouldIncludeParameter = (name, value) => {
|
|
212
|
+
return name !== "Yielded" || value !== "{}";
|
|
213
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enableReportingOfCypressScreenshots = exports.registerCypressEventListeners = void 0;
|
|
4
|
+
const commandLog_js_1 = require("../commandLog.js");
|
|
5
|
+
const lifecycle_js_1 = require("../lifecycle.js");
|
|
6
|
+
const steps_js_1 = require("../steps.js");
|
|
7
|
+
const utils_js_1 = require("../utils.js");
|
|
8
|
+
const registerCypressEventListeners = () => Cypress.on("fail", onFail).on("log:added", onLogAdded);
|
|
9
|
+
exports.registerCypressEventListeners = registerCypressEventListeners;
|
|
10
|
+
const enableReportingOfCypressScreenshots = () => Cypress.Screenshot.defaults({ onAfterScreenshot });
|
|
11
|
+
exports.enableReportingOfCypressScreenshots = enableReportingOfCypressScreenshots;
|
|
12
|
+
const onAfterScreenshot = (...[, { name: originalName, path }]) => {
|
|
13
|
+
const name = originalName ?? (0, utils_js_1.getFileNameFromPath)(path);
|
|
14
|
+
(0, lifecycle_js_1.reportScreenshot)(path, name);
|
|
15
|
+
(0, commandLog_js_1.setupScreenshotAttachmentStep)(originalName, name);
|
|
16
|
+
};
|
|
17
|
+
const onLogAdded = (_, entry) => {
|
|
18
|
+
if ((0, commandLog_js_1.shouldCreateStepFromCommandLogEntry)(entry)) {
|
|
19
|
+
(0, commandLog_js_1.startCommandLogStep)(entry);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const onFail = (error) => {
|
|
23
|
+
(0, steps_js_1.reportStepError)(error);
|
|
24
|
+
if (noSubsequentFailListeners()) {
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Returns true if this handler is the last registered "fail" listener in Cypress.
|
|
30
|
+
*
|
|
31
|
+
* In that case we rethrow the error so that Cypress can apply its default error handling.
|
|
32
|
+
* If there are other listeners after ours, we assume they will handle the error.
|
|
33
|
+
*/
|
|
34
|
+
const noSubsequentFailListeners = () => {
|
|
35
|
+
const failListeners = Cypress.listeners("fail");
|
|
36
|
+
const lastFailListener = failListeners.at(-1);
|
|
37
|
+
return Object.is(lastFailListener, onFail);
|
|
38
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enableReportingOfCypressScreenshots = exports.enableTms = void 0;
|
|
4
|
+
const patching_js_1 = require("../patching.js");
|
|
5
|
+
const cypress_js_1 = require("./cypress.js");
|
|
6
|
+
const mocha_js_1 = require("./mocha.js");
|
|
7
|
+
const enableTms = () => {
|
|
8
|
+
(0, mocha_js_1.registerMochaEventListeners)();
|
|
9
|
+
(0, cypress_js_1.registerCypressEventListeners)();
|
|
10
|
+
(0, mocha_js_1.injectFlushMessageHooks)();
|
|
11
|
+
(0, patching_js_1.enableScopeLevelAfterHookReporting)();
|
|
12
|
+
};
|
|
13
|
+
exports.enableTms = enableTms;
|
|
14
|
+
var cypress_js_2 = require("./cypress.js");
|
|
15
|
+
Object.defineProperty(exports, "enableReportingOfCypressScreenshots", { enumerable: true, get: function () { return cypress_js_2.enableReportingOfCypressScreenshots; } });
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.injectFlushMessageHooks = exports.registerMochaEventListeners = exports.TMS_REPORT_SYSTEM_HOOK = void 0;
|
|
4
|
+
const lifecycle_js_1 = require("../lifecycle.js");
|
|
5
|
+
const runtime_js_1 = require("../runtime.js");
|
|
6
|
+
const testplan_js_1 = require("../testplan.js");
|
|
7
|
+
const utils_js_1 = require("../utils.js");
|
|
8
|
+
exports.TMS_REPORT_SYSTEM_HOOK = "__tms_report_system_hook__";
|
|
9
|
+
const registerMochaEventListeners = () => {
|
|
10
|
+
Cypress.mocha.getRunner()
|
|
11
|
+
.on("start", onStart)
|
|
12
|
+
.on("suite", onSuite)
|
|
13
|
+
.on("suite end", lifecycle_js_1.reportSuiteEnd)
|
|
14
|
+
.on("hook", onHook)
|
|
15
|
+
.on("hook end", onHookEnd)
|
|
16
|
+
.on("test", onTest)
|
|
17
|
+
.on("pass", lifecycle_js_1.reportTestPass)
|
|
18
|
+
.on("fail", onFail)
|
|
19
|
+
.on("pending", lifecycle_js_1.reportTestSkip)
|
|
20
|
+
.on("test end", lifecycle_js_1.reportTestEnd);
|
|
21
|
+
};
|
|
22
|
+
exports.registerMochaEventListeners = registerMochaEventListeners;
|
|
23
|
+
const injectFlushMessageHooks = () => {
|
|
24
|
+
afterEach(exports.TMS_REPORT_SYSTEM_HOOK, lifecycle_js_1.flushRuntimeMessages);
|
|
25
|
+
after(exports.TMS_REPORT_SYSTEM_HOOK, onAfterAll);
|
|
26
|
+
};
|
|
27
|
+
exports.injectFlushMessageHooks = injectFlushMessageHooks;
|
|
28
|
+
const onStart = () => {
|
|
29
|
+
(0, runtime_js_1.initTestRuntime)();
|
|
30
|
+
(0, lifecycle_js_1.reportRunStart)();
|
|
31
|
+
};
|
|
32
|
+
const onSuite = (suite) => {
|
|
33
|
+
if (suite.root) {
|
|
34
|
+
(0, testplan_js_1.applyTestPlan)(Cypress.spec, suite);
|
|
35
|
+
}
|
|
36
|
+
(0, lifecycle_js_1.reportSuiteStart)(suite);
|
|
37
|
+
};
|
|
38
|
+
const onHook = (hook) => {
|
|
39
|
+
if ((0, utils_js_1.isTmsHook)(hook)) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
(0, lifecycle_js_1.reportHookStart)(hook);
|
|
43
|
+
};
|
|
44
|
+
const onHookEnd = (hook) => {
|
|
45
|
+
if ((0, utils_js_1.isTmsHook)(hook)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
(0, lifecycle_js_1.reportHookEnd)(hook);
|
|
49
|
+
};
|
|
50
|
+
const onTest = (test) => {
|
|
51
|
+
// Cypress emits an extra EVENT_TEST_BEGIN if the test is skipped.
|
|
52
|
+
// reportTestSkip does that already, so we need to filter the extra event out.
|
|
53
|
+
if (!(0, utils_js_1.isTestReported)(test)) {
|
|
54
|
+
(0, lifecycle_js_1.reportTestStart)(test);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const onFail = (testOrHook, err) => {
|
|
58
|
+
const isHook = "hookName" in testOrHook;
|
|
59
|
+
if (isHook && (0, utils_js_1.isRootAfterAllHook)(testOrHook)) {
|
|
60
|
+
// Errors in spec-level 'after all' hooks are handled by Tms wrappers.
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const isTmsHookFailure = isHook && (0, utils_js_1.isTmsHook)(testOrHook);
|
|
64
|
+
if (isTmsHookFailure) {
|
|
65
|
+
// Normally, Tms hooks are skipped from the report.
|
|
66
|
+
// In case of errors, it will be helpful to see them.
|
|
67
|
+
(0, lifecycle_js_1.reportHookStart)(testOrHook, Date.now() - (testOrHook.duration ?? 0));
|
|
68
|
+
}
|
|
69
|
+
// This will mark the fixture and the test (if any) as failed/broken.
|
|
70
|
+
(0, lifecycle_js_1.reportTestOrHookFail)(err);
|
|
71
|
+
if (isHook) {
|
|
72
|
+
// This will end the fixture and test (if any) and will report the remaining
|
|
73
|
+
// tests in the hook's suite (the ones that will be skipped by Cypress/Mocha).
|
|
74
|
+
(0, lifecycle_js_1.completeHookErrorReporting)(testOrHook, err);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const onAfterAll = function () {
|
|
78
|
+
(0, lifecycle_js_1.flushRuntimeMessages)();
|
|
79
|
+
(0, lifecycle_js_1.completeSpecIfNoAfterHookLeft)(this);
|
|
80
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const initializeTms: () => void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.initializeTms = void 0;
|
|
4
|
+
const index_js_1 = require("./events/index.js");
|
|
5
|
+
const state_js_1 = require("./state.js");
|
|
6
|
+
const initializeTms = () => {
|
|
7
|
+
if ((0, state_js_1.isTmsInitialized)()) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
(0, state_js_1.setTmsInitialized)();
|
|
11
|
+
(0, index_js_1.enableTms)();
|
|
12
|
+
(0, index_js_1.enableReportingOfCypressScreenshots)();
|
|
13
|
+
};
|
|
14
|
+
exports.initializeTms = initializeTms;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CypressHook, CypressSuite, CypressTest, StepDescriptor, StatusDetails } from "../models/types.js";
|
|
2
|
+
import { Status } from "../models/status.js";
|
|
3
|
+
export declare const reportRunStart: () => void;
|
|
4
|
+
export declare const reportSuiteStart: (suite: CypressSuite) => void;
|
|
5
|
+
export declare const reportSuiteEnd: (suite: CypressSuite) => void;
|
|
6
|
+
export declare const reportHookStart: (hook: CypressHook, start?: number) => void;
|
|
7
|
+
export declare const reportHookEnd: (hook: CypressHook) => void;
|
|
8
|
+
export declare const reportTestStart: (test: CypressTest) => void;
|
|
9
|
+
export declare const reportStepStart: (id: string, name: string) => void;
|
|
10
|
+
export declare const reportStepStop: (step: StepDescriptor, status?: Status, statusDetails?: StatusDetails) => void;
|
|
11
|
+
export declare const reportTestPass: () => void;
|
|
12
|
+
export declare const reportTestOrHookFail: (err: Error) => void;
|
|
13
|
+
export declare const completeHookErrorReporting: (hook: CypressHook, err: Error) => void;
|
|
14
|
+
export declare const reportTestSkip: (test: CypressTest) => void;
|
|
15
|
+
export declare const reportTestEnd: (test: CypressTest) => void;
|
|
16
|
+
export declare const reportScreenshot: (path: string, name: string) => void;
|
|
17
|
+
export declare const completeSpecIfNoAfterHookLeft: (context: Mocha.Context) => Cypress.Chainable<unknown> | undefined;
|
|
18
|
+
export declare const completeSpecOnAfterHookFailure: (context: Mocha.Context, hookError: Error) => Cypress.Chainable<unknown> | undefined;
|
|
19
|
+
export declare const throwAfterSpecCompletion: (context: Mocha.Context, err: any) => void;
|
|
20
|
+
export declare const flushRuntimeMessages: () => void;
|
|
21
|
+
export declare const completeSpecAsync: () => Cypress.Chainable<unknown> | undefined;
|