Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const config: PlaywrightTestConfig = {
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
reporter: [['html'], ['./src/setup/slowStepReporter.ts']],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
viewport: { width: 828, height: 1792 },
Expand Down
2 changes: 2 additions & 0 deletions src/ApiService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Page } from '@playwright/test';
import ICredentials from './ICredentials';
import { step } from './setup/step';

export default class ApiService {
private readonly page: Page;
Expand All @@ -8,6 +9,7 @@ export default class ApiService {
this.page = page;
}

@step()
public async logIn(credentials: ICredentials): Promise<void> {
const { userName } = credentials;
const cookiesData = {
Expand Down
2 changes: 2 additions & 0 deletions src/base/BasePage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Page } from '@playwright/test';
import { step } from '../setup/step';

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

@step()
public async open(): Promise<void> {
await this.page.goto(`${this.BASE_PAGE}${this.PAGE_URL}`);
}
Expand Down
3 changes: 3 additions & 0 deletions src/components/Header.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Page } from '@playwright/test';
import BaseComponent from '../base/BaseComponent';
import { step } from '../setup/step';

export default class Header extends BaseComponent {

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

@step()
public async clickOnSlideMenu(): Promise<void> {
await this.burgerButton.click();
}

@step()
public async clickOnLogOutInSlideMenu(): Promise<void> {
await this.burgerMenuItemList.filter({ hasText: 'Logout' }).click();
}
Expand Down
6 changes: 6 additions & 0 deletions src/pages/LoginPage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Page } from '@playwright/test';
import BasePage from '../base/BasePage';
import ICredentials from '../ICredentials';
import { step } from '../setup/step';

export default class LoginPage extends BasePage {

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

@step()
private async enterEmail(userName: string): Promise<void> {
await this.inputEmailField.fill(userName);
}

@step()
private async enterPassword(userPassword: string): Promise<void> {
await this.inputPasswordField.fill(userPassword);
}

@step()
private async clickOnLogInButton(): Promise<void> {
await this.logInButton.click();
}

@step()
public async logInWithCredentials(credentials: ICredentials): Promise<void> {
const { userName, password } = credentials;
await this.enterEmail(userName);
await this.enterPassword(password);
await this.clickOnLogInButton();
}

@step()
public async getErrorMessage(): Promise<string> {
return await this.errorText.innerText();
}
Expand Down
48 changes: 48 additions & 0 deletions src/setup/slowStepReporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import type {
Reporter,
TestCase,
TestResult,
TestStep,
} from '@playwright/test/reporter';

class SlowStepReporter implements Reporter {
private steps: Array<{
count: number;
name: string;
location: string | undefined;
duration: number;
}> = [];

onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
if (step.category === 'test.step') {
const stepToReport = {
count: 1,
name: step.titlePath().join('->'),
location: `${step.location?.file}:${step.location?.line}`,
duration: step.duration,
};
const alreadyReported = this.steps.find(
(s) => s.name === stepToReport.name
);

if (alreadyReported) {
alreadyReported.count++;
} else {
this.steps.push(stepToReport);
}
}
}

onEnd() {
console.warn('TOP-10 slowest steps');
console.table(
// Slowest first
this.steps
.sort((a, b) => b.duration - a.duration)
// TOP-10 slowest steps
.slice(0, 10)
);
}
}
export default SlowStepReporter;
39 changes: 39 additions & 0 deletions src/setup/step.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { test } from '@playwright/test';

/**
* Decorator that wraps a function with a Playwright test step.
* Used for reporting purposes.
*
* @example
```
import { step } from './step_decorator';
class MyTestClass {
@step('optional step name')
async myTestFunction() {
// Test code goes here
}
}
```
*/
export function step<This, Args extends any[], Return>(message?: string) {
return function actualDecorator(
target: (this: This, ...args: Args) => Promise<Return>,
context: ClassMethodDecoratorContext<
This,
(this: This, ...args: Args) => Promise<Return>
>
) {
function replacementMethod(this: any, ...args: Args) {
const name =
message ?? `${this.constructor.name}.${context.name as string}`;

return test.step(name, async () => target.call(this, ...args), {
box: true,
});
}

return replacementMethod;
};
}
Loading