From d3ff48780f3cc46f855855ac2a618171b6d6f0ea Mon Sep 17 00:00:00 2001 From: RomanIovlev Date: Wed, 7 Feb 2024 13:19:25 +0100 Subject: [PATCH 1/5] add e2e tests --- .env | 9 + .gitignore | 4 +- apps/golden-sample-app-e2e/.eslintrc.json | 10 -- .../data/data-types/user.ts | 5 + apps/golden-sample-app-e2e/data/users.ts | 12 ++ apps/golden-sample-app-e2e/example.spec.ts | 7 - .../page-objects/pages/_base-page.ts | 42 +++++ .../page-objects/pages/identity-page.ts | 23 +++ .../page-objects/screen.ts | 4 + .../page-objects/test-runner.ts | 30 ++++ .../ui-components/_base-component.ts | 24 +++ .../ui-components/_page-component.ts | 17 ++ .../ui-components/language-selector.ts | 18 ++ apps/golden-sample-app-e2e/project.json | 16 -- .../golden-sample-app-e2e/specs/login.spec.ts | 52 ++++++ apps/golden-sample-app-e2e/test-config.ts | 23 +++ .../utils/locator-options.ts | 8 + .../utils/playwright-utils.ts | 14 ++ .../golden-sample-app-e2e/utils/time-utils.ts | 6 + .../utils/timer-utils.ts | 9 + global-setup.ts | 8 + package-lock.json | 159 +++++++++++++++--- package.json | 8 +- playwright.config.ts | 126 +++++--------- 24 files changed, 491 insertions(+), 143 deletions(-) create mode 100644 .env delete mode 100644 apps/golden-sample-app-e2e/.eslintrc.json create mode 100644 apps/golden-sample-app-e2e/data/data-types/user.ts create mode 100644 apps/golden-sample-app-e2e/data/users.ts delete mode 100644 apps/golden-sample-app-e2e/example.spec.ts create mode 100644 apps/golden-sample-app-e2e/page-objects/pages/_base-page.ts create mode 100644 apps/golden-sample-app-e2e/page-objects/pages/identity-page.ts create mode 100644 apps/golden-sample-app-e2e/page-objects/screen.ts create mode 100644 apps/golden-sample-app-e2e/page-objects/test-runner.ts create mode 100644 apps/golden-sample-app-e2e/page-objects/ui-components/_base-component.ts create mode 100644 apps/golden-sample-app-e2e/page-objects/ui-components/_page-component.ts create mode 100644 apps/golden-sample-app-e2e/page-objects/ui-components/language-selector.ts delete mode 100644 apps/golden-sample-app-e2e/project.json create mode 100644 apps/golden-sample-app-e2e/specs/login.spec.ts create mode 100644 apps/golden-sample-app-e2e/test-config.ts create mode 100644 apps/golden-sample-app-e2e/utils/locator-options.ts create mode 100644 apps/golden-sample-app-e2e/utils/playwright-utils.ts create mode 100644 apps/golden-sample-app-e2e/utils/time-utils.ts create mode 100644 apps/golden-sample-app-e2e/utils/timer-utils.ts create mode 100644 global-setup.ts diff --git a/.env b/.env new file mode 100644 index 00000000..5a3f174a --- /dev/null +++ b/.env @@ -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 diff --git a/.gitignore b/.gitignore index ac6d7827..fad56025 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,6 @@ Thumbs.db .angular test-results/ playwright-report/ -.ngapimock \ No newline at end of file +.ngapimock +allure-results/ +test-output/ diff --git a/apps/golden-sample-app-e2e/.eslintrc.json b/apps/golden-sample-app-e2e/.eslintrc.json deleted file mode 100644 index cbdb2cd0..00000000 --- a/apps/golden-sample-app-e2e/.eslintrc.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], - "overrides": [ - { - "files": ["*.ts"], - "extends": ["plugin:playwright/playwright-test"] - } - ] -} diff --git a/apps/golden-sample-app-e2e/data/data-types/user.ts b/apps/golden-sample-app-e2e/data/data-types/user.ts new file mode 100644 index 00000000..f5de59c5 --- /dev/null +++ b/apps/golden-sample-app-e2e/data/data-types/user.ts @@ -0,0 +1,5 @@ +// TODO Remove or update while using on project +export interface User { + username: string; + password: string; +} diff --git a/apps/golden-sample-app-e2e/data/users.ts b/apps/golden-sample-app-e2e/data/users.ts new file mode 100644 index 00000000..37d45aa0 --- /dev/null +++ b/apps/golden-sample-app-e2e/data/users.ts @@ -0,0 +1,12 @@ +import { User } from './data-types/user'; + +// TODO Remove or update while using on project +export const defaultUser: User = { + username: '', + password: '', +}; + +export const wrongUser: User = { + username: 'someone', + password: 'some_password', +}; diff --git a/apps/golden-sample-app-e2e/example.spec.ts b/apps/golden-sample-app-e2e/example.spec.ts deleted file mode 100644 index 600622fa..00000000 --- a/apps/golden-sample-app-e2e/example.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('basic test', async ({ page }) => { - await page.goto('http://localhost:4200'); - const title = page.locator('#kc-page-title'); - await expect(title).toHaveText('Welcome to your online banking'); -}); diff --git a/apps/golden-sample-app-e2e/page-objects/pages/_base-page.ts b/apps/golden-sample-app-e2e/page-objects/pages/_base-page.ts new file mode 100644 index 00000000..b6e3e0b1 --- /dev/null +++ b/apps/golden-sample-app-e2e/page-objects/pages/_base-page.ts @@ -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; + } +} diff --git a/apps/golden-sample-app-e2e/page-objects/pages/identity-page.ts b/apps/golden-sample-app-e2e/page-objects/pages/identity-page.ts new file mode 100644 index 00000000..86237630 --- /dev/null +++ b/apps/golden-sample-app-e2e/page-objects/pages/identity-page.ts @@ -0,0 +1,23 @@ +import { User } from '../../data/data-types/user'; +import { LanguageSelector } from '../ui-components/language-selector'; +import { BasePage } from './_base-page'; +import { defaultUser } from '../../data/users'; + +// TODO Remove or update while using on project +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'); + languageSelector = new LanguageSelector(this.page); + + async login(user: User = defaultUser) { + await this.open(); + await this.userName.fill(user.username); + await this.password.fill(user.password); + await this.loginButton.click(); + } +} diff --git a/apps/golden-sample-app-e2e/page-objects/screen.ts b/apps/golden-sample-app-e2e/page-objects/screen.ts new file mode 100644 index 00000000..9f1dca68 --- /dev/null +++ b/apps/golden-sample-app-e2e/page-objects/screen.ts @@ -0,0 +1,4 @@ +export enum Screen { + loggedIn, + loggedOut, +} diff --git a/apps/golden-sample-app-e2e/page-objects/test-runner.ts b/apps/golden-sample-app-e2e/page-objects/test-runner.ts new file mode 100644 index 00000000..32a13b75 --- /dev/null +++ b/apps/golden-sample-app-e2e/page-objects/test-runner.ts @@ -0,0 +1,30 @@ +import { test as baseTest } from '@playwright/test'; +import { IdentityPage } from './pages/identity-page'; +import { testConfig } from '../test-config'; +import 'dotenv/config'; +import { Screen } from './screen'; + +export const test = baseTest.extend<{ + // Pages + identityPage: IdentityPage; + // States + startFrom: Screen; +}>({ + startFrom: [Screen.loggedIn, {}], + identityPage: async ({ page }, use, testInfo) => { + await use( + new IdentityPage(page, testInfo, { url: testConfig.appBaseUrl() }), + ); + }, + storageState: async ({ identityPage, startFrom }, use) => { + switch (startFrom) { + case Screen.loggedOut: + await identityPage.open(); + break; + case Screen.loggedIn: + await identityPage.login(); + break; + } + await use({ cookies: [], origins: [] }); + }, +}); diff --git a/apps/golden-sample-app-e2e/page-objects/ui-components/_base-component.ts b/apps/golden-sample-app-e2e/page-objects/ui-components/_base-component.ts new file mode 100644 index 00000000..c0f586c8 --- /dev/null +++ b/apps/golden-sample-app-e2e/page-objects/ui-components/_base-component.ts @@ -0,0 +1,24 @@ +import { expect, Locator } from '@playwright/test'; + +export abstract class BaseComponent { + constructor(protected root: Locator) {} + + $( + selector: string | Locator, + options?: { + has?: Locator; + hasNot?: Locator; + hasNotText?: string | RegExp; + hasText?: string | RegExp; + }, + ): Locator { + return this.root.locator(selector, options); + } + + async toBeVisible() { + await expect(this.root).toBeVisible(); + } + async toBeHidden() { + await expect(this.root).not.toBeVisible(); + } +} diff --git a/apps/golden-sample-app-e2e/page-objects/ui-components/_page-component.ts b/apps/golden-sample-app-e2e/page-objects/ui-components/_page-component.ts new file mode 100644 index 00000000..b25afa2a --- /dev/null +++ b/apps/golden-sample-app-e2e/page-objects/ui-components/_page-component.ts @@ -0,0 +1,17 @@ +import { Locator, Page } from '@playwright/test'; + +export abstract class PageComponent { + constructor(protected page: Page) {} + + $( + selector: string, + options?: { + has?: Locator; + hasNot?: Locator; + hasNotText?: string | RegExp; + hasText?: string | RegExp; + }, + ): Locator { + return this.page.locator(selector, options); + } +} diff --git a/apps/golden-sample-app-e2e/page-objects/ui-components/language-selector.ts b/apps/golden-sample-app-e2e/page-objects/ui-components/language-selector.ts new file mode 100644 index 00000000..df814a21 --- /dev/null +++ b/apps/golden-sample-app-e2e/page-objects/ui-components/language-selector.ts @@ -0,0 +1,18 @@ +import { expect } from '@playwright/test'; +import { PageComponent } from './_page-component'; + +// TODO Remove or update while using on project +export class LanguageSelector extends PageComponent { + toggle = this.$('#kc-current-locale-link'); + languages = (language: string) => this.$('.dropdown-item', { hasText: language }); + + async openSelector() { + await expect(this.toggle).toBeVisible(); + await this.toggle.click(); + } + + async selectLanguage(language: string) { + await this.openSelector(); + await this.languages(language).click(); + } +} diff --git a/apps/golden-sample-app-e2e/project.json b/apps/golden-sample-app-e2e/project.json deleted file mode 100644 index 7acd0244..00000000 --- a/apps/golden-sample-app-e2e/project.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "golden-sample-app-e2e", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "apps/golden-sample-app-e2e/src", - "projectType": "application", - "tags": ["type:app"], - "implicitDependencies": ["golden-sample-app"], - "targets": { - "lint": { - "executor": "@nx/linter:eslint", - "options": { - "lintFilePatterns": ["apps/golden-sample-app-e2e/**/*.ts"] - } - } - } -} diff --git a/apps/golden-sample-app-e2e/specs/login.spec.ts b/apps/golden-sample-app-e2e/specs/login.spec.ts new file mode 100644 index 00000000..29021544 --- /dev/null +++ b/apps/golden-sample-app-e2e/specs/login.spec.ts @@ -0,0 +1,52 @@ +import { expect } from '@playwright/test'; +import { test } from '../page-objects/test-runner'; +import { wrongUser } from '../data/users'; +import { Screen } from '../page-objects/screen'; + +const i18n = { + identity: { + username: 'Username or email', + password: 'Password', + loginButton: 'Log in', + error: + 'Incorrect username or passwordPlease check your credentials and try again.', + }, +}; + +// TODO Remove or update while using on project +test.describe.configure({ mode: 'parallel' }); + +test.describe('@feature @i18n Login tests', () => { + test.use({ startFrom: Screen.loggedOut }); + test('Empty user name', async ({ identityPage }) => { + 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('Validate Login Form visual layout', async () => { + // await expect(identityPage.loginForm) + // .toHaveScreenshot(`en-translation.png`); + // }); + 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); + }); + // await test.step('Validate Failed login error visual layout', async () => { + // await expect(identityPage.errorMessage) + // .toHaveScreenshot(`en-error.png`); + // }); + }); + +}); diff --git a/apps/golden-sample-app-e2e/test-config.ts b/apps/golden-sample-app-e2e/test-config.ts new file mode 100644 index 00000000..9401c89c --- /dev/null +++ b/apps/golden-sample-app-e2e/test-config.ts @@ -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'; + + 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(); diff --git a/apps/golden-sample-app-e2e/utils/locator-options.ts b/apps/golden-sample-app-e2e/utils/locator-options.ts new file mode 100644 index 00000000..f0f57602 --- /dev/null +++ b/apps/golden-sample-app-e2e/utils/locator-options.ts @@ -0,0 +1,8 @@ +import { Locator } from '@playwright/test'; + +export interface LocatorOptions { + has?: Locator; + hasNot?: Locator; + hasNotText?: string | RegExp; + hasText?: string | RegExp; +} diff --git a/apps/golden-sample-app-e2e/utils/playwright-utils.ts b/apps/golden-sample-app-e2e/utils/playwright-utils.ts new file mode 100644 index 00000000..ca17bb76 --- /dev/null +++ b/apps/golden-sample-app-e2e/utils/playwright-utils.ts @@ -0,0 +1,14 @@ +import { Locator } from '@playwright/test'; + +export const isLocator = (param: any): param is Locator => + typeof param === 'object' && param.toString().split('@')[0] === 'Locator'; + +export const getSelector = (locator?: string | Locator): string => { + if (!locator) return ''; + if (typeof locator === 'string') return locator; + + const locatorAsString = locator.toString(); + const index = locatorAsString.indexOf('@') + 1; + + return locatorAsString.substring(index); +}; diff --git a/apps/golden-sample-app-e2e/utils/time-utils.ts b/apps/golden-sample-app-e2e/utils/time-utils.ts new file mode 100644 index 00000000..a78d7709 --- /dev/null +++ b/apps/golden-sample-app-e2e/utils/time-utils.ts @@ -0,0 +1,6 @@ +export const timeID = () => + new Date() + .toISOString() + .split('.')[0] + .replace(/T/g, '-') + .replace(/:/g, '-'); diff --git a/apps/golden-sample-app-e2e/utils/timer-utils.ts b/apps/golden-sample-app-e2e/utils/timer-utils.ts new file mode 100644 index 00000000..e5c45ef9 --- /dev/null +++ b/apps/golden-sample-app-e2e/utils/timer-utils.ts @@ -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); +} diff --git a/global-setup.ts b/global-setup.ts new file mode 100644 index 00000000..8944881b --- /dev/null +++ b/global-setup.ts @@ -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; diff --git a/package-lock.json b/package-lock.json index d105b684..01e7269e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "ngx-cookie-service": "^16.0.0", "ngx-mask": "^13.0.0", "ngx-quill": "^16.2.1", + "playwright": "1.41.2", "postcss-preset-env": "^8.4.2", "quill": "^1.3.7", "rxjs": "^7.8.1", @@ -70,13 +71,15 @@ "@nx/js": "16.4.1", "@nx/linter": "16.4.1", "@nx/workspace": "16.4.1", - "@playwright/test": "^1.18.1", + "@playwright/test": "1.41.2", "@schematics/angular": "16.0.4", "@types/jest": "^29.4.4", "@types/node": "^18.17.14", "@types/quill": "^1.3.10", "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "5.59.8", + "allure-playwright": "^2.9.2", + "dotenv": "^16.3.1", "eslint": "^8.31.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.27.5", @@ -587,6 +590,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/@angular-eslint/builder/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/@angular-eslint/builder/node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -1064,6 +1076,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/@angular-eslint/schematics/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/@angular-eslint/schematics/node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -7153,6 +7174,15 @@ "nx": ">= 15 <= 17" } }, + "node_modules/@nx/cypress/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/@nx/cypress/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -7488,6 +7518,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/@nx/jest/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/@nx/jest/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -8163,6 +8202,15 @@ "node": ">=10" } }, + "node_modules/@nx/webpack/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/@nx/webpack/node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -8687,6 +8735,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/@nx/workspace/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/@nx/workspace/node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -9490,12 +9547,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.39.0.tgz", - "integrity": "sha512-3u1iFqgzl7zr004bGPYiN/5EZpRUSFddQBra8Rqll5N0/vfpqlP9I9EXqAoGacuAbX6c9Ulg/Cjqglp5VkK6UQ==", + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz", + "integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==", "dev": true, "dependencies": { - "playwright": "1.39.0" + "playwright": "1.41.2" }, "bin": { "playwright": "cli.js" @@ -10229,9 +10286,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.18.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.8.tgz", - "integrity": "sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==", + "version": "18.19.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.14.tgz", + "integrity": "sha512-EnQ4Us2rmOS64nHDWr0XqAD8DsO6f3XR6lf9UIIrZQpUzPVdN/oPuEzfDWNHSyXLvoGgjuEm/sPwFGSSs35Wtg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -11252,6 +11309,46 @@ "ajv": "^8.8.2" } }, + "node_modules/allure-js-commons": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/allure-js-commons/-/allure-js-commons-2.12.0.tgz", + "integrity": "sha512-uVMKT2LBRJQ9nPTrfE61zwryF3WhaUGIaj0PrVP7AoUpAexzGx0nOUjsJxUes1BwomcTIH1ORZo6r3kmIs7N9g==", + "dev": true, + "dependencies": { + "properties": "^1.2.1", + "strip-ansi": "^5.2.0" + } + }, + "node_modules/allure-js-commons/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/allure-js-commons/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/allure-playwright": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/allure-playwright/-/allure-playwright-2.12.0.tgz", + "integrity": "sha512-H2S7bEGqH/I3kR+6cnUpgRMfUOD1i73Sa8kQtPUGzavAZ6ZuF0vITf8jrlddNo1o4NKm2/HymUp0NnDBphT7uQ==", + "dev": true, + "dependencies": { + "allure-js-commons": "2.12.0" + } + }, "node_modules/angular-oauth2-oidc": { "version": "15.0.1", "resolved": "https://registry.npmjs.org/angular-oauth2-oidc/-/angular-oauth2-oidc-15.0.1.tgz", @@ -13911,12 +14008,15 @@ } }, "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz", + "integrity": "sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, "node_modules/duplexer": { @@ -22102,6 +22202,15 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/nx/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/nx/node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -22986,12 +23095,11 @@ } }, "node_modules/playwright": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.39.0.tgz", - "integrity": "sha512-naE5QT11uC/Oiq0BwZ50gDmy8c8WLPRTEWuSSFVG2egBka/1qMoSqYQcROMT9zLwJ86oPofcTH2jBY/5wWOgIw==", - "dev": true, + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz", + "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==", "dependencies": { - "playwright-core": "1.39.0" + "playwright-core": "1.41.2" }, "bin": { "playwright": "cli.js" @@ -23004,10 +23112,9 @@ } }, "node_modules/playwright-core": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", - "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", - "dev": true, + "version": "1.41.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz", + "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==", "bin": { "playwright-core": "cli.js" }, @@ -23019,7 +23126,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -24359,6 +24465,15 @@ "node": ">= 6" } }, + "node_modules/properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/properties/-/properties-1.2.1.tgz", + "integrity": "sha512-qYNxyMj1JeW54i/EWEFsM1cVwxJbtgPp8+0Wg9XjNaK6VE/c4oRi6PNu5p7w1mNXEIQIjV5Wwn8v8Gz82/QzdQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", diff --git a/package.json b/package.json index 1f8456e4..fd919311 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "affected:test": "nx affected:test", "xi18n": "ng extract-i18n golden-sample-app --output-path=apps/golden-sample-app/src/locale && xliffmerge --profile apps/golden-sample-app/src/xliffmerge.json nl-NL", "e2e": "nx e2e", + "e2e-test": "npx playwright test --update-snapshots", "lint": "nx run-many --all --target=lint", "format": "prettier --write \"{apps,libs}/**/*.{ts,md,html}\" \"!{apps,libs}/**/coverage\" ", "format:check": "prettier --list-different \"{apps,libs}/**/*.{ts,md}\" \"!{apps,libs}/**/coverage\"" @@ -59,6 +60,7 @@ "ngx-cookie-service": "^16.0.0", "ngx-mask": "^13.0.0", "ngx-quill": "^16.2.1", + "playwright": "1.41.2", "postcss-preset-env": "^8.4.2", "quill": "^1.3.7", "rxjs": "^7.8.1", @@ -87,7 +89,7 @@ "@nx/js": "16.4.1", "@nx/linter": "16.4.1", "@nx/workspace": "16.4.1", - "@playwright/test": "^1.18.1", + "@playwright/test": "1.41.2", "@schematics/angular": "16.0.4", "@types/jest": "^29.4.4", "@types/node": "^18.17.14", @@ -113,6 +115,8 @@ "ts-node": "^10.9.1", "tslib": "^2.4.0", "typescript": "5.1.6", - "webpack-bundle-analyzer": "^4.5.0" + "webpack-bundle-analyzer": "^4.5.0", + "allure-playwright": "^2.9.2", + "dotenv": "^16.3.1" } } diff --git a/playwright.config.ts b/playwright.config.ts index f1983d50..7abbd943 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,101 +1,57 @@ import { PlaywrightTestConfig, devices } from '@playwright/test'; +import 'dotenv/config'; -/** - * See https://playwright.dev/docs/test-configuration. - */ +// Reference: https://playwright.dev/docs/test-configuration const config: PlaywrightTestConfig = { - testDir: './apps/golden-sample-app-e2e', - - /* Maximum time one test can run for. */ - timeout: 30 * 1000, - + timeout: (Number(process.env['TIMEOUT']) || 120) * 1000, + testDir: './apps/golden-sample-app-e2e/specs/', + retries: process.env['CI'] ? 1 : 0, + grep: process.env['TEST_TAG'] ? RegExp(process.env['TEST_TAG']) : undefined, + outputDir: process.env['OUTPUT_DIR'] || './test-output', + forbidOnly: !!process.env['CI'], + workers: process.env['CI'] ? 4 : 1, expect: { - /** - * Maximum time expect() should wait for the condition to be met. - * For example in `await expect(locator).toHaveText();` - */ - timeout: 5000, + timeout: (Number(process.env['EXPECT_TIMEOUT']) || 5) * 1000, + toHaveScreenshot: { + maxDiffPixelRatio: 0.01, + }, }, - - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - - /* 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', - - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + reporter: [['list'], ['allure-playwright'], ['html']], + globalSetup: require.resolve(__dirname + '/global-setup.ts'), use: { - /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ - actionTimeout: 0, - - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://localhost:3000', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', + trace: 'retain-on-failure', + baseURL: process.env['BASE_URL'] || 'http://localhost:4200/', + ignoreHTTPSErrors: true, + screenshot: 'only-on-failure', + video: 'on-first-retry', + viewport: { + width: Number(process.env['SCREEN_WIDTH']) || 1280, + height: Number(process.env['SCREEN_HEIGHT']) || 720, + }, + contextOptions: { + ignoreHTTPSErrors: true, + acceptDownloads: true, + }, }, - - /* Configure projects for major browsers */ projects: [ { - name: 'chromium', - - /* Project-specific settings. */ + name: 'Web Chrome', use: { ...devices['Desktop Chrome'], + headless: process.env['HEADLESS']?.toLowerCase() === 'true', + launchOptions: { + chromiumSandbox: false, + args: [ + // List of Chrome arguments: http://peter.sh/experiments/chromium-command-line-switches/ + '--disable-gpu', + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + ], + }, }, }, - - { - name: 'firefox', - use: { - ...devices['Desktop Firefox'], - }, - }, - - { - name: 'webkit', - use: { - ...devices['Desktop Safari'], - }, - }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { - // ...devices['Pixel 5'], - // }, - // }, - // { - // name: 'Mobile Safari', - // use: { - // ...devices['iPhone 12'], - // }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { - // channel: 'msedge', - // }, - // }, - // { - // name: 'Google Chrome', - // use: { - // channel: 'chrome', - // }, - // }, ], - - /* Folder for test artifacts such as screenshots, videos, traces, etc. */ - // outputDir: 'test-results/', }; + export default config; From 652b965f40d36900aafc1d081c803dac3515332c Mon Sep 17 00:00:00 2001 From: RomanIovlev Date: Tue, 13 Feb 2024 00:15:28 +0100 Subject: [PATCH 2/5] remove unnecessary todos --- .../data/{users.ts => credentials.ts} | 0 .../page-objects/pages/identity-page.ts | 2 +- .../ui-components/_page-component.ts | 17 ----------------- .../ui-components/language-selector.ts | 18 ------------------ apps/golden-sample-app-e2e/specs/login.spec.ts | 2 +- 5 files changed, 2 insertions(+), 37 deletions(-) rename apps/golden-sample-app-e2e/data/{users.ts => credentials.ts} (100%) delete mode 100644 apps/golden-sample-app-e2e/page-objects/ui-components/_page-component.ts delete mode 100644 apps/golden-sample-app-e2e/page-objects/ui-components/language-selector.ts diff --git a/apps/golden-sample-app-e2e/data/users.ts b/apps/golden-sample-app-e2e/data/credentials.ts similarity index 100% rename from apps/golden-sample-app-e2e/data/users.ts rename to apps/golden-sample-app-e2e/data/credentials.ts diff --git a/apps/golden-sample-app-e2e/page-objects/pages/identity-page.ts b/apps/golden-sample-app-e2e/page-objects/pages/identity-page.ts index 86237630..b5e91d3c 100644 --- a/apps/golden-sample-app-e2e/page-objects/pages/identity-page.ts +++ b/apps/golden-sample-app-e2e/page-objects/pages/identity-page.ts @@ -1,7 +1,7 @@ import { User } from '../../data/data-types/user'; import { LanguageSelector } from '../ui-components/language-selector'; import { BasePage } from './_base-page'; -import { defaultUser } from '../../data/users'; +import { defaultUser } from '../../data/credentials'; // TODO Remove or update while using on project export class IdentityPage extends BasePage { diff --git a/apps/golden-sample-app-e2e/page-objects/ui-components/_page-component.ts b/apps/golden-sample-app-e2e/page-objects/ui-components/_page-component.ts deleted file mode 100644 index b25afa2a..00000000 --- a/apps/golden-sample-app-e2e/page-objects/ui-components/_page-component.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Locator, Page } from '@playwright/test'; - -export abstract class PageComponent { - constructor(protected page: Page) {} - - $( - selector: string, - options?: { - has?: Locator; - hasNot?: Locator; - hasNotText?: string | RegExp; - hasText?: string | RegExp; - }, - ): Locator { - return this.page.locator(selector, options); - } -} diff --git a/apps/golden-sample-app-e2e/page-objects/ui-components/language-selector.ts b/apps/golden-sample-app-e2e/page-objects/ui-components/language-selector.ts deleted file mode 100644 index df814a21..00000000 --- a/apps/golden-sample-app-e2e/page-objects/ui-components/language-selector.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { expect } from '@playwright/test'; -import { PageComponent } from './_page-component'; - -// TODO Remove or update while using on project -export class LanguageSelector extends PageComponent { - toggle = this.$('#kc-current-locale-link'); - languages = (language: string) => this.$('.dropdown-item', { hasText: language }); - - async openSelector() { - await expect(this.toggle).toBeVisible(); - await this.toggle.click(); - } - - async selectLanguage(language: string) { - await this.openSelector(); - await this.languages(language).click(); - } -} diff --git a/apps/golden-sample-app-e2e/specs/login.spec.ts b/apps/golden-sample-app-e2e/specs/login.spec.ts index 29021544..51108e72 100644 --- a/apps/golden-sample-app-e2e/specs/login.spec.ts +++ b/apps/golden-sample-app-e2e/specs/login.spec.ts @@ -1,6 +1,6 @@ import { expect } from '@playwright/test'; import { test } from '../page-objects/test-runner'; -import { wrongUser } from '../data/users'; +import { wrongUser } from '../data/credentials'; import { Screen } from '../page-objects/screen'; const i18n = { From cf434e608745a1de215a6517581599ab62a2c5f1 Mon Sep 17 00:00:00 2001 From: RomanIovlev Date: Tue, 13 Feb 2024 00:26:49 +0100 Subject: [PATCH 3/5] remove unnecessary code --- .../golden-sample-app-e2e/data/credentials.ts | 1 - .../data/data-types/user.ts | 1 - .../page-objects/pages/identity-page.ts | 3 -- .../ui-components/_base-component.ts | 34 +++++++++---------- .../golden-sample-app-e2e/specs/login.spec.ts | 9 ----- .../utils/playwright-utils.ts | 11 +----- 6 files changed, 17 insertions(+), 42 deletions(-) diff --git a/apps/golden-sample-app-e2e/data/credentials.ts b/apps/golden-sample-app-e2e/data/credentials.ts index 37d45aa0..ca4b9712 100644 --- a/apps/golden-sample-app-e2e/data/credentials.ts +++ b/apps/golden-sample-app-e2e/data/credentials.ts @@ -1,6 +1,5 @@ import { User } from './data-types/user'; -// TODO Remove or update while using on project export const defaultUser: User = { username: '', password: '', diff --git a/apps/golden-sample-app-e2e/data/data-types/user.ts b/apps/golden-sample-app-e2e/data/data-types/user.ts index f5de59c5..307a12c9 100644 --- a/apps/golden-sample-app-e2e/data/data-types/user.ts +++ b/apps/golden-sample-app-e2e/data/data-types/user.ts @@ -1,4 +1,3 @@ -// TODO Remove or update while using on project export interface User { username: string; password: string; diff --git a/apps/golden-sample-app-e2e/page-objects/pages/identity-page.ts b/apps/golden-sample-app-e2e/page-objects/pages/identity-page.ts index b5e91d3c..ffa66bc9 100644 --- a/apps/golden-sample-app-e2e/page-objects/pages/identity-page.ts +++ b/apps/golden-sample-app-e2e/page-objects/pages/identity-page.ts @@ -1,9 +1,7 @@ import { User } from '../../data/data-types/user'; -import { LanguageSelector } from '../ui-components/language-selector'; import { BasePage } from './_base-page'; import { defaultUser } from '../../data/credentials'; -// TODO Remove or update while using on project export class IdentityPage extends BasePage { userName = this.$('#username'); userNameLabel = this.$('label[for="username"]'); @@ -12,7 +10,6 @@ export class IdentityPage extends BasePage { errorMessage = this.$('#input-error'); loginButton = this.$('.btn[value="Log in"]'); loginForm = this.$('.identity-container__panel'); - languageSelector = new LanguageSelector(this.page); async login(user: User = defaultUser) { await this.open(); diff --git a/apps/golden-sample-app-e2e/page-objects/ui-components/_base-component.ts b/apps/golden-sample-app-e2e/page-objects/ui-components/_base-component.ts index c0f586c8..589dbcd7 100644 --- a/apps/golden-sample-app-e2e/page-objects/ui-components/_base-component.ts +++ b/apps/golden-sample-app-e2e/page-objects/ui-components/_base-component.ts @@ -1,24 +1,22 @@ -import { expect, Locator } from '@playwright/test'; +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 { - constructor(protected root: Locator) {} - - $( - selector: string | Locator, - options?: { - has?: Locator; - hasNot?: Locator; - hasNotText?: string | RegExp; - hasText?: string | RegExp; - }, - ): Locator { - return this.root.locator(selector, options); + get page(): Page { + if (isLocator(this.accessor)) + throw new Error('Root locator is not defined'); + return this.accessor as Page; } - - async toBeVisible() { - await expect(this.root).toBeVisible(); + get root(): Locator { + if (!isLocator(this.accessor)) + throw new Error('Root locator is not defined'); + return this.accessor as Locator; } - async toBeHidden() { - await expect(this.root).not.toBeVisible(); + + constructor(private accessor: Page | Locator) { } + + $(selector: string, options?: LocatorOptions): Locator { + return this.root.locator(selector, options); } } diff --git a/apps/golden-sample-app-e2e/specs/login.spec.ts b/apps/golden-sample-app-e2e/specs/login.spec.ts index 51108e72..8342e88d 100644 --- a/apps/golden-sample-app-e2e/specs/login.spec.ts +++ b/apps/golden-sample-app-e2e/specs/login.spec.ts @@ -13,7 +13,6 @@ const i18n = { }, }; -// TODO Remove or update while using on project test.describe.configure({ mode: 'parallel' }); test.describe('@feature @i18n Login tests', () => { @@ -25,10 +24,6 @@ test.describe('@feature @i18n Login tests', () => { await expect.soft(identityPage.passwordLabel, { message: `Expect Password label: "${i18n.identity.password}"` }) .toHaveText(i18n.identity.password); }); - // await test.step('Validate Login Form visual layout', async () => { - // await expect(identityPage.loginForm) - // .toHaveScreenshot(`en-translation.png`); - // }); await test.step( `Fill in credentials: "${wrongUser.username}/${wrongUser.password}"`, async () => { @@ -43,10 +38,6 @@ test.describe('@feature @i18n Login tests', () => { await expect(identityPage.errorMessage, { message: `Expect error message: "${i18n.identity.error}"` }) .toHaveText(i18n.identity.error); }); - // await test.step('Validate Failed login error visual layout', async () => { - // await expect(identityPage.errorMessage) - // .toHaveScreenshot(`en-error.png`); - // }); }); }); diff --git a/apps/golden-sample-app-e2e/utils/playwright-utils.ts b/apps/golden-sample-app-e2e/utils/playwright-utils.ts index ca17bb76..779e08e7 100644 --- a/apps/golden-sample-app-e2e/utils/playwright-utils.ts +++ b/apps/golden-sample-app-e2e/utils/playwright-utils.ts @@ -2,13 +2,4 @@ import { Locator } from '@playwright/test'; export const isLocator = (param: any): param is Locator => typeof param === 'object' && param.toString().split('@')[0] === 'Locator'; - -export const getSelector = (locator?: string | Locator): string => { - if (!locator) return ''; - if (typeof locator === 'string') return locator; - - const locatorAsString = locator.toString(); - const index = locatorAsString.indexOf('@') + 1; - - return locatorAsString.substring(index); -}; + \ No newline at end of file From f2e649655ffbf76078dabdf8cc02c6928eb8ff98 Mon Sep 17 00:00:00 2001 From: Roman Iovlev Date: Fri, 16 Feb 2024 13:20:42 +0100 Subject: [PATCH 4/5] remove storageState --- apps/golden-sample-app-e2e/page-objects/screen.ts | 4 ---- .../page-objects/test-runner.ts | 15 --------------- apps/golden-sample-app-e2e/specs/login.spec.ts | 4 ++-- 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 apps/golden-sample-app-e2e/page-objects/screen.ts diff --git a/apps/golden-sample-app-e2e/page-objects/screen.ts b/apps/golden-sample-app-e2e/page-objects/screen.ts deleted file mode 100644 index 9f1dca68..00000000 --- a/apps/golden-sample-app-e2e/page-objects/screen.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum Screen { - loggedIn, - loggedOut, -} diff --git a/apps/golden-sample-app-e2e/page-objects/test-runner.ts b/apps/golden-sample-app-e2e/page-objects/test-runner.ts index 32a13b75..9e95a762 100644 --- a/apps/golden-sample-app-e2e/page-objects/test-runner.ts +++ b/apps/golden-sample-app-e2e/page-objects/test-runner.ts @@ -2,29 +2,14 @@ import { test as baseTest } from '@playwright/test'; import { IdentityPage } from './pages/identity-page'; import { testConfig } from '../test-config'; import 'dotenv/config'; -import { Screen } from './screen'; export const test = baseTest.extend<{ // Pages identityPage: IdentityPage; - // States - startFrom: Screen; }>({ - startFrom: [Screen.loggedIn, {}], identityPage: async ({ page }, use, testInfo) => { await use( new IdentityPage(page, testInfo, { url: testConfig.appBaseUrl() }), ); }, - storageState: async ({ identityPage, startFrom }, use) => { - switch (startFrom) { - case Screen.loggedOut: - await identityPage.open(); - break; - case Screen.loggedIn: - await identityPage.login(); - break; - } - await use({ cookies: [], origins: [] }); - }, }); diff --git a/apps/golden-sample-app-e2e/specs/login.spec.ts b/apps/golden-sample-app-e2e/specs/login.spec.ts index 8342e88d..a4a6d9d3 100644 --- a/apps/golden-sample-app-e2e/specs/login.spec.ts +++ b/apps/golden-sample-app-e2e/specs/login.spec.ts @@ -1,7 +1,6 @@ import { expect } from '@playwright/test'; import { test } from '../page-objects/test-runner'; import { wrongUser } from '../data/credentials'; -import { Screen } from '../page-objects/screen'; const i18n = { identity: { @@ -16,8 +15,9 @@ const i18n = { test.describe.configure({ mode: 'parallel' }); test.describe('@feature @i18n Login tests', () => { - test.use({ startFrom: Screen.loggedOut }); + 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); From abdb5c5785f51e822bb976729aea9ecd071f82e4 Mon Sep 17 00:00:00 2001 From: Roman Iovlev Date: Fri, 16 Feb 2024 14:57:55 +0100 Subject: [PATCH 5/5] fix review comments --- .gitignore | 1 + apps/golden-sample-app-e2e/test-config.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index fad56025..5c0bdf64 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ testem.log .DS_Store Thumbs.db +.ssh .angular test-results/ playwright-report/ diff --git a/apps/golden-sample-app-e2e/test-config.ts b/apps/golden-sample-app-e2e/test-config.ts index 9401c89c..1e1dff47 100644 --- a/apps/golden-sample-app-e2e/test-config.ts +++ b/apps/golden-sample-app-e2e/test-config.ts @@ -3,8 +3,8 @@ import config from '../../playwright.config'; import 'dotenv/config'; export class TestConfig { - baseUrl: string = config.use?.baseURL || 'BASE URL UNDEFINED'; - locale: string = 'en'; + baseUrl = config.use?.baseURL || 'BASE URL UNDEFINED'; + locale = 'en'; appBaseUrl() { return this.baseUrl;