Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add e2e tests #228

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 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
9 changes: 9 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
BASE_URL=http://localhost:4200/
TIMEOUT=20
EXPECT_TIMEOUT=5
SCREEN_WIDTH=1280,
SCREEN_HEIGHT=720,
OUTPUT_DIR=./test-output
HEADLESS=true
# TEST_TAG=[SMOKE]
# PWDEBUG=console npm run test
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@ Thumbs.db
.angular
test-results/
playwright-report/
.ngapimock
.ngapimock
allure-results/
test-output/
10 changes: 0 additions & 10 deletions apps/golden-sample-app-e2e/.eslintrc.json

This file was deleted.

11 changes: 11 additions & 0 deletions apps/golden-sample-app-e2e/data/credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { User } from './data-types/user';

export const defaultUser: User = {
username: '<USER_NAME>',
password: '<PASSWORD>',
};

export const wrongUser: User = {
username: 'someone',
password: 'some_password',
};
4 changes: 4 additions & 0 deletions apps/golden-sample-app-e2e/data/data-types/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface User {
username: string;
password: string;
}
7 changes: 0 additions & 7 deletions apps/golden-sample-app-e2e/example.spec.ts

This file was deleted.

42 changes: 42 additions & 0 deletions apps/golden-sample-app-e2e/page-objects/pages/_base-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { expect, Locator, Page, test, TestInfo } from '@playwright/test';
import { LocatorOptions } from '../../utils/locator-options';

export abstract class BasePage {
url: string;
title: string | RegExp;

constructor(
public page: Page,
public testInfo: TestInfo,
options?: Partial<{ url: string; title: string | RegExp }>,
) {
this.url = options?.url || '';
this.title = options?.title || '';
}

async open() {
await test.step(`Open ${this.getPageName(this)} page`, async () => {
await this.page.goto(this.url);
});
}

$(selector: string, options?: LocatorOptions): Locator {
return this.page.locator(selector, options);
}

async toBeOpened() {
if (this.url) {
await expect(this.page).toHaveURL(this.url);
}
if (this.title) {
await expect(this.page).toHaveTitle(this.title);
}
}

protected getPageName(page: object): string {
const pageName = page.constructor.name;
return pageName.toLowerCase().endsWith('page')
? pageName.substring(0, pageName.length - 4)
: pageName;
}
}
20 changes: 20 additions & 0 deletions apps/golden-sample-app-e2e/page-objects/pages/identity-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { User } from '../../data/data-types/user';
import { BasePage } from './_base-page';
import { defaultUser } from '../../data/credentials';

export class IdentityPage extends BasePage {
userName = this.$('#username');
userNameLabel = this.$('label[for="username"]');
password = this.$('#password');
passwordLabel = this.$('label[for="password"]');
errorMessage = this.$('#input-error');
loginButton = this.$('.btn[value="Log in"]');
loginForm = this.$('.identity-container__panel');

async login(user: User = defaultUser) {
await this.open();
await this.userName.fill(user.username);
await this.password.fill(user.password);
await this.loginButton.click();
}
}
15 changes: 15 additions & 0 deletions apps/golden-sample-app-e2e/page-objects/test-runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { test as baseTest } from '@playwright/test';
import { IdentityPage } from './pages/identity-page';
import { testConfig } from '../test-config';
import 'dotenv/config';

