@orangebeard-io/playwright-orangebeard-reporter 1.0.1 → 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.
@@ -1,144 +0,0 @@
1
- name: release
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
-
8
- jobs:
9
- get-version:
10
- runs-on: ubuntu-latest
11
- outputs:
12
- releaseVersion: ${{ steps.exposeVersion.outputs.releaseVersion }}
13
- steps:
14
- - name: Checkout repository
15
- uses: actions/checkout@v4
16
- - name: Setup Node.js
17
- uses: actions/setup-node@v4
18
- with:
19
- node-version: '20'
20
- - name: Cache node modules
21
- uses: actions/cache@v4
22
- with:
23
- path: node_modules
24
- key: node_modules-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
25
- restore-keys: |
26
- node_modules-
27
- - name: Install Node dependencies
28
- run: npm install
29
- - name: Get version from package.json
30
- id: exposeVersion
31
- run: echo "releaseVersion=$(npm run get-version --silent)" >> $GITHUB_OUTPUT
32
- prepare-release:
33
- needs: get-version
34
- runs-on: ubuntu-latest
35
- outputs:
36
- versionInfo: ${{ steps.readChangelogEntry.outputs.log_entry }}
37
- steps:
38
- - name: Checkout repository
39
- uses: actions/checkout@v4
40
- - name: Configure git
41
- run: |
42
- git config --global user.email "info@orangebeard.io"
43
- git config --global user.name "Orangebeard.io"
44
- - name: Create tag
45
- run: |
46
- git tag -a v${{ needs.get-version.outputs.releaseVersion }} -m ${{ needs.get-version.outputs.releaseVersion }}
47
- git push origin main --follow-tags
48
- - name: Setup Node.js
49
- uses: actions/setup-node@v4
50
- with:
51
- node-version: '20'
52
- - name: Cache node modules
53
- uses: actions/cache@v4
54
- with:
55
- path: node_modules
56
- key: node_modules-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
57
- restore-keys: |
58
- node_modules-
59
- - name: Install Node dependencies
60
- run: npm install
61
- - name: Create CHANGELOG.md
62
- run: npm run create-changelog
63
- - name: Upload changelog as artifact
64
- uses: actions/upload-artifact@v4
65
- with:
66
- name: changelog
67
- path: CHANGELOG.md
68
- create-release:
69
- needs: [get-version, prepare-release]
70
- runs-on: ubuntu-latest
71
- steps:
72
- - name: Checkout repository
73
- uses: actions/checkout@v4
74
- - name: Download changelog for artifact
75
- uses: actions/download-artifact@v4
76
- with:
77
- name: changelog
78
- - name: Create Release
79
- id: createRelease
80
- uses: ncipollo/release-action@v1
81
- env:
82
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
83
- with:
84
- tag: v${{ needs.get-version.outputs.releaseVersion }}
85
- name: ${{ needs.get-version.outputs.releaseVersion }}
86
- bodyFile: CHANGELOG.md
87
- publish-release:
88
- needs: [get-version, prepare-release, create-release]
89
- runs-on: ubuntu-latest
90
- permissions:
91
- contents: read
92
- id-token: write
93
- steps:
94
- - name: Checkout repository
95
- uses: actions/checkout@v4
96
- - name: Setup Node.js
97
- uses: actions/setup-node@v4
98
- with:
99
- node-version: '20'
100
- registry-url: 'https://registry.npmjs.org'
101
- - name: Cache node modules
102
- uses: actions/cache@v4
103
- with:
104
- path: node_modules
105
- key: node_modules-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
106
- restore-keys: |
107
- node_modules-
108
- - name: Transpile to JS
109
- run: npm run build
110
- - name: Install Node dependencies
111
- run: npm install
112
- - name: Publish to NPM
113
- run: npm publish --provenance --access public
114
- env:
115
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
116
- update-version:
117
- needs: [get-version, prepare-release, create-release, publish-release]
118
- runs-on: ubuntu-latest
119
- steps:
120
- - name: Checkout repository
121
- uses: actions/checkout@v4
122
- - name: Configure git
123
- run: |
124
- git config --global user.email "info@orangebeard.io"
125
- git config --global user.name "Orangebeard.io"
126
- - name: Setup Node.js
127
- uses: actions/setup-node@v4
128
- with:
129
- node-version: '20'
130
- - name: Cache node modules
131
- uses: actions/cache@v4
132
- with:
133
- path: node_modules
134
- key: node_modules-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
135
- restore-keys: |
136
- node_modules-
137
- - name: Install Node dependencies
138
- run: npm install
139
- - name: Update version
140
- run: |
141
- npm run update-version
142
- git add package.json package-lock.json
143
- git commit -m "Update version"
144
- git push origin main
package/.prettierrc DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "arrowParens": "always",
3
- "singleQuote": true,
4
- "trailingComma": "all",
5
- "printWidth": 100,
6
- "endOfLine": "auto"
7
- }
@@ -1,29 +0,0 @@
1
- {{#each releases}}
2
- {{#if summary}}
3
- {{summary}}
4
- {{/if}}
5
-
6
- {{#if merges}}
7
- ### :twisted_rightwards_arrows: Merged
8
-
9
- {{#each merges}}
10
- - {{#if commit.breaking}}**Breaking change:** {{/if}}{{message}} {{#if href}}[`#{{id}}`]({{href}}){{/if}}
11
- {{/each}}
12
- {{/if}}
13
-
14
- {{#if fixes}}
15
- ### :bug: Fixed
16
-
17
- {{#each fixes}}
18
- - {{#if commit.breaking}}**Breaking change:** {{/if}}{{commit.subject}}{{#each fixes}} {{#if href}}[`#{{id}}`]({{href}}){{/if}}{{/each}}
19
- {{/each}}
20
- {{/if}}
21
-
22
- {{#commit-list commits heading='### :mag: Commits'}}
23
- - {{#if breaking}}**Breaking change:** {{/if}}{{subject}} {{#if href}}[`{{shorthash}}`]({{href}}){{/if}}
24
- {{/commit-list}}
25
-
26
- {{#if href}}
27
- See full comparison at [{{title}}]({{href}})
28
- {{/if}}
29
- {{/each}}
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- import {OrangebeardReporter} from "./reporter/OrangebeardReporter";
2
-
3
- export default OrangebeardReporter;
@@ -1,265 +0,0 @@
1
- import {UUID} from 'crypto';
2
- import {Reporter, TestCase, TestResult, TestStep} from '@playwright/test/reporter'
3
- import {ansiToMarkdown, getBytes, getCodeSnippet, getTime, removeAnsi, testStatusMap} from './utils'
4
- import {OrangebeardParameters} from "@orangebeard-io/javascript-client/dist/client/models/OrangebeardParameters";
5
- import OrangebeardAsyncV3Client from "@orangebeard-io/javascript-client/dist/client/OrangebeardAsyncV3Client";
6
- import {StartTest} from "@orangebeard-io/javascript-client/dist/client/models/StartTest";
7
- import {Attachment} from "@orangebeard-io/javascript-client/dist/client/models/Attachment";
8
- import {Log} from "@orangebeard-io/javascript-client/dist/client/models/Log";
9
- import {Attribute} from "@orangebeard-io/javascript-client/dist/client/models/Attribute";
10
- import {FinishStep} from "@orangebeard-io/javascript-client/dist/client/models/FinishStep";
11
- import TestType = StartTest.TestType;
12
- import LogFormat = Log.LogFormat;
13
- import LogLevel = Log.LogLevel;
14
- import Status = FinishStep.Status;
15
- import * as path from "node:path";
16
-
17
- export class OrangebeardReporter implements Reporter {
18
-
19
- config: OrangebeardParameters;
20
- client: OrangebeardAsyncV3Client;
21
-
22
- //CONTEXT TRACKING
23
- testRunId: UUID;
24
- suites: Map<string, UUID> = new Map<string, UUID>(); //suiteNames , uuid
25
- tests: Map<string, UUID> = new Map<string, UUID>(); //testId, uuid
26
- steps: Map<string, UUID> = new Map<string, UUID>(); //testId_stepPath, uuid
27
- promises: Promise<void>[] = [];
28
-
29
- constructor() {
30
- this.client = new OrangebeardAsyncV3Client();
31
- this.config = this.client.config;
32
- }
33
-
34
- onBegin(): void {
35
- this.testRunId = this.client.startTestRun({
36
- testSetName: this.config.testset,
37
- description: this.config.description,
38
- startTime: getTime(),
39
- attributes: this.config.attributes
40
- })
41
- }
42
-
43
- async onEnd(): Promise<void> {
44
- await Promise.all(this.promises)
45
- return this.client.finishTestRun(this.testRunId, {endTime: getTime()})
46
- }
47
-
48
- onStdErr(chunk: string | Buffer, test: void | TestCase, _result: void | TestResult): void {
49
- //log error level
50
-
51
- if (typeof test === 'object' && test !== null) {
52
- const testUUID = this.tests.get(test.id);
53
- const message = chunk.toString();
54
- this.client.log({
55
- logFormat: LogFormat.PLAIN_TEXT,
56
- logLevel: LogLevel.ERROR,
57
- logTime: getTime(),
58
- message: message,
59
- testRunUUID: this.testRunId,
60
- testUUID: testUUID
61
- });
62
- }
63
- }
64
-
65
- onStdOut(chunk: string | Buffer, test: void | TestCase, _result: void | TestResult): void {
66
- if (typeof test === 'object' && test !== null) {
67
- const testUUID = this.tests.get(test.id);
68
- const message = chunk.toString();
69
- this.client.log({
70
- logFormat: LogFormat.PLAIN_TEXT,
71
- logLevel: LogLevel.INFO,
72
- logTime: getTime(),
73
- message: message,
74
- testRunUUID: this.testRunId,
75
- testUUID: testUUID
76
- });
77
- }
78
- }
79
-
80
- onStepBegin(test: TestCase, _result: TestResult, step: TestStep): void {
81
- //start step
82
- const testUUID = this.tests.get(test.id);
83
-
84
- const stepUUID = this.client.startStep({
85
- startTime: getTime(),
86
- stepName: step.title,
87
- description: step.location ? `${path.basename(step.location.file)}:${step.location.line}`: undefined,
88
- testRunUUID: this.testRunId,
89
- testUUID: testUUID,
90
- parentStepUUID: step.parent ? this.steps.get(test.id + "|" + step.parent.titlePath()) : undefined,
91
- })
92
- this.steps.set(test.id + "|" + step.titlePath(), stepUUID)
93
-
94
- if(step.location) {
95
- this.client.log({
96
- logFormat: LogFormat.MARKDOWN,
97
- logLevel: LogLevel.INFO,
98
- logTime: getTime(),
99
- message: getCodeSnippet(step.location.file, step.location.line),
100
- testRunUUID: this.testRunId,
101
- testUUID: testUUID,
102
- stepUUID: stepUUID
103
- });
104
- }
105
- }
106
-
107
- onStepEnd(test: TestCase, _result: TestResult, step: TestStep): void {
108
- const testUUID = this.tests.get(test.id);
109
- const stepUUID = this.steps.get(test.id + "|" + step.titlePath())
110
- if(step.error) {
111
- const message = step.error.message;
112
- this.client.log({
113
- logFormat: LogFormat.MARKDOWN,
114
- logLevel: LogLevel.ERROR,
115
- logTime: getTime(),
116
- message: ansiToMarkdown(message),
117
- testRunUUID: this.testRunId,
118
- testUUID: testUUID,
119
- stepUUID: stepUUID
120
- });
121
-
122
- if (step.error.snippet) {
123
- this.client.log({
124
- logFormat: LogFormat.MARKDOWN,
125
- logLevel: LogLevel.ERROR,
126
- logTime: getTime(),
127
- message: `\`\`\`js\n${removeAnsi(step.error.snippet)}\n\`\`\``,
128
- testRunUUID: this.testRunId,
129
- testUUID: testUUID,
130
- stepUUID: stepUUID
131
- });
132
- }
133
- }
134
-
135
- this.client.finishStep(this.steps.get(test.id + "|" + step.titlePath()), {
136
- endTime: getTime(),
137
- status: step.error ? Status.FAILED : Status.PASSED,
138
- testRunUUID: this.testRunId
139
- })
140
- this.steps.delete(test.id + "|" + step.titlePath())
141
- }
142
-
143
- onTestBegin(test: TestCase): void {
144
- //check suite
145
- const suiteUUID = this.getOrStartSuite(test.parent.titlePath())
146
- const attributes: Array<Attribute> = [];
147
- for (const tag of test.tags) {
148
- attributes.push({value: tag})
149
- }
150
- const testUUID = this.client.startTest({
151
- testType: TestType.TEST,
152
- testRunUUID: this.testRunId,
153
- suiteUUID: suiteUUID,
154
- testName: test.title,
155
- startTime: getTime(),
156
- description: this.getTestDescription(test),
157
- attributes: attributes
158
- });
159
- this.tests.set(test.id, testUUID);
160
- }
161
-
162
- async onTestEnd(test: TestCase, result: TestResult): Promise<void> {
163
- const testUUID = this.tests.get(test.id);
164
- if (result.attachments.length > 0) {
165
- let message = "";
166
- for (const attachment of result.attachments) {
167
- message += `- ${attachment.name} (${attachment.contentType})\n`
168
- }
169
- const attachmentsLogUUID = this.client.log({
170
- logFormat: LogFormat.MARKDOWN,
171
- logLevel: LogLevel.INFO,
172
- logTime: getTime(),
173
- message: message,
174
- testRunUUID: this.testRunId,
175
- testUUID: testUUID
176
- })
177
- for (const attachment of result.attachments) {
178
- this.promises.push(this.logAttachment(attachment, testUUID, attachmentsLogUUID));
179
- }
180
- }
181
-
182
- //determine status
183
- const status = testStatusMap[result.status]
184
-
185
- //finish test
186
- this.client.finishTest(testUUID, {
187
- testRunUUID: this.testRunId,
188
- status: status,
189
- endTime: getTime()
190
- });
191
- this.tests.delete(test.id);
192
- }
193
-
194
- printsToStdio(): boolean {
195
- return false;
196
- }
197
-
198
- private getOrStartSuite(suitePath: Array<string>): UUID {
199
- const filteredSuitePath = suitePath.filter(name => name !== "");
200
- let currentPath: Array<string> = [];
201
- let parentSuiteUUID: UUID | undefined = undefined;
202
-
203
- for (const suiteName of filteredSuitePath) {
204
- currentPath.push(suiteName);
205
- const existingSuiteUUID = this.suites.get(currentPath.join('|'));
206
-
207
- if (existingSuiteUUID) {
208
- parentSuiteUUID = existingSuiteUUID;
209
- } else {
210
- const newSuitesUUIDs = this.client.startSuite({
211
- testRunUUID: this.testRunId,
212
- parentSuiteUUID: parentSuiteUUID,
213
- suiteNames: [suiteName],
214
- });
215
-
216
- if (newSuitesUUIDs && newSuitesUUIDs.length > 0) {
217
- parentSuiteUUID = newSuitesUUIDs[0];
218
- this.suites.set(currentPath.join('|'), parentSuiteUUID);
219
- } else {
220
- console.error(`Failed to create suite for path: ${currentPath.join(' > ')}`);
221
- }
222
- }
223
- }
224
- return parentSuiteUUID as UUID;
225
- }
226
-
227
- private getTestDescription(test: TestCase): string {
228
- let description = `${path.basename(test.location.file)}:${test.location.line}\n`;
229
- for (const annotation of test.annotations) {
230
- description = `${description + annotation.type}: ${annotation.description}\n`
231
- }
232
- return description;
233
- }
234
-
235
- private async logAttachment(attachment: {
236
- name: string,
237
- path?: string,
238
- body?: Buffer,
239
- contentType: string
240
- }, testUUID: UUID, logUUID: UUID) {
241
- let content: Buffer;
242
- if (attachment.body) {
243
- content = attachment.body;
244
- } else if (attachment.path) {
245
- content = await getBytes(attachment.path);
246
- } else {
247
- throw new Error("Attachment must have either body or path defined.");
248
- }
249
-
250
- const orangebeardAttachment: Attachment = {
251
- file: {
252
- name: path.basename(attachment.path),
253
- content: content,
254
- contentType: attachment.contentType,
255
- },
256
- metaData: {
257
- testRunUUID: this.testRunId,
258
- testUUID: testUUID,
259
- logUUID: logUUID,
260
- attachmentTime: getTime()
261
- },
262
- };
263
- this.client.sendAttachment(orangebeardAttachment);
264
- }
265
- }
@@ -1,160 +0,0 @@
1
- import {ZonedDateTime} from "@js-joda/core";
2
- import {FinishTest} from "@orangebeard-io/javascript-client/dist/client/models/FinishTest";
3
- import * as fs from "node:fs";
4
- import {promisify} from "util";
5
- import Status = FinishTest.Status;
6
-
7
- const stat = promisify(fs.stat);
8
- const access = promisify(fs.access);
9
-
10
- export function getTime() {
11
- return ZonedDateTime.now().withFixedOffsetZone().toString();
12
- }
13
-
14
- export const testStatusMap = {
15
- "passed": Status.PASSED,
16
- "failed": Status.FAILED,
17
- "timedOut": Status.TIMED_OUT,
18
- "skipped": Status.SKIPPED,
19
- "interrupted": Status.STOPPED
20
- };
21
-
22
- export function removeAnsi(ansiString: string): string {
23
- const parts = ansiString.split(/(\u001b\[[0-9;]*[mG])/);
24
- let result = "";
25
- for (const part of parts) {
26
- if (!part.startsWith("\u001b[")) {
27
- result += part;
28
- }
29
- }
30
- return result;
31
- }
32
-
33
- export function ansiToMarkdown(ansiString: string): string {
34
- let markdown = "";
35
- let currentStyle: { italic?: boolean, code?: boolean } = {};
36
-
37
- const ansiCodes = {
38
- "31": {italic: true},
39
- "32": {italic: true},
40
- "39": {italic: false}, // Reset styles
41
- "2": {code: true},
42
- "22": {code: false},
43
- };
44
-
45
- const parts = ansiString.split(/(\u001b\[[0-9;]*[mG])/);
46
-
47
- for (const part of parts) {
48
- if (part.startsWith("\u001b[")) {
49
- const code = part.slice(2, -1);
50
- const codes = code.split(';');
51
- for (const c of codes) {
52
- const style = ansiCodes[c as keyof typeof ansiCodes]; // Type guard
53
- if (style) {
54
- currentStyle = {...currentStyle, ...style};
55
- }
56
- }
57
- } else {
58
- let formattedPart = part.replace(/\n/g, " \n");
59
-
60
- if (currentStyle.italic) {
61
- formattedPart = formattedPart.endsWith(" ") ? `*${formattedPart.trim()}* ` : `*${formattedPart}*`;
62
-
63
- }
64
- if (currentStyle.code) {
65
- formattedPart = `${formattedPart}`;
66
- }
67
-
68
- markdown += formattedPart
69
-
70
- }
71
- }
72
-
73
- return markdown;
74
- }
75
-
76
- /**
77
- * Reads a 3-line snippet from a file, centered around the specified line number.
78
- *
79
- * @param filePath - The path to the file.
80
- * @param lineNumber - The line number to center the snippet around (1-based index).
81
- * @returns A promise that resolves with the 3-line snippet or an error message if the line is out of range.
82
- */
83
- export /**
84
- * Reads a 3-line snippet from a file, centered around the specified line number.
85
- *
86
- * @param filePath - The path to the file.
87
- * @param lineNumber - The line number to center the snippet around (1-based index).
88
- * @returns The 3-line snippet or an error message if the line is out of range.
89
- */
90
- function getCodeSnippet(filePath: string, lineNumber: number): string {
91
- if (lineNumber < 1) {
92
- throw new Error('Line number must be 1 or greater.');
93
- }
94
-
95
- const fileContent = fs.readFileSync(filePath, 'utf8');
96
- const lines = fileContent.split(/\r?\n/); // Support both Unix and Windows line endings
97
-
98
- const startLine = Math.max(0, lineNumber - 2); // Zero-based index for one line before
99
- const endLine = Math.min(lines.length, lineNumber + 1); // One line after
100
-
101
- if (startLine >= lines.length) {
102
- throw new Error('Line number is out of range.');
103
- }
104
-
105
- let snippet = lines.slice(startLine, endLine);
106
- if (snippet.length > 0 && snippet[0].trim() === "") {
107
- snippet = snippet.slice(1);
108
- }
109
-
110
- return `\`\`\`js\n${snippet.join('\n')}\n\`\`\``;
111
- }
112
-
113
- const fileExists = async (filepath: string) => {
114
- try {
115
- await access(filepath, fs.constants.F_OK);
116
- return true;
117
- } catch {
118
- return false;
119
- }
120
- };
121
-
122
- const waitForFile = async (filepath: string, interval = 1000, timeout = 60000) => {
123
- const start = Date.now();
124
-
125
- while (true) {
126
- const now = Date.now();
127
- if (now - start > timeout) {
128
- throw new Error(`Timeout: ${filepath} did not become available within ${timeout}ms`);
129
- }
130
-
131
- if (await fileExists(filepath)) {
132
- const stats = [];
133
- for (let i = 0; i < 2; i++) {
134
- stats.push(await stat(filepath));
135
- await new Promise((resolve) => setTimeout(resolve, interval));
136
- }
137
-
138
- const [first, second] = stats;
139
- if (
140
- first.mtimeMs === second.mtimeMs &&
141
- first.size === second.size
142
- ) {
143
- return;
144
- }
145
- }
146
-
147
- await new Promise((resolve) => setTimeout(resolve, interval));
148
- }
149
- };
150
-
151
- export const getBytes = async (filePath: string) => {
152
- try {
153
- await waitForFile(filePath, 100, 5000)
154
- return fs.readFileSync(filePath);
155
- } catch (err) {
156
- console.error('Error reading file:', err);
157
- throw err;
158
- }
159
- };
160
-
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "es6",
4
- "module": "commonjs",
5
- "allowJs": false,
6
- "outDir": "dist",
7
- "rootDir": "src",
8
- "noImplicitAny": true,
9
- "esModuleInterop": true,
10
- "resolveJsonModule": true,
11
- "moduleResolution": "node",
12
- "baseUrl": ".",
13
- "paths": {
14
- "*": ["node_modules/*"]
15
- },
16
- "typeRoots": ["./node_modules/@types"]
17
- },
18
- "include": ["./src/**/*"],
19
- "exclude": ["node_modules"],
20
- }