@lokalise/playwright-reporters 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,6 +42,42 @@ export default defineConfig({
42
42
  });
43
43
  ```
44
44
 
45
+ ## Steps Duration Reporter
46
+
47
+ This reporter aggregates the duration of each discrete test step and reports their total duration at the end of the test run. The reporter uses the custom [Playwright Reporter](https://playwright.dev/docs/test-reporters#custom-reporters) implementation to collect the step durations. Follow the steps in the [official Playwright documentation](https://playwright.dev/docs/test-reporters#multiple-reporters) to add the custom reporter to your test suite.
48
+
49
+ ### Usage
50
+
51
+ Once the reporter is enabled, it will output a `JSON` file `./reporters/results/steps.json` containing the duration of each step in the test suite.
52
+
53
+ ## Test Similarity Reporter
54
+
55
+ This reporter calculates the similarity between tests based on the test steps. The reporter does not use the custom [Playwright Reporter](https://playwright.dev/docs/test-reporters#custom-reporters) implementation. Instead, it takes the simplified reporter from the undocumented `timeline` reporter.
56
+
57
+ ### Usage
58
+
59
+ 1. Run the test suite with the `timeline` reporter enabled. This will generate a `JSON` file `./reporters/results/timeline.json`
60
+ 1. Run the similarity reporter with the following command: `npx ts-node reporters/testSimilarity.ts`
61
+ 1. After analysing the results, the reporter will output a `JSON` file `./reporters/results/distance.json` containing the similarity between tests based on the test steps.
62
+
63
+ ### Output
64
+
65
+ The report has the following format:
66
+
67
+ ```json
68
+ [
69
+ {
70
+ "title": "Title of the test being compared",
71
+ "relatedTests": [
72
+ ["Title of the first related test", 0],
73
+ ["Title of the second related test", 50]
74
+ ]
75
+ }
76
+ ]
77
+ ```
78
+
79
+ The `relatedTests` array contains the title of the related test and the distance score. The similarity percentage is calculated based on the [Levenshtein distance](https://en.wikipedia.org/wiki/Levenshtein_distance) between the test steps. The lower the distance, the higher the similarity between the tests.
80
+
45
81
  ## Husky
46
82
 
47
83
  By default, pre-commit hook will run `npm run lint:fix`. Feel free to remove that if it's undesirable or add your own
@@ -15,7 +15,7 @@ export declare const formFailedTestData: (test: TestCase, result: TestResult, te
15
15
  name: string;
16
16
  team: string | null;
17
17
  testRunId: string;
18
- status: import("playwright/types/test").TestStatus;
18
+ status: "failed" | "interrupted" | "passed" | "timedOut" | "skipped";
19
19
  hooksDuration: number;
20
20
  beforeHookDuration: number;
21
21
  afterHookDuration: number;
@@ -32,7 +32,7 @@ export declare const formFlakyTestData: (test: TestCase, result: TestResult, tes
32
32
  name: string;
33
33
  team: string | null;
34
34
  testRunId: string;
35
- status: import("playwright/types/test").TestStatus;
35
+ status: "failed" | "interrupted" | "passed" | "timedOut" | "skipped";
36
36
  hooksDuration: number;
37
37
  beforeHookDuration: number;
38
38
  afterHookDuration: number;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  import AnalyticsReporter, { defineAnalyticsReporterConfig } from './analytics/reporter';
2
+ import PermissionsReporter, { definePermissionsReporterConfig } from './permissions/reporter';
2
3
  import RetryReporter, { defineRetryReporterConfig } from './retry/reporter';
3
- export { AnalyticsReporter, defineAnalyticsReporterConfig, RetryReporter, defineRetryReporterConfig, };
4
+ import StepDurationReporter, { defineStepDurationReporterConfig } from './stepDuration/reporter';
5
+ import TimelineReporter, { defineTimelineReporterConfig } from './timeline/reporter';
6
+ export { AnalyticsReporter, defineAnalyticsReporterConfig, RetryReporter, defineRetryReporterConfig, TimelineReporter, defineTimelineReporterConfig, StepDurationReporter, defineStepDurationReporterConfig, PermissionsReporter, definePermissionsReporterConfig };
package/dist/index.js CHANGED
@@ -23,10 +23,19 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.defineRetryReporterConfig = exports.RetryReporter = exports.defineAnalyticsReporterConfig = exports.AnalyticsReporter = void 0;
26
+ exports.definePermissionsReporterConfig = exports.PermissionsReporter = exports.defineStepDurationReporterConfig = exports.StepDurationReporter = exports.defineTimelineReporterConfig = exports.TimelineReporter = exports.defineRetryReporterConfig = exports.RetryReporter = exports.defineAnalyticsReporterConfig = exports.AnalyticsReporter = void 0;
27
27
  const reporter_1 = __importStar(require("./analytics/reporter"));
28
28
  exports.AnalyticsReporter = reporter_1.default;
29
29
  Object.defineProperty(exports, "defineAnalyticsReporterConfig", { enumerable: true, get: function () { return reporter_1.defineAnalyticsReporterConfig; } });
30
- const reporter_2 = __importStar(require("./retry/reporter"));
31
- exports.RetryReporter = reporter_2.default;
32
- Object.defineProperty(exports, "defineRetryReporterConfig", { enumerable: true, get: function () { return reporter_2.defineRetryReporterConfig; } });
30
+ const reporter_2 = __importStar(require("./permissions/reporter"));
31
+ exports.PermissionsReporter = reporter_2.default;
32
+ Object.defineProperty(exports, "definePermissionsReporterConfig", { enumerable: true, get: function () { return reporter_2.definePermissionsReporterConfig; } });
33
+ const reporter_3 = __importStar(require("./retry/reporter"));
34
+ exports.RetryReporter = reporter_3.default;
35
+ Object.defineProperty(exports, "defineRetryReporterConfig", { enumerable: true, get: function () { return reporter_3.defineRetryReporterConfig; } });
36
+ const reporter_4 = __importStar(require("./stepDuration/reporter"));
37
+ exports.StepDurationReporter = reporter_4.default;
38
+ Object.defineProperty(exports, "defineStepDurationReporterConfig", { enumerable: true, get: function () { return reporter_4.defineStepDurationReporterConfig; } });
39
+ const reporter_5 = __importStar(require("./timeline/reporter"));
40
+ exports.TimelineReporter = reporter_5.default;
41
+ Object.defineProperty(exports, "defineTimelineReporterConfig", { enumerable: true, get: function () { return reporter_5.defineTimelineReporterConfig; } });
@@ -0,0 +1,13 @@
1
+ import type { PermissionsReportData } from "./reporter";
2
+ export type ElasticOptions = {
3
+ permissionsIndex: string;
4
+ elasticUrl: string;
5
+ elasticToken: string;
6
+ };
7
+ export declare class Elastic {
8
+ private readonly options;
9
+ private request;
10
+ constructor(options: ElasticOptions);
11
+ setRequestContext(): Promise<void>;
12
+ savePermissionsData(testData: PermissionsReportData): Promise<void>;
13
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Elastic = void 0;
4
+ const test_1 = require("@playwright/test");
5
+ class Elastic {
6
+ constructor(options) {
7
+ this.options = options;
8
+ }
9
+ async setRequestContext() {
10
+ this.request = await test_1.request.newContext({
11
+ baseURL: this.options.elasticUrl,
12
+ ignoreHTTPSErrors: true,
13
+ ...(this.options.elasticToken && {
14
+ extraHTTPHeaders: { Authorization: `ApiKey ${this.options.elasticToken}` },
15
+ }),
16
+ });
17
+ }
18
+ async savePermissionsData(testData) {
19
+ try {
20
+ await this.request.post(`/${this.options.permissionsIndex}/_doc`, { data: testData });
21
+ }
22
+ catch (error) {
23
+ console.error("Failed to save permissions data");
24
+ console.error(error);
25
+ }
26
+ }
27
+ }
28
+ exports.Elastic = Elastic;
@@ -0,0 +1,25 @@
1
+ import type { TestCase, TestResult, Reporter, TestStep } from '@playwright/test/reporter';
2
+ import { type ElasticOptions } from './elastic';
3
+ type ReporterOptions = {
4
+ annotationName: string;
5
+ stepName: string;
6
+ debug: boolean;
7
+ } & ElasticOptions;
8
+ export type PermissionsReportData = {
9
+ title: string;
10
+ permissions: Record<string, boolean>;
11
+ failedStep: string;
12
+ timestamp: number;
13
+ };
14
+ export default class PermissionsReporter implements Reporter {
15
+ private readonly options;
16
+ private readonly elastic;
17
+ private readonly debugLogging;
18
+ private failedTestData;
19
+ constructor(options: ReporterOptions);
20
+ onBegin(): Promise<void>;
21
+ onTestEnd(test: TestCase): Promise<void>;
22
+ onStepEnd(test: TestCase, _result: TestResult, step: TestStep): void;
23
+ }
24
+ export declare const definePermissionsReporterConfig: (options: ConstructorParameters<typeof PermissionsReporter>[0]) => ReporterOptions;
25
+ export {};
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.definePermissionsReporterConfig = void 0;
4
+ const elastic_1 = require("./elastic");
5
+ // eslint-disable-next-line import/no-default-export
6
+ class PermissionsReporter {
7
+ constructor(options) {
8
+ this.elastic = new elastic_1.Elastic(options);
9
+ this.options = options;
10
+ this.failedTestData = {};
11
+ this.debugLogging = options.debug ? console.info : () => { };
12
+ }
13
+ async onBegin() {
14
+ await this.elastic.setRequestContext();
15
+ }
16
+ async onTestEnd(test) {
17
+ // Omit the results if the test is passing
18
+ if (test.outcome() === 'expected') {
19
+ this.debugLogging('Not reporting test - test passed');
20
+ return;
21
+ }
22
+ // Omit the results if the test does not contain the annotation
23
+ if (!test.annotations.find((annotation) => annotation.type === this.options.annotationName)) {
24
+ this.debugLogging('Not reporting test - no permission annotation');
25
+ return;
26
+ }
27
+ // Test Title
28
+ const testTitle = test.title;
29
+ // Permissions as object
30
+ const testPermissions = test.annotations.find((annotation) => annotation.type === this.options.annotationName);
31
+ const permissionContents = testPermissions?.description ? JSON.parse(testPermissions.description) : {};
32
+ // Which step failed
33
+ const failedStep = this.failedTestData[test.id];
34
+ // Send payload body
35
+ const reportData = {
36
+ title: testTitle,
37
+ permissions: permissionContents,
38
+ failedStep,
39
+ timestamp: Date.now()
40
+ };
41
+ await this.elastic.savePermissionsData(reportData);
42
+ }
43
+ onStepEnd(test, _result, step) {
44
+ if (!step.title.includes(this.options.stepName)) {
45
+ this.debugLogging('Not reporting step - no step annotation', step.title);
46
+ return;
47
+ }
48
+ if (!step.error) {
49
+ this.debugLogging("Not reporting step - no error");
50
+ return;
51
+ }
52
+ this.debugLogging('Captured step name with error:', step.title);
53
+ this.failedTestData[test.id] = step.title;
54
+ }
55
+ }
56
+ exports.default = PermissionsReporter;
57
+ const definePermissionsReporterConfig = (options) => options;
58
+ exports.definePermissionsReporterConfig = definePermissionsReporterConfig;
@@ -0,0 +1,15 @@
1
+ import type { Reporter, TestCase, TestResult, TestStep } from '@playwright/test/reporter';
2
+ interface ReporterOptions {
3
+ writeToFile: boolean;
4
+ filePath?: string;
5
+ }
6
+ declare class StepDurationReporter implements Reporter {
7
+ private stepStorage;
8
+ private readonly ignoredSteps;
9
+ private readonly options;
10
+ constructor(options: ReporterOptions);
11
+ onStepEnd(_test: TestCase, _result: TestResult, step: TestStep): void;
12
+ onEnd(): void;
13
+ }
14
+ export default StepDurationReporter;
15
+ export declare const defineStepDurationReporterConfig: (options: ConstructorParameters<typeof StepDurationReporter>[0]) => ReporterOptions;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.defineStepDurationReporterConfig = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ class StepDurationReporter {
9
+ constructor(options) {
10
+ this.options = options;
11
+ this.stepStorage = {};
12
+ this.ignoredSteps = [
13
+ 'expect.',
14
+ /attach.*(response|request|trace|login)/i,
15
+ /hook(s?)$/i,
16
+ /tracing\.(start|stop)/,
17
+ /apiResponse\.(json|text)/,
18
+ /sending .* to/,
19
+ ];
20
+ }
21
+ onStepEnd(_test, _result, step) {
22
+ const stepName = step.title;
23
+ const shouldIgnoreStep = this.ignoredSteps.some((ignoredStep) => {
24
+ if (typeof ignoredStep === 'string') {
25
+ return stepName.toLowerCase().includes(ignoredStep);
26
+ }
27
+ return ignoredStep.test(stepName.toLowerCase());
28
+ });
29
+ if (shouldIgnoreStep) {
30
+ return;
31
+ }
32
+ if (!(stepName in this.stepStorage)) {
33
+ this.stepStorage[stepName] = { totalTime: 0, minTime: 0, maxTime: 0, count: 0 };
34
+ }
35
+ const stepDuration = Date.now() - step.startTime.getTime();
36
+ this.stepStorage[stepName].totalTime += stepDuration;
37
+ this.stepStorage[stepName].count += 1;
38
+ this.stepStorage[stepName].minTime = Math.min(this.stepStorage[stepName].minTime === 0 ? stepDuration : this.stepStorage[stepName].minTime, stepDuration);
39
+ this.stepStorage[stepName].maxTime = Math.max(this.stepStorage[stepName].maxTime, stepDuration);
40
+ }
41
+ onEnd() {
42
+ const sortedSteps = Object.fromEntries(Object.entries(this.stepStorage).sort((a, b) => b[1].totalTime - a[1].totalTime));
43
+ const reportString = JSON.stringify(sortedSteps, null, 2);
44
+ if (this.options.writeToFile) {
45
+ const filePath = this.options.filePath ?? './reporters/results/steps.json';
46
+ node_fs_1.default.writeFileSync(filePath, reportString);
47
+ }
48
+ else {
49
+ console.log(reportString);
50
+ }
51
+ }
52
+ }
53
+ // eslint-disable-next-line import/no-default-export
54
+ exports.default = StepDurationReporter;
55
+ const defineStepDurationReporterConfig = (options) => options;
56
+ exports.defineStepDurationReporterConfig = defineStepDurationReporterConfig;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_fs_1 = __importDefault(require("node:fs"));
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const fastest_levenshtein_1 = require("fastest-levenshtein");
9
+ const replaceTitleParameters = (title) => {
10
+ const numberInUrl = /\/(\d+)(?:\/|$)/gm;
11
+ const projectId = /[a-z0-9]+\.[a-z0-9]+/gm;
12
+ return title.replace(numberInUrl, '/:id/').replace(projectId, ':projectId');
13
+ };
14
+ const updateLog = (message) => {
15
+ process.stdout.clearLine(0);
16
+ process.stdout.cursorTo(0);
17
+ process.stdout.write(message);
18
+ };
19
+ const stepsToIgnore = [
20
+ 'Create Browser',
21
+ 'Sending post to /signup/test-user',
22
+ 'open and match condition at Application',
23
+ ];
24
+ const pathVariable = process.env.REPORTS_PATH;
25
+ const timelinePath = node_path_1.default.resolve(node_path_1.default.join(pathVariable, './results/timeline.json'));
26
+ const timeline = JSON.parse(node_fs_1.default.readFileSync(timelinePath, 'utf-8'));
27
+ const timelineWithoutEmptySteps = timeline.tests.filter((test) => {
28
+ return test.steps.length > 0;
29
+ });
30
+ const simplifiedTimeline = timelineWithoutEmptySteps.map((test) => {
31
+ return {
32
+ title: test.title,
33
+ steps: test.steps
34
+ .filter((step) => step.category === 'test.step')
35
+ .filter((step) => !stepsToIgnore.includes(step.title))
36
+ .map((step) => replaceTitleParameters(step.title)),
37
+ };
38
+ });
39
+ const timelineAsText = simplifiedTimeline.map((test) => {
40
+ return {
41
+ ...test,
42
+ stepsAsText: test.steps.join(' '),
43
+ };
44
+ });
45
+ const distances = {};
46
+ const iterationCount = (timelineAsText.length * (timelineAsText.length - 1)) / 2;
47
+ let currentIteration = 0;
48
+ for (let testIndex = 0; testIndex < timelineAsText.length; testIndex++) {
49
+ updateLog(`Processing iteration ${currentIteration} of ${iterationCount} (${Math.floor((currentIteration / iterationCount) * 100)}%)`);
50
+ currentIteration += timelineAsText.length - testIndex;
51
+ const keyName = timelineAsText[testIndex].title;
52
+ if (!(keyName in distances)) {
53
+ distances[keyName] = [];
54
+ }
55
+ for (let compareIndex = testIndex + 1; compareIndex < timelineAsText.length; compareIndex++) {
56
+ const currentTest = timelineAsText[testIndex];
57
+ const compareTest = timelineAsText[compareIndex];
58
+ const distanceValue = (0, fastest_levenshtein_1.distance)(currentTest.stepsAsText, compareTest.stepsAsText);
59
+ distances[keyName].push([compareTest.title, distanceValue]);
60
+ }
61
+ }
62
+ const sortedDistances = Object.entries(distances)
63
+ .map(([key, value]) => {
64
+ return {
65
+ title: key,
66
+ relatedTests: value.sort((a, b) => a[1] - b[1]),
67
+ };
68
+ })
69
+ .sort((a, b) => a.relatedTests[0][1] - b.relatedTests[0][1]);
70
+ const resultsPath = node_path_1.default.join(pathVariable, './results/distances.json');
71
+ node_fs_1.default.writeFileSync(resultsPath, JSON.stringify(sortedDistances, null, 2));
72
+ console.log(`\nDone. Check the results in ${resultsPath}`);
@@ -0,0 +1,47 @@
1
+ import type { FullResult, Reporter, TestCase, TestResult, TestStatus, TestStep } from '@playwright/test/reporter';
2
+ export interface TestRunData {
3
+ startTime: number;
4
+ endTime: number;
5
+ duration: number;
6
+ tests: TestData[];
7
+ }
8
+ export interface TestData {
9
+ title: string;
10
+ startTime: number;
11
+ endTime: number;
12
+ status: TestStatus | 'running';
13
+ parallelIndex: number;
14
+ id: string;
15
+ retry: number;
16
+ steps: TestStepData[];
17
+ }
18
+ type StepCategory = 'hook' | 'expect' | 'pw:api' | 'test.step' | 'fixture' | 'attach';
19
+ export interface TestStepData {
20
+ title: string;
21
+ id: string;
22
+ category: StepCategory;
23
+ isWithinHook?: boolean;
24
+ startTime: number;
25
+ endTime: number;
26
+ depth: number;
27
+ hasError: boolean;
28
+ }
29
+ interface ReporterOptions {
30
+ writeToFile: boolean;
31
+ filePath?: string;
32
+ }
33
+ declare class TimelineReporter implements Reporter {
34
+ private readonly testData;
35
+ private readonly options;
36
+ constructor(options?: ReporterOptions);
37
+ private findMatchingTest;
38
+ onBegin(): void;
39
+ onTestBegin(test: TestCase, result: TestResult): void;
40
+ onTestEnd(test: TestCase, result: TestResult): void;
41
+ onStepEnd(test: TestCase, result: TestResult, step: TestStep): void;
42
+ onEnd(result: FullResult): void;
43
+ private writeReportToFile;
44
+ getReport(): TestRunData;
45
+ }
46
+ export default TimelineReporter;
47
+ export declare const defineTimelineReporterConfig: (options: ConstructorParameters<typeof TimelineReporter>[0]) => ReporterOptions | undefined;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.defineTimelineReporterConfig = void 0;
7
+ const node_crypto_1 = __importDefault(require("node:crypto"));
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const getStepDepthFromStepPath = (step) => {
10
+ const stepPath = step.titlePath();
11
+ const stepDepth = stepPath.length - 1;
12
+ return stepDepth;
13
+ };
14
+ const isStepWithinHook = (step) => {
15
+ if (step === undefined) {
16
+ return false;
17
+ }
18
+ if (step.category === 'hook') {
19
+ return true;
20
+ }
21
+ return isStepWithinHook(step?.parent);
22
+ };
23
+ class TimelineReporter {
24
+ constructor(options) {
25
+ this.options = { writeToFile: true, ...options };
26
+ this.testData = {
27
+ startTime: 0,
28
+ endTime: 0,
29
+ duration: 0,
30
+ tests: [],
31
+ };
32
+ }
33
+ findMatchingTest(test, result) {
34
+ const matchingTest = this.testData.tests.find((testData) => {
35
+ const matchesId = testData.id === test.id;
36
+ const matchesRetry = testData.retry === (result?.retry ?? 0);
37
+ return matchesId && matchesRetry;
38
+ });
39
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
40
+ return matchingTest;
41
+ }
42
+ onBegin() {
43
+ this.testData.startTime = Date.now();
44
+ }
45
+ onTestBegin(test, result) {
46
+ this.testData.tests.push({
47
+ title: test.title,
48
+ startTime: result.startTime.getTime(),
49
+ endTime: 0,
50
+ retry: result?.retry ?? 0,
51
+ status: 'running',
52
+ parallelIndex: result.parallelIndex,
53
+ id: test.id,
54
+ steps: [],
55
+ });
56
+ }
57
+ onTestEnd(test, result) {
58
+ const finishedTest = this.findMatchingTest(test, result);
59
+ finishedTest.endTime = finishedTest.startTime + result.duration;
60
+ finishedTest.status = result.status;
61
+ }
62
+ onStepEnd(test, result, step) {
63
+ const matchingTest = this.findMatchingTest(test, result);
64
+ const startTime = step.startTime.getTime();
65
+ const endTime = startTime + step.duration;
66
+ matchingTest.steps.push({
67
+ category: step.category,
68
+ isWithinHook: isStepWithinHook(step),
69
+ title: step.title,
70
+ id: node_crypto_1.default.randomUUID(),
71
+ startTime,
72
+ endTime,
73
+ depth: getStepDepthFromStepPath(step),
74
+ hasError: step.error !== undefined,
75
+ });
76
+ }
77
+ onEnd(result) {
78
+ this.testData.endTime = Date.now();
79
+ this.testData.duration = result.duration;
80
+ if (this?.options?.writeToFile === true) {
81
+ this.writeReportToFile();
82
+ }
83
+ }
84
+ writeReportToFile() {
85
+ const filePath = this.options?.filePath ?? 'customResults.json';
86
+ const report = this.getReport();
87
+ const reportString = JSON.stringify(report, null, 2);
88
+ node_fs_1.default.writeFileSync(filePath, reportString);
89
+ }
90
+ getReport() {
91
+ return this.testData;
92
+ }
93
+ }
94
+ // eslint-disable-next-line import/no-default-export
95
+ exports.default = TimelineReporter;
96
+ const defineTimelineReporterConfig = (options) => options;
97
+ exports.defineTimelineReporterConfig = defineTimelineReporterConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lokalise/playwright-reporters",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "scripts": {
5
5
  "lint:eslint": "eslint --cache . --ext .js,.cjs,.ts",
6
6
  "lint:ts": "tsc --noEmit",
@@ -29,35 +29,52 @@
29
29
  "./analytics": {
30
30
  "import": "./dist/analytics/reporter.js",
31
31
  "require": "./dist/analytics/reporter.js"
32
+ },
33
+ "./timeline": {
34
+ "import": "./dist/timeline/reporter.js",
35
+ "require": "./dist/timeline/reporter.js"
36
+ },
37
+ "./stepDuration": {
38
+ "import": "./dist/stepDuration/reporter.js",
39
+ "require": "./dist/stepDuration/reporter.js"
40
+ },
41
+ "./testSimilarity": {
42
+ "import": "./dist/testSimilarity/reporter.js",
43
+ "require": "./dist/testSimilarity/reporter.js"
44
+ },
45
+ "./permissions": {
46
+ "import": "./dist/permissions/reporter.js",
47
+ "require": "./dist/permissions/reporter.js"
32
48
  }
33
49
  },
34
50
  "publishConfig": {
35
51
  "access": "public"
36
52
  },
37
53
  "devDependencies": {
38
- "@commitlint/cli": "19.1.0",
39
- "@commitlint/config-conventional": "19.1.0",
40
- "@commitlint/prompt-cli": "19.1.0",
41
- "@lokalise/eslint-config-frontend": "^4.3.2",
42
- "@lokalise/prettier-config": "^1.0.0",
54
+ "@commitlint/cli": "19.3.0",
55
+ "@commitlint/config-conventional": "19.2.2",
56
+ "@commitlint/prompt-cli": "19.3.1",
57
+ "@lokalise/eslint-config-frontend": "^4.6.0",
58
+ "@lokalise/prettier-config": "^1.0.1",
43
59
  "@semantic-release/changelog": "6.0.3",
44
- "@semantic-release/commit-analyzer": "11.1.0",
60
+ "@semantic-release/commit-analyzer": "13.0.0",
45
61
  "@semantic-release/git": "10.0.1",
46
- "@semantic-release/github": "10.0.2",
47
- "@semantic-release/npm": "11.0.3",
48
- "@semantic-release/release-notes-generator": "12.1.0",
49
- "@types/lodash": "^4.17.0",
50
- "@types/node": "^20.11.27",
62
+ "@semantic-release/github": "10.0.6",
63
+ "@semantic-release/npm": "12.0.1",
64
+ "@semantic-release/release-notes-generator": "13.0.0",
65
+ "@types/lodash": "^4.17.5",
66
+ "@types/node": "^20.14.2",
51
67
  "eslint-config-prettier": "^9.1.0",
52
68
  "eslint-plugin-prettier": "^5.1.3",
53
69
  "husky": "9.0.11",
54
- "prettier": "^3.2.5",
55
- "semantic-release": "23.0.2",
56
- "typescript": "5.4.2"
70
+ "prettier": "^3.3.2",
71
+ "semantic-release": "23.1.1",
72
+ "typescript": "5.4.5"
57
73
  },
58
74
  "dependencies": {
59
75
  "@faker-js/faker": "^8.4.1",
60
- "@playwright/test": "^1.42.1",
76
+ "@playwright/test": "^1.44.1",
77
+ "fastest-levenshtein": "^1.0.16",
61
78
  "lodash": "^4.17.21"
62
79
  },
63
80
  "prettier": "@lokalise/prettier-config"