@bigbinary/neeto-playwright-reporter 1.0.1 → 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/index.d.ts CHANGED
@@ -1,8 +1,20 @@
1
- import { Reporter, FullConfig, Suite } from '@playwright/test/reporter';
1
+ import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
2
2
 
3
+ interface ReporterOptionParams {
4
+ projectKey: string;
5
+ baseURL: string;
6
+ ciBuildId: string;
7
+ }
3
8
  declare class MyReporter implements Reporter {
4
- constructor(options: Record<string, string>);
5
- onBegin: (config: FullConfig, suite: Suite) => Promise<void>;
9
+ attempts: Record<string, string>;
10
+ ciBuildId: string;
11
+ config: FullConfig | undefined;
12
+ currentShard: number | undefined;
13
+ constructor(options: ReporterOptionParams);
14
+ onBegin: (config: FullConfig, rootSuite: Suite) => Promise<void>;
15
+ onTestBegin: ({ id, title }: TestCase, { retry }: TestResult) => Promise<void>;
16
+ onTestEnd: ({ id, title }: TestCase, { status, duration, errors, retry, attachments }: TestResult) => Promise<void>;
17
+ onEnd: ({ status, duration }: FullResult) => Promise<void>;
6
18
  }
7
19
 
8
20
  export { MyReporter as default };
package/index.js CHANGED
@@ -1,15 +1,16 @@
1
+ import require$$6 from 'fs';
1
2
  import require$$1, { TextEncoder } from 'util';
2
3
  import stream, { Readable } from 'stream';
3
4
  import require$$1$1 from 'path';
4
5
  import require$$3 from 'http';
5
6
  import require$$4 from 'https';
6
7
  import require$$0$1 from 'url';
7
- import require$$6 from 'fs';
8
8
  import require$$4$1 from 'assert';
9
9
  import require$$1$2 from 'tty';
10
10
  import require$$0$2 from 'os';
11
11
  import zlib from 'zlib';
12
12
  import EventEmitter from 'events';
13
+ import childProcess from 'child_process';
13
14
 
