UNPKG

@moroo/wdio-slack-reporter

Version:

Reporter from WebdriverIO using Incoming webhook and Web API to send results to Slack.

1 lines 57.5 kB
{"version":3,"sources":["../src/reporter.ts"],"sourcesContent":["/**\n * Copyright (c) moroo\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport path from 'node:path';\nimport util from 'node:util';\n\nimport WDIOReporter from '@wdio/reporter';\n\nimport { SlackWebClient, SlackWebhookClient } from './client.js';\nimport {\n DEFAULT_COLOR,\n DEFAULT_INDENT,\n EMOJI_SYMBOLS,\n FAILED_COLOR,\n SLACK_ICON_URL,\n SLACK_NAME,\n ERROR_MESSAGES,\n EVENTS,\n SLACK_REQUEST_TYPE,\n FINISHED_COLOR,\n} from './constants.js';\nimport { logger } from './utils.js';\n\nimport type {\n SlackRequestType,\n EmojiSymbols,\n StateCount,\n CucumberStats,\n TestResultType,\n FilesUploadV2Options,\n SlackReporterOptions,\n} from './types.js';\nimport type {\n Block,\n ChatPostMessageArguments,\n ChatPostMessageResponse,\n FilesCompleteUploadExternalResponse,\n FilesUploadArguments,\n KnownBlock,\n WebAPICallResult,\n} from '@slack/web-api';\nimport type {\n IncomingWebhookResult,\n IncomingWebhookSendArguments,\n} from '@slack/webhook';\nimport type {\n HookStats,\n RunnerStats,\n SuiteStats,\n TestStats,\n} from '@wdio/reporter';\nimport type { Capabilities } from '@wdio/types';\n\nclass SlackReporter extends WDIOReporter {\n private static resultsUrl?: string;\n private _slackRequestQueue: SlackRequestType[] = [];\n private _thread?: string;\n private _pendingSlackRequestCount = 0;\n private _stateCounts: StateCount = {\n passed: 0,\n failed: 0,\n skipped: 0,\n };\n private _useScenarioBasedStateCounts = false;\n private _webClient?: SlackWebClient;\n private _webhookClient?: SlackWebhookClient;\n private _channel?: string;\n private _symbols: EmojiSymbols;\n private _isCucumberFramework: boolean = false;\n private _title?: string;\n private _notifyTestStartMessage: boolean = true;\n private _notifyFailedCase: boolean = true;\n private _uploadScreenshotOfFailedCase: boolean = true;\n private _notifyTestFinishMessage: boolean = true;\n private _notifyDetailResultThread: boolean = true;\n private _filterForDetailResults: TestResultType[] = [\n 'passed',\n 'failed',\n 'pending',\n 'skipped',\n ];\n private _isSynchronizing: boolean = false;\n private _interval: NodeJS.Timeout;\n private _hasRunnerEnd = false;\n private _lastScreenshotBuffer?: Buffer = undefined;\n private _suiteUids = new Set<string>();\n private _orderedSuites: SuiteStats[] = [];\n private _cucumberOrderedTests: CucumberStats[] = [];\n private _indents: number = 0;\n private _suiteIndents: Record<string, number> = {};\n private _currentSuite?: SuiteStats;\n\n constructor(options: SlackReporterOptions) {\n super(Object.assign({ stdout: true }, options));\n\n if (!options.slackOptions) {\n logger.error(ERROR_MESSAGES.UNDEFINED_SLACK_OPTION);\n logger.debug(options.slackOptions);\n throw new Error(ERROR_MESSAGES.UNDEFINED_SLACK_OPTION);\n }\n if (options.slackOptions.type === 'web-api') {\n if (options.slackOptions.slackBotToken) {\n logger.warn(\n '\"slackBotToken\" property is deprecated. Please use the inherited \"token\" property instead. Will be removed in the next major version.'\n );\n options.slackOptions.token = options.slackOptions.slackBotToken;\n }\n this._webClient = new SlackWebClient(options.slackOptions);\n logger.info('Created Slack Web API Client Instance.');\n logger.debug('Slack Web API Client', {\n token: options.slackOptions.slackBotToken,\n channel: options.slackOptions.channel,\n });\n this._channel = options.slackOptions.channel;\n if (options.slackOptions.notifyDetailResultThread !== undefined) {\n if (options.notifyTestFinishMessage === false) {\n logger.warn(\n 'Notify is not possible. because the notifyResultMessage option is off.'\n );\n }\n this._notifyDetailResultThread =\n options.slackOptions.notifyDetailResultThread;\n }\n if (options.slackOptions.filterForDetailResults !== undefined) {\n if (options.slackOptions.notifyDetailResultThread === false) {\n logger.warn(\n 'Detail result filters does not work. because the notifyDetailResultThread option is off.'\n );\n }\n if (options.slackOptions.filterForDetailResults.length === 0) {\n logger.info(\n 'If there are no filters (array is empty), all filters are applied.'\n );\n } else {\n this._filterForDetailResults = [\n ...options.slackOptions.filterForDetailResults,\n ];\n }\n }\n if (options.slackOptions.uploadScreenshotOfFailedCase !== undefined) {\n this._uploadScreenshotOfFailedCase =\n options.slackOptions.uploadScreenshotOfFailedCase;\n }\n if (options.slackOptions.uploadScreenshotOfFailedCase !== undefined) {\n this._uploadScreenshotOfFailedCase =\n options.slackOptions.uploadScreenshotOfFailedCase;\n }\n if (options.slackOptions.createScreenshotPayload) {\n this.createScreenshotPayload =\n options.slackOptions.createScreenshotPayload.bind(this);\n logger.info(\n 'The [createScreenshotPayload] function has been overridden.'\n );\n logger.debug('RESULT', this.createScreenshotPayload.toString());\n }\n if (options.slackOptions.createResultDetailPayload) {\n this.createResultDetailPayload =\n options.slackOptions.createResultDetailPayload.bind(this);\n logger.info(\n 'The [createResultDetailPayload] function has been overridden.'\n );\n logger.debug('RESULT', this.createResultDetailPayload.toString());\n }\n } else {\n if (options.slackOptions.slackName) {\n logger.warn(\n '\"slackName\" property is deprecated. Please use the inherited \"username\" property instead. Will be removed in the next major version.'\n );\n options.slackOptions.username = options.slackOptions.slackName;\n }\n if (options.slackOptions.slackIconUrl) {\n logger.warn(\n '\"slackIconUrl\" property is deprecated. Please use the inherited \"icon_url\" property instead. Will be removed in the next major version.'\n );\n options.slackOptions.icon_url = options.slackOptions.slackIconUrl;\n }\n this._webhookClient = new SlackWebhookClient({\n username: options.slackOptions.username ?? SLACK_NAME,\n icon_url:\n !options.slackOptions.icon_url && !options.slackOptions.icon_emoji\n ? SLACK_ICON_URL\n : undefined,\n ...options.slackOptions,\n });\n logger.info('Created Slack Webhook Instance.');\n logger.debug('IncomingWebhook', {\n username: options.slackOptions.username ?? SLACK_NAME,\n icon_url:\n !options.slackOptions.icon_url && !options.slackOptions.icon_emoji\n ? SLACK_ICON_URL\n : undefined,\n ...options.slackOptions,\n });\n }\n this._symbols = {\n passed: options.emojiSymbols?.passed || EMOJI_SYMBOLS.PASSED,\n skipped: options.emojiSymbols?.skipped || EMOJI_SYMBOLS.SKIPPED,\n failed: options.emojiSymbols?.failed || EMOJI_SYMBOLS.FAILED,\n pending: options.emojiSymbols?.pending || EMOJI_SYMBOLS.PENDING,\n start: options.emojiSymbols?.start || EMOJI_SYMBOLS.ROCKET,\n finished: options.emojiSymbols?.finished || EMOJI_SYMBOLS.CHECKERED_FLAG,\n watch: options.emojiSymbols?.watch || EMOJI_SYMBOLS.STOPWATCH,\n };\n this._title = options.title;\n\n if (options.resultsUrl !== undefined) {\n SlackReporter.setResultsUrl(options.resultsUrl);\n }\n\n if (options.notifyTestStartMessage !== undefined) {\n this._notifyTestStartMessage = options.notifyTestStartMessage;\n }\n\n if (options.notifyFailedCase !== undefined) {\n this._notifyFailedCase = options.notifyFailedCase;\n }\n\n if (options.notifyTestFinishMessage !== undefined) {\n this._notifyTestFinishMessage = options.notifyTestFinishMessage;\n }\n\n if (options.useScenarioBasedStateCounts !== undefined) {\n this._useScenarioBasedStateCounts = options.useScenarioBasedStateCounts;\n }\n\n this._interval = global.setInterval(this.sync.bind(this), 100);\n\n if (options.createStartPayload) {\n this.createStartPayload = options.createStartPayload.bind(this);\n logger.info('The [createStartPayload] function has been overridden.');\n logger.debug('RESULT', this.createStartPayload.toString());\n }\n if (options.createFailedTestPayload) {\n this.createFailedTestPayload = options.createFailedTestPayload.bind(this);\n logger.info(\n 'The [createFailedTestPayload] function has been overridden.'\n );\n logger.debug('RESULT', this.createFailedTestPayload.toString());\n }\n if (options.createResultPayload) {\n this.createResultPayload = options.createResultPayload.bind(this);\n logger.info('The [createResultPayload] function has been overridden.');\n logger.debug('RESULT', this.createResultPayload.toString());\n }\n\n process.on(EVENTS.POST_MESSAGE, this.postMessage.bind(this));\n process.on(EVENTS.UPLOAD, this.upload.bind(this));\n process.on(EVENTS.SEND, this.send.bind(this));\n process.on(EVENTS.SCREENSHOT, this.uploadFailedTestScreenshot.bind(this));\n }\n\n static getResultsUrl(): string | undefined {\n return SlackReporter.resultsUrl;\n }\n static setResultsUrl(url: string | undefined): void {\n SlackReporter.resultsUrl = url;\n }\n /**\n * Upload failed test screenshot\n * @param {string | Buffer} data Screenshot buffer\n */\n static uploadFailedTestScreenshot(data: string | Buffer): void {\n let buffer: Buffer;\n\n if (typeof data === 'string') {\n buffer = Buffer.from(data, 'base64');\n } else {\n buffer = data;\n }\n\n process.emit(EVENTS.SCREENSHOT, {\n buffer,\n });\n }\n /**\n * Post message from Slack web-api\n * @param {ChatPostMessageArguments} payload Parameters used by Slack web-api\n * @return {Promise<WebAPICallResult>}\n */\n static postMessage(\n payload: ChatPostMessageArguments\n ): Promise<WebAPICallResult> {\n return new Promise((resolve, reject) => {\n process.emit(EVENTS.POST_MESSAGE, payload);\n process.once(EVENTS.RESULT, ({ result, error }) => {\n if (result) {\n resolve(result as WebAPICallResult);\n }\n reject(error);\n });\n });\n }\n /**\n * Upload from Slack web-api\n * @param {FilesUploadArguments} payload Parameters used by Slack web-api\n * @return {WebAPICallResult}\n */\n static async upload(\n payload: FilesUploadArguments,\n options: FilesUploadV2Options = {\n waitForUpload: true,\n timeout: 30,\n interval: 1000,\n }\n ): Promise<\n WebAPICallResult & {\n files: FilesCompleteUploadExternalResponse[];\n }\n > {\n return new Promise((resolve, reject) => {\n void process.emit(EVENTS.UPLOAD, { payload, options });\n process.once(EVENTS.RESULT, ({ result, error }) => {\n if (result) {\n resolve(\n result as WebAPICallResult & {\n files: FilesCompleteUploadExternalResponse[];\n }\n );\n }\n reject(error);\n });\n });\n }\n /**\n * Send from Slack webhook\n * @param {IncomingWebhookSendArguments} payload Parameters used by Slack webhook\n * @return {IncomingWebhookResult}\n */\n static async send(\n payload: IncomingWebhookSendArguments\n ): Promise<IncomingWebhookResult> {\n return new Promise((resolve, reject) => {\n process.emit(EVENTS.SEND, payload);\n process.once(EVENTS.RESULT, ({ result, error }) => {\n if (result) {\n resolve(result as IncomingWebhookResult);\n }\n reject(error);\n });\n });\n }\n\n private uploadFailedTestScreenshot(buffer: Buffer): void {\n if (this._webClient) {\n if (this._notifyFailedCase && this._uploadScreenshotOfFailedCase) {\n this._lastScreenshotBuffer = buffer;\n return;\n } else {\n logger.warn(ERROR_MESSAGES.DISABLED_OPTIONS);\n }\n } else {\n logger.warn(ERROR_MESSAGES.NOT_USING_WEB_API);\n }\n\n // return new Promise((resolve, reject) => {\n // \tconst interval = setInterval(() => {\n // \t\tif (this._lastScreenshotBuffer === undefined) {\n // \t\t\tclearInterval(interval);\n // \t\t\tif (this._webClient && this._notifyFailedCase) {\n // \t\t\t\tthis._lastScreenshotBuffer = buffer;\n // \t\t\t} else {\n // \t\t\t\tlogger.warn(\n // \t\t\t\t\tERROR_MESSAGES.NOT_USING_WEB_API_OR_DISABLED_NOTIFY_FAILED_CASE\n // \t\t\t\t);\n // \t\t\t}\n // \t\t\tresolve();\n // \t\t}\n // \t}, 100);\n // });\n }\n private async postMessage(\n payload: ChatPostMessageArguments\n ): Promise<WebAPICallResult> {\n if (this._webClient) {\n try {\n logger.debug('COMMAND', `postMessage(${payload})`);\n this._pendingSlackRequestCount++;\n const result = await this._webClient.postMessage(payload);\n logger.debug('RESULT', util.inspect(result));\n process.emit(EVENTS.RESULT, { result, error: undefined });\n return result;\n } catch (error) {\n logger.error(error);\n process.emit(EVENTS.RESULT, { result: undefined, error });\n throw error;\n } finally {\n this._pendingSlackRequestCount--;\n }\n }\n\n logger.error(ERROR_MESSAGES.NOT_USING_WEB_API);\n throw new Error(ERROR_MESSAGES.NOT_USING_WEB_API);\n }\n\n private async upload(\n payload: FilesUploadArguments,\n options?: FilesUploadV2Options\n ): Promise<\n WebAPICallResult & {\n files: FilesCompleteUploadExternalResponse[];\n }\n > {\n if (this._webClient) {\n try {\n logger.debug('COMMAND', `upload(${payload})`);\n this._pendingSlackRequestCount++;\n\n const result = await this._webClient.uploadV2(payload, options);\n\n logger.debug('RESULT', util.inspect(result));\n process.emit(EVENTS.RESULT, { result, error: undefined });\n return result;\n } catch (error) {\n logger.error(error);\n process.emit(EVENTS.RESULT, { result: undefined, error });\n throw error;\n } finally {\n this._pendingSlackRequestCount--;\n }\n }\n\n logger.error(ERROR_MESSAGES.NOT_USING_WEB_API);\n throw new Error(ERROR_MESSAGES.NOT_USING_WEB_API);\n }\n\n private async send(\n payload: IncomingWebhookSendArguments\n ): Promise<IncomingWebhookResult> {\n if (this._webhookClient) {\n try {\n logger.debug('COMMAND', `send(${payload})`);\n this._pendingSlackRequestCount++;\n const result = await this._webhookClient.send(payload);\n logger.debug('RESULT', util.inspect(result));\n process.emit(EVENTS.RESULT, { result, error: undefined });\n return result;\n } catch (error) {\n logger.error(error);\n process.emit(EVENTS.RESULT, { result: undefined, error });\n throw error;\n } finally {\n this._pendingSlackRequestCount--;\n }\n }\n\n logger.error(ERROR_MESSAGES.NOT_USING_WEBHOOK);\n throw new Error(ERROR_MESSAGES.NOT_USING_WEBHOOK);\n }\n\n get isSynchronised(): boolean {\n return this._pendingSlackRequestCount === 0 && !this._isSynchronizing;\n }\n\n private async sync(): Promise<void> {\n if (\n this._hasRunnerEnd &&\n this._slackRequestQueue.length === 0 &&\n this._pendingSlackRequestCount === 0\n ) {\n clearInterval(this._interval);\n }\n if (\n this._isSynchronizing ||\n this._slackRequestQueue.length === 0 ||\n this._pendingSlackRequestCount > 0\n ) {\n return;\n }\n\n try {\n this._isSynchronizing = true;\n logger.info('Start Synchronisation');\n await this.next();\n } catch (error) {\n logger.error(error);\n throw error;\n } finally {\n this._isSynchronizing = false;\n logger.info('End Synchronisation');\n }\n }\n\n private async next(): Promise<void> {\n const request = this._slackRequestQueue.shift();\n let result:\n | ChatPostMessageResponse\n | IncomingWebhookResult\n | (WebAPICallResult & {\n files: FilesCompleteUploadExternalResponse[];\n });\n\n logger.info('POST', `Slack Request ${request?.type}`);\n logger.debug('DATA', util.inspect(request?.payload));\n if (request) {\n try {\n this._pendingSlackRequestCount++;\n\n switch (request.type) {\n case SLACK_REQUEST_TYPE.WEB_API_POST_MESSAGE: {\n if (this._webClient) {\n const payload: ChatPostMessageArguments =\n request.payload as ChatPostMessageArguments;\n\n if (this._thread) {\n payload.thread_ts = this._thread;\n }\n\n result = await this._webClient.postMessage({\n ...payload,\n });\n this._thread = result.ts;\n logger.debug('RESULT', util.inspect(result));\n }\n break;\n }\n case SLACK_REQUEST_TYPE.WEB_API_UPLOAD: {\n if (this._webClient) {\n const payload: FilesUploadArguments = request.payload;\n\n if (\n 'file' in payload &&\n (payload.file as any as { type: string; data: number[] })\n .type === 'Buffer'\n ) {\n payload.file = Buffer.from(\n (payload.file as any as { type: string; data: number[] }).data\n );\n }\n\n // TODO 임시\n if ('file' in payload && 'buffer' in (payload.file as any)) {\n payload.file = (payload.file as any).buffer as any;\n }\n\n if (this._thread) {\n payload.thread_ts = this._thread;\n }\n\n result = await this._webClient.uploadV2(\n {\n ...payload,\n },\n { ...(request.options || {}) }\n );\n logger.debug('RESULT', util.inspect(result));\n }\n break;\n }\n case SLACK_REQUEST_TYPE.WEBHOOK_SEND: {\n if (this._webhookClient) {\n result = await this._webhookClient.send(request.payload);\n logger.debug('RESULT', util.inspect(result));\n }\n break;\n }\n }\n } catch (error) {\n logger.error(error);\n } finally {\n this._pendingSlackRequestCount--;\n }\n\n if (this._slackRequestQueue.length > 0) {\n await this.next();\n }\n }\n }\n\n /**\n * Convert error stack to string\n * @param {string} stack Error stack\n * @return {string} Converted error stack\n */\n private convertErrorStack(stack: string): string {\n return stack.replace(\n /[\\u001b\\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,\n ''\n );\n }\n /**\n * Get information about the environment\n * @description\n * Referenced from [Spec Reporter](https://github.com/webdriverio/webdriverio/blob/c6cf43f67aa46a294a4df158ddd194d79f11ac90/packages/wdio-spec-reporter/src/index.ts#L653)\n * @param {Capabilities.ResolvedTestrunnerCapabilities} capability Capabilities\n * @param {boolean} isMultiremote Is multiremote\n * @return {string} Environment string\n */\n private getEnvironmentCombo(\n capability: Capabilities.ResolvedTestrunnerCapabilities,\n isMultiremote = false\n ): string {\n let output = '';\n const capabilities =\n 'alwaysMatch' in capability ? capability.alwaysMatch : capability;\n const drivers: {\n driverName?: string;\n capability: WebdriverIO.Capabilities;\n }[] = [];\n\n if (isMultiremote) {\n output += '*MultiRemote*: \\n';\n\n Object.keys(capabilities).forEach((key) => {\n drivers.push({\n driverName: key,\n capability: (\n capabilities as Record<string, WebdriverIO.Capabilities>\n )[key],\n });\n });\n } else {\n drivers.push({\n capability: capabilities,\n });\n }\n\n drivers.forEach(({ driverName, capability }, index, array) => {\n const isLastIndex = array.length - 1 === index;\n let env = '';\n const caps =\n 'alwaysMatch' in capability\n ? (capability.alwaysMatch as WebdriverIO.Capabilities)\n : capability;\n const device = caps['appium:deviceName'];\n // prettier-ignore\n // @ts-expect-error outdated JSONWP capabilities\n const app = (capability['appium:app'] || capability.app || '').replace('sauce-storage:', '');\n const appName =\n caps['appium:bundleId'] ||\n caps['appium:appPackage'] ||\n caps['appium:appActivity'] ||\n (path.isAbsolute(app) ? path.basename(app) : app);\n\n // @ts-expect-error outdated JSONWP capabilities\n const browser = capability.browserName || capability.browser || appName;\n /**\n * fallback to different capability types:\n * browserVersion: W3C format\n * version: JSONWP format\n * platformVersion: mobile format\n * browser_version: invalid BS capability\n */\n // prettier-ignore\n // @ts-expect-error outdated JSONWP capabilities\n const version = caps.browserVersion || caps.version || caps['appium:platformVersion'] || caps.browser_version;\n /**\n * fallback to different capability types:\n * platformName: W3C format\n * platform: JSONWP format\n * os, os_version: invalid BS capability\n */\n // prettier-ignore\n // @ts-expect-error outdated JSONWP capabilities\n const platform = caps.platformName || caps.platform || (caps.os ? caps.os + (caps.os_version ? ` ${caps.os_version}` : '') : '(unknown)');\n if (device) {\n const program = appName || caps.browserName;\n const executing = program ? `executing ${program}` : '';\n\n env = `${device} on ${platform} ${version} ${executing}`.trim();\n } else {\n env = browser + (version ? ` (v${version})` : '') + ` on ${platform}`;\n }\n\n output += isMultiremote ? `- *${driverName}*: ` : '*Driver*: ';\n output += env;\n output += isLastIndex ? '' : '\\n';\n });\n\n return output;\n }\n\n /**\n * Indent a suite based on where how it's nested\n * @param {String} uid Unique suite key\n * @return {String} Spaces for indentation\n */\n private indent(uid: string): string {\n const indents = this._suiteIndents[uid];\n return indents === 0 ? '' : Array(indents).join(DEFAULT_INDENT);\n }\n\n /**\n * Indent a suite based on where how it's nested\n * @param {StateCount} stateCounts Stat count\n * @return {String} String to the stat count to be displayed in Slack\n */\n private getCounts(stateCounts: StateCount): string {\n return `*${this._symbols.passed} Passed: ${stateCounts.passed} | ${this._symbols.failed} Failed: ${stateCounts.failed} | ${this._symbols.skipped} Skipped: ${stateCounts.skipped}*`;\n }\n\n private createStartPayload(\n runnerStats: RunnerStats\n ): ChatPostMessageArguments | IncomingWebhookSendArguments {\n const text = `${\n this._title ? '*Title*: `' + this._title + '`\\n' : ''\n }${this.getEnvironmentCombo(\n runnerStats.capabilities,\n runnerStats.isMultiremote\n )}`;\n\n const payload: ChatPostMessageArguments | IncomingWebhookSendArguments = {\n channel: this._channel,\n text: `${this._symbols.start} Start testing${\n this._title ? 'for ' + this._title : ''\n }`,\n blocks: [\n {\n type: 'header',\n text: {\n type: 'plain_text',\n text: `${this._symbols.start} Start testing`,\n emoji: true,\n },\n },\n ],\n attachments: [\n {\n color: DEFAULT_COLOR,\n text,\n ts: Date.now().toString(),\n },\n ],\n };\n\n return payload;\n }\n\n private createFailedTestPayload(\n hookAndTest: HookStats | TestStats\n ): ChatPostMessageArguments | IncomingWebhookSendArguments {\n const stack = hookAndTest.error?.stack\n ? '```' + this.convertErrorStack(hookAndTest.error.stack) + '```'\n : '';\n const payload: ChatPostMessageArguments | IncomingWebhookSendArguments = {\n channel: this._channel,\n text: `${this._symbols.failed} Test failure`,\n blocks: [\n {\n type: 'header',\n text: {\n type: 'plain_text',\n text: `${this._symbols.failed} Test failure`,\n emoji: true,\n },\n },\n ],\n attachments: [\n {\n color: FAILED_COLOR,\n title: `${\n this._currentSuite ? this._currentSuite.title : hookAndTest.parent\n }`,\n text: `* » ${hookAndTest.title}*\\n${stack}`,\n },\n ],\n };\n\n return payload;\n }\n\n private createScreenshotPayload(\n testStats: TestStats,\n screenshotBuffer: string | Buffer\n ): FilesUploadArguments {\n if (this._channel && this._thread) {\n const payload: FilesUploadArguments = {\n channel_id: this._channel,\n thread_ts: this._thread,\n initial_comment: `Screenshot for Fail to ${testStats.title}`,\n filename: `${testStats.uid}.png`,\n file: screenshotBuffer,\n };\n return payload;\n } else {\n const payload: FilesUploadArguments = {\n channel_id: this._channel,\n initial_comment: `Screenshot for Fail to ${testStats.title}`,\n filename: `${testStats.uid}.png`,\n file: screenshotBuffer,\n };\n return payload;\n }\n }\n\n private createResultPayload(\n runnerStats: RunnerStats,\n stateCounts: StateCount\n ): ChatPostMessageArguments | IncomingWebhookSendArguments {\n const resultsUrl = SlackReporter.getResultsUrl();\n const counts = this.getCounts(stateCounts);\n const payload: ChatPostMessageArguments | IncomingWebhookSendArguments = {\n channel: this._channel,\n text: `${this._symbols.finished} End of test${\n this._title ? ' - ' + this._title : ''\n }\\n${counts}`,\n blocks: [\n {\n type: 'header',\n text: {\n type: 'plain_text',\n text: `${this._symbols.finished} End of test - ${\n this._symbols.watch\n } ${runnerStats.duration / 1000}s`,\n emoji: true,\n },\n },\n ],\n attachments: [\n {\n color: FINISHED_COLOR,\n text: `${this._title ? `*Title*: \\`${this._title}\\`\\n` : ''}${\n resultsUrl ? `*Results*: <${resultsUrl}>\\n` : ''\n }${counts}`,\n ts: Date.now().toString(),\n },\n ],\n };\n\n return payload;\n }\n\n private createResultDetailPayload(\n runnerStats: RunnerStats,\n stateCounts: StateCount\n ): ChatPostMessageArguments | IncomingWebhookSendArguments {\n const counts = this.getCounts(stateCounts);\n const payload: ChatPostMessageArguments | IncomingWebhookSendArguments = {\n channel: this._channel,\n text: `${this._title ? this._title + '\\n' : ''}${counts}`,\n blocks: [\n {\n type: 'header',\n text: {\n type: 'plain_text',\n text: 'Result Details',\n emoji: true,\n },\n },\n ...this.getResultDetailPayloads(),\n {\n type: 'section',\n text: {\n type: 'mrkdwn',\n text: `${counts}\\n${this._symbols.watch} ${\n runnerStats.duration / 1000\n }s`,\n },\n },\n {\n type: 'context',\n elements: [\n {\n type: 'mrkdwn',\n text: `*Filter*: ${this._filterForDetailResults\n .map((filter) => '`' + filter + '`')\n .join(', ')}`,\n },\n ],\n },\n ],\n };\n\n return payload;\n }\n\n private getResultDetailPayloads(): (Block | KnownBlock)[] {\n const output: string[] = [];\n let suites = this._isCucumberFramework\n ? this.getOrderedCucumberTests()\n : this.getOrderedSuites();\n\n const blocks: (Block | KnownBlock)[] = [];\n\n // Filter Detailed suites by state (Cucumber only)\n if (this._isCucumberFramework && this._notifyDetailResultThread) {\n suites = (suites as CucumberStats[]).filter(({ state }) =>\n this._filterForDetailResults.includes(state)\n );\n }\n\n for (const suite of suites) {\n // Don't do anything if a suite has no tests or sub suites\n if (\n suite.tests.length === 0 &&\n suite.suites.length === 0 &&\n suite.hooks.length === 0\n ) {\n continue;\n }\n\n let eventsToReport = this.getEventsToReport(suite);\n // Filter Detailed tests results by state (if needed)\n if (!this._isCucumberFramework && this._notifyDetailResultThread) {\n eventsToReport = eventsToReport.filter(\n ({ state }) => state && this._filterForDetailResults.includes(state)\n );\n }\n\n if (eventsToReport.length === 0) {\n continue;\n }\n\n // Get the indent/starting point for this suite\n const suiteIndent = this.indent(suite.uid);\n\n // Display the title of the suite\n if (suite.type) {\n output.push(`*${suiteIndent}${suite.title}*`);\n }\n\n // display suite description (Cucumber only)\n if (suite.description) {\n output.push(\n ...suite.description\n .trim()\n .split('\\n')\n .map((l) => `${suiteIndent}${l.trim()}`)\n );\n }\n\n for (const test of eventsToReport) {\n const testTitle = test.title;\n const testState = test.state;\n const testIndent = `${DEFAULT_INDENT}${suiteIndent}`;\n\n // Output for a single test\n output.push(\n `*${testIndent}${\n testState ? `${this._symbols[testState]} ` : ''\n }${testTitle}*`\n );\n }\n\n // Put a line break after each suite (only if tests exist in that suite)\n if (eventsToReport.length) {\n const block: Block | KnownBlock = {\n type: 'section',\n text: {\n type: 'mrkdwn',\n text: output.join('\\n'),\n },\n };\n output.length = 0;\n blocks.push(block);\n }\n }\n if (blocks.length === 0) {\n const block: Block | KnownBlock = {\n type: 'section',\n text: {\n type: 'mrkdwn',\n text: '*`No filter Results.`*',\n },\n };\n blocks.push(block);\n }\n return blocks;\n }\n\n private getOrderedSuites(): SuiteStats[] {\n if (this._orderedSuites.length) {\n return this._orderedSuites;\n }\n\n this._orderedSuites = [];\n for (const uid of this._suiteUids) {\n for (const [suiteUid, suite] of Object.entries(this.suites)) {\n if (suiteUid !== uid) {\n continue;\n }\n\n this._orderedSuites.push(suite);\n }\n }\n\n return this._orderedSuites;\n }\n\n private getOrderedCucumberTests(): CucumberStats[] {\n if (this._cucumberOrderedTests.length) {\n return this._cucumberOrderedTests;\n }\n\n this._cucumberOrderedTests = [];\n for (const uid of this._suiteUids) {\n for (const [suiteUid, suite] of Object.entries(this.suites)) {\n if (suiteUid !== uid) {\n continue;\n }\n if (suite.type === 'scenario') {\n let testState: CucumberStats['state'] = 'passed';\n if (suite.tests.some((test) => test.state === 'failed')) {\n testState = 'failed';\n } else if (suite.tests.every((test) => test.state === 'skipped')) {\n testState = 'skipped';\n }\n this._cucumberOrderedTests.push(\n Object.assign(suite, { state: testState })\n );\n }\n }\n }\n\n return this._cucumberOrderedTests;\n }\n\n private getCucumberTestsCounts(): StateCount {\n if (this._isCucumberFramework) {\n const suitesData = this.getOrderedCucumberTests();\n const suiteStats: StateCount = {\n passed: suitesData.filter(({ state }) => state === 'passed').length,\n failed: suitesData.filter(({ state }) => state === 'failed').length,\n skipped: suitesData.filter(({ state }) => state === 'skipped').length,\n };\n\n return suiteStats;\n } else {\n logger.warn(\n 'Since the Cucumber Framework is not being used, the state is counted based on the tests(steps).'\n );\n return this._stateCounts;\n }\n }\n\n /**\n * returns everything worth reporting from a suite\n * @param {Object} suite test suite containing tests and hooks\n * @return {Object[]} list of events to report\n */\n private getEventsToReport(suite: SuiteStats): (TestStats | HookStats)[] {\n return [\n /**\n * report all tests and only hooks that failed\n */\n ...suite.hooksAndTests.filter((item) => {\n return item.type === 'test' || Boolean(item.error);\n }),\n ];\n }\n\n onRunnerStart(runnerStats: RunnerStats): void {\n logger.info('INFO', `Test Framework: ${runnerStats.config.framework}`);\n if (runnerStats.config.framework === 'cucumber') {\n this._isCucumberFramework = true;\n }\n if (this._notifyTestStartMessage) {\n try {\n if (this._webClient) {\n logger.info('INFO', `ON RUNNER START: POST MESSAGE`);\n this._slackRequestQueue.push({\n type: SLACK_REQUEST_TYPE.WEB_API_POST_MESSAGE,\n payload: this.createStartPayload(\n runnerStats\n ) as ChatPostMessageArguments,\n });\n } else if (this._webhookClient) {\n logger.info('INFO', `ON RUNNER START: SEND`);\n this._slackRequestQueue.push({\n type: SLACK_REQUEST_TYPE.WEBHOOK_SEND,\n payload: this.createStartPayload(\n runnerStats\n ) as IncomingWebhookSendArguments,\n });\n }\n } catch (error) {\n logger.error(error);\n throw error;\n }\n }\n }\n\n // onBeforeCommand(commandArgs: BeforeCommandArgs): void {}\n // onAfterCommand(commandArgs: AfterCommandArgs): void {}\n\n onSuiteStart(suiteStats: SuiteStats): void {\n this._currentSuite = suiteStats;\n\n this._suiteUids.add(suiteStats.uid);\n if (this._isCucumberFramework) {\n if (suiteStats.type === 'feature') {\n this._indents = 0;\n this._suiteIndents[suiteStats.uid] = this._indents;\n }\n } else {\n this._suiteIndents[suiteStats.uid] = ++this._indents;\n }\n }\n\n // onHookStart(hookStat: HookStats): void {}\n onHookEnd(hookStats: HookStats): void {\n if (hookStats.error) {\n this._stateCounts.failed++;\n\n if (this._notifyFailedCase) {\n if (this._webClient) {\n this._slackRequestQueue.push({\n type: SLACK_REQUEST_TYPE.WEB_API_POST_MESSAGE,\n payload: this.createFailedTestPayload(\n hookStats\n ) as ChatPostMessageArguments,\n });\n } else {\n this._slackRequestQueue.push({\n type: SLACK_REQUEST_TYPE.WEBHOOK_SEND,\n payload: this.createFailedTestPayload(\n hookStats\n ) as ChatPostMessageArguments,\n });\n }\n }\n }\n }\n\n // onTestStart(testStats: TestStats): void {}\n onTestPass(testStats: TestStats): void {\n this._stateCounts.passed++;\n }\n onTestFail(testStats: TestStats): void {\n this._stateCounts.failed++;\n\n if (this._notifyFailedCase) {\n if (this._webClient) {\n this._slackRequestQueue.push({\n type: SLACK_REQUEST_TYPE.WEB_API_POST_MESSAGE,\n payload: this.createFailedTestPayload(\n testStats\n ) as ChatPostMessageArguments,\n });\n\n if (this._uploadScreenshotOfFailedCase && this._lastScreenshotBuffer) {\n this._slackRequestQueue.push({\n type: SLACK_REQUEST_TYPE.WEB_API_UPLOAD,\n payload: this.createScreenshotPayload(\n testStats,\n this._lastScreenshotBuffer\n ) as FilesUploadArguments,\n });\n this._lastScreenshotBuffer = undefined;\n }\n } else {\n this._slackRequestQueue.push({\n type: SLACK_REQUEST_TYPE.WEBHOOK_SEND,\n payload: this.createFailedTestPayload(\n testStats\n ) as ChatPostMessageArguments,\n });\n }\n }\n }\n // onTestRetry(testStats: TestStats): void {}\n onTestSkip(testStats: TestStats): void {\n this._stateCounts.skipped++;\n }\n\n // onTestEnd(testStats: TestStats): void {}\n\n onSuiteEnd(suiteStats: SuiteStats): void {\n this._indents--;\n }\n\n onRunnerEnd(runnerStats: RunnerStats): void {\n if (this._notifyTestFinishMessage) {\n const stateCount = this._useScenarioBasedStateCounts\n ? this.getCucumberTestsCounts()\n : this._stateCounts;\n\n try {\n if (this._webClient) {\n this._slackRequestQueue.push({\n type: SLACK_REQUEST_TYPE.WEB_API_POST_MESSAGE,\n payload: this.createResultPayload(\n runnerStats,\n stateCount\n ) as ChatPostMessageArguments,\n });\n\n if (this._notifyDetailResultThread) {\n this._slackRequestQueue.push({\n type: SLACK_REQUEST_TYPE.WEB_API_POST_MESSAGE,\n payload: this.createResultDetailPayload(\n runnerStats,\n stateCount\n ) as ChatPostMessageArguments,\n isDetailResult: true,\n });\n }\n } else {\n this._slackRequestQueue.push({\n type: SLACK_REQUEST_TYPE.WEBHOOK_SEND,\n payload: this.createResultPayload(\n runnerStats,\n stateCount\n ) as IncomingWebhookSendArguments,\n });\n }\n } catch (error) {\n logger.error(error);\n throw error;\n }\n }\n\n this._hasRunnerEnd = true;\n }\n}\n\nexport default SlackReporter;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,uBAAiB;AACjB,uBAAiB;AAEjB,sBAAyB;AAEzB,oBAAmD;AACnD,uBAWO;AACP,mBAAuB;AAgCvB,MAAM,sBAAsB,gBAAAA,QAAa;AAAA,EACvC,OAAe;AAAA,EACP,qBAAyC,CAAC;AAAA,EAC1C;AAAA,EACA,4BAA4B;AAAA,EAC5B,eAA2B;AAAA,IACjC,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,EACX;AAAA,EACQ,+BAA+B;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,uBAAgC;AAAA,EAChC;AAAA,EACA,0BAAmC;AAAA,EACnC,oBAA6B;AAAA,EAC7B,gCAAyC;AAAA,EACzC,2BAAoC;AAAA,EACpC,4BAAqC;AAAA,EACrC,0BAA4C;AAAA,IAClD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACQ,mBAA4B;AAAA,EAC5B;AAAA,EACA,gBAAgB;AAAA,EAChB,wBAAiC;AAAA,EACjC,aAAa,oBAAI,IAAY;AAAA,EAC7B,iBAA+B,CAAC;AAAA,EAChC,wBAAyC,CAAC;AAAA,EAC1C,WAAmB;AAAA,EACnB,gBAAwC,CAAC;AAAA,EACzC;AAAA,EAER,YAAY,SAA+B;AACzC,UAAM,OAAO,OAAO,EAAE,QAAQ,KAAK,GAAG,OAAO,CAAC;AAE9C,QAAI,CAAC,QAAQ,cAAc;AACzB,0BAAO,MAAM,gCAAe,sBAAsB;AAClD,0BAAO,MAAM,QAAQ,YAAY;AACjC,YAAM,IAAI,MAAM,gCAAe,sBAAsB;AAAA,IACvD;AACA,QAAI,QAAQ,aAAa,SAAS,WAAW;AAC3C,UAAI,QAAQ,aAAa,eAAe;AACtC,4BAAO;AAAA,UACL;AAAA,QACF;AACA,gBAAQ,aAAa,QAAQ,QAAQ,aAAa;AAAA,MACpD;AACA,WAAK,aAAa,IAAI,6BAAe,QAAQ,YAAY;AACzD,0BAAO,KAAK,wCAAwC;AACpD,0BAAO,MAAM,wBAAwB;AAAA,QACnC,OAAO,QAAQ,aAAa;AAAA,QAC5B,SAAS,QAAQ,aAAa;AAAA,MAChC,CAAC;AACD,WAAK,WAAW,QAAQ,aAAa;AACrC,UAAI,QAAQ,aAAa,6BAA6B,QAAW;AAC/D,YAAI,QAAQ,4BAA4B,OAAO;AAC7C,8BAAO;AAAA,YACL;AAAA,UACF;AAAA,QACF;AACA,aAAK,4BACH,QAAQ,aAAa;AAAA,MACzB;AACA,UAAI,QAAQ,aAAa,2BAA2B,QAAW;AAC7D,YAAI,QAAQ,aAAa,6BAA6B,OAAO;AAC3D,8BAAO;AAAA,YACL;AAAA,UACF;AAAA,QACF;AACA,YAAI,QAAQ,aAAa,uBAAuB,WAAW,GAAG;AAC5D,8BAAO;AAAA,YACL;AAAA,UACF;AAAA,QACF,OAAO;AACL,eAAK,0BAA0B;AAAA,YAC7B,GAAG,QAAQ,aAAa;AAAA,UAC1B;AAAA,QACF;AAAA,MACF;AACA,UAAI,QAAQ,aAAa,iCAAiC,QAAW;AACnE,aAAK,gCACH,QAAQ,aAAa;AAAA,MACzB;AACA,UAAI,QAAQ,aAAa,iCAAiC,QAAW;AACnE,aAAK,gCACH,QAAQ,aAAa;AAAA,MACzB;AACA,UAAI,QAAQ,aAAa,yBAAyB;AAChD,aAAK,0BACH,QAAQ,aAAa,wBAAwB,KAAK,IAAI;AACxD,4BAAO;AAAA,UACL;AAAA,QACF;AACA,4BAAO,MAAM,UAAU,KAAK,wBAAwB,SAAS,CAAC;AAAA,MAChE;AACA,UAAI,QAAQ,aAAa,2BAA2B;AAClD,aAAK,4BACH,QAAQ,aAAa,0BAA0B,KAAK,IAAI;AAC1D,4BAAO;AAAA,UACL;AAAA,QACF;AACA,4BAAO,MAAM,UAAU,KAAK,0BAA0B,SAAS,CAAC;AAAA,MAClE;AAAA,IACF,OAAO;AACL,UAAI,QAAQ,aAAa,WAAW;AAClC,4BAAO;AAAA,UACL;AAAA,QACF;AACA,gBAAQ,aAAa,WAAW,QAAQ,aAAa;AAAA,MACvD;AACA,UAAI,QAAQ,aAAa,cAAc;AACrC,4BAAO;AAAA,UACL;AAAA,QACF;AACA,gBAAQ,aAAa,WAAW,QAAQ,aAAa;AAAA,MACvD;AACA,WAAK,iBAAiB,IAAI,iCAAmB;AAAA,QAC3C,UAAU,QAAQ,aAAa,YAAY;AAAA,QAC3C,UACE,CAAC,QAAQ,aAAa,YAAY,CAAC,QAAQ,aAAa,aACpD,kCACA;AAAA,QACN,GAAG,QAAQ;AAAA,MACb,CAAC;AACD,0BAAO,KAAK,iCAAiC;AAC7C,0BAAO,MAAM,mBAAmB;AAAA,QAC9B,UAAU,QAAQ,aAAa,YAAY;AAAA,QAC3C,UACE,CAAC,QAAQ,aAAa,YAAY,CAAC,QAAQ,aAAa,aACpD,kCACA;AAAA,QACN,GAAG,QAAQ;AAAA,MACb,CAAC;AAAA,IACH;AACA,SAAK,WAAW;AAAA,MACd,QAAQ,QAAQ,cAAc,UAAU,+BAAc;AAAA,MACtD,SAAS,QAAQ,cAAc,WAAW,+BAAc;AAAA,MACxD,QAAQ,QAAQ,cAAc,UAAU,+BAAc;AAAA,MACtD,SAAS,QAAQ,cAAc,WAAW,+BAAc;AAAA,MACxD,OAAO,QAAQ,cAAc,SAAS,+BAAc;AAAA,MACpD,UAAU,QAAQ,cAAc,YAAY,+BAAc;AAAA,MAC1D,OAAO,QAAQ,cAAc,SAAS,+BAAc;AAAA,IACtD;AACA,SAAK,SAAS,QAAQ;AAEtB,QAAI,QAAQ,eAAe,QAAW;AACpC,oBAAc,cAAc,QAAQ,UAAU;AAAA,IAChD;AAEA,QAAI,QAAQ,2BAA2B,QAAW;AAChD,WAAK,0BAA0B,QAAQ;AAAA,IACzC;AAEA,QAAI,QAAQ,qBAAqB,QAAW;AAC1C,WAAK,oBAAoB,QAAQ;AAAA,IACnC;AAEA,QAAI,QAAQ,4BAA4B,QAAW;AACjD,WAAK,2BAA2B,QAAQ;AAAA,IAC1C;AAEA,QAAI,QAAQ,gCAAgC,QAAW;AACrD,WAAK,+BAA+B,QAAQ;AAAA,IAC9C;AAEA,SAAK,YAAY,OAAO,YAAY,KAAK,KAAK,KAAK,IAAI,GAAG,GAAG;AAE7D,QAAI,QAAQ,oBAAoB;AAC9B,WAAK,qBAAqB,QAAQ,mBAAmB,KAAK,IAAI;AAC9D,0BAAO,KAAK,wDAAwD;AACpE,0BAAO,MAAM,UAAU,KAAK,mBAAmB,SAAS,CAAC;AAAA,IAC3D;AACA,QAAI,QAAQ,yBAAyB;AACnC,WAAK,0BAA0B,QAAQ,wBAAwB,KAAK,IAAI;AACxE,0BAAO;AAAA,QACL;AAAA,MACF;AACA,0BAAO,MAAM,UAAU,KAAK,wBAAwB,SAAS,CAAC;AAAA,IAChE;AACA,QAAI,QAAQ,qBAAqB;AAC/B,WAAK,sBAAsB,QAAQ,oBAAoB,KAAK,IAAI;AAChE,0BAAO,KAAK,yDAAyD;AACrE,0BAAO,MAAM,UAAU,KAAK,oBAAoB,SAAS,CAAC;AAAA,IAC5D;AAEA,YAAQ,GAAG,wBAAO,cAAc,KAAK,YAAY,KAAK,IAAI,CAAC;AAC3D,YAAQ,GAAG,wBAAO,QAAQ,KAAK,OAAO,KAAK,IAAI,CAAC;AAChD,YAAQ,GAAG,wBAAO,MAAM,KAAK,KAAK,KAAK,IAAI,CAAC;AAC5C,YAAQ,GAAG,wBAAO,YAAY,KAAK,2BAA2B,KAAK,IAAI,CAAC;AAAA,EAC1E;AAAA,EAEA,OAAO,gBAAoC;AACzC,WAAO,cAAc;AAAA,EACvB;AAAA,EACA,OAAO,cAAc,KAA+B;AAClD,kBAAc,aAAa;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,2BAA2B,MAA6B;AAC7D,QAAI;AAEJ,QAAI,OAAO,SAAS,UAAU;AAC5B,eAAS,OAAO,KAAK,MAAM,QAAQ;AAAA,IACrC,OAAO;AACL,eAAS;AAAA,IACX;AAEA,YAAQ,KAAK,wBAAO,YAAY;AAAA,MAC9B;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,YACL,SAC2B;AAC3B,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,cAAQ,KAAK,wBAAO,cAAc,OAAO;AACzC,cAAQ,KAAK,wBAAO,QAAQ,CAAC,EAAE,QAAQ,MAAM,MAAM;AACjD,YAAI,QAAQ;AACV,kBAAQ,MAA0B;AAAA,QACpC;AACA,eAAO,KAAK;AAAA,MACd,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,OACX,SACA,UAAgC;AAAA,IAC9B,eAAe;AAAA,IACf,SAAS;AAAA,IACT,UAAU;AAAA,EACZ,GAKA;AACA,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,QAAQ,KAAK,wBAAO,QAAQ,EAAE,SAAS,QAAQ,CAAC;AACrD,cAAQ,KAAK,wBAAO,QAAQ,CAAC,EAAE,QAAQ,MAAM,MAAM;AACjD,YAAI,QAAQ;AACV;AAAA,YACE;AAAA,UAGF;AAAA,QACF;AACA,eAAO,KAAK;AAAA,MACd,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,KACX,SACgC;AAChC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,cAAQ,KAAK,wBAAO,MAAM,OAAO;AACjC,cAAQ,KAAK,wBAAO,QAAQ,CAAC,EAAE,QAAQ,MAAM,MAAM;AACjD,YAAI,QAAQ;AACV,kBAAQ,MAA+B;AAAA,QACzC;AACA,eAAO,KAAK;AAAA,MACd,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEQ,2BAA2B,QAAsB;AACvD,QAAI,KAAK,YAAY;AACnB,UAAI,KAAK,qBAAqB,KAAK,+BAA+B;AAChE,aAAK,wBAAwB;AAC7B;AAAA,MACF,OAAO;AACL,4BAAO,KAAK,gCAAe,gBAAgB;AAAA,MAC7C;AAAA,IACF,OAAO;AACL,0BAAO,KAAK,gCAAe,iBAAiB;AAAA,IAC9C;AAAA,EAiBF;AAAA,EACA,MAAc,YACZ,SAC2B;AAC3B,QAAI,KAAK,YAAY;AACnB,UAAI;AACF,4BAAO,MAAM,WAAW,eAAe,OAAO,GAAG;AACjD,aAAK;AACL,cAAM,SAAS,MAAM,KAAK,WAAW,YAAY,OAAO;AACxD,4BAAO,MAAM,UAAU,iBAAAC,QAAK,QAAQ,MAAM,CAAC;AAC3C,gBAAQ,KAAK,wBAAO,QAAQ,EAAE,QAAQ,OAAO,OAAU,CAAC;AACxD,eAAO;AAAA,MACT,SAAS,OAAO;AACd,4BAAO,MAAM,KAAK;AAClB,gBAAQ,KAAK,wBAAO,QAAQ,EAAE,QAAQ,QAAW,MAAM,CAAC;AACxD,cAAM;AAAA,MACR,UAAE;AACA,aAAK;AAAA,MACP;AAAA,IACF;AAEA,wBAAO,MAAM,gCAAe,iBAAiB;AAC7C,UAAM,IAAI,MAAM,gCAAe,iBAAiB;AAAA,EAClD;AAAA,EAEA,MAAc,OACZ,SACA,SAKA;AACA,QAAI,KAAK,YAAY;AACnB,UAAI;AACF,4BAAO,MAAM,WAAW,UAAU,OAAO,GAAG;AAC5C,aAAK;AAEL,cAAM,SAAS,MAAM,KAAK,WAAW,SAAS,SAAS,OAAO;AAE9D,4BAAO,MAAM,UAAU,iBAAAA,QAAK,QAAQ,MAAM,CAAC;AAC3C,gBAAQ,KAAK,wBAAO,QAAQ,EAAE,QAAQ,OAAO,OAAU,CAAC;AACxD,eAAO;AAAA,MACT,SAAS,OAAO;AACd,4BAAO,MAAM,KAAK;AAClB,gBAAQ,KAAK,wBAAO,QAAQ,EAAE,QAAQ,QAAW,MAAM,CAAC;AACxD,cAAM;AAAA,MACR,UAAE;AACA,aAAK;AAAA,MACP;AAAA,IACF;AAEA,wBAAO,MAAM,gCAAe,iBAAiB;AAC7C,UAAM,IAAI,MAAM,gCAAe,iBAAiB;AAAA,EAClD;AAAA,EAEA,MAAc,KACZ,SACgC;AAChC,QAAI,KAAK,gBAAgB;AACvB,UAAI;AACF,4BAAO,MAAM,WAAW,QAAQ,OAAO,GAAG;AAC1C,aAAK;AACL,cAAM,SAAS,MAAM,KAAK,eAAe,KAAK,OAAO;AACrD,4BAAO,MAAM,UAAU,iBAAAA,QAAK,QAAQ,MAAM,CAAC;AAC3C,gBAAQ,KAAK,wBAAO,QAAQ,EAAE,QAAQ,OAAO,OAAU,CAAC;AACxD,eAAO;AAAA,MACT,SAAS,OAAO;AACd,4BAAO,MAAM,KAAK;AAClB,gBAAQ,KAAK,wBAAO,QAAQ,EAAE,QAAQ,QAAW,MAAM,CAAC;AACxD,cAAM;AAAA,MACR,UAAE;AACA,aAAK;AAAA,MACP;AAAA,IACF;AAEA,wBAAO,MAAM,gCAAe,iBAAiB;AAC7C,UAAM,IAAI,MAAM,gCAAe,iBAAiB;AAAA,EAClD;AAAA,EAEA,IAAI,iBAA0B;AAC5B,WAAO,KAAK,8BAA8B,KAAK,CAAC,KAAK;AAAA,EACvD;AAAA,EAEA,MAAc,OAAsB;AAClC,QACE,KAAK,iBACL,KAAK,mBAAmB,WAAW,KACnC,KAAK,8BAA8B,GACnC;AACA,oBAAc,KAAK,SAAS;AAAA,IAC9B;AACA,QACE,KAAK,oBACL,KAAK,mBAAmB,WAAW,KACnC,KAAK,4BAA4B,GACjC;AACA;AAAA,IACF;AAEA,QAAI;AACF,WAAK,mBAAmB;AACxB,0BAAO,KAAK,uBAAuB;AACnC,YAAM,KAAK,KAAK;AAAA,IAClB,SAAS,OAAO;AACd,0BAAO,MAAM,KAAK;AAClB,YAAM;AAAA,IACR,UAAE;AACA,WAAK,mBAAmB;AACxB,0BAAO,KAAK,qBAAqB;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,MAAc,OAAsB;AAClC,UAAM,UAAU,KAAK,mBAAmB,MAAM;AAC9C,QAAI;AAOJ,wBAAO,KAAK,QAAQ,iBAAiB,SAAS,IAAI,EAAE;AACpD,wBAAO,MAAM,QAAQ,iBAAAA,QAAK,QAAQ,SAAS,OAAO,CAAC;AACnD,QAAI,SAAS;AACX,UAAI;AACF,aAAK;AAEL,gBAAQ,QAAQ,MAAM;AAAA,UACpB,KAAK,oCAAmB,sBAAsB;AAC5C,gBAAI,KAAK,YAAY;AACnB,oBAAM,UACJ,QAAQ;AAEV,kBAAI,KAAK,SAAS;AAChB,wBAAQ,YAAY,KAAK;AAAA,cAC3B;AAEA,uBAAS,MAAM,KAAK,WAAW,YAAY;AAAA,gBACzC,GAAG;AAAA,cACL,CAAC;AACD,mBAAK,UAAU,OAAO;AACtB,kCAAO,MAAM,UAAU,iBAAAA,QAAK,QAAQ,MAAM,CAAC;AAAA,YAC7C;AACA;AAAA,UACF;AAAA,UACA,KAAK,oCAAmB,gBAAgB;AACtC,gBAAI,KAAK,YAAY;AACnB,oBAAM,UAAgC,QAAQ;AAE9C,kBACE,UAAU,WACT,QAAQ,KACN,SAAS,UACZ;AACA,wBAAQ,OAAO,OAAO;AAAA,kBACnB,QAAQ,KAAiD;AAAA,gBAC5D;AAAA,cACF;AAGA,kBAAI,UAAU,WAAW,YAAa,QAAQ,MAAc;AAC1D,wBAAQ,OAAQ,QAAQ,KAAa;AAAA,cACvC;AAEA,kBAAI,KAAK,SAAS;AAChB,wBAAQ,YAAY,KAAK;AAAA,cAC3B;AAEA,uBAAS,MAAM,KAAK,WAAW;AAAA,gBAC7B;AAAA,kBACE,GAAG;AAAA,gBACL;AAAA,gBACA,EAAE,GAAI,QAAQ,WAAW,CAAC,EAAG;AAAA,cAC/B;AACA,kCAAO,MAAM,UAAU,iBAAAA,QAAK,QAAQ,MAAM,CAAC;AAAA,YAC7C;AACA;AAAA,UACF;AAAA,UACA,KAAK,oCAAmB,cAAc;AACpC,gBAAI,KAAK,gBAAgB;AACvB,uBAAS,MAAM,KAAK,eAAe,KAAK,QAAQ,OAAO;AACvD,kCAAO,MAAM,UAAU,iBAAAA,QAAK,QAAQ,MAAM,CAAC;AAAA,YAC7C;AACA;AAAA,UACF;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,4BAAO,MAAM,KAAK;AAAA,MACpB,UAAE;AACA,aAAK;AAAA,MACP;AAEA,UAAI,KAAK,mBAAmB,SAAS,GAAG;AACtC,cAAM,KAAK,KAAK;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,kBAAkB,OAAuB;AAC/C,WAAO,MAAM;AAAA,MACX;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,oBACN,YACA,gBAAgB,OACR;AACR,QAAI,SAAS;AACb,UAAM,eACJ,iBAAiB,aAAa,WAAW,cAAc;AACzD,UAAM,UAGA,CAAC;AAEP,QAAI,eAAe;AACjB,gBAAU;AAEV,aAAO,KAAK,YAAY,EAAE,QAAQ,CAAC,QAAQ;AACzC,gBAAQ,KAAK;AAAA,UACX,YAAY;AAAA,UACZ,YACE,aACA,GAAG;AAAA,QACP,CAAC;AAAA,MACH,CAAC;AAAA,IACH,OAAO;AACL,cAAQ,KAAK;AAAA,QACX,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAEA,YAAQ,QAAQ,CAAC,EAAE,YAAY,YAAAC,YAAW,GAAG,OAAO,UAAU;AAC5D,YAAM,cAAc,MAAM,SAAS,MAAM;AACzC,UAAI,MAAM;AACV,YAAM,OACJ,iBAAiBA,cACZA,YAAW,cACZA;AACN,YAAM,SAAS,KAAK,mBAAmB;AAGvC,YAAM,OAAOA,YAAW,YAAY,KAAKA,YAAW,OAAO,IAAI,QAAQ,kBAAkB,EAAE;AAC3F,YAAM,UACJ,KAAK,iBAAiB,KACtB,KAAK,mBAAmB,KACxB,KAAK,oBAAoB,MACxB,iBAAAC,QAAK,WAAW,GAAG,IAAI,iBAAAA,QAAK,SAAS,GAAG,IAAI;AAG/C,YAAM,UAAUD,YAAW,eAAeA,YAAW,WAAW;AAUhE,YAAM,UAAU,KAAK,kBAAkB,KAAK,WAAW,KAAK,wBAAwB,KAAK,KAAK;AAS9F,YAAM,WAAW,KAAK,gBAAgB,KAAK,aAAa,KAAK,KAAK,KAAK,MAAM,KAAK,aAAa,IAAI,KAAK,UAAU,KAAK,MAAM;AAC7H,UAAI,QAAQ;AACV,cAAM,UAAU,WAAW,KAAK;AAChC,cAAM,YAAY,UAAU,aAAa,OAAO,KAAK;AAErD,cAAM,GAAG,MAAM,OAAO,QAAQ,IAAI,OAAO,IAAI,SAAS,GAAG,KAAK;AAAA,MAChE,OAAO;AACL,cAAM,WAAW,UAAU,MAAM,OAAO,MAAM,MAAM,OAAO,QAAQ;AAAA,MACrE;AAEA,gBAAU,gBAAgB,MAAM,UAAU,QAAQ;AAClD,gBAAU;AACV,gBAAU,cAAc,KAAK;AAAA,IAC/B,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,OAAO,KAAqB;AAClC,UAAM,UAAU,KAAK,cAAc,GAAG;AACtC,WAAO,YAAY,IAAI,KAAK,MAAM,OAAO,EAAE,KAAK,+BAAc;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,UAAU,aAAiC;AACjD,WAAO,IAAI,KAAK,SAAS,MAAM,YAAY,YAAY,MAAM,MAAM,KAAK,SAAS,MAAM,YAAY,YAAY,MAAM,MAAM,KAAK,SAAS,OAAO,aAAa,YAAY,OAAO;AAAA,EAClL;AAAA,EAEQ,mBACN,aACyD;AACzD,UAAM,OAAO,GACX,KAAK,SAAS,eAAe,KAAK,SAAS,QAAQ,EACrD,GAAG,KAAK;AAAA,MACN,YAAY;AAAA,MACZ,YAAY;AAAA,IACd,CAAC;AAED,UAAM,UAAmE;AAAA,MACvE,SAAS,KAAK;AAAA,MACd,MAAM,GAAG,KAAK,SAAS,KAAK,iBAC1B,KAAK,SAAS,SAAS,KAAK,SAAS,EACvC;AAAA,MACA,QAAQ;AAAA,QACN;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,YACJ,MAAM;AAAA,YACN,MAAM,GAAG,KAAK,SAAS,KAAK;AAAA,YAC5B,OAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAAA,MACA,aAAa;AAAA,QACX;AAAA,UACE,OAAO;AAAA,UACP;AAAA,UACA,IAAI,KAAK,IAAI,EAAE,SAAS;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,wBACN,aACyD;AACzD,UAAM,QAAQ,YAAY,OAAO,QAC7B,QAAQ,KAAK,kBAAkB,