testit-adapter-jest 1.1.0

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 ADDED
@@ -0,0 +1,190 @@
1
+ # Test IT TMS adapters for Jest
2
+ ![Test IT](https://raw.githubusercontent.com/testit-tms/adapters-js/master/images/banner.png)
3
+
4
+ # Cucumber
5
+
6
+ ## Getting Started
7
+
8
+ ### Compatibility
9
+
10
+ | Test IT | Adapter |
11
+ |---------|---------|
12
+ | 3.5 | 1.0 |
13
+ |---------|---------|
14
+ | 4.0 | 1.1 |
15
+
16
+ ### Installation
17
+ ```
18
+ npm install testit-adapter-jest
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ### API client
24
+
25
+ To use adapter you need to install `testit-api-client`:
26
+ ```
27
+ npm install testit-api-client
28
+ ```
29
+
30
+ ### Configuration
31
+
32
+ #### File
33
+
34
+ 1. You need to set custom jest test environment, setup and teardown in `jest.config.js`.
35
+
36
+ ```js
37
+ module.exports = {
38
+ testEnvironment: 'testit-adapter-jest',
39
+ globalSetup: 'testit-adapter-jest/dist/globalSetup.js',
40
+ globalTeardown: 'testit-adapter-jest/dist/globalTeardown.js',
41
+ testEnvironmentOptions: {
42
+ url: '<url>',
43
+ privateToken: '<token>',
44
+ projectId: '<id>',
45
+ configurationId: '<id>',
46
+ testRunId: '<optional id>',
47
+ },
48
+ };
49
+ ```
50
+
51
+ 2. You also can extract environment configuration to external config and launch tests with `jest --config ./testit.jest.config.js`.
52
+
53
+ ```js
54
+ // testit.jest.config.js
55
+ const defaultConfig = require('./jest.config');
56
+
57
+ module.exports = {
58
+ ...defaultConfig,
59
+ testEnvironment: 'testit-adapter-jest',
60
+ globalSetup: 'testit-adapter-jest/dist/globalSetup.js',
61
+ globalTeardown: 'testit-adapter-jest/dist/globalTeardown.js',
62
+ testEnvironmentOptions: {
63
+ url: '<url>',
64
+ privateToken: '<token>',
65
+ projectId: '<id>',
66
+ configurationId: '<id>',
67
+ testRunId: '<optional id>',
68
+ },
69
+ };
70
+ ```
71
+
72
+ 3. Fill parameters with your configuration, where:
73
+ * `url` - location of the TMS instance
74
+
75
+ * `privateToken` - API secret key
76
+ 1. go to the https://{DOMAIN}/user-profile profile
77
+ 2. copy the API secret key
78
+
79
+ * `projectId` - ID of project in TMS instance.
80
+
81
+ 1. create a project
82
+ 2. open DevTools -> network
83
+ 3. go to the project https://{DOMAIN}/projects/{PROJECT_ID}/tests
84
+ 4. GET-request project, Preview tab, copy id field
85
+
86
+ * `configurationId` - ID of configuration in TMS instance.
87
+
88
+ 1. create a project
89
+ 2. open DevTools -> network
90
+ 3. go to the project https://{DOMAIN}/projects/{PROJECT_ID}/tests
91
+ 4. GET-request configurations, Preview tab, copy id field
92
+
93
+ * `testRunId` - id of the created test run in TMS instance. `testRunId` is optional. If it is not provided, it is created automatically.
94
+
95
+ * `testRunName` - parameter for specifying the name of test run in TMS instance. `testRunName` is optional. If it is not provided, it is created automatically.
96
+
97
+ #### Command line
98
+
99
+ You can also specify options via cli arguments `jest --testEnvironment testit-adapter-jest --testEnvironmentOptions "{\"url\":\"<url>\",\"privateToken\":\"<token>\",\"projectId\":\"<id>\",\"configurationId\":\"<id>\",\"testRunId\":\"<optional id>\"}" --globalSetup testit-adapter-jest/dist/globalSetup.js --globalTeardown testit-adapter-jest/dist/globalTeardown.js`
100
+
101
+
102
+ ### Methods
103
+
104
+ Methods can be used to specify information about autotest.
105
+
106
+ Description of metadata methods:
107
+ - `testit.workItemIds` - linking an autotest to a test case
108
+ - `testit.displayName` - name of the autotest in the Test IT system (can be replaced with documentation strings)
109
+ - `testit.externalId` - ID of the autotest within the project in the Test IT System
110
+ - `testit.title` - title in the autotest card
111
+ - `testit.description` - description in the autotest card
112
+ - `testit.labels` - tags in the work item
113
+ - `testit.link` - links in the autotest card
114
+
115
+ Description of methods:
116
+ - `testit.addLinks` - links in the autotest result
117
+ - `testit.addAttachments` - uploading files in the autotest result
118
+ - `testit.addMessage` - information about autotest in the autotest result
119
+
120
+ ### Examples
121
+
122
+ #### Simple test
123
+ ```js
124
+ test('All annotations', () => {
125
+ testit.externalId('all_annotations');
126
+ testit.displayName('All annotations');
127
+ testit.title('All annotations title');
128
+ testit.description('Test with all annotations');
129
+ testit.labels(['label1', 'label2']);
130
+
131
+ testit.addMessage('This is a message');
132
+ testit.addLinks([
133
+ {
134
+ url: 'https://www.google.com',
135
+ title: 'Google',
136
+ description: 'This is a link to Google',
137
+ type: 'Related',
138
+ },
139
+ ]);
140
+
141
+ testit.addAttachments([join(__dirname, 'attachment1.txt')]);
142
+ testit.addAttachments('This is a custom attachment', 'custom.txt');
143
+
144
+ expect(1).toBe(1);
145
+ });
146
+ ```
147
+
148
+ #### Parameterized test
149
+ ```js
150
+ test.each([1, 2, 3, 4])('Primitive params', (number) => {
151
+ testit.params(number);
152
+ expect(number).toBe(number);
153
+ });
154
+
155
+ test.each([
156
+ {
157
+ a: 1,
158
+ b: 2,
159
+ sum: 3,
160
+ },
161
+ {
162
+ a: 2,
163
+ b: 3,
164
+ sum: 5,
165
+ },
166
+ {
167
+ a: 4,
168
+ b: 3,
169
+ sum: 5,
170
+ }
171
+ ])('Object params', (params) => {
172
+ testit.params(params);
173
+ expect(params.a + params.b).toBe(params.sum);
174
+ });
175
+ ```
176
+
177
+
178
+ # Contributing
179
+
180
+ You can help to develop the project. Any contributions are **greatly appreciated**.
181
+
182
+ * 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.
183
+ * Please make sure you check your spelling and grammar.
184
+ * Create individual PR for each suggestion.
185
+ * 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.
186
+
187
+ # License
188
+
189
+ Distributed under the Apache-2.0 License. See [LICENSE](https://github.com/testit-tms/adapters-js/blob/master/LICENSE.md) for more information.
190
+
@@ -0,0 +1 @@
1
+ export type AdapterMode = 0 | 1 | 2;
package/dist/config.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ import d from 'debug';
2
+ export declare const debug: d.Debugger;
package/dist/debug.js ADDED
@@ -0,0 +1,8 @@
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.debug = void 0;
7
+ const debug_1 = __importDefault(require("debug"));
8
+ exports.debug = (0, debug_1.default)('testit');
@@ -0,0 +1,3 @@
1
+ import { Config } from '@jest/reporters';
2
+ declare const _default: (globalConfig: Config.GlobalConfig, projectConfig: Config.ProjectConfig) => Promise<void>;
3
+ export default _default;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const testClient_1 = require("./testClient");
4
+ const utils_1 = require("./utils");
5
+ exports.default = async (globalConfig, projectConfig) => {
6
+ const adapterMode = projectConfig.testEnvironmentOptions?.adapterMode ?? 0;
7
+ let testRunId;
8
+ try {
9
+ switch (adapterMode) {
10
+ case 0:
11
+ case 1: {
12
+ testRunId = projectConfig.testEnvironmentOptions?.testRunId;
13
+ if (!testRunId) {
14
+ throw new Error('testRunId is required when mode is 1');
15
+ }
16
+ globalThis.testClient = new testClient_1.TestClient(projectConfig.testEnvironmentOptions);
17
+ break;
18
+ }
19
+ case 2: {
20
+ globalThis.testClient = new testClient_1.TestClient(projectConfig.testEnvironmentOptions);
21
+ testRunId = await globalThis.testClient.createTestRun();
22
+ break;
23
+ }
24
+ default:
25
+ throw new Error(`Unknown adapter mode ${adapterMode}`);
26
+ }
27
+ }
28
+ catch (err) {
29
+ console.error('Failed to setup', (0, utils_1.formatError)(err));
30
+ process.exit(1);
31
+ }
32
+ projectConfig.globals['testRunId'] = testRunId;
33
+ };
@@ -0,0 +1,3 @@
1
+ import { Config } from '@jest/reporters';
2
+ declare const _default: (globalConfig: Config.GlobalConfig, projectConfig: Config.ProjectConfig) => Promise<void>;
3
+ export default _default;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("./utils");
4
+ exports.default = async (globalConfig, projectConfig) => {
5
+ const testRunId = projectConfig.globals['testRunId'];
6
+ if (!testRunId) {
7
+ console.error('Looks like globalSetup was not called');
8
+ return;
9
+ }
10
+ try {
11
+ await globalThis.testClient.completeTestRun();
12
+ }
13
+ catch (err) {
14
+ console.error('Failed to complete test run', (0, utils_1.formatError)(err));
15
+ }
16
+ };
@@ -0,0 +1,7 @@
1
+ import { AttachmentPut, AttachmentPutModelAutotestStepResults, AutotestStep, Parameters } from 'testit-api-client';
2
+ import { StepData } from './types';
3
+ export declare function mapDate(date: number): string;
4
+ export declare function mapStep(step: StepData): AutotestStep;
5
+ export declare function mapParams(params: any): Parameters;
6
+ export declare function mapStepResult(step: StepData): AttachmentPutModelAutotestStepResults;
7
+ export declare function mapAttachments(attachments: string[]): AttachmentPut[];
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mapAttachments = exports.mapStepResult = exports.mapParams = exports.mapStep = exports.mapDate = void 0;
4
+ function mapDate(date) {
5
+ return new Date(date).toISOString();
6
+ }
7
+ exports.mapDate = mapDate;
8
+ function mapStep(step) {
9
+ return {
10
+ title: step.title,
11
+ description: step.description,
12
+ };
13
+ }
14
+ exports.mapStep = mapStep;
15
+ function mapParams(params) {
16
+ switch (typeof params) {
17
+ case 'string':
18
+ case 'bigint':
19
+ case 'number':
20
+ case 'boolean':
21
+ return { value: params.toString() };
22
+ case 'object':
23
+ if (params === null) {
24
+ return {};
25
+ }
26
+ return Object.keys(params).reduce((acc, key) => {
27
+ acc[key] = params[key].toString();
28
+ return acc;
29
+ }, {});
30
+ default:
31
+ return {};
32
+ }
33
+ }
34
+ exports.mapParams = mapParams;
35
+ function mapStepResult(step) {
36
+ return {
37
+ title: step.title,
38
+ description: step.description,
39
+ attachments: mapAttachments(step.attachments),
40
+ };
41
+ }
42
+ exports.mapStepResult = mapStepResult;
43
+ function mapAttachments(attachments) {
44
+ return attachments.map((id) => ({ id }));
45
+ }
46
+ exports.mapAttachments = mapAttachments;
@@ -0,0 +1,18 @@
1
+ import { AutotestPost, AutotestResultsForTestRun, ClientConfig } from 'testit-api-client';
2
+ export declare class TestClient {
3
+ constructor(clientConfig?: Partial<ClientConfig>);
4
+ private client;
5
+ private _testRunId;
6
+ get testRunId(): string;
7
+ get projectId(): string;
8
+ get configurationId(): string;
9
+ createTestRun(): Promise<string>;
10
+ startTestRun(): Promise<void>;
11
+ completeTestRun(): Promise<void>;
12
+ loadAutotest(autotestPost: AutotestPost): Promise<string>;
13
+ loadPassedAutotest(autotest: AutotestPost): Promise<void>;
14
+ getAutotestId(externalId: string): Promise<string>;
15
+ linkWorkItem(externalId: string, workItemId: string): Promise<void>;
16
+ loadAutotestResults(results: AutotestResultsForTestRun[]): Promise<void>;
17
+ uploadAttachments(attachments: string[]): Promise<string[]>;
18
+ }
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TestClient = void 0;
4
+ const testit_api_client_1 = require("testit-api-client");
5
+ const debug_1 = require("./debug");
6
+ const utils_1 = require("./utils");
7
+ const log = debug_1.debug.extend('client');
8
+ class TestClient {
9
+ constructor(clientConfig = {}) {
10
+ this.client = new testit_api_client_1.Client(clientConfig);
11
+ this._testRunId = clientConfig.testRunId;
12
+ }
13
+ get testRunId() {
14
+ if (this._testRunId === undefined) {
15
+ throw new Error('Test run id is not set');
16
+ }
17
+ return this._testRunId;
18
+ }
19
+ get projectId() {
20
+ return this.client.getConfig().projectId;
21
+ }
22
+ get configurationId() {
23
+ return this.client.getConfig().configurationId;
24
+ }
25
+ async createTestRun() {
26
+ const { projectId, testRunId } = this.client.getConfig();
27
+ if (testRunId === undefined) {
28
+ log('Test run id is not provided, creating test run for project %s', projectId);
29
+ this._testRunId = await this.client
30
+ .createTestRun({ projectId })
31
+ .then((testRun) => testRun.id);
32
+ }
33
+ else {
34
+ log('Using provided test run id %s', testRunId);
35
+ this._testRunId = testRunId;
36
+ }
37
+ log('Starting test run %s', this.testRunId);
38
+ return this.testRunId;
39
+ }
40
+ async startTestRun() {
41
+ log('Starting test run %s', this.testRunId);
42
+ await this.client.startTestRun(this.testRunId);
43
+ }
44
+ async completeTestRun() {
45
+ log('Completing test run %s', this.testRunId);
46
+ const testRun = await this.client.getTestRun(this.testRunId);
47
+ if (testRun.stateName === 'InProgress') {
48
+ await this.client.completeTestRun(this.testRunId);
49
+ }
50
+ }
51
+ async loadAutotest(autotestPost) {
52
+ try {
53
+ log('Creating autotest %o', autotestPost);
54
+ const { id } = await this.client.createAutotest(autotestPost);
55
+ return id;
56
+ }
57
+ catch (err) {
58
+ const axiosError = err;
59
+ if (axiosError.response?.status === 409) {
60
+ log('Autotest %s already exists, updating with %o', autotestPost.externalId, autotestPost);
61
+ const [autotest] = await this.client.getAutotest({
62
+ projectId: this.client.getConfig().projectId,
63
+ externalId: autotestPost.externalId,
64
+ });
65
+ await this.client.updateAutotest({
66
+ ...autotest,
67
+ links: autotest.links,
68
+ });
69
+ return autotest.id;
70
+ }
71
+ else {
72
+ console.error((0, utils_1.formatError)(err));
73
+ throw err;
74
+ }
75
+ }
76
+ }
77
+ async loadPassedAutotest(autotest) {
78
+ try {
79
+ log('Creating autotest %o', autotest);
80
+ await this.client.createAutotest(autotest);
81
+ }
82
+ catch (err) {
83
+ const axiosError = err;
84
+ if (axiosError.response?.status === 409) {
85
+ log('Autotest %s already exists, updating with %o', autotest.externalId, autotest);
86
+ await this.client.updateAutotest(autotest);
87
+ }
88
+ else {
89
+ console.error((0, utils_1.formatError)(err));
90
+ throw err;
91
+ }
92
+ }
93
+ }
94
+ async getAutotestId(externalId) {
95
+ const [autotest] = await this.client.getAutotest({
96
+ projectId: this.client.getConfig().projectId,
97
+ externalId,
98
+ });
99
+ return autotest.id;
100
+ }
101
+ async linkWorkItem(externalId, workItemId) {
102
+ log('Linking work item %s to autotest %s', workItemId, externalId);
103
+ return this.client.linkToWorkItem(externalId, { id: workItemId });
104
+ }
105
+ async loadAutotestResults(results) {
106
+ log('Loading autotest results %o', results);
107
+ await this.client.loadTestRunResults(this.testRunId, results);
108
+ }
109
+ async uploadAttachments(attachments) {
110
+ const attachmentIds = [];
111
+ for (const attachment of attachments) {
112
+ try {
113
+ log('Uploading attachment %s', attachment);
114
+ const { id } = await this.client.loadAttachment(attachment);
115
+ if (!id) {
116
+ log('Attachment id is not returned');
117
+ continue;
118
+ }
119
+ attachmentIds.push(id);
120
+ }
121
+ catch (err) {
122
+ console.error(`Failed to load attachment`, (0, utils_1.formatError)(err));
123
+ }
124
+ }
125
+ return attachmentIds;
126
+ }
127
+ }
128
+ exports.TestClient = TestClient;
@@ -0,0 +1,55 @@
1
+ /// <reference types="node" />
2
+ import type { EnvironmentContext, JestEnvironmentConfig } from '@jest/environment';
3
+ import { Event, State } from 'jest-circus';
4
+ import NodeEnvironment from 'jest-environment-node';
5
+ import { LinkPost } from 'testit-api-client';
6
+ export default class TestItEnvironment extends NodeEnvironment {
7
+ private autotestData;
8
+ private currentStepData;
9
+ private testClient;
10
+ private beforeAllSteps;
11
+ private afterAllSteps;
12
+ private currentType;
13
+ private autotestResults;
14
+ private autotests;
15
+ private testPath;
16
+ private attachmentsQueue;
17
+ constructor(config: JestEnvironmentConfig, context: EnvironmentContext);
18
+ setup(): Promise<void>;
19
+ teardown(): Promise<void>;
20
+ getVmContext(): import("vm").Context | null;
21
+ exportConditions(): string[];
22
+ handleTestEvent(event: Event, state: State): Promise<void>;
23
+ startHookCapture(hook: Extract<Event, {
24
+ name: 'hook_start';
25
+ }>['hook']): void;
26
+ finishHookCapture(hook: Extract<Event, {
27
+ name: 'hook_start';
28
+ }>['hook']): void;
29
+ startTestCapture(test: Extract<Event, {
30
+ name: 'test_fn_start';
31
+ }>['test']): void;
32
+ finishTestCapture(test: Extract<Event, {
33
+ name: 'test_fn_success';
34
+ }>['test']): void;
35
+ saveResult(test: Extract<Event, {
36
+ name: 'test_done';
37
+ }>['test']): void;
38
+ loadResults(): Promise<void>;
39
+ resetTest(): void;
40
+ resetStep(): void;
41
+ setExternalId(id: string): void;
42
+ setDisplayName(name: string): void;
43
+ setTitle(title: string): void;
44
+ setDescription(description: string): void;
45
+ addAttachments(attachment: string, name?: string): void;
46
+ addAttachments(attachments: string[]): void;
47
+ addLinks(links: LinkPost[]): void;
48
+ addMessage(message: string): void;
49
+ setAutotestLinks(links: LinkPost[]): void;
50
+ setLabels(labels: string[]): void;
51
+ setWorkItems(workItems: string[]): void;
52
+ setParams(params: any): void;
53
+ startStep(name: string, description?: string): void;
54
+ generateExternalId(testName: string): string;
55
+ }
@@ -0,0 +1,352 @@
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
+ const jest_environment_node_1 = __importDefault(require("jest-environment-node"));
7
+ const debug_1 = require("./debug");
8
+ const mappers_1 = require("./mappers");
9
+ const testClient_1 = require("./testClient");
10
+ const utils_1 = require("./utils");
11
+ const log = debug_1.debug.extend('environment');
12
+ const emptyTitle = '__EMPTY__';
13
+ const emptyAutotestData = () => ({
14
+ name: emptyTitle,
15
+ steps: [],
16
+ afterEach: [],
17
+ beforeEach: [],
18
+ attachments: [],
19
+ links: [],
20
+ runtimeLinks: [],
21
+ labels: [],
22
+ workItems: [],
23
+ });
24
+ const emptyStepData = () => ({
25
+ title: emptyTitle,
26
+ attachments: [],
27
+ });
28
+ class TestItEnvironment extends jest_environment_node_1.default {
29
+ constructor(config, context) {
30
+ super(config, context);
31
+ this.autotestData = emptyAutotestData();
32
+ this.currentStepData = emptyStepData();
33
+ this.beforeAllSteps = [];
34
+ this.afterAllSteps = [];
35
+ this.autotestResults = [];
36
+ this.autotests = [];
37
+ this.attachmentsQueue = [];
38
+ const testRunId = config.projectConfig.globals['testRunId'];
39
+ if (!testRunId || typeof testRunId !== 'string') {
40
+ throw new Error('Looks like globalSetup was not called');
41
+ }
42
+ this.testClient = new testClient_1.TestClient({
43
+ ...config.projectConfig.testEnvironmentOptions,
44
+ testRunId,
45
+ });
46
+ this.testPath = (0, utils_1.excludePath)(context.testPath, config.globalConfig.rootDir);
47
+ }
48
+ async setup() {
49
+ await super.setup();
50
+ (0, utils_1.createTempDir)();
51
+ this.global.testit = {
52
+ externalId: this.setExternalId.bind(this),
53
+ displayName: this.setDisplayName.bind(this),
54
+ links: this.setAutotestLinks.bind(this),
55
+ labels: this.setLabels.bind(this),
56
+ workItemIds: this.setWorkItems.bind(this),
57
+ params: this.setParams.bind(this),
58
+ step: this.startStep.bind(this),
59
+ title: this.setTitle.bind(this),
60
+ description: this.setDescription.bind(this),
61
+ addAttachments: this.addAttachments.bind(this),
62
+ addLinks: this.addLinks.bind(this),
63
+ addMessage: this.addMessage.bind(this),
64
+ };
65
+ }
66
+ async teardown() {
67
+ await super.teardown();
68
+ (0, utils_1.removeTempDir)();
69
+ }
70
+ getVmContext() {
71
+ return super.getVmContext();
72
+ }
73
+ exportConditions() {
74
+ return super.exportConditions();
75
+ }
76
+ async handleTestEvent(event, state) {
77
+ switch (event.name) {
78
+ case 'hook_start': {
79
+ this.startHookCapture(event.hook);
80
+ break;
81
+ }
82
+ case 'hook_success':
83
+ case 'hook_failure': {
84
+ this.finishHookCapture(event.hook);
85
+ break;
86
+ }
87
+ case 'test_fn_start': {
88
+ this.startTestCapture(event.test);
89
+ break;
90
+ }
91
+ case 'test_fn_success':
92
+ case 'test_fn_failure': {
93
+ this.finishTestCapture(event.test);
94
+ break;
95
+ }
96
+ case 'test_done': {
97
+ this.saveResult(event.test);
98
+ break;
99
+ }
100
+ case 'test_skip': {
101
+ this.resetTest();
102
+ break;
103
+ }
104
+ case 'run_finish': {
105
+ await this.loadResults();
106
+ break;
107
+ }
108
+ }
109
+ }
110
+ startHookCapture(hook) {
111
+ log('Starting hook capture %s', hook.type);
112
+ this.currentType = hook.type;
113
+ // Use the hook type as the step name
114
+ this.currentStepData.title = hook.type;
115
+ }
116
+ finishHookCapture(hook) {
117
+ log('Finishing hook capture %s', hook.type);
118
+ switch (hook.type) {
119
+ case 'beforeAll': {
120
+ this.beforeAllSteps.push(this.currentStepData);
121
+ break;
122
+ }
123
+ case 'afterAll': {
124
+ this.afterAllSteps.push(this.currentStepData);
125
+ break;
126
+ }
127
+ case 'beforeEach': {
128
+ this.autotestData.beforeEach.push(this.currentStepData);
129
+ break;
130
+ }
131
+ case 'afterEach': {
132
+ this.autotestData.afterEach.push(this.currentStepData);
133
+ break;
134
+ }
135
+ }
136
+ this.resetStep();
137
+ }
138
+ startTestCapture(test) {
139
+ log('Starting test capture %s', test.name);
140
+ this.currentType = 'test';
141
+ this.setDisplayName(test.name);
142
+ }
143
+ finishTestCapture(test) {
144
+ log('Finishing test capture %s', test.name);
145
+ if (this.currentStepData.title !== emptyTitle) {
146
+ this.autotestData.steps.push(this.currentStepData);
147
+ this.resetStep();
148
+ }
149
+ this.currentType = undefined;
150
+ }
151
+ saveResult(test) {
152
+ log('Saving result for %s', test.name);
153
+ const errorMessage = test.errors.length > 0
154
+ ? test.errors.map((err) => err[0]?.message).join('\n')
155
+ : undefined;
156
+ const errorTraces = test.errors.length > 0
157
+ ? test.errors.map((err) => err[0]?.stack).join('\n')
158
+ : undefined;
159
+ const result = {
160
+ isFailed: test.errors.length > 0,
161
+ startedAt: test.startedAt ?? undefined,
162
+ duration: test.duration ?? undefined,
163
+ finishedAt: test.startedAt && (0, utils_1.isDefined)(test.duration)
164
+ ? test.startedAt + test.duration
165
+ : undefined,
166
+ message: errorMessage,
167
+ trace: errorTraces,
168
+ };
169
+ this.autotests.push(this.autotestData);
170
+ this.autotestResults.push(result);
171
+ this.resetTest();
172
+ }
173
+ async loadResults() {
174
+ log('Waiting for attachments to be uploaded');
175
+ await Promise.all(this.attachmentsQueue);
176
+ const results = [];
177
+ for (let i = 0; i < this.autotests.length; i++) {
178
+ const autotest = this.autotests[i];
179
+ const result = this.autotestResults[i];
180
+ log('Mapping autotest %s', autotest.name);
181
+ const setupSteps = this.beforeAllSteps.concat(autotest.beforeEach);
182
+ const teardownSteps = autotest.afterEach.concat(this.afterAllSteps);
183
+ const autotestPost = {
184
+ projectId: this.testClient.projectId,
185
+ externalId: autotest.externalId ?? this.generateExternalId(autotest.name),
186
+ title: autotest.title,
187
+ name: autotest.name,
188
+ description: autotest.description,
189
+ namespace: (0, utils_1.getDir)(this.testPath),
190
+ classname: (0, utils_1.getFileName)(this.testPath),
191
+ setup: setupSteps.map(mappers_1.mapStep),
192
+ steps: autotest.steps.map(mappers_1.mapStep),
193
+ teardown: teardownSteps.map(mappers_1.mapStep),
194
+ links: autotest.links,
195
+ labels: autotest.labels.map((label) => ({ name: label })),
196
+ };
197
+ if (!result.isFailed) {
198
+ await this.testClient.loadPassedAutotest(autotestPost);
199
+ }
200
+ else {
201
+ await this.testClient.loadAutotest(autotestPost);
202
+ }
203
+ if (autotest.workItems.length > 0) {
204
+ const id = await this.testClient.getAutotestId(autotestPost.externalId);
205
+ try {
206
+ await Promise.all(autotest.workItems.map((workItem) => {
207
+ return this.testClient.linkWorkItem(id, workItem);
208
+ }));
209
+ }
210
+ catch (err) {
211
+ console.error('Failed to link work items', (0, utils_1.formatError)(err));
212
+ }
213
+ }
214
+ const messages = [];
215
+ if (autotest.message) {
216
+ messages.push(autotest.message);
217
+ }
218
+ if (result.message) {
219
+ messages.push(result.message);
220
+ }
221
+ results.push({
222
+ autotestExternalId: autotestPost.externalId,
223
+ configurationId: this.testClient.configurationId,
224
+ outcome: result.isFailed ? 'Failed' : 'Passed',
225
+ startedOn: result.startedAt ? (0, mappers_1.mapDate)(result.startedAt) : undefined,
226
+ duration: result.duration ? result.duration : undefined,
227
+ completeOn: result.finishedAt ? (0, mappers_1.mapDate)(result.finishedAt) : undefined,
228
+ attachments: (0, mappers_1.mapAttachments)(autotest.attachments),
229
+ message: messages.length > 0 ? messages.join('\n') : undefined,
230
+ traces: result.trace,
231
+ stepResults: autotest.steps.map(mappers_1.mapStepResult),
232
+ setupResults: setupSteps.map(mappers_1.mapStepResult),
233
+ teardownResults: teardownSteps.map(mappers_1.mapStepResult),
234
+ links: autotest.runtimeLinks,
235
+ parameters: autotest.params !== undefined
236
+ ? (0, mappers_1.mapParams)(autotest.params)
237
+ : undefined,
238
+ });
239
+ }
240
+ log('Loading results');
241
+ await this.testClient.loadAutotestResults(results);
242
+ }
243
+ resetTest() {
244
+ this.autotestData = emptyAutotestData();
245
+ this.currentType = undefined;
246
+ }
247
+ resetStep() {
248
+ this.currentStepData = emptyStepData();
249
+ }
250
+ setExternalId(id) {
251
+ log('Setting external id to %s', id);
252
+ this.autotestData.externalId = id;
253
+ }
254
+ setDisplayName(name) {
255
+ log('Setting display name to %s', name);
256
+ this.autotestData.name = name;
257
+ }
258
+ setTitle(title) {
259
+ if (this.currentType === 'test') {
260
+ log('Setting autotest title to %s', title);
261
+ this.autotestData.title = title;
262
+ }
263
+ else {
264
+ log('Setting step title to %s', title);
265
+ this.currentStepData.title = title;
266
+ }
267
+ }
268
+ setDescription(description) {
269
+ if (this.currentType === 'test') {
270
+ log('Setting autotest description to %s', description);
271
+ this.autotestData.description = description;
272
+ }
273
+ else {
274
+ log('Setting step description to %s', description);
275
+ this.currentStepData.description = description;
276
+ }
277
+ }
278
+ addAttachments(attachments, name) {
279
+ const autotest = this.autotestData;
280
+ const step = this.currentStepData;
281
+ let target;
282
+ if (this.currentType === 'test') {
283
+ log('Adding attachments to %s', autotest.name);
284
+ target = autotest.attachments;
285
+ }
286
+ else {
287
+ log('Adding attachments to %s', step.title);
288
+ target = step.attachments;
289
+ }
290
+ let files;
291
+ if (Array.isArray(attachments)) {
292
+ files = attachments;
293
+ }
294
+ else {
295
+ if (!name) {
296
+ name = (0, utils_1.generateFileName)();
297
+ }
298
+ const path = (0, utils_1.createTempFile)(name, attachments);
299
+ files = [path];
300
+ }
301
+ const promise = this.testClient.uploadAttachments(files).then((ids) => {
302
+ target.push(...ids);
303
+ });
304
+ this.attachmentsQueue.push(promise);
305
+ return promise;
306
+ }
307
+ addLinks(links) {
308
+ log('Adding links to %s', this.autotestData.name);
309
+ this.autotestData.runtimeLinks.push(...links);
310
+ }
311
+ addMessage(message) {
312
+ log('Adding message to %s', this.autotestData.name);
313
+ this.autotestData.message = message;
314
+ }
315
+ setAutotestLinks(links) {
316
+ log('Setting autotest links to %s', this.autotestData.name);
317
+ this.autotestData.links = links;
318
+ }
319
+ setLabels(labels) {
320
+ log('Setting labels to %s', this.autotestData.name);
321
+ this.autotestData.labels = labels;
322
+ }
323
+ setWorkItems(workItems) {
324
+ log('Setting work items to %s', this.autotestData.name);
325
+ this.autotestData.workItems = workItems;
326
+ }
327
+ setParams(params) {
328
+ log('Setting params to %s', this.autotestData.name);
329
+ this.autotestData.params = params;
330
+ }
331
+ startStep(name, description) {
332
+ log('Starting step %s', name);
333
+ if (this.currentType !== 'test' && this.currentType !== 'step') {
334
+ log('Step can only be started in test');
335
+ return;
336
+ }
337
+ if (this.currentType === 'step') {
338
+ this.autotestData.steps.push(this.currentStepData);
339
+ this.resetStep();
340
+ }
341
+ this.currentType = 'step';
342
+ this.currentStepData.title = name;
343
+ this.currentStepData.description = description;
344
+ }
345
+ generateExternalId(testName) {
346
+ return (0, utils_1.getHash)(JSON.stringify({
347
+ path: this.testPath,
348
+ name: testName,
349
+ }));
350
+ }
351
+ }
352
+ exports.default = TestItEnvironment;
@@ -0,0 +1,32 @@
1
+ import { LinkPost } from 'testit-api-client';
2
+ export type StepData = {
3
+ title: string;
4
+ description?: string;
5
+ attachments: string[];
6
+ };
7
+ export type AutotestData = {
8
+ name: string;
9
+ title?: string;
10
+ description?: string;
11
+ externalId?: string;
12
+ namespace?: string;
13
+ classname?: string;
14
+ attachments: string[];
15
+ beforeEach: StepData[];
16
+ steps: StepData[];
17
+ afterEach: StepData[];
18
+ links: LinkPost[];
19
+ runtimeLinks: LinkPost[];
20
+ message?: string;
21
+ labels: string[];
22
+ workItems: string[];
23
+ params?: any;
24
+ };
25
+ export type AutotestResult = {
26
+ startedAt?: number;
27
+ duration?: number;
28
+ finishedAt?: number;
29
+ isFailed: boolean;
30
+ message?: string;
31
+ trace?: string;
32
+ };
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,13 @@
1
+ export declare function isDefined<T>(value: T | undefined | null): value is T;
2
+ export declare function nullToUndefined<T>(value: T | undefined | null): T | undefined;
3
+ export declare function excludePath(source: string, toRemove: string): string;
4
+ export declare function normalizePath(path: string): string;
5
+ export declare function getHash(input: string): string;
6
+ export declare function getDir(file: string): string;
7
+ export declare function getFileName(file: string): string;
8
+ export declare function createTempDir(): void;
9
+ export declare function createTempFile(name: string, content: string): string;
10
+ export declare function removeTempDir(): void;
11
+ export declare function generateExternalId(path: string, testName: string): string;
12
+ export declare function generateFileName(): string;
13
+ export declare function formatError(error: any): any;
package/dist/utils.js ADDED
@@ -0,0 +1,85 @@
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.formatError = exports.generateFileName = exports.generateExternalId = exports.removeTempDir = exports.createTempFile = exports.createTempDir = exports.getFileName = exports.getDir = exports.getHash = exports.normalizePath = exports.excludePath = exports.nullToUndefined = exports.isDefined = void 0;
7
+ const crypto_1 = require("crypto");
8
+ const fs_1 = require("fs");
9
+ const os_1 = require("os");
10
+ const path_1 = require("path");
11
+ const axios_1 = __importDefault(require("axios"));
12
+ const debug_1 = require("./debug");
13
+ const log = debug_1.debug.extend('utils');
14
+ function isDefined(value) {
15
+ return value !== undefined && value !== null;
16
+ }
17
+ exports.isDefined = isDefined;
18
+ function nullToUndefined(value) {
19
+ return value ?? undefined;
20
+ }
21
+ exports.nullToUndefined = nullToUndefined;
22
+ function excludePath(source, toRemove) {
23
+ return source.replace(toRemove, '');
24
+ }
25
+ exports.excludePath = excludePath;
26
+ function normalizePath(path) {
27
+ return (0, path_1.normalize)(path);
28
+ }
29
+ exports.normalizePath = normalizePath;
30
+ function getHash(input) {
31
+ return (0, crypto_1.createHash)('md5').update(input).digest('hex');
32
+ }
33
+ exports.getHash = getHash;
34
+ function getDir(file) {
35
+ return (0, path_1.dirname)(file);
36
+ }
37
+ exports.getDir = getDir;
38
+ function getFileName(file) {
39
+ return (0, path_1.parse)(file).base;
40
+ }
41
+ exports.getFileName = getFileName;
42
+ const dirPrefix = 'testit-';
43
+ let tempDir;
44
+ function createTempDir() {
45
+ log('Creating temp dir');
46
+ tempDir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), dirPrefix));
47
+ log('Created temp dir %s', tempDir);
48
+ }
49
+ exports.createTempDir = createTempDir;
50
+ function createTempFile(name, content) {
51
+ if (!tempDir) {
52
+ throw new Error('Temp dir is not created');
53
+ }
54
+ log('Creating temp file %s in %s', name, tempDir);
55
+ const path = (0, path_1.join)(tempDir, name);
56
+ (0, fs_1.writeFileSync)(path, content);
57
+ return path;
58
+ }
59
+ exports.createTempFile = createTempFile;
60
+ function removeTempDir() {
61
+ if (tempDir) {
62
+ log('Removing temp dir %s', tempDir);
63
+ (0, fs_1.rmSync)(tempDir, { recursive: true });
64
+ }
65
+ }
66
+ exports.removeTempDir = removeTempDir;
67
+ function generateExternalId(path, testName) {
68
+ return getHash(JSON.stringify({
69
+ path: path,
70
+ name: testName,
71
+ }));
72
+ }
73
+ exports.generateExternalId = generateExternalId;
74
+ function generateFileName() {
75
+ return `${(0, crypto_1.randomUUID)()}-attachment.txt`;
76
+ }
77
+ exports.generateFileName = generateFileName;
78
+ function formatError(error) {
79
+ return axios_1.default.isAxiosError(error)
80
+ ? error.response?.data ?? error.response?.statusText ?? error.message
81
+ : error instanceof Error
82
+ ? error.message
83
+ : error;
84
+ }
85
+ exports.formatError = formatError;
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "testit-adapter-jest",
3
+ "version": "1.1.0",
4
+ "description": "",
5
+ "main": "dist/testitEnvironment.js",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "dev": "tsc --watch"
9
+ },
10
+ "keywords": [],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "dependencies": {
14
+ "debug": "^4.3.4",
15
+ "testit-api-client": "^1.0.5"
16
+ },
17
+ "devDependencies": {
18
+ "@types/debug": "^4.1.7",
19
+ "@types/jest": "^29.2.2",
20
+ "@types/node": "^18.11.9",
21
+ "jest": "^29.3.1",
22
+ "typescript": "^4.8.4"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/testit-tms/adapters-js/issues"
26
+ },
27
+ "homepage": "https://github.com/testit-tms/adapters-js",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/testit-tms/adapters-js.git"
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ]
35
+ }