Skip to content

Commit

Permalink
DTSCCI-1013 Playwright Setup Bootstrap Projects (#4951)
Browse files Browse the repository at this point in the history
* playwright-e2e setup

* adding users to playwright-e2e

* add more users

* enforcing one userKey per user

* complete setup of playwright test repo

* moved 'wa' folder to e2e

* remove unneeded projects

* add export to env.d.ts

* updating gitignorec

* adding playwright-core, required for axe-core

* update yarn lock

* fixed lint issues

* playwright sonar exclusions

* remove unneeded code and updated sonar properties

* move wa folder back to root

* updating yarn version

* updating node version

* setting version of yarn in project

* setup eslint

* updating eslint and adding prettier for codeceptjs tests and playwright tests

* removing old eslint files

* Fix eslint and prettier errors

* only run prettier on playwright-e2e files

* reconfigure eslint.config.mjs for javascript files

* update eslint config so it takes into account js files

* fixed all lint issues

* remove unneccessary line

* add prettier

* updating eslint and adding prettier for codeceptjs tests and playwright tests

* add prettier

* Fix eslint and prettier errors

* fixed all lint issues

* Fix eslint and prettier errors

* adding husky and lint-staged

* add lint:staged command

* only run prettier against playwright files

* update script commands

* custom run commands in series script and update husky, and update eslint.config.mjs

* eslint, prettier and lint-stage configuration

* fixed a few eslint errors

* update eslint config

* updating eslint and adding prettier for codeceptjs tests and playwright tests

* Fix eslint and prettier errors

* fixed all lint issues

* custom run commands in series script and update husky, and update eslint.config.mjs

* steps, factories and pages for auth setup and user data setup

* remove uneeded config in lint

* Only run no-dup-classes when relevant files in staged

* updating totp-generator and cleaning code

* update pre-commit

* updating eslint and adding prettier for codeceptjs tests and playwright tests

* fixed all lint issues

* add prettier

* custom run commands in series script and update husky, and update eslint.config.mjs

* updating eslint and adding prettier for codeceptjs tests and playwright tests

* steps, factories and pages for auth setup and user data setup

* more test bootstrap changes

* setup bootstrap processes: user data setup and user auth setup

* adding playwright projects for setting up user data and cookies for quick authentication during tests

* small edit of playwright test users

* playwright-e2e setup

* adding users to playwright-e2e

* add more users

* enforcing one userKey per user

* complete setup of playwright test repo

* moved 'wa' folder to e2e

* remove unneeded code and updated sonar properties

* move wa folder back to root

* setup eslint

* updating eslint and adding prettier for codeceptjs tests and playwright tests

* removing old eslint files

* Fix eslint and prettier errors

* only run prettier on playwright-e2e files

* reconfigure eslint.config.mjs for javascript files

* update eslint config so it takes into account js files

* fixed all lint issues

* remove unneccessary line

* add prettier

* updating eslint and adding prettier for codeceptjs tests and playwright tests

* add prettier

* Fix eslint and prettier errors

* fixed all lint issues

* Fix eslint and prettier errors

* adding husky and lint-staged

* add lint:staged command

* only run prettier against playwright files

* update script commands

* custom run commands in series script and update husky, and update eslint.config.mjs

* eslint, prettier and lint-stage configuration

* fixed a few eslint errors

* update eslint config

* updating eslint and adding prettier for codeceptjs tests and playwright tests

* Fix eslint and prettier errors

* fixed all lint issues

* custom run commands in series script and update husky, and update eslint.config.mjs

* steps, factories and pages for auth setup and user data setup

* remove uneeded config in lint

* Only run no-dup-classes when relevant files in staged

* updating totp-generator and cleaning code

* update pre-commit

* update typescript dependency

* update typescript

* update typescript

* Merge branch 'DTSCCI-865-eslint-and-lint-staged-and-husky-setup' into playwright-setup-bootstrap-projects

* small code changes

* reduce information on page accessibility failures

* changed axebuilder to instance rather than singleton

* update toHaveNoViolationsCache

* update violations

* update yarn lock

* Delete .env

* remove eslint-plugin-prettier

* update yarn.lock

* update baseRequests to verify responseJson/responseText when retryRequest or request is called

* Update method names in base-request

* update base request

---------

Co-authored-by: Ruban <[email protected]>
  • Loading branch information
kdaHMCTS and ruban72 authored Oct 18, 2024
1 parent 854d473 commit 9a486b8
Show file tree
Hide file tree
Showing 51 changed files with 941 additions and 439 deletions.
1 change: 0 additions & 1 deletion .env

This file was deleted.

2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"trailingComma": "all",
"overrides": [
{
"files": ["*.spec.ts", "playwright-e2e/playwright-fixtures/**/*.ts"],
"files": ["playwright-e2e/tests/**/*.ts", "playwright-e2e/playwright-fixtures/**/*.ts"],
"options": {
"arrowParens": "always",
"trailingComma": "none",
Expand Down
2 changes: 1 addition & 1 deletion env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ declare global {
RUN_ACCESSIBILITY_TESTS: string;
S2S_SECRET: string;
XUI_S2S_SECRET: string;
SKIP_AUTH_SETUP: string;
RUN_SETUP: string;
}
}
}
Expand Down
21 changes: 17 additions & 4 deletions playwright-e2e/base/base-api-steps.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import BaseSteps from './base-steps';
import TestData from '../types/test-data';
import TestData from '../models/test-data';
import RequestsFactory from '../requests/requests-factory';
import User from '../types/user';
import UserStateHelper from '../helpers/users-state-helper';
import User from '../models/user';

export default abstract class BaseApiSteps extends BaseSteps {
private _requestsFactory: RequestsFactory;
Expand All @@ -12,7 +11,21 @@ export default abstract class BaseApiSteps extends BaseSteps {
this._requestsFactory = requestsFactory;
}

get requestsFactory() {
protected get requestsFactory() {
return this._requestsFactory;
}

protected async setupUserData(user: User) {
if (!user.accessToken || !user.userId) {
const { idamRequests } = this.requestsFactory;
if (!user.accessToken) {
const accessToken = await idamRequests.getAccessToken(user);
user.accessToken = accessToken;
}
if (!user.userId) {
const userId = await idamRequests.getUserId(user);
user.userId = userId;
}
}
}
}
9 changes: 1 addition & 8 deletions playwright-e2e/base/base-page-factory.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
import AxeBuilder from '@axe-core/playwright';
import { Page } from '@playwright/test';

export default abstract class BasePageFactory {
private _page: Page;
private _axeBuilder: AxeBuilder;

constructor(page: Page, axeBuilder: AxeBuilder) {
constructor(page: Page) {
this._page = page;
this._axeBuilder = axeBuilder;
}

protected get page() {
return this._page;
}

protected get axeBuilder() {
return this._axeBuilder;
}
}
137 changes: 97 additions & 40 deletions playwright-e2e/base/base-page.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Page } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
import config from '../config/config';
import Cookie from '../types/cookie';
import Cookie from '../models/cookie';
import { TruthyParams } from '../decorators/truthy-params';
import { pageExpect, test } from '../playwright-fixtures';
import Timer from '../helpers/timer';
Expand All @@ -12,11 +12,9 @@ import ClassMethodHelper from '../helpers/class-method-helper';
const classKey = 'BasePage';
export default abstract class BasePage {
private page: Page;
private axeBuilder?: AxeBuilder;

constructor(page: Page, axeBuilder?: AxeBuilder) {
constructor(page: Page) {
this.page = page;
this.axeBuilder = axeBuilder;
}

@BoxedDetailedStep(classKey, 'selector')
Expand Down Expand Up @@ -194,7 +192,7 @@ export default abstract class BasePage {
Array.isArray(expects) ? await Promise.all(expects) : await expects;
}

if (config.runAxeTests && this.axeBuilder && axe) {
if (config.runAxeTests && axe) {
await this.expectAxeToPass(axeExclusions);
}
}
Expand All @@ -205,17 +203,21 @@ export default abstract class BasePage {
) {
await this.retryReloadTimeout(assertions, { timeout, interval: 2000 });

if (config.runAxeTests && this.axeBuilder && axe) {
if (config.runAxeTests && axe) {
await this.expectAxeToPass(axeExclusions);
}
}

@BoxedDetailedStep(classKey)
private async expectAxeToPass(axeExclusions: string[]) {
let axeBuilder = this.axeBuilder;
let axeBuilder = new AxeBuilder({ page: this.page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22a', 'wcag22aa'])
.setLegacyMode(true);

for (const axeExclusion of axeExclusions) {
axeBuilder = axeBuilder.exclude(axeExclusion);
}

const pageName = ClassMethodHelper.formatClassName(this.constructor.name);

const errorsNumBefore = test.info().errors.length;
Expand All @@ -228,50 +230,84 @@ export default abstract class BasePage {
}

@BoxedDetailedStep(classKey, 'domain')
protected async expectDomain(domain: string, options: { timeout?: number } = {}) {
await pageExpect(this.page).toHaveURL(new RegExp(`https?://${domain}.*`), {
...options,
});
protected async expectDomain(
domain: string,
options: { message?: string; timeout?: number } = {},
) {
await pageExpect(this.page, { message: options.message }).toHaveURL(
new RegExp(`https?://${domain}.*`),
{
timeout: options.timeout,
},
);
}

@BoxedDetailedStep(classKey, 'path')
protected async expectUrlStart(path: string, options: { timeout?: number } = {}) {
await pageExpect(this.page).toHaveURL(new RegExp(`^${path}`), {
...options,
protected async expectUrlStart(
path: string,
options: { message?: string; timeout?: number } = {},
) {
await pageExpect(this.page, { message: options.message }).toHaveURL(new RegExp(`^${path}`), {
timeout: options.timeout,
});
}

@BoxedDetailedStep(classKey, 'endpoints')
protected async expectUrlEnd(endpoints: string | string[], options: { timeout?: number } = {}) {
protected async expectUrlEnd(
endpoints: string | string[],
options: { timeout?: number; message?: string } = {},
) {
const regex = new RegExp(
Array.isArray(endpoints) ? `(${endpoints.join('|')})$` : `${endpoints}$`,
);
await pageExpect(this.page).toHaveURL(regex, { ...options });
await pageExpect(this.page, { message: options.message }).toHaveURL(regex, {
timeout: options.timeout,
});
}

@BoxedDetailedStep(classKey, 'text')
protected async expectHeading(text: string, options?: { timeout?: number }) {
await pageExpect(this.page.locator('h1', { hasText: text })).toBeVisible(options);
protected async expectHeading(
text: string,
options: { message?: string; timeout?: number } = {},
) {
await pageExpect(this.page.locator('h1', { hasText: text }), {
message: options.message,
}).toBeVisible({ timeout: options.timeout });
}

@BoxedDetailedStep(classKey, 'text')
protected async expectSubHeading(text: string, options?: { timeout?: number }) {
await pageExpect(this.page.locator('h2', { hasText: text })).toBeVisible(options);
protected async expectSubHeading(
text: string,
options: { message?: string; timeout?: number } = {},
) {
await pageExpect(this.page.locator('h2', { hasText: text }), {
message: options.message,
}).toBeVisible({ timeout: options.timeout });
}

@BoxedDetailedStep(classKey, 'selector')
protected async expectSelector(selector: string, options: { timeout?: number } = {}) {
await pageExpect(this.page.locator(selector)).toBeVisible(options);
protected async expectSelector(
selector: string,
options: { message?: string; timeout?: number } = {},
) {
await pageExpect(this.page.locator(selector), { message: options.message }).toBeVisible({
timeout: options.timeout,
});
}

@BoxedDetailedStep(classKey, 'selector')
protected async expectNoSelector(selector: string, options: { timeout?: number } = {}) {
protected async expectNoSelector(
selector: string,
options: { message?: string; timeout?: number } = {},
) {
const locator = this.page.locator(selector);
try {
await locator.waitFor({ state: 'visible', timeout: 500 });
// eslint-disable-next-line no-empty
} catch (err) {}
await pageExpect(locator).toBeHidden(options);
await pageExpect(locator, { message: options.message }).toBeHidden({
timeout: options.timeout,
});
}

private getTextLocator(
Expand All @@ -291,14 +327,15 @@ export default abstract class BasePage {
protected async expectText(
text: string | number,
options: {
message?: string;
exact?: boolean;
selector?: string;
first?: boolean;
timeout?: number;
} = {},
) {
const locator = this.getTextLocator(text, options.exact, options.selector, options.first);
await pageExpect(locator).toBeVisible({
await pageExpect(locator, { message: options.message }).toBeVisible({
timeout: options.timeout,
});
}
Expand All @@ -308,6 +345,7 @@ export default abstract class BasePage {
protected async expectNoText(
text: string | number,
options: {
message?: string;
exact?: boolean;
selector?: string;
first?: boolean;
Expand All @@ -319,63 +357,82 @@ export default abstract class BasePage {
await locator.waitFor({ state: 'visible', timeout: 500 });
// eslint-disable-next-line no-empty
} catch (err) {}
await pageExpect(locator).toBeHidden(options);
await pageExpect(locator, { message: options.message }).toBeHidden({
timeout: options.timeout,
});
}

@BoxedDetailedStep(classKey, 'label')
protected async expectLabel(
label: string,
options: { exact?: boolean; timeout?: number } = { exact: false },
options: { message?: string; exact?: boolean; timeout?: number } = { exact: false },
) {
await pageExpect(this.page.getByLabel(label, { exact: options.exact })).toBeVisible({
await pageExpect(this.page.getByLabel(label, { exact: options.exact }), {
message: options.message,
}).toBeVisible({
timeout: options.timeout,
});
}

@BoxedDetailedStep(classKey, 'name')
protected async expectLink(
name: string,
options: { exact?: boolean; timeout?: number } = { exact: false },
options: { message?: string; exact?: boolean; timeout?: number } = { exact: false },
) {
await pageExpect(this.page.getByRole('link', { name, exact: options.exact })).toBeVisible({
await pageExpect(this.page.getByRole('link', { name, exact: options.exact }), {
message: options.message,
}).toBeVisible({
timeout: options.timeout,
});
}

@BoxedDetailedStep(classKey, 'selector')
@TruthyParams(classKey, 'selector')
protected async expectOptionChecked(selector: string, options?: { timeout?: number }) {
await pageExpect(this.page.locator(selector)).toBeChecked(options);
protected async expectOptionChecked(
selector: string,
options: { message?: string; timeout?: number } = {},
) {
await pageExpect(this.page.locator(selector), { message: options.message }).toBeChecked({
timeout: options.timeout,
});
}

@BoxedDetailedStep(classKey, 'selector', 'text')
@TruthyParams(classKey, 'selector', 'text')
protected async expectInputValue(selector: string, text: string, options?: { timeout?: number }) {
await pageExpect(this.page.locator(selector)).toHaveValue(text, options);
protected async expectInputValue(
selector: string,
text: string,
options: { message?: string; timeout?: number } = {},
) {
await pageExpect(this.page.locator(selector), { message: options.message }).toHaveValue(text, {
timeout: options.timeout,
});
}

@BoxedDetailedStep(classKey, 'selector', 'option')
@TruthyParams(classKey, 'selector', 'option')
protected async expectDropdownOption(
selector: string,
option: string,
options?: { timeout?: number },
options: { message?: string; timeout?: number } = {},
) {
await pageExpect(this.page.locator(selector)).toHaveText(option, options);
await pageExpect(this.page.locator(selector), { message: options.message }).toHaveText(option, {
timeout: options.timeout,
});
}

@BoxedDetailedStep(classKey, 'text', 'selector')
@TruthyParams(classKey, 'text', 'selector')
protected async expectTableRowValue(
text: string,
selector: string,
options: { rowNum: number; timeout?: number; tableName?: string } = {
options: { message?: string; rowNum: number; timeout?: number; tableName?: string } = {
rowNum: 0,
},
) {
await pageExpect(
this.page.locator(`${selector} >> tr`).nth(options.rowNum).getByText(text),
).toBeVisible({ timeout: options.timeout });
await pageExpect(this.page.locator(`${selector} >> tr`).nth(options.rowNum).getByText(text), {
message: options.message,
}).toBeVisible({ timeout: options.timeout });
}

protected async retryAction(
Expand Down
Loading

0 comments on commit 9a486b8

Please sign in to comment.