export const test = baseTest.extend<{
// Pages
identityPage: IdentityPage;
}>({
identityPage: async ({ page }, use, testInfo) => {
await use(
new IdentityPage(page, testInfo, { url: testConfig.appBaseUrl() }),
);
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Locator, Page } from '@playwright/test';
import { LocatorOptions } from '../../utils/locator-options';
import { isLocator } from 'apps/golden-sample-app-e2e/utils/playwright-utils';

export abstract class BaseComponent {
get page(): Page {
if (isLocator(this.accessor))
throw new Error('Root locator is not defined');
return this.accessor as Page;
}
get root(): Locator {
if (!isLocator(this.accessor))
throw new Error('Root locator is not defined');
return this.accessor as Locator;
}

constructor(private accessor: Page | Locator) { }

$(selector: string, options?: LocatorOptions): Locator {
return this.root.locator(selector, options);
}
}
16 changes: 0 additions & 16 deletions apps/golden-sample-app-e2e/project.json

This file was deleted.

43 changes: 43 additions & 0 deletions apps/golden-sample-app-e2e/specs/login.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { expect } from '@playwright/test';
import { test } from '../page-objects/test-runner';
import { wrongUser } from '../data/credentials';

const i18n = {
identity: {
username: 'Username or email',
password: 'Password',
loginButton: 'Log in',
error:
'Incorrect username or passwordPlease check your credentials and try again.',
},
};

test.describe.configure({ mode: 'parallel' });

test.describe('@feature @i18n Login tests', () => {

test('Empty user name', async ({ identityPage }) => {
identityPage.open();
await test.step('Validate Input fields labels', async () => {
await expect.soft(identityPage.userNameLabel, { message: `Expect Username label: "${i18n.identity.username}"` })
.toHaveText(i18n.identity.username);
await expect.soft(identityPage.passwordLabel, { message: `Expect Password label: "${i18n.identity.password}"` })
.toHaveText(i18n.identity.password);
});
await test.step(
`Fill in credentials: "${wrongUser.username}/${wrongUser.password}"`,
async () => {
await identityPage.userName.fill(wrongUser.username);
await identityPage.password.fill(wrongUser.password);
},
);
await test.step('Try to login"', async () => {
await identityPage.loginButton.click();
});
await test.step('Validate Failed login error message', async () => {
await expect(identityPage.errorMessage, { message: `Expect error message: "${i18n.identity.error}"` })
.toHaveText(i18n.identity.error);
});
});

});
23 changes: 23 additions & 0 deletions apps/golden-sample-app-e2e/test-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { User } from './data/data-types/user';
import config from '../../playwright.config';
import 'dotenv/config';

export class TestConfig {
baseUrl: string = config.use?.baseURL || 'BASE URL UNDEFINED';
locale: string = 'en';
RomanIovlev marked this conversation as resolved.
Show resolved Hide resolved

appBaseUrl() {
return this.baseUrl;
}

init(
options: Partial<{ user: User; url: string; }> = {
url: this.baseUrl,
},
) {
if (options.url) {
this.baseUrl = options.url;
}
}
}
export const testConfig = new TestConfig();
8 changes: 8 additions & 0 deletions apps/golden-sample-app-e2e/utils/locator-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Locator } from '@playwright/test';

export interface LocatorOptions {
has?: Locator;
hasNot?: Locator;
hasNotText?: string | RegExp;
hasText?: string | RegExp;
}
5 changes: 5 additions & 0 deletions apps/golden-sample-app-e2e/utils/playwright-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Locator } from '@playwright/test';

export const isLocator = (param: any): param is Locator =>
typeof param === 'object' && param.toString().split('@')[0] === 'Locator';

6 changes: 6 additions & 0 deletions apps/golden-sample-app-e2e/utils/time-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const timeID = () =>
new Date()
.toISOString()
.split('.')[0]
.replace(/T/g, '-')
.replace(/:/g, '-');
9 changes: 9 additions & 0 deletions apps/golden-sample-app-e2e/utils/timer-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export async function waitDelay(timeoutMSec: number) {
await new Promise(function (r) {
setTimeout(r, timeoutMSec);
});
}

export async function waitSeconds(timeoutSec: number) {
await waitDelay(timeoutSec * 1000);
}
8 changes: 8 additions & 0 deletions global-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { FullConfig } from '@playwright/test';

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function globalSetup(config: FullConfig) {
// Add your global setup code here
}

export default globalSetup;
Loading
Loading