playwright-slack-report
Advanced tools
Comparing version 1.1.25 to 1.1.54
/// <reference types="node" /> | ||
export declare type SummaryResults = { | ||
export type SummaryResults = { | ||
passed: number; | ||
@@ -30,5 +30,195 @@ failed: number; | ||
}; | ||
export declare type failure = { | ||
export type failure = { | ||
test: string; | ||
failureReason: string; | ||
}; | ||
export interface JSONResult { | ||
config: Config; | ||
suites: Suite[]; | ||
errors: any[]; | ||
stats: Stats; | ||
} | ||
export interface Config { | ||
configFile: string; | ||
rootDir: string; | ||
forbidOnly: boolean; | ||
fullyParallel: boolean; | ||
globalSetup: any; | ||
globalTeardown: any; | ||
globalTimeout: number; | ||
grep: Grep; | ||
grepInvert: any; | ||
maxFailures: number; | ||
metadata: Metadata; | ||
preserveOutput: string; | ||
reporter: [string, any][]; | ||
reportSlowTests: ReportSlowTests; | ||
quiet: boolean; | ||
projects: Project[]; | ||
shard: any; | ||
updateSnapshots: string; | ||
version: string; | ||
workers: number; | ||
webServer: any; | ||
} | ||
export interface Grep { | ||
} | ||
export interface Metadata { | ||
actualWorkers: number; | ||
} | ||
export interface ReportSlowTests { | ||
max: number; | ||
threshold: number; | ||
} | ||
export interface Project { | ||
outputDir: string; | ||
repeatEach: number; | ||
retries: number; | ||
id: string; | ||
name: string; | ||
testDir: string; | ||
testIgnore: any[]; | ||
testMatch: string[]; | ||
timeout: number; | ||
} | ||
export interface Suite { | ||
title: string; | ||
file: string; | ||
column: number; | ||
line: number; | ||
specs: Spec[]; | ||
suites: Suites[]; | ||
} | ||
export interface Spec { | ||
title: string; | ||
ok: boolean; | ||
tags: any[]; | ||
tests: Test[]; | ||
id: string; | ||
file: string; | ||
line: number; | ||
column: number; | ||
} | ||
export interface Test { | ||
timeout: number; | ||
annotations: any[]; | ||
expectedStatus: string; | ||
projectId: string; | ||
projectName: string; | ||
results: Result[]; | ||
status: string; | ||
} | ||
export interface Result { | ||
workerIndex: number; | ||
status: string; | ||
duration: number; | ||
errors: Error[]; | ||
stdout: any[]; | ||
stderr: any[]; | ||
retry: number; | ||
startTime: string; | ||
attachments: Attachment[]; | ||
error?: { | ||
message: string; | ||
stack: string; | ||
location: { | ||
file: string; | ||
column: number; | ||
line: number; | ||
}; | ||
snippet: string; | ||
}; | ||
errorLocation?: ErrorLocation; | ||
} | ||
export interface Error { | ||
location: Location; | ||
message: string; | ||
} | ||
export interface Location { | ||
file: string; | ||
column: number; | ||
line: number; | ||
} | ||
export interface Attachment { | ||
name: string; | ||
contentType: string; | ||
path: string; | ||
} | ||
export interface ErrorLocation { | ||
file: string; | ||
column: number; | ||
line: number; | ||
} | ||
export interface Suites { | ||
title: string; | ||
file: string; | ||
line: number; | ||
column: number; | ||
specs: Specs[]; | ||
} | ||
export interface Specs { | ||
title: string; | ||
ok: boolean; | ||
tags: any[]; | ||
tests: Tests[]; | ||
id: string; | ||
file: string; | ||
line: number; | ||
column: number; | ||
} | ||
export interface Tests { | ||
timeout: number; | ||
annotations: any[]; | ||
expectedStatus: string; | ||
projectId: string; | ||
projectName: string; | ||
results: Results[]; | ||
status: string; | ||
} | ||
export interface Results { | ||
workerIndex: number; | ||
status: string; | ||
duration: number; | ||
error: { | ||
message: string; | ||
stack: string; | ||
location: { | ||
file: string; | ||
column: number; | ||
line: number; | ||
}; | ||
snippet: string; | ||
}; | ||
errors: Errors[]; | ||
stdout: any[]; | ||
stderr: any[]; | ||
retry: number; | ||
startTime: string; | ||
attachments: Attachments[]; | ||
errorLocation: { | ||
file: string; | ||
column: number; | ||
line: number; | ||
}; | ||
} | ||
export interface Errors { | ||
location: { | ||
file: string; | ||
column: number; | ||
line: number; | ||
}; | ||
message: string; | ||
} | ||
export interface Attachments { | ||
name: string; | ||
contentType: string; | ||
path: string; | ||
} | ||
export interface Stats { | ||
startTime: string; | ||
duration: number; | ||
expected: number; | ||
skipped: number; | ||
unexpected: number; | ||
flaky: number; | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
//# sourceMappingURL=index.js.map |
@@ -77,1 +77,2 @@ "use strict"; | ||
exports.generateFallbackText = generateFallbackText; | ||
//# sourceMappingURL=LayoutGenerator.js.map |
/// <reference types="node" /> | ||
import { TestCase } from '@playwright/test/reporter'; | ||
import { failure, SummaryResults } from '.'; | ||
export declare type testResult = { | ||
import { failure, Spec, SummaryResults } from '.'; | ||
export type testResult = { | ||
suiteName: string; | ||
@@ -22,3 +22,3 @@ name: string; | ||
}; | ||
export declare type testSuite = { | ||
export type testSuite = { | ||
testSuite: { | ||
@@ -33,2 +33,6 @@ title: string; | ||
constructor(); | ||
parseFromJsonFile(filePath: string): Promise<SummaryResults>; | ||
parseTestSuite(suites: any, retries: number, suiteIndex?: number): Promise<void>; | ||
parseTests(suiteName: any, specs: any, retries: number): Promise<testResult[]>; | ||
getFailure(snippet: string, stack: string): string; | ||
getParsedResults(allTests: Array<TestCase>): Promise<SummaryResults>; | ||
@@ -40,2 +44,9 @@ getFailures(): Promise<Array<failure>>; | ||
}): void; | ||
addTestResultFromJson({ suiteName, spec, testCase, projectBrowserMapping, retries, }: { | ||
suiteName: any; | ||
spec: Spec; | ||
testCase: any; | ||
projectBrowserMapping: any; | ||
retries: number; | ||
}): void; | ||
addTestResult(suiteName: any, testCase: any, projectBrowserMapping: any): void; | ||
@@ -42,0 +53,0 @@ safelyDetermineFailure(result: { |
@@ -8,3 +8,27 @@ "use strict"; | ||
/* eslint-disable no-param-reassign */ | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const fs = __importStar(require("fs")); | ||
class ResultsParser { | ||
@@ -15,2 +39,75 @@ result; | ||
} | ||
async parseFromJsonFile(filePath) { | ||
const data = fs.readFileSync(filePath, 'utf-8'); | ||
const parsedData = JSON.parse(data); | ||
const { retries } = parsedData.config.projects[0]; | ||
await this.parseTestSuite(parsedData.suites, retries); | ||
const failures = await this.getFailures(); | ||
const summary = { | ||
passed: parsedData.stats.expected, | ||
failed: parsedData.stats.unexpected, | ||
flaky: parsedData.stats.flaky, | ||
skipped: parsedData.stats.skipped, | ||
failures, | ||
tests: [], | ||
}; | ||
for (const suite of this.result) { | ||
summary.tests = summary.tests.concat(suite.testSuite.tests); | ||
} | ||
// console.log('🚀~ summary:', JSON.stringify(summary, null, 2)); | ||
return summary; | ||
} | ||
async parseTestSuite(suites, retries, suiteIndex = 0) { | ||
let testResults = []; | ||
if (suites[0].suites?.length > 0) { | ||
testResults = await this.parseTests(suites[0].title, suites[0].specs, retries); | ||
this.updateResults({ | ||
testSuite: { | ||
title: suites[0].title, | ||
tests: testResults, | ||
}, | ||
}); | ||
await this.parseTestSuite(suites[suiteIndex].suites, retries, (suiteIndex += 1)); | ||
} | ||
else { | ||
testResults = await this.parseTests(suites[0].title, suites[0].specs, retries); | ||
this.updateResults({ | ||
testSuite: { | ||
title: suites[0].title, | ||
tests: testResults, | ||
}, | ||
}); | ||
// eslint-disable-next-line no-useless-return | ||
return; | ||
} | ||
} | ||
async parseTests(suiteName, specs, retries) { | ||
const testResults = []; | ||
for (const spec of specs) { | ||
for (const test of spec.tests) { | ||
for (const result of test.results) { | ||
testResults.push({ | ||
suiteName, | ||
name: spec.title, | ||
status: result.status === 'unexpected' ? 'failed' : result.status, | ||
browser: test.projectName, | ||
projectName: test.projectName, | ||
retry: result.retry, | ||
retries, | ||
startedAt: result.startTime, | ||
endedAt: new Date(new Date(result.startTime).getTime() + result.duration).toISOString(), | ||
reason: result.error | ||
? this.getFailure(result.error.snippet, result.error.stack) | ||
: '', | ||
attachments: result.attachments, | ||
}); | ||
} | ||
} | ||
} | ||
return testResults; | ||
} | ||
getFailure(snippet, stack) { | ||
const fullError = `${snippet}\r\n${stack || ''}`; | ||
return this.cleanseReason(fullError); | ||
} | ||
async getParsedResults(allTests) { | ||
@@ -21,3 +118,6 @@ const failures = await this.getFailures(); | ||
const stats = { | ||
expected: 0, skipped: 0, unexpected: 0, flaky: 0, | ||
expected: 0, | ||
skipped: 0, | ||
unexpected: 0, | ||
flaky: 0, | ||
}; | ||
@@ -72,2 +172,27 @@ // eslint-disable-next-line no-plusplus | ||
} | ||
addTestResultFromJson({ suiteName, spec, testCase, projectBrowserMapping, retries, }) { | ||
const testResults = []; | ||
const projectSettings = this.determineBrowser(projectBrowserMapping[0].projectName, projectBrowserMapping); | ||
for (const result of testCase.results) { | ||
testResults.push({ | ||
suiteName, | ||
name: spec.title, | ||
status: result.status, | ||
browser: projectSettings.browser, | ||
projectName: projectSettings.projectName, | ||
retry: result.retry, | ||
retries, | ||
startedAt: new Date(result.startTime).toISOString(), | ||
endedAt: new Date(new Date(result.startTime).getTime() + result.duration).toISOString(), | ||
reason: this.safelyDetermineFailure(result), | ||
attachments: result.attachments, | ||
}); | ||
} | ||
this.updateResults({ | ||
testSuite: { | ||
title: suiteName, | ||
tests: testResults, | ||
}, | ||
}); | ||
} | ||
addTestResult(suiteName, testCase, projectBrowserMapping) { | ||
@@ -132,1 +257,2 @@ const testResults = []; | ||
exports.default = ResultsParser; | ||
//# sourceMappingURL=ResultsParser.js.map |
import { WebClient, KnownBlock, Block, ChatPostMessageResponse, LogLevel } from '@slack/web-api'; | ||
import { SummaryResults } from '.'; | ||
export declare type additionalInfo = Array<{ | ||
export type additionalInfo = Array<{ | ||
key: string; | ||
@@ -5,0 +5,0 @@ value: string; |
@@ -104,1 +104,2 @@ "use strict"; | ||
exports.default = SlackClient; | ||
//# sourceMappingURL=SlackClient.js.map |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -6,5 +9,5 @@ const web_api_1 = require("@slack/web-api"); | ||
const webhook_1 = require("@slack/webhook"); | ||
const ResultsParser_1 = require("./ResultsParser"); | ||
const SlackClient_1 = require("./SlackClient"); | ||
const SlackWebhookClient_1 = require("./SlackWebhookClient"); | ||
const ResultsParser_1 = __importDefault(require("./ResultsParser")); | ||
const SlackClient_1 = __importDefault(require("./SlackClient")); | ||
const SlackWebhookClient_1 = __importDefault(require("./SlackWebhookClient")); | ||
class SlackReporter { | ||
@@ -187,1 +190,2 @@ customLayout; | ||
exports.default = SlackReporter; | ||
//# sourceMappingURL=SlackReporter.js.map |
@@ -43,1 +43,2 @@ "use strict"; | ||
exports.default = SlackWebhookClient; | ||
//# sourceMappingURL=SlackWebhookClient.js.map |
@@ -5,6 +5,9 @@ { | ||
"@slack/webhook": "^7.0.1", | ||
"https-proxy-agent": "^7.0.1" | ||
"commander": "^11.1.0", | ||
"https-proxy-agent": "^7.0.1", | ||
"ts-node": "^10.9.1", | ||
"zod": "^3.22.4" | ||
}, | ||
"devDependencies": { | ||
"@playwright/test": "^1.23.3", | ||
"@playwright/test": "^1.40.0", | ||
"@slack/types": "^2.7.0", | ||
@@ -31,7 +34,12 @@ "@typescript-eslint/eslint-plugin": "^5.30.6", | ||
"lint": "npx eslint . --ext .ts", | ||
"lint-fix": "npx eslint . --ext .ts --fix" | ||
"lint-fix": "npx eslint . --ext .ts --fix", | ||
"cli": "yarn build && node dist/cli.js", | ||
"cli-debug": "yarn build && npx . -c ./tests/test_data/valid_cli_config.json -j ./tests/test_data/valid_test_results.json" | ||
}, | ||
"name": "playwright-slack-report", | ||
"version": "1.1.25", | ||
"main": "index.js", | ||
"version": "1.1.54", | ||
"bin": { | ||
"playwright-slack-report": "dist/cli.js" | ||
}, | ||
"main": "dist/cli.js", | ||
"types": "dist/index.d.ts", | ||
@@ -56,5 +64,5 @@ "repository": "git@github.com:ryanrosello-og/playwright-slack-report.git", | ||
"exclude": [ | ||
"./src/custom_block/my_block.ts" | ||
"./custom_block/my_block.ts" | ||
] | ||
} | ||
} |
@@ -14,2 +14,3 @@ # playwright-slack-report ![Builds](https://github.com/ryanrosello-og/playwright-slack-report/actions/workflows/playwright.yml/badge.svg) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ryanrosello-og/playwright-slack-report/blob/master/LICENSE) [![Coverage Status](https://coveralls.io/repos/github/ryanrosello-og/playwright-slack-report/badge.svg?branch=main)](https://coveralls.io/github/ryanrosello-og/playwright-slack-report?branch=main) | ||
- 💌 Send results your Playwright test results to one or more Slack channels | ||
- 🎚️ Leverage JSON results created by Playwright and seamlessly post them on Slack | ||
- 📊 Conditionally send results to Slack channels based on test results | ||
@@ -74,3 +75,3 @@ - 📄 Include additional meta information into your test summary e.g. Branch, BuildId etc | ||
# Option B | ||
# Option B - send your results via a Slack bot user | ||
Run your tests by providing your `SLACK_BOT_USER_OAUTH_TOKEN` as an environment variable or specifying `slackOAuthToken` option in the config: | ||
@@ -127,3 +128,50 @@ | ||
--- | ||
# Option C - send your JSON results via CLI | ||
Playwright now provides a nice way to [merge multiple reports from multiple shards](https://playwright.dev/docs/test-sharding#merging-reports-from-multiple-shards). You can use this feature to generate a single JSON report and then send it to Slack, alleviating the need to have separate messages sent per shard: | ||
`npx playwright merge-reports --reporter json ./all-blob-reports` | ||
^ It is important that you set the `--reporter` to `json` otherwise the report will not be generated in the correct format. | ||
Next, you will need to configure the cli. See example below: | ||
*cli_config.json:* | ||
```json | ||
{ | ||
"sendResults": "always", | ||
"slackLogLevel": "error", | ||
"sendUsingBot": { | ||
"channels": ["demo"] | ||
}, | ||
"showInThread": true, | ||
"meta": [ | ||
{ "key": "build", "value" : "1.0.0"}, | ||
{ "key": "branch", "value" : "master"}, | ||
{ "key": "commit", "value" : "1234567890"}, | ||
{ "key": "results", "value" : "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"} | ||
], | ||
"maxNumberOfFailures": 4, | ||
"disableUnfurl": true | ||
} | ||
The config file also supports the follow extra options for using a proxy and sending results via a webhook: | ||
```json | ||
... | ||
"proxy": "http://proxy.mycompany.com:8080", | ||
"sendUsingWebhook": { | ||
"webhookUrl": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" | ||
}, | ||
... | ||
``` | ||
Once you have generated the JSON report and defined your config file, you can send it to Slack using the following command: | ||
`SLACK_BOT_USER_OAUTH_TOKEN=[your Slack bot user OAUTH token] npx playwright-slack-report -c cli_config.json -j ./merged_tests_results.json` | ||
# ⚙️ Configuration | ||
@@ -130,0 +178,0 @@ |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
95757
28
1407
661
6
8
2
+ Addedcommander@^11.1.0
+ Addedts-node@^10.9.1
+ Addedzod@^3.22.4
+ Added@cspotcode/source-map-support@0.8.1(transitive)
+ Added@jridgewell/resolve-uri@3.1.2(transitive)
+ Added@jridgewell/sourcemap-codec@1.5.0(transitive)
+ Added@jridgewell/trace-mapping@0.3.9(transitive)
+ Added@tsconfig/node10@1.0.11(transitive)
+ Added@tsconfig/node12@1.0.11(transitive)
+ Added@tsconfig/node14@1.0.3(transitive)
+ Added@tsconfig/node16@1.0.4(transitive)
+ Addedacorn@8.14.0(transitive)
+ Addedacorn-walk@8.3.4(transitive)
+ Addedarg@4.1.3(transitive)
+ Addedcommander@11.1.0(transitive)
+ Addedcreate-require@1.1.1(transitive)
+ Addeddiff@4.0.2(transitive)
+ Addedmake-error@1.3.6(transitive)
+ Addedts-node@10.9.2(transitive)
+ Addedtypescript@5.7.3(transitive)
+ Addedv8-compile-cache-lib@3.0.1(transitive)
+ Addedyn@3.1.1(transitive)
+ Addedzod@3.24.1(transitive)