Sets up the core infrastructure for Playwright-based automated testing, including: - Docker support for consistent environments - Azure Pipelines configuration for CI/CD - Page Object Model structure for maintainable tests - Test data, validation, and rule engine implementation - Test configuration for alerts and other scenarios
179 lines
No EOL
6.7 KiB
TypeScript
179 lines
No EOL
6.7 KiB
TypeScript
import LoggingUtils from '@impl/utils/LoggingUtils';
|
|
import { Page } from '@playwright/test';
|
|
import { TestInfo } from '@business-logic/types/Test';
|
|
import { TestInfoError } from "@playwright/test";
|
|
import { DateTime } from 'luxon';
|
|
import { expect as pw_expect } from '@playwright/test';
|
|
|
|
export default class Soft {
|
|
private static _instance: Soft | null = null;
|
|
private testInfo: TestInfo;
|
|
private page: Page;
|
|
private failedAssertions: string[] = [];
|
|
private errorCounter: number = 0;
|
|
private errorsOnly: boolean = false;
|
|
|
|
private constructor(testInfo: TestInfo, page: Page) {
|
|
this.testInfo = testInfo;
|
|
this.page = page;
|
|
}
|
|
|
|
public static initialize(testInfo: TestInfo, page: Page): void {
|
|
Soft._instance = new Soft(testInfo, page);
|
|
}
|
|
|
|
public static setOptions(options: { errorsOnly: boolean }): void {
|
|
Soft.getInstance().errorsOnly = options.errorsOnly;
|
|
}
|
|
|
|
public static getOptions(): { errorsOnly: boolean } {
|
|
return { errorsOnly: Soft.getInstance().errorsOnly };
|
|
}
|
|
|
|
private static getInstance(): Soft {
|
|
if (!Soft._instance)
|
|
throw new Error("Soft is not initialized. Call Soft.initialize(testInfo, page) first!");
|
|
return Soft._instance;
|
|
}
|
|
|
|
public async handleAssertion(
|
|
matcherFull: string,
|
|
matcherDisplay: string,
|
|
matcherFunction: () => Promise<void>,
|
|
reason?: string): Promise<void> {
|
|
reason = reason ? reason : ''
|
|
const reasonText = reason ? `'${reason}' ` : '';
|
|
try {
|
|
await matcherFunction();
|
|
if (!this.errorsOnly)
|
|
console.log(LoggingUtils.logValidate(`Validation ${reasonText}passed: ${matcherDisplay}!`, true));
|
|
} catch (error) {
|
|
console.log(LoggingUtils.logValidate(`Validation ${reasonText}failed: ${matcherDisplay}!`, false));
|
|
//const errorMessage = error instanceof Error ? error.message : String(error);
|
|
this.failedAssertions.push(`\n${++this.errorCounter}_Validation ${reasonText}failed:\n${LoggingUtils.replaceEmptyLinesWithMiddleDot(matcherFull)}!\n${error.stack}\n`);
|
|
// NOTE: I find it more helpful for the stack trace to be included here, so we know which line the validation is failing
|
|
try {
|
|
const screenshot: Buffer = await this.page.screenshot({ fullPage: true });
|
|
const name: string = LoggingUtils.sanitizeFileName(`${this.errorCounter}_Validation_${reason}${DateTime.now().valueOf()}`);
|
|
await this.testInfo.attach(name, {
|
|
body: screenshot,
|
|
contentType: 'image/png'
|
|
});
|
|
} catch(err){
|
|
// ohwell
|
|
console.error(error)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public static expect(value: any, reason?: string): ExpectationChain {
|
|
return new ExpectationChain(value, Soft.getInstance(), reason);
|
|
}
|
|
|
|
public static getFailedAssertions(): string[] {
|
|
return Soft.getInstance().failedAssertions;
|
|
}
|
|
|
|
public static hasFailedAssertions(): boolean {
|
|
return Soft.getInstance().failedAssertions.length > 0;
|
|
}
|
|
|
|
public static getFailureCount(): number {
|
|
return Soft.getInstance().failedAssertions.length;
|
|
}
|
|
|
|
public static clearFailedAssertions(): void {
|
|
Soft.getInstance().failedAssertions = [];
|
|
}
|
|
}
|
|
|
|
export class SoftError implements TestInfoError {
|
|
public readonly message?: string | undefined;
|
|
constructor(msg: string) {
|
|
this.message = msg;
|
|
}
|
|
}
|
|
|
|
export class ExpectationChain {
|
|
constructor(private value: any, private soft: Soft, private reason?: string) { }
|
|
|
|
private formatValue(value: any): string {
|
|
return LoggingUtils.truncateString(value);
|
|
}
|
|
|
|
public async toBe(expected: any): Promise<ExpectationChain> {
|
|
await this.soft.handleAssertion(
|
|
`expect(${this.value}).toBe(${expected})`,
|
|
`expect(${this.formatValue(this.value)}).toBe(${this.formatValue(expected)})`,
|
|
async () => await pw_expect(this.value).toBe(expected),
|
|
this.reason
|
|
);
|
|
return this;
|
|
}
|
|
|
|
public async toEqual(expected: any): Promise<ExpectationChain> {
|
|
await this.soft.handleAssertion(
|
|
`expect(${JSON.stringify(this.value)}).toEqual(${expected})`,
|
|
`expect(${this.formatValue(JSON.stringify(this.value))}).toEqual(${this.formatValue(expected)})`,
|
|
async () => await pw_expect(this.value).toEqual(expected),
|
|
this.reason
|
|
);
|
|
return this;
|
|
}
|
|
|
|
public async toContain(expected: any): Promise<ExpectationChain> {
|
|
await this.soft.handleAssertion(
|
|
`expect(${this.value}).toContain(${expected})`,
|
|
`expect(${this.formatValue(this.value)}).toContain(${this.formatValue(expected)})`,
|
|
async () => await pw_expect(this.value).toContain(expected),
|
|
this.reason
|
|
);
|
|
return this;
|
|
}
|
|
|
|
public async toHaveText(expected: string): Promise<ExpectationChain> {
|
|
await this.soft.handleAssertion(
|
|
`expect(${this.value}).toHaveText(${expected})`,
|
|
`expect(${this.formatValue(this.value)}).toHaveText(${this.formatValue(expected)})`,
|
|
async () => {
|
|
if (typeof this.value.textContent !== 'function') {
|
|
throw new Error('value does not have a textContent method');
|
|
}
|
|
const text = await this.value.textContent();
|
|
await pw_expect(text).toHaveText(expected);
|
|
}, this.reason
|
|
);
|
|
return this;
|
|
}
|
|
|
|
public async toBeGreaterThan(expected: number): Promise<ExpectationChain> {
|
|
await this.soft.handleAssertion(
|
|
`expect(${this.value}).toBeGreaterThan(${expected})`,
|
|
`expect(${this.formatValue(this.value)}).toBeGreaterThan(${this.formatValue(expected)})`,
|
|
async () => await pw_expect(this.value).toBeGreaterThan(expected),
|
|
this.reason
|
|
);
|
|
return this;
|
|
}
|
|
|
|
public async toBeTruthy(): Promise<ExpectationChain> {
|
|
await this.soft.handleAssertion(
|
|
`expect(${this.value}).toBeTruthy`,
|
|
`expect(${this.formatValue(this.value)}).toBeTruthy`,
|
|
async () => await pw_expect(this.value).toBeTruthy(),
|
|
this.reason
|
|
);
|
|
return this;
|
|
}
|
|
|
|
public async toBeFalsy(): Promise<ExpectationChain> {
|
|
await this.soft.handleAssertion(
|
|
`expect(${this.value}).toBeFalsy`,
|
|
`expect(${this.formatValue(this.value)}).toBeFalsy`,
|
|
async () => await pw_expect(this.value).toBeFalsy(),
|
|
this.reason
|
|
);
|
|
return this;
|
|
}
|
|
} |