14
15
  function bind(fn, thisArg) {
15
16
  return function wrap() {
@@ -18429,54 +18430,207 @@ axios.HttpStatusCode = HttpStatusCode;
18429
18430
 
18430
18431
  axios.default = axios;
18431
18432
 
18432
- // dummy endpoint until neeto-playwright-reporter dashboard is up
18433
- const create = (payload) => axios.post(`https://webhook.site/1bff83d0-03a0-412f-a70a-2f11f3209534`, payload);
18434
- const dummyApi = { create };
18435
-
18436
- const getDescribePath = ({ titlePath, title, project, spec, }) => {
18437
- const describePaths = titlePath.filter((item, index) => index !== 0 && item !== title && item !== project && item !== spec);
18438
- return describePaths.join(" > ");
18439
- };
18440
-
18441
18433
  const HEADERS_KEYS = {
18442
18434
  applicationKey: "Application-Key",
18443
- xCsrfToken: "X-CSRF-TOKEN",
18444
18435
  contentType: "Content-Type",
18445
18436
  accept: "Accept",
18437
+ apiKey: "X-Api-Key",
18438
+ projectKey: "Project-Key",
18439
+ };
18440
+ const API_BASE_URL = "/api/v1";
18441
+
18442
+ const create$2 = (ciBuildId, history_id, payload) => axios.post(`${API_BASE_URL}/reporter/runs/${ciBuildId}/test_entities/${history_id}/attempts`, payload, { headers: { "Content-Type": "multipart/form-data" } });
18443
+ const update$1 = (ciBuildId, history_id, id, payload) => axios.put(`${API_BASE_URL}/reporter/runs/${ciBuildId}/test_entities/${history_id}/attempts/${id}`, payload, { headers: { "Content-Type": "multipart/form-data" } });
18444
+ const attemptsApi = { create: create$2, update: update$1 };
18445
+
18446
+ const ERRORS = {
18447
+ onBegin: {
18448
+ failedToGetCommitSha: "Failed to get current commit SHA.",
18449
+ failedToGetCommitMessage: "Failed to get current commit message.",
18450
+ failedToInitializeRun: "Failed to initialize run in reporter",
18451
+ },
18452
+ onTestBegin: {
18453
+ failedToReportTest: (testTitle, historyId) => `Failed to report test "${testTitle}" with history ID ${historyId}`,
18454
+ },
18455
+ onEnd: {
18456
+ failedToReportRunStatus: "Failed to report run status",
18457
+ },
18446
18458
  };
18447
18459
 
18448
- const setAuthHeaders = (applicationKey) => {
18460
+ const MESSAGES = {
18461
+ onBegin: {
18462
+ testStarted: "Test has started reporting to neetoPlaywrightReporter",
18463
+ ciBuildId: (currentCiBuildId) => `CI BUILD ID: ${currentCiBuildId}`,
18464
+ totalShards: (totalShards) => `Total shards: ${totalShards}`,
18465
+ currentShard: (currentShard) => `Current shard: ${currentShard}`,
18466
+ },
18467
+ };
18468
+
18469
+ const consoleLogFormatted = {
18470
+ bold: (message) => console.log(console.log("\x1b[1m", message, "\x1b[0m")),
18471
+ dim: (message) => console.log(console.log("\x1b[2m", message, "\x1b[0m")),
18472
+ underline: (message) => console.log(console.log("\x1b[4m", message, "\x1b[0m")),
18473
+ invertBackground: (message) => console.log(console.log("\x1b[7m", message, "\x1b[0m")),
18474
+ hidden: (message) => console.log(console.log("\x1b[8m", message, "\x1b[0m")),
18475
+ error: (message) => console.log("\u001b[31m", "\x1b[1m", message, "\x1b[0m", "\u001b[0m"),
18476
+ warning: (message) => console.log("\u001b[33m", "\x1b[1m", message, "\x1b[0m", "\u001b[0m"),
18477
+ };
18478
+ const executeCommandLine = ({ command, messageOnError, shouldThrowError = false, logLevel = "warning", }) => {
18479
+ try {
18480
+ return childProcess.execSync(command).toString().trim();
18481
+ }
18482
+ catch (err) {
18483
+ if (shouldThrowError) {
18484
+ throw err;
18485
+ }
18486
+ else {
18487
+ consoleLogFormatted[logLevel](messageOnError);
18488
+ }
18489
+ }
18490
+ };
18491
+
18492
+ const createShardObject = ({ currentShard = 0, status = "running", duration = null, }) => ({
18493
+ [currentShard]: { status, duration },
18494
+ });
18495
+ const convertBufferToBlob = (buffer, contentType) => new Blob([buffer], { type: contentType });
18496
+
18497
+ const getDescribePath = ({ titlePath, title, project, spec, }) => titlePath.filter((item, index) => index !== 0 && item !== title && item !== project && item !== spec);
18498
+ const getCurrentCommitSha = () => executeCommandLine({
18499
+ command: "git rev-parse HEAD",
18500
+ messageOnError: ERRORS.onBegin.failedToGetCommitSha,
18501
+ });
18502
+ const getCurrentCommitMessage = () => executeCommandLine({
18503
+ command: "git show-branch --no-name HEAD",
18504
+ messageOnError: ERRORS.onBegin.failedToGetCommitMessage,
18505
+ });
18506
+ const getInitializerData = ({ rootDir }, rootSuite) => rootSuite.allTests().map(test => {
18507
+ var _a;
18508
+ const { title, parent, id: history_id, location: { file }, } = test;
18509
+ const titlePath = test.titlePath();
18510
+ const project = (_a = parent.project()) === null || _a === void 0 ? void 0 : _a.name;
18511
+ const spec = file.replace(`${rootDir}/`, "");
18512
+ const describe = getDescribePath({ titlePath, title, spec, project });
18513
+ return { title, describe, project, spec, history_id };
18514
+ });
18515
+
18516
+ const setAuthHeaders = (projectKey) => {
18517
+ var _a;
18449
18518
  axios.defaults.headers = {
18450
18519
  ...axios.defaults.headers,
18451
- [HEADERS_KEYS.applicationKey]: applicationKey,
18520
+ [HEADERS_KEYS.projectKey]: projectKey,
18521
+ [HEADERS_KEYS.apiKey]: (_a = process.env.API_KEY) !== null && _a !== void 0 ? _a : "",
18452
18522
  [HEADERS_KEYS.accept]: "application/json",
18453
18523
  [HEADERS_KEYS.contentType]: "application/json",
18454
18524
  };
18455
18525
  };
18456
- function initializeAxios(applicationKey) {
18457
- setAuthHeaders(applicationKey);
18526
+ function initializeAxios({ projectKey, baseURL, }) {
18527
+ axios.defaults.baseURL = baseURL;
18528
+ setAuthHeaders(projectKey);
18458
18529
  }
18459
18530
 
18531
+ const create$1 = (payload) => axios.post(`${API_BASE_URL}/reporter/runs`, {
18532
+ run: payload,
18533
+ });
18534
+ const update = (ciBuildId, payload) => axios.put(`${API_BASE_URL}/reporter/runs/${ciBuildId}`, {
18535
+ run: payload,
18536
+ });
18537
+ const runsApi = { create: create$1, update };
18538
+
18539
+ const create = (ciBuildId, payload) => axios.post(`${API_BASE_URL}/reporter/runs/${ciBuildId}/test_entities`, {
18540
+ test_entity: payload,
18541
+ });
18542
+ const testEntitiesApi = { create };
18543
+
18460
18544
  class MyReporter {
18461
18545
  constructor(options) {
18462
- this.onBegin = async (config, suite) => {
18463
- console.log("Run begin".padStart(5, "\n").padEnd(5, "\n"));
18464
- console.log("Config".padStart(20, "*").padEnd(40, "*"));
18465
- console.log(JSON.stringify(config, null, 4));
18466
- console.log("Suite".padStart(20, "*").padEnd(40, "*"));
18467
- const initializerData = suite.allTests().map(test => {
18468
- var _a;
18469
- const { rootDir } = config;
18470
- const { title, parent, id: history_id, location: { file }, } = test;
18471
- const titlePath = test.titlePath();
18472
- const project = (_a = parent.project()) === null || _a === void 0 ? void 0 : _a.name;
18473
- const spec = file.replace(`${rootDir}/`, "");
18474
- const describe = getDescribePath({ titlePath, title, spec, project });
18475
- return { title, describe, project, spec, history_id };
18476
- });
18477
- await dummyApi.create({ test_entity: { initializerData, config } });
18546
+ this.onBegin = async (config, rootSuite) => {
18547
+ const shard = config.shard;
18548
+ let attempts;
18549
+ try {
18550
+ const runDetails = {
18551
+ commit_id: getCurrentCommitSha(),
18552
+ commit_name: getCurrentCommitMessage(),
18553
+ ci_build_id: this.ciBuildId,
18554
+ configuration: config,
18555
+ shards: createShardObject({ currentShard: shard === null || shard === void 0 ? void 0 : shard.current }),
18556
+ };
18557
+ await runsApi.create(runDetails);
18558
+ ({ data: attempts } = await testEntitiesApi.create(this.ciBuildId, {
18559
+ test_entities: getInitializerData(config, rootSuite),
18560
+ }));
18561
+ }
18562
+ catch (error) {
18563
+ consoleLogFormatted.error(error);
18564
+ throw new Error(ERRORS.onBegin.failedToInitializeRun);
18565
+ }
18566
+ consoleLogFormatted.underline(MESSAGES.onBegin.testStarted);
18567
+ consoleLogFormatted.dim(MESSAGES.onBegin.ciBuildId(this.ciBuildId));
18568
+ if (shard) {
18569
+ consoleLogFormatted.dim(MESSAGES.onBegin.totalShards(shard.total));
18570
+ consoleLogFormatted.dim(MESSAGES.onBegin.currentShard(shard.current));
18571
+ }
18572
+ this.attempts = attempts;
18573
+ this.config = config;
18574
+ this.currentShard = shard === null || shard === void 0 ? void 0 : shard.current;
18575
+ };
18576
+ this.onTestBegin = async ({ id, title }, { retry }) => {
18577
+ try {
18578
+ const formData = new FormData();
18579
+ formData.append("attempt[status]", "running");
18580
+ retry === 0 &&
18581
+ (await attemptsApi.update(this.ciBuildId, id, this.attempts[id], formData));
18582
+ }
18583
+ catch (error) {
18584
+ consoleLogFormatted.error(ERRORS.onTestBegin.failedToReportTest(title, id));
18585
+ console.log(error);
18586
+ }
18587
+ };
18588
+ this.onTestEnd = async ({ id, title }, { status, duration, errors, retry, attachments }) => {
18589
+ try {
18590
+ const testResult = {
18591
+ status,
18592
+ duration,
18593
+ log: errors.map(error => { var _a; return (_a = error.message) !== null && _a !== void 0 ? _a : ""; }).join("\n"),
18594
+ };
18595
+ consoleLogFormatted.underline(title);
18596
+ const formData = new FormData();
18597
+ attachments.map(({ name, path, body, contentType }) => {
18598
+ consoleLogFormatted.dim(`${name}: ${path}`);
18599
+ if (["screenshot", "video", "trace"].includes(name)) {
18600
+ const buffer = path ? require$$6.readFileSync(path) : body;
18601
+ formData.append(`attempt[${name}s][]`, convertBufferToBlob(buffer, contentType));
18602
+ }
18603
+ });
18604
+ Object.entries(testResult).map(([resultKey, resultValue]) => {
18605
+ formData.append(`attempt[${resultKey}]`, resultValue.toString());
18606
+ });
18607
+ retry === 0
18608
+ ? await attemptsApi.update(this.ciBuildId, id, this.attempts[id], formData)
18609
+ : await attemptsApi.create(this.ciBuildId, id, formData);
18610
+ }
18611
+ catch (error) {
18612
+ consoleLogFormatted.error(ERRORS.onTestBegin.failedToReportTest(title, id));
18613
+ console.log(error);
18614
+ }
18615
+ };
18616
+ this.onEnd = async ({ status, duration }) => {
18617
+ try {
18618
+ await runsApi.update(this.ciBuildId, {
18619
+ shards: createShardObject({
18620
+ currentShard: this.currentShard,
18621
+ status,
18622
+ duration,
18623
+ }),
18624
+ });
18625
+ }
18626
+ catch (error) {
18627
+ console.log(error);
18628
+ throw new Error(ERRORS.onEnd.failedToReportRunStatus);
18629
+ }
18478
18630
  };
18479
- initializeAxios(options.applicationKey);
18631
+ initializeAxios(options);
18632
+ this.attempts = {};
18633
+ this.ciBuildId = options.ciBuildId;
18480
18634
  }
18481
18635
  }
18482
18636