Skip to content

Commit 45041f7

Browse files
Merge pull request #15 from VadimNastoyashchy/new-performance-reporter
Added new functionality
2 parents 674e2e5 + fb6ce60 commit 45041f7

File tree

7 files changed

+101
-1
lines changed

7 files changed

+101
-1
lines changed

playwright.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const config: PlaywrightTestConfig = {
3030
/* Opt out of parallel tests on CI. */
3131
workers: process.env.CI ? 1 : undefined,
3232
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
33-
reporter: 'html',
33+
reporter: [['html'], ['./src/setup/slowStepReporter.ts']],
3434
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
3535
use: {
3636
viewport: { width: 828, height: 1792 },

src/ApiService.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Page } from '@playwright/test';
22
import ICredentials from './ICredentials';
3+
import { step } from './setup/step';
34

45
export default class ApiService {
56
private readonly page: Page;
@@ -8,6 +9,7 @@ export default class ApiService {
89
this.page = page;
910
}
1011

12+
@step()
1113
public async logIn(credentials: ICredentials): Promise<void> {
1214
const { userName } = credentials;
1315
const cookiesData = {

src/base/BasePage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Page } from '@playwright/test';
2+
import { step } from '../setup/step';
23

34
export default abstract class BasePage {
45
protected readonly BASE_PAGE = 'https://www.saucedemo.com/';
@@ -13,6 +14,7 @@ export default abstract class BasePage {
1314
this.PAGE_URL = pageUrl;
1415
}
1516

17+
@step()
1618
public async open(): Promise<void> {
1719
await this.page.goto(`${this.BASE_PAGE}${this.PAGE_URL}`);
1820
}

src/components/Header.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Page } from '@playwright/test';
22
import BaseComponent from '../base/BaseComponent';
3+
import { step } from '../setup/step';
34

45
export default class Header extends BaseComponent {
56

@@ -11,10 +12,12 @@ export default class Header extends BaseComponent {
1112
super(page);
1213
}
1314

15+
@step()
1416
public async clickOnSlideMenu(): Promise<void> {
1517
await this.burgerButton.click();
1618
}
1719

20+
@step()
1821
public async clickOnLogOutInSlideMenu(): Promise<void> {
1922
await this.burgerMenuItemList.filter({ hasText: 'Logout' }).click();
2023
}

src/pages/LoginPage.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Page } from '@playwright/test';
22
import BasePage from '../base/BasePage';
33
import ICredentials from '../ICredentials';
4+
import { step } from '../setup/step';
45

56
export default class LoginPage extends BasePage {
67

@@ -13,25 +14,30 @@ export default class LoginPage extends BasePage {
1314
super(page, 'LogIn Page');
1415
}
1516

17+
@step()
1618
private async enterEmail(userName: string): Promise<void> {
1719
await this.inputEmailField.fill(userName);
1820
}
1921

22+
@step()
2023
private async enterPassword(userPassword: string): Promise<void> {
2124
await this.inputPasswordField.fill(userPassword);
2225
}
2326

27+
@step()
2428
private async clickOnLogInButton(): Promise<void> {
2529
await this.logInButton.click();
2630
}
2731

32+
@step()
2833
public async logInWithCredentials(credentials: ICredentials): Promise<void> {
2934
const { userName, password } = credentials;
3035
await this.enterEmail(userName);
3136
await this.enterPassword(password);
3237
await this.clickOnLogInButton();
3338
}
3439

40+
@step()
3541
public async getErrorMessage(): Promise<string> {
3642
return await this.errorText.innerText();
3743
}

src/setup/slowStepReporter.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
2+
import type {
3+
Reporter,
4+
TestCase,
5+
TestResult,
6+
TestStep,
7+
} from '@playwright/test/reporter';
8+
9+
class SlowStepReporter implements Reporter {
10+
private steps: Array<{
11+
count: number;
12+
name: string;
13+
location: string | undefined;
14+
duration: number;
15+
}> = [];
16+
17+
onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
18+
if (step.category === 'test.step') {
19+
const stepToReport = {
20+
count: 1,
21+
name: step.titlePath().join('->'),
22+
location: `${step.location?.file}:${step.location?.line}`,
23+
duration: step.duration,
24+
};
25+
const alreadyReported = this.steps.find(
26+
(s) => s.name === stepToReport.name
27+
);
28+
29+
if (alreadyReported) {
30+
alreadyReported.count++;
31+
} else {
32+
this.steps.push(stepToReport);
33+
}
34+
}
35+
}
36+
37+
onEnd() {
38+
console.warn('TOP-10 slowest steps');
39+
console.table(
40+
// Slowest first
41+
this.steps
42+
.sort((a, b) => b.duration - a.duration)
43+
// TOP-10 slowest steps
44+
.slice(0, 10)
45+
);
46+
}
47+
}
48+
export default SlowStepReporter;

src/setup/step.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
3+
import { test } from '@playwright/test';
4+
5+
/**
6+
* Decorator that wraps a function with a Playwright test step.
7+
* Used for reporting purposes.
8+
*
9+
* @example
10+
```
11+
import { step } from './step_decorator';
12+
class MyTestClass {
13+
@step('optional step name')
14+
async myTestFunction() {
15+
// Test code goes here
16+
}
17+
}
18+
```
19+
*/
20+
export function step<This, Args extends any[], Return>(message?: string) {
21+
return function actualDecorator(
22+
target: (this: This, ...args: Args) => Promise<Return>,
23+
context: ClassMethodDecoratorContext<
24+
This,
25+
(this: This, ...args: Args) => Promise<Return>
26+
>
27+
) {
28+
function replacementMethod(this: any, ...args: Args) {
29+
const name =
30+
message ?? `${this.constructor.name}.${context.name as string}`;
31+
32+
return test.step(name, async () => target.call(this, ...args), {
33+
box: true,
34+
});
35+
}
36+
37+
return replacementMethod;
38+
};
39+
}

0 commit comments

Comments
 (0)