@puppedo/core
Version:
PuppeDo is a runner for tests E2E in YAML style. With power of Playwright or Puppeteer.
190 lines (158 loc) • 5.5 kB
text/typescript
/* eslint-disable max-classes-per-file */
import { Page as PagePuppeteer, Frame as FramePuppeteer } from 'puppeteer';
import { Page as PagePlaywright, Frame as FramePlaywright } from 'playwright';
import {
BrowserPageType,
LogOptionsType,
Element,
TestArgsType,
LogFunctionType,
LogInputType,
ColorsType,
BrowserFrame,
} from './global.d';
import { Runner } from './Environment';
import { logArgs, logDebug, logError, logExtend, logExtendFileInfo, logTimer } from './Loggers/CustomLogEntries';
type EnginesType = 'puppeteer' | 'playwright';
type AtomInit = {
runner?: Runner;
page?: BrowserPageType | BrowserFrame;
};
const enginesAvailable: EnginesType[] = ['puppeteer', 'playwright'];
class AtomError extends Error {
constructor(message: string) {
super(message);
this.name = 'AtomError';
}
}
export default class Atom {
runner: Runner;
page: BrowserPageType | BrowserFrame;
log: LogFunctionType;
levelIndent: number;
logOptions: LogOptionsType;
options: Record<string, string>;
frame: string;
constructor(init: AtomInit = {}) {
this.page = init.page || this.page;
this.runner = init.runner || this.runner;
}
getEngine(engine: EnginesType | null): boolean | EnginesType {
const atomEngine = this.runner.getRunnerData().browser.engine;
if (!enginesAvailable.includes(atomEngine)) {
throw new Error(`There is unknown engine: ${atomEngine}. Use this engines: ${enginesAvailable}`);
}
return engine ? atomEngine === engine : atomEngine;
}
async getElement(
selector: string,
allElements = false,
elementPatent: BrowserPageType | BrowserFrame = this.page,
): Promise<Element[] | Element | boolean> {
if (selector && typeof selector === 'string') {
const selectorClean = selector
.replace(/^css[:=]/, '')
.replace(/^xpath[:=]/, '')
.replace(/^text[:=]/, '');
const isXPath = selector.match(/^xpath[:=]/);
const isText = selector.match(/^text[:=]/);
const isCSS = (!isXPath && !isText) || selector.match(/^css[:=]/);
let elements: Array<Element> = [];
if (this.getEngine('puppeteer')) {
const elementParentPuppeteer = elementPatent as PagePuppeteer;
if (isXPath) {
elements = await elementParentPuppeteer.$x(selectorClean);
}
if (isText) {
elements = await elementParentPuppeteer.$x(`//*[text()[contains(.,"${selectorClean}")]]`);
}
if (isCSS) {
elements = await elementParentPuppeteer.$$(selectorClean);
}
}
if (this.getEngine('playwright')) {
const elementParentPlaywright = elementPatent as PagePlaywright;
if (isXPath) {
elements = await elementParentPlaywright.$$(`xpath=${selectorClean}`);
}
if (isText) {
elements = await elementParentPlaywright.$$(`text=${selectorClean}`);
}
if (isCSS) {
elements = await elementParentPlaywright.$$(`css=${selectorClean}`);
}
}
if (!allElements && elements.length) {
return elements[0];
}
return elements;
}
return false;
}
// eslint-disable-next-line class-methods-use-this
async atomRun(): Promise<Record<string, unknown>> {
throw new AtomError('Empty Atom Run');
}
async updateFrame(): Promise<void> {
if (!this.frame) {
return;
}
let elementHandle;
if (this.getEngine('puppeteer')) {
const page = this.page as FramePuppeteer;
elementHandle = await page.$$(`iframe[name="${this.frame}"]`);
}
if (this.getEngine('playwright')) {
const page = this.page as FramePlaywright;
elementHandle = await page.$$(`iframe[name="${this.frame}"]`);
}
const frame = elementHandle && (await elementHandle.contentFrame());
if (frame) {
this.page = frame;
}
}
async runTest(args?: TestArgsType): Promise<Record<string, unknown>> {
const startTime = process.hrtime.bigint();
const testArgs = Object.entries(args || {});
testArgs.forEach((entry) => {
const [key, value] = entry;
if (Object.prototype.hasOwnProperty.call(args, key)) {
this[key] = value;
}
});
const logOptionsDefault: LogOptionsType = {
screenshot: false,
fullpage: false,
};
this.log = async (customLog: LogInputType): Promise<void> => {
if (args) {
const logOptions = { ...logOptionsDefault, ...(args.logOptions ?? {}), ...(customLog.logOptions ?? {}) };
const logMeta = { ...{ breadcrumbs: args.breadcrumbs ?? [] }, ...(customLog.logMeta ?? {}) };
const logData = {
level: 'raw' as ColorsType,
levelIndent: (args.levelIndent ?? 0) + 1,
logOptions,
logMeta,
...customLog,
};
logData.logOptions.logThis = logData.level === 'error' ? true : logData.logOptions.logThis;
await args.log(logData);
}
};
try {
await this.updateFrame();
const result = await this.atomRun();
await logExtend(this.log, args);
const endTime = process.hrtime.bigint();
await logTimer(this.log, startTime, endTime, args);
return result;
} catch (error) {
await logError(this.log, error);
await logExtend(this.log, args, true);
await logArgs(this.log);
await logDebug(this.log, args);
await logExtendFileInfo(this.log, args);
throw new AtomError('Error in Atom');
}
}
}