diff --git a/@bellatrix/appium/tsconfig.json b/@bellatrix/appium/tsconfig.json index c221af3..6251374 100644 --- a/@bellatrix/appium/tsconfig.json +++ b/@bellatrix/appium/tsconfig.json @@ -3,10 +3,6 @@ "baseUrl": "src", "rootDir": "src", "outDir": "lib", - "paths": { - "*": [ "@bellatrix/appium/*" ], - "@bellatrix/core/*": [ "../../core/src/*" ] - }, "lib": [ "ESNext" ], "module": "esnext", "target": "esnext", @@ -24,9 +20,6 @@ "forceConsistentCasingInFileNames": true, "allowJs": false }, - "references": [ - { "path": "../core" } - ], "include": ["**/*.ts"], "exclude": ["node_modules"] } diff --git a/@bellatrix/core/src/types/config.ts b/@bellatrix/core/src/types/config.ts index 133537f..9082617 100644 --- a/@bellatrix/core/src/types/config.ts +++ b/@bellatrix/core/src/types/config.ts @@ -45,7 +45,7 @@ export type BellatrixConfigurationOverride = BellatrixConfiguration // "delayBeforeAction": 0 // }, // "executionSettings": { -// "browserAutomationTool": "playwright", // selenium, cypress +// "browserController": "playwright", // selenium, cypress // "browser": "chrome", // firefox, safari, edge // "headless": false, // "executionType": "local" // remote diff --git a/@bellatrix/extras/package-lock.json b/@bellatrix/extras/package-lock.json new file mode 100644 index 0000000..18b1b5a --- /dev/null +++ b/@bellatrix/extras/package-lock.json @@ -0,0 +1,79 @@ +{ + "name": "@bellatrix/extras", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@bellatrix/extras", + "version": "0.0.1", + "dependencies": { + "@bellatrix/core": "file:../core", + "@bellatrix/web": "file:../web" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "../core": { + "version": "0.0.1", + "dependencies": { + "@jest/globals": "^29.7.0", + "@playwright/test": "^1.41.0", + "jasmine": "^5.1.0", + "jasmine-core": "^5.1.1", + "mocha": "^10.2.0", + "reflect-metadata": "^0.2.1", + "vitest": "^1.2.1" + }, + "devDependencies": { + "c8": "^9.1.0", + "chai": "^4.4.1", + "cross-env": "^7.0.3", + "fix-esm-import-path": "^1.5.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "../web": { + "version": "0.0.1", + "dependencies": { + "@bellatrix/core": "file:../core", + "@xmldom/xmldom": "^0.8.10", + "selenium-webdriver": "^4.16.0", + "stack-trace": "^1.0.0-pre2", + "xpath": "^0.0.34" + }, + "devDependencies": { + "@types/selenium-webdriver": "^4.1.21", + "@types/stack-trace": "^0.0.33" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "node_modules/@bellatrix/core": { + "resolved": "../core", + "link": true + }, + "node_modules/@bellatrix/web": { + "resolved": "../web", + "link": true + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/@bellatrix/extras/package.json b/@bellatrix/extras/package.json new file mode 100644 index 0000000..a92d73d --- /dev/null +++ b/@bellatrix/extras/package.json @@ -0,0 +1,16 @@ +{ + "name": "@bellatrix/extras", + "version": "0.0.1", + "type": "module", + "exports": { + "./hooks": "./src/hooks/index.ts", + "./plugins": "./src/plugins/index.ts" + }, + "dependencies": { + "@bellatrix/core": "file:../core", + "@bellatrix/web": "file:../web" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/@bellatrix/web/src/components/hooks/DefaultWebComponentHooks.ts b/@bellatrix/extras/src/hooks/ExtraWebHooks.ts similarity index 88% rename from @bellatrix/web/src/components/hooks/DefaultWebComponentHooks.ts rename to @bellatrix/extras/src/hooks/ExtraWebHooks.ts index 4ee3a6b..5af895a 100644 --- a/@bellatrix/web/src/components/hooks/DefaultWebComponentHooks.ts +++ b/@bellatrix/extras/src/hooks/ExtraWebHooks.ts @@ -1,10 +1,10 @@ import { WebComponentHooks } from '@bellatrix/web/components/utilities'; import { Anchor, Button, CheckBox, ColorInput, DateInput, DateTimeInput, EmailField, FileInput, MonthInput, NumberInput, PasswordField, PhoneField, RangeInput, SearchField, Select, TextArea, TextField, TimeInput, UrlField, WebComponent, WeekInput } from '@bellatrix/web/components'; -export class DefaultWebComponentHooks { +// TODO: maybe decouple it in different web-extras plugin so extras does not import @bellatrix/web too? +export class ExtraWebHooks { static addComponentBDDLogging(): void { - const locale = Intl.DateTimeFormat().resolvedOptions().locale;// TODO: make it configurable - const shouldObfuscatePassword = true; // TODO: add as option in configuration + const locale = Intl.DateTimeFormat().resolvedOptions().locale; // TODO: add locale option in the config WebComponentHooks.addListenerTo(Anchor).before('click', (anchor) => console.log(`clicking ${anchor.componentName}`)); WebComponentHooks.addListenerTo(Button).before('click', (button) => console.log(`clicking ${button.componentName}`)); @@ -17,7 +17,7 @@ export class DefaultWebComponentHooks { WebComponentHooks.addListenerTo(FileInput).before('upload', (fileInput, filePath) => console.log(`uploading '${filePath}' into ${fileInput.componentName}`)); WebComponentHooks.addListenerTo(MonthInput).before('setMonth', (monthInput, year, month) => console.log(`setting ${monthInput} to ${new Date(year, month - 1).toLocaleDateString(locale, { month: 'long', year: 'numeric' })}`)); WebComponentHooks.addListenerTo(NumberInput).before('setNumber', (numberInput, number) => console.log(`setting ${numberInput.componentName} to ${number}`)); - WebComponentHooks.addListenerTo(PasswordField).before('setPassword', (passwordField, password) => console.log(`typing ${shouldObfuscatePassword ? '********' : password} into ${passwordField.componentName}`)); + WebComponentHooks.addListenerTo(PasswordField).before('setPassword', (passwordField, _) => console.log(`typing '********' into ${passwordField.componentName}`)); WebComponentHooks.addListenerTo(PhoneField).before('setPhone', (phoneField, phone) => console.log(`typing '${phone}' into ${phoneField.componentName}`)); WebComponentHooks.addListenerTo(RangeInput).before('setValue', (rangeInput, value) => console.log(`setting ${rangeInput.componentName} to ${value}`)); WebComponentHooks.addListenerTo(SearchField).before('setSearch', (searchField, search) => console.log(`typing '${search}' into ${searchField.componentName}`)); @@ -29,7 +29,8 @@ export class DefaultWebComponentHooks { WebComponentHooks.addListenerTo(TimeInput).before('setTime', (timeInput, hours, minutes, seconds) => console.log(`setting ${timeInput.componentName} to ${[hours, minutes, seconds].map(n => String(n ?? 0).padStart(2, '0')).join(':')}`)); WebComponentHooks.addListenerTo(UrlField).before('setUrl', (urlField, url) => console.log(`typing '${url}' into ${urlField.componentName}`)); WebComponentHooks.addListenerTo(WeekInput).before('setWeek', (weekInput, year, weekNumber) => console.log(`setting ${weekInput.componentName} to ${year}-W${weekNumber.toString().padStart(2, '0')}`)); - WebComponentHooks.addListenerTo(WebComponent).before('scrollToVisible', (component) => console.log(`scrolling ${component} into view`)); - WebComponentHooks.addListenerTo(WebComponent).before('hover', (component) => console.log(`hovering ${component}`)); // TODO: add focus method? + WebComponentHooks.addListenerTo(WebComponent).before('scrollIntoView', (component) => console.log(`scrolling ${component} into view`)); + WebComponentHooks.addListenerTo(WebComponent).before('hover', (component) => console.log(`hovering ${component}`)); + WebComponentHooks.addListenerTo(WebComponent).before('focus', (component) => console.log(`focusing ${component}`)); } } diff --git a/@bellatrix/extras/src/hooks/index.ts b/@bellatrix/extras/src/hooks/index.ts new file mode 100644 index 0000000..6a87dee --- /dev/null +++ b/@bellatrix/extras/src/hooks/index.ts @@ -0,0 +1 @@ +export * from './ExtraWebHooks'; diff --git a/@bellatrix/extras/src/plugins/LogLifecyclePlugin.ts b/@bellatrix/extras/src/plugins/LogLifecyclePlugin.ts new file mode 100644 index 0000000..0b5c048 --- /dev/null +++ b/@bellatrix/extras/src/plugins/LogLifecyclePlugin.ts @@ -0,0 +1,13 @@ +import { Plugin } from '@bellatrix/core/infrastructure'; +import { TestMetadata } from '@bellatrix/core/test/props'; + +export class LogLifecyclePlugin extends Plugin { + override async preBeforeTest(testMetadata: TestMetadata): Promise { + console.log('\n==================================================================================\n' + + `starting test: ${testMetadata.suiteClass.name} > ${testMetadata.testMethod.name}\n`); + } + + override async postAfterTest(_: TestMetadata): Promise { + console.log('\n==================================================================================\n'); + } +} diff --git a/@bellatrix/extras/src/plugins/index.ts b/@bellatrix/extras/src/plugins/index.ts new file mode 100644 index 0000000..5ad46e3 --- /dev/null +++ b/@bellatrix/extras/src/plugins/index.ts @@ -0,0 +1 @@ +export * from './LogLifecyclePlugin'; diff --git a/@bellatrix/extras/tsconfig.json b/@bellatrix/extras/tsconfig.json new file mode 100644 index 0000000..0c0ee61 --- /dev/null +++ b/@bellatrix/extras/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "baseUrl": "src", + "rootDir": "src", + "outDir": "lib", + "lib": [ "ESNext", "DOM" ], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "noImplicitOverride": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "composite": true, + "sourceMap": true, + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": false + }, + "include": ["**/*.ts"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/@bellatrix/web/package.json b/@bellatrix/web/package.json index c773350..b796beb 100644 --- a/@bellatrix/web/package.json +++ b/@bellatrix/web/package.json @@ -11,10 +11,12 @@ "./infrastructure": "./src/infrastructure/index.ts", "./pages": "./src/pages/index.ts", "./pages/decorators": "./src/pages/decorators/index.ts", - "./infrastructure/browserautomationtools/core": "./src/infrastructure/browserautomationtools/core/index.ts", - "./infrastructure/browserautomationtools/playwright": "./src/infrastructure/browserautomationtools/playwright/index.ts", - "./infrastructure/browserautomationtools/selenium": "./src/infrastructure/browserautomationtools/selenium/index.ts", + "./infrastructure/browsercontroller/core": "./src/infrastructure/browsercontroller/core/index.ts", + "./infrastructure/browsercontroller/playwright": "./src/infrastructure/browsercontroller/playwright/index.ts", + "./infrastructure/browsercontroller/selenium": "./src/infrastructure/browsercontroller/selenium/index.ts", "./services": "./src/services/index.ts", + "./services/decorators": "./src/services/decorators/index.ts", + "./services/utilities": "./src/services/utilities/index.ts", "./plugins": "./src/plugins/index.ts", "./test": "./src/test/index.ts", "./types": "./src/types/index.ts", diff --git a/@bellatrix/web/src/components/core/ComponentsList.ts b/@bellatrix/web/src/components/core/ComponentsList.ts index f5f3af1..ad5c30b 100644 --- a/@bellatrix/web/src/components/core/ComponentsList.ts +++ b/@bellatrix/web/src/components/core/ComponentsList.ts @@ -1,7 +1,7 @@ -import { BrowserAutomationTool } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { BrowserController } from '@bellatrix/web/infrastructure/browsercontroller/core'; +import { resolveParentElement } from '@bellatrix/web/components/decorators'; import { FindStrategy } from '@bellatrix/web/findstrategies'; import { ServiceLocator } from '@bellatrix/core/utilities'; -import { resolveParentElement } from '../decorators/BellatrixWebComponent'; import { ShadowRootContext, WebComponent } from '.'; import type { Ctor } from '@bellatrix/core/types'; @@ -11,11 +11,11 @@ export class ComponentsList { private _foundAll: boolean = false; private _type: Ctor>; private _findStrategy: FindStrategy; - private _driver: BrowserAutomationTool; + private _driver: BrowserController; private _parentComponent?: WebComponent | ShadowRootContext; private _componentName?: string; - constructor(type: Ctor>, findStrategy: FindStrategy, driver: BrowserAutomationTool, parentComponent?: WebComponent | ShadowRootContext, componentName?: string) { + constructor(type: Ctor>, findStrategy: FindStrategy, driver: BrowserController, parentComponent?: WebComponent | ShadowRootContext, componentName?: string) { this._type = type; this._findStrategy = findStrategy; this._driver = driver; @@ -31,7 +31,7 @@ export class ComponentsList { async get(index: number): Promise; async get(index?: number): Promise { if (index === undefined && !this._foundAll) { - const searchContext = this._parentComponent ? await resolveParentElement(this._parentComponent) : ServiceLocator.resolve(BrowserAutomationTool); + const searchContext = this._parentComponent ? await resolveParentElement(this._parentComponent) : ServiceLocator.resolve(BrowserController); const elements = await searchContext.findElements(this._findStrategy.convert()); const components = elements.map((element, index) => { const findStrategy = this.cloneFindStrategyWithUpdatedIndex(this._findStrategy, index); diff --git a/@bellatrix/web/src/components/core/ShadowRootContext.ts b/@bellatrix/web/src/components/core/ShadowRootContext.ts index e8aecda..45d0865 100644 --- a/@bellatrix/web/src/components/core/ShadowRootContext.ts +++ b/@bellatrix/web/src/components/core/ShadowRootContext.ts @@ -1,4 +1,4 @@ -import { BrowserAutomationTool, WebElement } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { BrowserController, WebElement } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { ComponentService } from '@bellatrix/web/services'; import type { Ctor } from '@bellatrix/core/types'; @@ -6,10 +6,10 @@ import { WebComponent } from '.'; export class ShadowRootContext { private _cachedElement: WebElement; - private _driver: BrowserAutomationTool; + private _driver: BrowserController; private _parentComponent: WebComponent | ShadowRootContext; - constructor(driver: BrowserAutomationTool, parentComponent: WebComponent | ShadowRootContext, cachedElement: WebElement) { + constructor(driver: BrowserController, parentComponent: WebComponent | ShadowRootContext, cachedElement: WebElement) { this._driver = driver; this._parentComponent = parentComponent; this._cachedElement = cachedElement; diff --git a/@bellatrix/web/src/components/core/WebComponent.ts b/@bellatrix/web/src/components/core/WebComponent.ts index 19a1624..bda66ef 100644 --- a/@bellatrix/web/src/components/core/WebComponent.ts +++ b/@bellatrix/web/src/components/core/WebComponent.ts @@ -1,4 +1,4 @@ -import { BrowserAutomationTool, WebElement } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { BrowserController, WebElement } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { Validator, StringValidator, NumberValidator, UnknownValidator, BooleanValidator } from '@bellatrix/web/validators'; import { BellatrixWebComponent } from '@bellatrix/web/components/decorators'; import { FindStrategy } from '@bellatrix/web/findstrategies'; @@ -9,21 +9,21 @@ import type { Ctor, MethodNamesStartingWith } from '@bellatrix/core/types'; import type { HtmlAttribute } from '@bellatrix/web/types'; @BellatrixWebComponent -export class WebComponent { +export class WebComponent { private _cachedElement!: WebElement; private _wait: ComponentWaitService; private _findStrategy: FindStrategy; - private _driver: BrowserAutomationTool; + private _driver: BrowserController; private _parentComponent?: WebComponent | ShadowRootContext; private _componentName?: string; - constructor(findStrategy: FindStrategy, driver: BrowserAutomationTool, parentComponent?: WebComponent | ShadowRootContext, cachedElement?: WebElement, componentName?: string) { + constructor(findStrategy: FindStrategy, driver: BrowserController, parentComponent?: WebComponent | ShadowRootContext, cachedElement?: WebElement, componentName?: string) { this._findStrategy = findStrategy; this._driver = driver; this._parentComponent = parentComponent; this._cachedElement = cachedElement!; this._componentName = componentName; - this._wait = new ComponentWaitService(this); + this._wait = new ComponentWaitService(driver, this); }; get wrappedElement(): WebElement { @@ -50,6 +50,10 @@ export class WebComponent { await this.wrappedElement.hover(); } + async focus(): Promise { + await this.wrappedElement.focus(); + } + async getAttribute(name: HtmlAttribute): Promise { return await this.wrappedElement.getAttribute(name); } @@ -66,8 +70,8 @@ export class WebComponent { return await this.wrappedElement.isClickable(); } - async scrollToVisible(): Promise { // TODO: maybe rename to scrollIntoView? - return await this.wrappedElement.scrollToVisible(); + async scrollIntoView(): Promise { + return await this.wrappedElement.scrollIntoView(); } async getOuterHtml(): Promise { diff --git a/@bellatrix/web/src/components/decorators/BellatrixWebComponent.ts b/@bellatrix/web/src/components/decorators/BellatrixWebComponent.ts index 50692fe..f4fdd68 100644 --- a/@bellatrix/web/src/components/decorators/BellatrixWebComponent.ts +++ b/@bellatrix/web/src/components/decorators/BellatrixWebComponent.ts @@ -1,9 +1,9 @@ -import { BrowserAutomationTool, SearchContext, WebElement } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { BrowserController, SearchContext, WebElement } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { ShadowRootContext, WebComponent } from '@bellatrix/web/components'; import { WebComponentListener } from '@bellatrix/web/components/utilities'; import { ServiceLocator } from '@bellatrix/core/utilities'; -export function BellatrixWebComponent>(target: TComponent) { +export function BellatrixWebComponent>(target: TComponent) { const originalMethods = Object.getOwnPropertyNames(target.prototype).filter(method => method !== 'constructor') as (keyof typeof target.prototype)[]; originalMethods.forEach((method) => { @@ -12,13 +12,11 @@ export function BellatrixWebComponent= 0) { + if (e instanceof Error && (e.name === 'StaleElementReferenceError') && !hasRetried) { this['_cachedElement'] = await searchContext.findElement(this.findStrategy.convert()); + hasRetried = true; continue; } + const beforeMethodListeners = ServiceLocator.resolveAll(WebComponentListener, `onError|${method}`); + for (const beforeMethodListener of beforeMethodListeners) { + if (beforeMethodListener.component !== this.constructor) { + continue; + } + + await beforeMethodListener.method(this, e, ...args); + } + throw e; } } @@ -65,7 +74,7 @@ export async function resolveParentElement(parentComponent: WebComponent | Shado } const parentOfParentComponent = (parentComponent as WebComponent)['_parentComponent']; - const searchContext: SearchContext = parentOfParentComponent ? await resolveParentElement(parentOfParentComponent as (WebComponent | ShadowRootContext)) : ServiceLocator.resolve(BrowserAutomationTool); + const searchContext: SearchContext = parentOfParentComponent ? await resolveParentElement(parentOfParentComponent as (WebComponent | ShadowRootContext)) : ServiceLocator.resolve(BrowserController); (parentComponent as WebComponent)['_cachedElement'] ??= await searchContext.findElement((parentComponent as WebComponent)['_findStrategy'].convert()); return parentComponent.wrappedElement; } diff --git a/@bellatrix/web/src/components/decorators/index.ts b/@bellatrix/web/src/components/decorators/index.ts index d1b9dcb..25ac919 100644 --- a/@bellatrix/web/src/components/decorators/index.ts +++ b/@bellatrix/web/src/components/decorators/index.ts @@ -1 +1 @@ -export { BellatrixWebComponent } from './BellatrixWebComponent'; +export * from './BellatrixWebComponent'; diff --git a/@bellatrix/web/src/components/hooks/index.ts b/@bellatrix/web/src/components/hooks/index.ts deleted file mode 100644 index 5a3799a..0000000 --- a/@bellatrix/web/src/components/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './DefaultWebComponentHooks'; diff --git a/@bellatrix/web/src/components/utilities/WebComponentHooks.ts b/@bellatrix/web/src/components/utilities/WebComponentHooks.ts index dad4ad7..f1272b8 100644 --- a/@bellatrix/web/src/components/utilities/WebComponentHooks.ts +++ b/@bellatrix/web/src/components/utilities/WebComponentHooks.ts @@ -3,18 +3,23 @@ import { WebComponent } from '@bellatrix/web/components'; import { ServiceLocator } from '@bellatrix/core/utilities'; type AsyncMethods = { - [K in keyof T]: T[K] extends (...args: infer _P) => Promise ? K : never; + [K in keyof T]: T[K] extends (...args: infer _P) => Promise ? K : never; }[keyof T]; export class WebComponentHooks { static addListenerTo(component: Ctor) { - return { - before>(methodName: Key, method: (componentInstance: TComponent, ...args: Parameters infer _R ? TComponent[Key] : never>) => unknown) { + return new class { + before, 'validateIs' | 'validateIsNot'>>(methodName: Key, method: (componentInstance: TComponent, ...args: Parameters infer _R ? TComponent[Key] : never>) => unknown): void { ServiceLocator.registerSingleton(WebComponentListener, new class extends WebComponentListener { }(component, method), `before|${String(methodName)}`); - }, - after>(methodName: Key, method: (componentInstance: TComponent, ...args: Parameters infer _R ? TComponent[Key] : never>) => unknown) { + } + + after, 'validateIs' | 'validateIsNot'>>(methodName: Key, method: (componentInstance: TComponent, ...args: Parameters infer _R ? TComponent[Key] : never>) => unknown): void { ServiceLocator.registerSingleton(WebComponentListener, new class extends WebComponentListener { }(component, method), `after|${String(methodName)}`); } + + onError, 'validateIs' | 'validateIsNot'>>(methodName: Key, method: (componentInstance: TComponent, error?: Error, ...args: Parameters infer _R ? TComponent[Key] : never>) => unknown): void { + ServiceLocator.registerSingleton(WebComponentListener, new class extends WebComponentListener { }(component, method), `onError|${String(methodName)}`); + } }; } } @@ -23,7 +28,7 @@ export abstract class WebComponentListener { readonly component: Ctor; readonly method: (component: TComponent, ...args: never) => unknown; - constructor(component: Ctor, method: (component: TComponent, ...args: never) => unknown) { + constructor(component: Ctor, method: (component: TComponent, ...args: never) => unknown) { this.component = component; this.method = method; } diff --git a/@bellatrix/web/src/findstrategies/AttributeContainingFindStrategy.ts b/@bellatrix/web/src/findstrategies/AttributeContainingFindStrategy.ts index c06eff2..7d3da02 100644 --- a/@bellatrix/web/src/findstrategies/AttributeContainingFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/AttributeContainingFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class AttributeContainingFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/AttributeFindStrategy.ts b/@bellatrix/web/src/findstrategies/AttributeFindStrategy.ts index fd12ba6..99aa241 100644 --- a/@bellatrix/web/src/findstrategies/AttributeFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/AttributeFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class AttributeFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/ClassContainingFindStrategy.ts b/@bellatrix/web/src/findstrategies/ClassContainingFindStrategy.ts index 0f0bb6c..d76dbda 100644 --- a/@bellatrix/web/src/findstrategies/ClassContainingFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/ClassContainingFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class ClassContainingFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/ClassFindStrategy.ts b/@bellatrix/web/src/findstrategies/ClassFindStrategy.ts index 737c4cf..af44cbf 100644 --- a/@bellatrix/web/src/findstrategies/ClassFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/ClassFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class ClassFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/CssFindStrategy.ts b/@bellatrix/web/src/findstrategies/CssFindStrategy.ts index 6be6588..b35152f 100644 --- a/@bellatrix/web/src/findstrategies/CssFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/CssFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class CssFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/FindStrategy.ts b/@bellatrix/web/src/findstrategies/FindStrategy.ts index b42c63a..e4d0be7 100644 --- a/@bellatrix/web/src/findstrategies/FindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/FindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; export abstract class FindStrategy { private _index: number = 0; diff --git a/@bellatrix/web/src/findstrategies/IdContainingFindStrategy.ts b/@bellatrix/web/src/findstrategies/IdContainingFindStrategy.ts index 13de3e9..3513407 100644 --- a/@bellatrix/web/src/findstrategies/IdContainingFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/IdContainingFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class IdContainingFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/IdEndingWithFindStrategy.ts b/@bellatrix/web/src/findstrategies/IdEndingWithFindStrategy.ts index 908d96b..8722e50 100644 --- a/@bellatrix/web/src/findstrategies/IdEndingWithFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/IdEndingWithFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class IdEndingWithFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/IdFindStrategy.ts b/@bellatrix/web/src/findstrategies/IdFindStrategy.ts index acf050e..41034e7 100644 --- a/@bellatrix/web/src/findstrategies/IdFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/IdFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class IdFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/InnerTextContainingFindStrategy.ts b/@bellatrix/web/src/findstrategies/InnerTextContainingFindStrategy.ts index 46cfe91..84f0609 100644 --- a/@bellatrix/web/src/findstrategies/InnerTextContainingFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/InnerTextContainingFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class InnerTextContainingFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/LinkTextContainingFindStrategy.ts b/@bellatrix/web/src/findstrategies/LinkTextContainingFindStrategy.ts index e21299d..12ae389 100644 --- a/@bellatrix/web/src/findstrategies/LinkTextContainingFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/LinkTextContainingFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class LinkTextContainingFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/LinkTextFindStrategy.ts b/@bellatrix/web/src/findstrategies/LinkTextFindStrategy.ts index 9d44ec0..5194cb8 100644 --- a/@bellatrix/web/src/findstrategies/LinkTextFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/LinkTextFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class LinkTextFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/NameEndingWithFindStrategy.ts b/@bellatrix/web/src/findstrategies/NameEndingWithFindStrategy.ts index 0db12f5..492e764 100644 --- a/@bellatrix/web/src/findstrategies/NameEndingWithFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/NameEndingWithFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class NameEndingWithFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/NameFindStrategy.ts b/@bellatrix/web/src/findstrategies/NameFindStrategy.ts index 1db6d1b..cf99ead 100644 --- a/@bellatrix/web/src/findstrategies/NameFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/NameFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class NameFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/TagFindStrategy.ts b/@bellatrix/web/src/findstrategies/TagFindStrategy.ts index ada1bd2..f9939ad 100644 --- a/@bellatrix/web/src/findstrategies/TagFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/TagFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class TagFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/ValueContainingFindStrategy.ts b/@bellatrix/web/src/findstrategies/ValueContainingFindStrategy.ts index c664d85..1774ae1 100644 --- a/@bellatrix/web/src/findstrategies/ValueContainingFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/ValueContainingFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class ValueContainingFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/findstrategies/XpathFindStrategy.ts b/@bellatrix/web/src/findstrategies/XpathFindStrategy.ts index 9f13d02..19f0fb0 100644 --- a/@bellatrix/web/src/findstrategies/XpathFindStrategy.ts +++ b/@bellatrix/web/src/findstrategies/XpathFindStrategy.ts @@ -1,4 +1,4 @@ -import { Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { FindStrategy } from '.'; export class XpathFindStrategy extends FindStrategy { diff --git a/@bellatrix/web/src/infrastructure/App.ts b/@bellatrix/web/src/infrastructure/App.ts index cd752c0..46c6d3f 100644 --- a/@bellatrix/web/src/infrastructure/App.ts +++ b/@bellatrix/web/src/infrastructure/App.ts @@ -1,6 +1,6 @@ import { ComponentService, CookiesService, NavigationService, BrowserService, ScriptService, DialogService } from '@bellatrix/web/services'; -import { BrowserAutomationTool } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { BrowserController } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { ServiceLocator, SingletonFactory } from '@bellatrix/core/utilities'; import { WebComponent } from '@bellatrix/web/components'; import { WebPage, WebPageAsserts, WebPageMap } from '@bellatrix/web/pages'; @@ -8,10 +8,10 @@ import { WebPage, WebPageAsserts, WebPageMap } from '@bellatrix/web/pages'; import type { Ctor, ParameterlessCtor } from '@bellatrix/core/types'; export class App { - private _driver: BrowserAutomationTool; + private _driver: BrowserController; constructor() { // make them in named container, to be context aware? and dispose method to unregister all with that name? - this._driver = ServiceLocator.resolve(BrowserAutomationTool); + this._driver = ServiceLocator.resolve(BrowserController); ServiceLocator.registerSingleton(NavigationService, new NavigationService(this._driver)); ServiceLocator.registerSingleton(CookiesService, new CookiesService(this._driver)); ServiceLocator.registerSingleton(BrowserService, new BrowserService(this._driver)); diff --git a/@bellatrix/web/src/services/BrowserAutomationToolLaunchService.ts b/@bellatrix/web/src/infrastructure/BrowserControllerLifecycleManager.ts similarity index 91% rename from @bellatrix/web/src/services/BrowserAutomationToolLaunchService.ts rename to @bellatrix/web/src/infrastructure/BrowserControllerLifecycleManager.ts index d23811a..26b25f6 100644 --- a/@bellatrix/web/src/services/BrowserAutomationToolLaunchService.ts +++ b/@bellatrix/web/src/infrastructure/BrowserControllerLifecycleManager.ts @@ -4,21 +4,21 @@ import * as mozillaFirefox from 'selenium-webdriver/firefox'; import * as microsoftEdge from 'selenium-webdriver/edge'; import { chromium, firefox, webkit } from '@playwright/test'; -import { BrowserAutomationTool } from '@bellatrix/web/infrastructure/browserautomationtools/core'; -import { SeleniumBrowserAutomationTool } from '@bellatrix/web/infrastructure/browserautomationtools/selenium'; -import { PlaywrightBrowserAutomationTool } from '@bellatrix/web/infrastructure/browserautomationtools/playwright'; +import { BrowserController } from '@bellatrix/web/infrastructure/browsercontroller/core'; +import { SeleniumBrowserController } from '@bellatrix/web/infrastructure/browsercontroller/selenium'; +import { PlaywrightBrowserController } from '@bellatrix/web/infrastructure/browsercontroller/playwright'; import { BellatrixSettings } from '@bellatrix/core/settings'; import { HttpClient } from '@bellatrix/core/http'; -import type { BrowserAutomationToolType, BrowserType } from '@bellatrix/web/types'; +import type { BrowserControllerType, BrowserType } from '@bellatrix/web/types'; export type BrowserConfiguration = { - type: BrowserAutomationToolType; + type: BrowserControllerType; browser: BrowserType; } -export class BrowserAutomationToolLaunchService { - static async launch(): Promise { +export class BrowserControllerLifecycleManager { + static async launch(): Promise { const webSettings = BellatrixSettings.get().webSettings; if (webSettings.executionSettings.executionType === 'remote') { @@ -32,7 +32,7 @@ export class BrowserAutomationToolLaunchService { const headless = webSettings.executionSettings.headless; const shouldStartMaximized = webSettings.executionSettings.startMaximized; - switch (webSettings.executionSettings.browserAutomationTool.toLowerCase()) { + switch (webSettings.executionSettings.browserController.toLowerCase()) { case 'playwright': { let browser; switch (webSettings.executionSettings.browser.toLowerCase()) { @@ -77,7 +77,7 @@ export class BrowserAutomationToolLaunchService { const page = await context.newPage(); - const webBrowser = new PlaywrightBrowserAutomationTool(browser, context, page); + const webBrowser = new PlaywrightBrowserController(browser, context, page); await webBrowser.open(baseUrl); return webBrowser; @@ -132,7 +132,7 @@ export class BrowserAutomationToolLaunchService { await driver.manage().window().setRect({ x: 0, y: 0, ...viewport }); } - const webBrowser = new SeleniumBrowserAutomationTool(driver); + const webBrowser = new SeleniumBrowserController(driver); await webBrowser.open(baseUrl); return webBrowser; @@ -142,7 +142,7 @@ export class BrowserAutomationToolLaunchService { } } - static async launchRemote(): Promise { + static async launchRemote(): Promise { const webSettings = BellatrixSettings.get().webSettings; const timeoutSettings = webSettings.timeoutSettings; @@ -151,7 +151,7 @@ export class BrowserAutomationToolLaunchService { const viewport = webSettings.executionSettings.viewport; const shouldStartMaximized = webSettings.executionSettings.startMaximized; - switch (webSettings.executionSettings.browserAutomationTool.toLowerCase()) { + switch (webSettings.executionSettings.browserController.toLowerCase()) { case 'playwright': { let browser; if (!webSettings.remoteExecutionSettings) { @@ -234,7 +234,7 @@ export class BrowserAutomationToolLaunchService { const page = await context.newPage(); - const webBrowser = new PlaywrightBrowserAutomationTool(browser, context, page); + const webBrowser = new PlaywrightBrowserController(browser, context, page); webBrowser.setGridSessionId(gridSessionId); await webBrowser.open(baseUrl); @@ -296,13 +296,13 @@ export class BrowserAutomationToolLaunchService { await driver.manage().window().setRect({ x: 0, y: 0, ...viewport }); } - const webBrowser = new SeleniumBrowserAutomationTool(driver); + const webBrowser = new SeleniumBrowserController(driver); await webBrowser.open(baseUrl); return webBrowser; } default: - throw new Error(`Unknown browser automation tool: ${webSettings.executionSettings.browserAutomationTool}`); + throw new Error(`Unknown browser automation tool: ${webSettings.executionSettings.browserController}`); } } } diff --git a/@bellatrix/web/src/infrastructure/TestExecutionEngine.ts b/@bellatrix/web/src/infrastructure/TestExecutionEngine.ts index 34013be..6e818fe 100644 --- a/@bellatrix/web/src/infrastructure/TestExecutionEngine.ts +++ b/@bellatrix/web/src/infrastructure/TestExecutionEngine.ts @@ -1,25 +1,23 @@ import { ServiceLocator } from '@bellatrix/core/utilities'; -import { BrowserAutomationToolLaunchService } from '@bellatrix/web/services'; -import { BrowserAutomationTool } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { BrowserControllerLifecycleManager } from '@bellatrix/web/infrastructure'; +import { BrowserController } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { App } from '.'; -import type { BrowserConfiguration } from '@bellatrix/web/services'; import { BellatrixSettings } from '@bellatrix/core/settings'; -import { HttpClient } from '@bellatrix/core/http'; export class TestExecutionEngine { static async startBrowser(): Promise { - const browserAutomationTool = await BrowserAutomationToolLaunchService.launch(); + const browserController = await BrowserControllerLifecycleManager.launch(); - ServiceLocator.registerSingleton(BrowserAutomationTool, browserAutomationTool); // make it universal, make WebDriver extend Driver + ServiceLocator.registerSingleton(BrowserController, browserController); // make it universal, make WebDriver extend Driver ServiceLocator.registerSingleton(App, new App); // move } static async dispose(): Promise { - const browserAutomationTool = ServiceLocator.resolve(BrowserAutomationTool); + const browserController = ServiceLocator.resolve(BrowserController); const executionSettings = BellatrixSettings.get().webSettings.executionSettings; - await browserAutomationTool.quit(); + await browserController.quit(); - ServiceLocator.unregister(BrowserAutomationTool); + ServiceLocator.unregister(BrowserController); } } diff --git a/@bellatrix/web/src/infrastructure/browserautomationtools/core/BrowserAutomationTool.ts b/@bellatrix/web/src/infrastructure/browsercontroller/core/BrowserController.ts similarity index 84% rename from @bellatrix/web/src/infrastructure/browserautomationtools/core/BrowserAutomationTool.ts rename to @bellatrix/web/src/infrastructure/browsercontroller/core/BrowserController.ts index 4e4084c..ca32f4a 100644 --- a/@bellatrix/web/src/infrastructure/browserautomationtools/core/BrowserAutomationTool.ts +++ b/@bellatrix/web/src/infrastructure/browsercontroller/core/BrowserController.ts @@ -11,7 +11,7 @@ export type Cookie = { sameSite?: 'Strict' | 'Lax' | 'None'; } -export abstract class BrowserAutomationTool implements SearchContext { +export abstract class BrowserController implements SearchContext { abstract get type(): string; abstract getUrl(): Promise; @@ -31,7 +31,7 @@ export abstract class BrowserAutomationTool implements SearchContext { abstract getAllCookies(): Promise; abstract clearCookies(): Promise; abstract executeJavascript(script: string | ((...args: VarArgs) => T), ...args: VarArgs): Promise; - abstract waitUntil(condition: (browserAutomationTool: Omit) => boolean | Promise, timeout: number, pollingInterval: number): Promise + abstract waitUntil(condition: (browserController: Omit) => boolean | Promise, timeout: number, pollingInterval: number): Promise abstract acceptDialog(promptText?: string): Promise; abstract dismissDialog(): Promise; diff --git a/@bellatrix/web/src/infrastructure/browserautomationtools/core/Locator.ts b/@bellatrix/web/src/infrastructure/browsercontroller/core/Locator.ts similarity index 100% rename from @bellatrix/web/src/infrastructure/browserautomationtools/core/Locator.ts rename to @bellatrix/web/src/infrastructure/browsercontroller/core/Locator.ts diff --git a/@bellatrix/web/src/infrastructure/browserautomationtools/core/SearchContext.ts b/@bellatrix/web/src/infrastructure/browsercontroller/core/SearchContext.ts similarity index 100% rename from @bellatrix/web/src/infrastructure/browserautomationtools/core/SearchContext.ts rename to @bellatrix/web/src/infrastructure/browsercontroller/core/SearchContext.ts diff --git a/@bellatrix/web/src/infrastructure/browserautomationtools/core/WebElement.ts b/@bellatrix/web/src/infrastructure/browsercontroller/core/WebElement.ts similarity index 94% rename from @bellatrix/web/src/infrastructure/browserautomationtools/core/WebElement.ts rename to @bellatrix/web/src/infrastructure/browsercontroller/core/WebElement.ts index bbfde31..91d09bb 100644 --- a/@bellatrix/web/src/infrastructure/browserautomationtools/core/WebElement.ts +++ b/@bellatrix/web/src/infrastructure/browsercontroller/core/WebElement.ts @@ -5,6 +5,7 @@ import type { HtmlAttribute } from '@bellatrix/web/types'; export abstract class WebElement implements SearchContext { abstract click(): Promise; abstract hover(): Promise; + abstract focus(): Promise; abstract setText(value: string): Promise; abstract clear(): Promise; abstract getAttribute(name: HtmlAttribute): Promise; @@ -29,5 +30,5 @@ export abstract class WebElement implements SearchContext { abstract isVisible(): Promise; abstract isClickable(): Promise; - abstract scrollToVisible(): Promise; + abstract scrollIntoView(): Promise; } diff --git a/@bellatrix/web/src/infrastructure/browserautomationtools/core/index.ts b/@bellatrix/web/src/infrastructure/browsercontroller/core/index.ts similarity index 68% rename from @bellatrix/web/src/infrastructure/browserautomationtools/core/index.ts rename to @bellatrix/web/src/infrastructure/browsercontroller/core/index.ts index 5ea1fdd..3d8ab8b 100644 --- a/@bellatrix/web/src/infrastructure/browserautomationtools/core/index.ts +++ b/@bellatrix/web/src/infrastructure/browsercontroller/core/index.ts @@ -1,4 +1,4 @@ export * from './Locator'; -export * from './BrowserAutomationTool'; +export * from './BrowserController'; export * from './WebElement'; export * from './SearchContext'; diff --git a/@bellatrix/web/src/infrastructure/browserautomationtools/playwright/PlaywrightBrowserAutomationTool.ts b/@bellatrix/web/src/infrastructure/browsercontroller/playwright/PlaywrightBrowserController.ts similarity index 94% rename from @bellatrix/web/src/infrastructure/browserautomationtools/playwright/PlaywrightBrowserAutomationTool.ts rename to @bellatrix/web/src/infrastructure/browsercontroller/playwright/PlaywrightBrowserController.ts index 65ce0fe..a827857 100644 --- a/@bellatrix/web/src/infrastructure/browserautomationtools/playwright/PlaywrightBrowserAutomationTool.ts +++ b/@bellatrix/web/src/infrastructure/browsercontroller/playwright/PlaywrightBrowserController.ts @@ -1,11 +1,11 @@ import { Browser, BrowserContext, Page, Locator as NativeLocator, Dialog } from '@playwright/test'; -import { Cookie, BrowserAutomationTool, WebElement, Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; -import { PlaywrightWebElement } from '@bellatrix/web/infrastructure/browserautomationtools/playwright'; +import { Cookie, BrowserController, WebElement, Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; +import { PlaywrightWebElement } from '@bellatrix/web/infrastructure/browsercontroller/playwright'; import { BellatrixSettings } from '@bellatrix/core/settings'; import { HttpClient } from '@bellatrix/core/http'; -export class PlaywrightBrowserAutomationTool extends BrowserAutomationTool { +export class PlaywrightBrowserController extends BrowserController { private _browser: Browser; private _context: BrowserContext; private _page: Page; @@ -155,7 +155,7 @@ export class PlaywrightBrowserAutomationTool extends BrowserAutomationTool { return await this._page.evaluate(new Function(`return (${script})(...arguments[0])`) as never, args); } - override async waitUntil(condition: (browserAutomationTool: Omit) => boolean | Promise, timeout: number, pollingInterval: number): Promise { + override async waitUntil(condition: (browserController: Omit) => boolean | Promise, timeout: number, pollingInterval: number): Promise { const startTime = Date.now(); const hasTimeoutEnded = () => Date.now() - startTime > timeout; let error: Error | undefined; diff --git a/@bellatrix/web/src/infrastructure/browserautomationtools/playwright/PlaywrightShadowRootWebElement.ts b/@bellatrix/web/src/infrastructure/browsercontroller/playwright/PlaywrightShadowRootWebElement.ts similarity index 99% rename from @bellatrix/web/src/infrastructure/browserautomationtools/playwright/PlaywrightShadowRootWebElement.ts rename to @bellatrix/web/src/infrastructure/browsercontroller/playwright/PlaywrightShadowRootWebElement.ts index cd80eb6..3c82748 100644 --- a/@bellatrix/web/src/infrastructure/browserautomationtools/playwright/PlaywrightShadowRootWebElement.ts +++ b/@bellatrix/web/src/infrastructure/browsercontroller/playwright/PlaywrightShadowRootWebElement.ts @@ -1,7 +1,7 @@ import { Locator as NativeLocator, ElementHandle as NativeElementHandle } from '@playwright/test'; import { Utilities } from '@bellatrix/web/utilities'; -import { Locator, WebElement } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator, WebElement } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { PlaywrightWebElement } from './PlaywrightWebElement'; export class PlaywrightShadowRootWebElement extends PlaywrightWebElement { diff --git a/@bellatrix/web/src/infrastructure/browserautomationtools/playwright/PlaywrightWebElement.ts b/@bellatrix/web/src/infrastructure/browsercontroller/playwright/PlaywrightWebElement.ts similarity index 97% rename from @bellatrix/web/src/infrastructure/browserautomationtools/playwright/PlaywrightWebElement.ts rename to @bellatrix/web/src/infrastructure/browsercontroller/playwright/PlaywrightWebElement.ts index e41f864..4abe042 100644 --- a/@bellatrix/web/src/infrastructure/browserautomationtools/playwright/PlaywrightWebElement.ts +++ b/@bellatrix/web/src/infrastructure/browsercontroller/playwright/PlaywrightWebElement.ts @@ -1,6 +1,6 @@ import { Locator as NativeLocator, ElementHandle as NativeElementHandle } from '@playwright/test'; -import { Locator, WebElement } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator, WebElement } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { BellatrixSettings } from '@bellatrix/core/settings'; import { PlaywrightShadowRootWebElement } from './PlaywrightShadowRootWebElement'; @@ -31,6 +31,10 @@ export class PlaywrightWebElement extends WebElement { await this._locator.hover(); } + override async focus(): Promise { + await this._locator.focus(); + } + override async setText(value: string) { await this._locator.fill(value); } @@ -151,7 +155,7 @@ export class PlaywrightWebElement extends WebElement { return await this._locator.isEnabled(); } - override async scrollToVisible(): Promise { + override async scrollIntoView(): Promise { await this._locator.scrollIntoViewIfNeeded(); } diff --git a/@bellatrix/web/src/infrastructure/browserautomationtools/playwright/index.ts b/@bellatrix/web/src/infrastructure/browsercontroller/playwright/index.ts similarity index 63% rename from @bellatrix/web/src/infrastructure/browserautomationtools/playwright/index.ts rename to @bellatrix/web/src/infrastructure/browsercontroller/playwright/index.ts index 5627b54..310dca2 100644 --- a/@bellatrix/web/src/infrastructure/browserautomationtools/playwright/index.ts +++ b/@bellatrix/web/src/infrastructure/browsercontroller/playwright/index.ts @@ -1,3 +1,3 @@ -export * from './PlaywrightBrowserAutomationTool'; +export * from './PlaywrightBrowserController'; export * from './PlaywrightShadowRootWebElement'; export * from './PlaywrightWebElement'; diff --git a/@bellatrix/web/src/infrastructure/browserautomationtools/selenium/SeleniumBrowserAutomationTool.ts b/@bellatrix/web/src/infrastructure/browsercontroller/selenium/SeleniumBrowserController.ts similarity index 92% rename from @bellatrix/web/src/infrastructure/browserautomationtools/selenium/SeleniumBrowserAutomationTool.ts rename to @bellatrix/web/src/infrastructure/browsercontroller/selenium/SeleniumBrowserController.ts index 4c18032..c59c263 100644 --- a/@bellatrix/web/src/infrastructure/browserautomationtools/selenium/SeleniumBrowserAutomationTool.ts +++ b/@bellatrix/web/src/infrastructure/browsercontroller/selenium/SeleniumBrowserController.ts @@ -1,10 +1,10 @@ import { By, WebDriver as NativeWebDriver, until } from 'selenium-webdriver'; -import { Cookie, BrowserAutomationTool, WebElement, Locator } from '@bellatrix/web/infrastructure/browserautomationtools/core'; -import { SeleniumShadowRootWebElement, SeleniumWebElement } from '@bellatrix/web/infrastructure/browserautomationtools/selenium'; +import { Cookie, BrowserController, WebElement, Locator } from '@bellatrix/web/infrastructure/browsercontroller/core'; +import { SeleniumShadowRootWebElement, SeleniumWebElement } from '@bellatrix/web/infrastructure/browsercontroller/selenium'; import { BellatrixSettings } from '@bellatrix/core/settings'; -export class SeleniumBrowserAutomationTool extends BrowserAutomationTool { +export class SeleniumBrowserController extends BrowserController { private _driver: NativeWebDriver; constructor(driver: NativeWebDriver) { @@ -127,7 +127,7 @@ export class SeleniumBrowserAutomationTool extends BrowserAutomationTool { return await this.wrappedDriver.executeScript(`return (${script})(...arguments)`, ...args); } - override async waitUntil(condition: (browserAutomationTool: Omit) => boolean | Promise, timeout: number, pollingInterval: number): Promise { + override async waitUntil(condition: (browserController: Omit) => boolean | Promise, timeout: number, pollingInterval: number): Promise { await this.wrappedDriver.wait(condition.bind(this, this), timeout, 'Condition failed.' /* TODO: better message */, pollingInterval); } diff --git a/@bellatrix/web/src/infrastructure/browserautomationtools/selenium/SeleniumShadowRootWebElement.ts b/@bellatrix/web/src/infrastructure/browsercontroller/selenium/SeleniumShadowRootWebElement.ts similarity index 99% rename from @bellatrix/web/src/infrastructure/browserautomationtools/selenium/SeleniumShadowRootWebElement.ts rename to @bellatrix/web/src/infrastructure/browsercontroller/selenium/SeleniumShadowRootWebElement.ts index 4520f8c..0793a5d 100644 --- a/@bellatrix/web/src/infrastructure/browserautomationtools/selenium/SeleniumShadowRootWebElement.ts +++ b/@bellatrix/web/src/infrastructure/browsercontroller/selenium/SeleniumShadowRootWebElement.ts @@ -1,7 +1,7 @@ import { WebElement as NativeWebElement, WebDriver as NativeWebDriver, By } from 'selenium-webdriver'; import { ShadowRoot as SeleniumShadowRoot } from 'selenium-webdriver/lib/webdriver'; -import { Locator, WebElement } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator, WebElement } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { SeleniumWebElement } from './SeleniumWebElement'; import { Utilities } from '@bellatrix/web/utilities'; diff --git a/@bellatrix/web/src/infrastructure/browserautomationtools/selenium/SeleniumWebElement.ts b/@bellatrix/web/src/infrastructure/browsercontroller/selenium/SeleniumWebElement.ts similarity index 96% rename from @bellatrix/web/src/infrastructure/browserautomationtools/selenium/SeleniumWebElement.ts rename to @bellatrix/web/src/infrastructure/browsercontroller/selenium/SeleniumWebElement.ts index 3cc0c5f..7ffcc34 100644 --- a/@bellatrix/web/src/infrastructure/browserautomationtools/selenium/SeleniumWebElement.ts +++ b/@bellatrix/web/src/infrastructure/browsercontroller/selenium/SeleniumWebElement.ts @@ -1,6 +1,6 @@ import { WebElement as NativeWebElement, WebDriver as NativeWebDriver, By } from 'selenium-webdriver'; -import { Locator, WebElement } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Locator, WebElement } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { SeleniumShadowRootWebElement } from './SeleniumShadowRootWebElement'; import type { HtmlAttribute } from '@bellatrix/web/types'; @@ -26,6 +26,10 @@ export class SeleniumWebElement extends WebElement { .perform(); } + override async focus() { + await this.evaluate((el: HTMLElement) => el.focus()); + } + override async setText(value: string) { await this._element.clear(); await this._element.sendKeys(value); @@ -150,7 +154,7 @@ export class SeleniumWebElement extends WebElement { return await this._element.isEnabled(); } - override async scrollToVisible(): Promise { + override async scrollIntoView(): Promise { await this.evaluate('el => el.scrollIntoView(true);'); } diff --git a/@bellatrix/web/src/infrastructure/browserautomationtools/selenium/index.ts b/@bellatrix/web/src/infrastructure/browsercontroller/selenium/index.ts similarity index 63% rename from @bellatrix/web/src/infrastructure/browserautomationtools/selenium/index.ts rename to @bellatrix/web/src/infrastructure/browsercontroller/selenium/index.ts index 9202cef..3ce3450 100644 --- a/@bellatrix/web/src/infrastructure/browserautomationtools/selenium/index.ts +++ b/@bellatrix/web/src/infrastructure/browsercontroller/selenium/index.ts @@ -1,3 +1,3 @@ -export * from './SeleniumBrowserAutomationTool'; +export * from './SeleniumBrowserController'; export * from './SeleniumShadowRootWebElement'; export * from './SeleniumWebElement'; diff --git a/@bellatrix/web/src/infrastructure/index.ts b/@bellatrix/web/src/infrastructure/index.ts index 9779c45..8dd2003 100644 --- a/@bellatrix/web/src/infrastructure/index.ts +++ b/@bellatrix/web/src/infrastructure/index.ts @@ -1,3 +1,4 @@ export * from './App'; +export * from './BrowserControllerLifecycleManager'; export * from './TestExecutionEngine'; export * from './WebTest'; diff --git a/@bellatrix/web/src/pages/WebPageMap.ts b/@bellatrix/web/src/pages/WebPageMap.ts index 0192eba..041baef 100644 --- a/@bellatrix/web/src/pages/WebPageMap.ts +++ b/@bellatrix/web/src/pages/WebPageMap.ts @@ -2,10 +2,10 @@ import { Ctor } from '@bellatrix/core/types'; import { ComponentService } from '@bellatrix/web/services'; import { WebComponent } from '@bellatrix/web/components'; import { ServiceLocator } from '@bellatrix/core/utilities'; -import { BrowserAutomationTool } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { BrowserController } from '@bellatrix/web/infrastructure/browsercontroller/core'; export abstract class WebPageMap { create(type: Ctor>) { - return new ComponentService(ServiceLocator.resolve(BrowserAutomationTool), type); + return new ComponentService(ServiceLocator.resolve(BrowserController), type); } } diff --git a/@bellatrix/web/src/services/BrowserService.ts b/@bellatrix/web/src/services/BrowserService.ts index bac5993..4fd1623 100644 --- a/@bellatrix/web/src/services/BrowserService.ts +++ b/@bellatrix/web/src/services/BrowserService.ts @@ -1,9 +1,11 @@ -import { BrowserAutomationTool } from '@bellatrix/web/infrastructure/browserautomationtools/core'; -import { WebService } from '.'; +import { BrowserController } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { BellatrixSettings } from '@bellatrix/core/settings'; +import { BellatrixWebService } from '@bellatrix/web/services/decorators'; +import { WebService } from '.'; +@BellatrixWebService export class BrowserService extends WebService { - constructor(driver: BrowserAutomationTool) { + constructor(driver: BrowserController) { super(driver); } diff --git a/@bellatrix/web/src/services/ComponentService.ts b/@bellatrix/web/src/services/ComponentService.ts index 2b2c4b4..466c15a 100644 --- a/@bellatrix/web/src/services/ComponentService.ts +++ b/@bellatrix/web/src/services/ComponentService.ts @@ -1,17 +1,19 @@ import { CssFindStrategy, XpathFindStrategy, NameFindStrategy, IdFindStrategy, ClassFindStrategy, FindStrategy, AttributeContainingFindStrategy, AttributeFindStrategy, ClassContainingFindStrategy, IdContainingFindStrategy, IdEndingWithFindStrategy, InnerTextContainingFindStrategy, LinkTextContainingFindStrategy, LinkTextFindStrategy, ValueContainingFindStrategy, TagFindStrategy, NameEndingWithFindStrategy } from '@bellatrix/web/findstrategies'; import { ComponentsList, ShadowRootContext, WebComponent } from '@bellatrix/web/components'; -import { BrowserAutomationTool } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { BrowserController } from '@bellatrix/web/infrastructure/browsercontroller/core'; +import { BellatrixWebService } from '@bellatrix/web/services/decorators'; import { get as stackTrace } from 'stack-trace'; import { WebService } from '.'; import type { Ctor } from '@bellatrix/core/types'; +@BellatrixWebService export class ComponentService extends WebService { private _type: Ctor; private _parentComponent?: WebComponent | ShadowRootContext; private _componentName?: string; - constructor(driver: BrowserAutomationTool, type: Ctor, parentComponent?: WebComponent | ShadowRootContext) { + constructor(driver: BrowserController, type: Ctor, parentComponent?: WebComponent | ShadowRootContext) { super(driver); this._type = type; this._parentComponent = parentComponent; diff --git a/@bellatrix/web/src/services/ComponentWaitService.ts b/@bellatrix/web/src/services/ComponentWaitService.ts index e719891..2121328 100644 --- a/@bellatrix/web/src/services/ComponentWaitService.ts +++ b/@bellatrix/web/src/services/ComponentWaitService.ts @@ -1,9 +1,14 @@ import { WebComponent } from '@bellatrix/web/components'; +import { WebService } from './WebService'; +import { BrowserController } from '@bellatrix/web/infrastructure/browsercontroller/core'; +import { BellatrixWebService } from '@bellatrix/web/services/decorators'; -export class ComponentWaitService { +@BellatrixWebService +export class ComponentWaitService extends WebService { private _component: WebComponent; - constructor(component: WebComponent) { + constructor(driver: BrowserController, component: WebComponent) { + super(driver); this._component = component; } diff --git a/@bellatrix/web/src/services/CookiesService.ts b/@bellatrix/web/src/services/CookiesService.ts index 4bdedab..b45fa33 100644 --- a/@bellatrix/web/src/services/CookiesService.ts +++ b/@bellatrix/web/src/services/CookiesService.ts @@ -1,8 +1,10 @@ -import { Cookie, BrowserAutomationTool } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { Cookie, BrowserController } from '@bellatrix/web/infrastructure/browsercontroller/core'; +import { BellatrixWebService } from '@bellatrix/web/services/decorators'; import { WebService } from '.'; +@BellatrixWebService export class CookiesService extends WebService { - constructor(driver: BrowserAutomationTool) { + constructor(driver: BrowserController) { super(driver); } diff --git a/@bellatrix/web/src/services/DialogService.ts b/@bellatrix/web/src/services/DialogService.ts index 17dcaee..04f5c16 100644 --- a/@bellatrix/web/src/services/DialogService.ts +++ b/@bellatrix/web/src/services/DialogService.ts @@ -1,5 +1,7 @@ +import { BellatrixWebService } from '@bellatrix/web/services/decorators'; import { WebService } from '.'; +@BellatrixWebService export class DialogService extends WebService { async accept(promptText?: string | undefined): Promise { await this.driver.acceptDialog(promptText); diff --git a/@bellatrix/web/src/services/NavigationService.ts b/@bellatrix/web/src/services/NavigationService.ts index 8e62399..b94b2cb 100644 --- a/@bellatrix/web/src/services/NavigationService.ts +++ b/@bellatrix/web/src/services/NavigationService.ts @@ -1,15 +1,15 @@ +import { BellatrixWebService } from '@bellatrix/web/services/decorators'; import * as path from 'path'; -import { BrowserAutomationTool } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { BrowserController } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { WebService } from '.'; +@BellatrixWebService export class NavigationService extends WebService { - constructor(driver: BrowserAutomationTool) { + constructor(driver: BrowserController) { super(driver); } - async navigate(url: string): Promise - async navigate(url: URL): Promise async navigate(url: string | URL): Promise { if (typeof url === 'string') { await this.driver.open(url); diff --git a/@bellatrix/web/src/services/ScriptService.ts b/@bellatrix/web/src/services/ScriptService.ts index 85985a7..d88ad44 100644 --- a/@bellatrix/web/src/services/ScriptService.ts +++ b/@bellatrix/web/src/services/ScriptService.ts @@ -1,6 +1,8 @@ +import { BellatrixWebService } from '@bellatrix/web/services/decorators'; import { WebComponent } from '@bellatrix/web/components'; import { WebService } from '.'; +@BellatrixWebService export class ScriptService extends WebService { async execute(script: string | ((...args: { [K in keyof VarArgs]: VarArgs[K] extends WebComponent ? T : VarArgs[K] diff --git a/@bellatrix/web/src/services/WebService.ts b/@bellatrix/web/src/services/WebService.ts index 36ceb63..8f20a71 100644 --- a/@bellatrix/web/src/services/WebService.ts +++ b/@bellatrix/web/src/services/WebService.ts @@ -1,13 +1,13 @@ -import { BrowserAutomationTool } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { BrowserController } from '@bellatrix/web/infrastructure/browsercontroller/core'; export abstract class WebService { - private _driver: BrowserAutomationTool; + private _driver: BrowserController; - constructor(driver: BrowserAutomationTool) { + constructor(driver: BrowserController) { this._driver = driver; } - protected get driver(): BrowserAutomationTool { + protected get driver(): BrowserController { return this._driver; } } diff --git a/@bellatrix/web/src/services/decorators/BellatrixWebService.ts b/@bellatrix/web/src/services/decorators/BellatrixWebService.ts new file mode 100644 index 0000000..2e5917d --- /dev/null +++ b/@bellatrix/web/src/services/decorators/BellatrixWebService.ts @@ -0,0 +1,94 @@ +import { ServiceLocator } from '@bellatrix/core/utilities'; +import { WebService } from '@bellatrix/web/services'; +import { WebServiceListener } from '@bellatrix/web/services/utilities'; + +import type { AbstractCtor } from '@bellatrix/core/types'; + +export function BellatrixWebService>(target: TService) { + const originalMethods = Object.getOwnPropertyNames(target.prototype).filter(method => method !== 'constructor') as (keyof typeof target.prototype & string)[]; + + originalMethods.forEach((method) => { + if (typeof target.prototype[method] === 'function') { + const originalMethod = target.prototype[method] as Function & { [Symbol.toStringTag]?: string }; + const isAsync = originalMethod[Symbol.toStringTag] === 'AsyncFunction'; + + if (isAsync) { + target.prototype[method] = async function (this: typeof target.prototype, ...args: never) { + const beforeMethodListeners = ServiceLocator.resolveAll(WebServiceListener, `before|${method}`); + for (const beforeMethodListener of beforeMethodListeners) { + if (beforeMethodListener.service !== this.constructor) { + continue; + } + + await beforeMethodListener.method(this, ...args); + } + + let result; + try { + result = await originalMethod.apply(this, args); + } catch (e) { + const beforeMethodListeners = ServiceLocator.resolveAll(WebServiceListener, `onError|${method}`); + for (const beforeMethodListener of beforeMethodListeners) { + if (beforeMethodListener.service !== this.constructor) { + continue; + } + + await beforeMethodListener.method(this, e, ...args); + } + + throw e; + } + + const afterMethodListeners = ServiceLocator.resolveAll(WebServiceListener, `after|${method}`); + for (const afterMethodListener of afterMethodListeners) { + if (afterMethodListener.service !== this.constructor) { + continue; + } + + await afterMethodListener.method(this, ...args); + } + + return result; + }; + } else { + target.prototype[method] = function (this: typeof target.prototype, ...args: never) { + const beforeMethodListeners = ServiceLocator.resolveAll(WebServiceListener, `before|${method}`); + for (const beforeMethodListener of beforeMethodListeners) { + if (beforeMethodListener.service !== this.constructor) { + continue; + } + + beforeMethodListener.method(this, ...args); + } + + let result; + try { + result = originalMethod.apply(this, args); + } catch (e) { + const beforeMethodListeners = ServiceLocator.resolveAll(WebServiceListener, `onError|${method}`); + for (const beforeMethodListener of beforeMethodListeners) { + if (beforeMethodListener.service !== this.constructor) { + continue; + } + + beforeMethodListener.method(this, e, ...args); + } + + throw e; + } + + const afterMethodListeners = ServiceLocator.resolveAll(WebServiceListener, `after|${method}`); + for (const afterMethodListener of afterMethodListeners) { + if (afterMethodListener.service !== this.constructor) { + continue; + } + + afterMethodListener.method(this, ...args); + } + + return result; + }; + } + } + }); +} diff --git a/@bellatrix/web/src/services/decorators/index.ts b/@bellatrix/web/src/services/decorators/index.ts new file mode 100644 index 0000000..c5b59f8 --- /dev/null +++ b/@bellatrix/web/src/services/decorators/index.ts @@ -0,0 +1 @@ +export * from './BellatrixWebService'; diff --git a/@bellatrix/web/src/services/index.ts b/@bellatrix/web/src/services/index.ts index a34892a..edee4b8 100644 --- a/@bellatrix/web/src/services/index.ts +++ b/@bellatrix/web/src/services/index.ts @@ -1,5 +1,4 @@ export * from './WebService'; -export * from './BrowserAutomationToolLaunchService'; export * from './ComponentService'; export * from './NavigationService'; export * from './CookiesService'; diff --git a/@bellatrix/web/src/services/utilities/WebServiceHooks.ts b/@bellatrix/web/src/services/utilities/WebServiceHooks.ts new file mode 100644 index 0000000..f7a7a72 --- /dev/null +++ b/@bellatrix/web/src/services/utilities/WebServiceHooks.ts @@ -0,0 +1,47 @@ +import type { Ctor } from '@bellatrix/core/types'; +import { ServiceLocator } from '@bellatrix/core/utilities'; +import { ComponentService, ComponentWaitService, NavigationService, ScriptService, WebService } from '@bellatrix/web/services'; + +type NonAsyncMethods = { + [K in keyof T]: T[K] extends (...args: infer _P) => Promise ? never : K; +}[keyof T]; + +type AsyncMethods = { + [K in keyof T]: T[K] extends (...args: infer _P) => Promise ? K : never; +}[keyof T]; + +type Methods = NonAsyncMethods | AsyncMethods + +export class WebServiceHooks { + static addListenerTo(service: Ctor) { + return new class { + before, R = void>(methodName: Key, method: (serviceInstance: TService, ...args: Parameters infer _R ? TService[Key] : never>) => R extends Promise ? never : R): void; + before>(methodName: Key, method: (serviceInstance: TService, ...args: Parameters infer _R ? TService[Key] : never>) => void | Promise): void; + before>(methodName: Key, method: (serviceInstance: TService, ...args: Parameters infer _R ? TService[Key] : never>) => void): void { + ServiceLocator.registerSingleton(WebServiceListener, new class extends WebServiceListener { }(service, method), `before|${String(methodName)}`); + } + + after, R = void>(methodName: Key, method: (serviceInstance: TService, ...args: Parameters infer _R ? TService[Key] : never>) => R extends Promise ? never : R): void; + after>(methodName: Key, method: (serviceInstance: TService, ...args: Parameters infer _R ? TService[Key] : never>) => Promise): void; + after>(methodName: Key, method: (serviceInstance: TService, ...args: Parameters infer _R ? TService[Key] : never>) => void): void { + ServiceLocator.registerSingleton(WebServiceListener, new class extends WebServiceListener { }(service, method), `after|${String(methodName)}`); + } + + onError, R = void>(methodName: Key, method: (serviceInstance: TService, error?: Error, ...args: Parameters infer _R ? TService[Key] : never>) => R extends Promise ? never : R): void; + onError>(methodName: Key, method: (serviceInstance: TService, error?: Error, ...args: Parameters infer _R ? TService[Key] : never>) => Promise): void; + onError>(methodName: Key, method: (serviceInstance: TService, error?: Error, ...args: Parameters infer _R ? TService[Key] : never>) => void): void { + ServiceLocator.registerSingleton(WebServiceListener, new class extends WebServiceListener { }(service, method), `onError|${String(methodName)}`); + } + }; + } +} + +export abstract class WebServiceListener { + readonly service: Ctor; + readonly method: (...args: never) => unknown; + + constructor(service: Ctor, method: (service: TService, ...args: never) => unknown) { + this.service = service; + this.method = method; + } +} diff --git a/@bellatrix/web/src/services/utilities/index.ts b/@bellatrix/web/src/services/utilities/index.ts new file mode 100644 index 0000000..51aa0c2 --- /dev/null +++ b/@bellatrix/web/src/services/utilities/index.ts @@ -0,0 +1 @@ +export * from './WebServiceHooks'; diff --git a/@bellatrix/web/src/test/index.ts b/@bellatrix/web/src/test/index.ts index 827f4a1..95036c0 100644 --- a/@bellatrix/web/src/test/index.ts +++ b/@bellatrix/web/src/test/index.ts @@ -5,7 +5,7 @@ import { App, WebTest } from '@bellatrix/web/infrastructure'; import type { ubyte } from '@bellatrix/core/types'; -import type { BrowserAutomationToolType, BrowserType, ExecutionType } from '@bellatrix/web/types'; +import type { BrowserControllerType, BrowserType, ExecutionType } from '@bellatrix/web/types'; class WebTestProps extends TestProps { app = () => ServiceLocator.resolve(App); @@ -46,7 +46,7 @@ interface ActionSettings { } interface ExecutionSettings { - browserAutomationTool: BrowserAutomationToolType; + browserController: BrowserControllerType; browser: BrowserType, headless: boolean, viewport?: { width: number, height: number }, diff --git a/@bellatrix/web/src/types/test.ts b/@bellatrix/web/src/types/test.ts index da4d4fa..cdf3ac5 100644 --- a/@bellatrix/web/src/types/test.ts +++ b/@bellatrix/web/src/types/test.ts @@ -1,4 +1,4 @@ -export type BrowserAutomationToolType = 'selenium' | 'playwright'; +export type BrowserControllerType = 'selenium' | 'playwright'; export type BrowserType = 'chrome' | 'edge' | 'firefox' | 'safari' // more to be added export type ExecutionType = 'local' | 'remote' diff --git a/@bellatrix/web/src/utilities/Utilities.ts b/@bellatrix/web/src/utilities/Utilities.ts index 40e1669..0cbd3c1 100644 --- a/@bellatrix/web/src/utilities/Utilities.ts +++ b/@bellatrix/web/src/utilities/Utilities.ts @@ -1,7 +1,7 @@ import { DOMParser } from '@xmldom/xmldom'; import { select } from 'xpath'; -import { WebElement } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { WebElement } from '@bellatrix/web/infrastructure/browsercontroller/core'; export class Utilities { static async relativeToAbsoluteXpath(searchContext: WebElement, xpath: string): Promise { @@ -25,7 +25,7 @@ export class Utilities { el = parentNode; } - return [`${paths.toReversed().join('/')}`, (el as unknown as InnerHTML).innerHTML] as const; + return [`${paths.toReversed().join('/')}`, (el as HTMLElement).innerHTML] as const; }); const xpathToStartFrom = data[0]; diff --git a/@bellatrix/web/src/validators/Validator.ts b/@bellatrix/web/src/validators/Validator.ts index 6426d78..f79e111 100644 --- a/@bellatrix/web/src/validators/Validator.ts +++ b/@bellatrix/web/src/validators/Validator.ts @@ -1,5 +1,5 @@ import { ServiceLocator } from '@bellatrix/core/utilities'; -import { BrowserAutomationTool } from '@bellatrix/web/infrastructure/browserautomationtools/core'; +import { BrowserController } from '@bellatrix/web/infrastructure/browsercontroller/core'; import { StringValidator, NumberValidator, BooleanValidator, UnknownValidator } from '.'; import { BellatrixSettings } from '@bellatrix/core/settings'; import { Assert, is } from '@bellatrix/core/assertions'; @@ -238,8 +238,8 @@ export class Validator implements StringValidator, NumberValidator, BooleanValid } } - private get driver(): BrowserAutomationTool { - return ServiceLocator.resolve(BrowserAutomationTool); + private get driver(): BrowserController { + return ServiceLocator.resolve(BrowserController); } private async getResult(): Promise { diff --git a/@bellatrix/web/tsconfig.json b/@bellatrix/web/tsconfig.json index 7d755b3..0c0ee61 100644 --- a/@bellatrix/web/tsconfig.json +++ b/@bellatrix/web/tsconfig.json @@ -3,9 +3,6 @@ "baseUrl": "src", "rootDir": "src", "outDir": "lib", - "paths": { - "@bellatrix/core/*": [ "../../core/src/*" ] - }, "lib": [ "ESNext", "DOM" ], "module": "esnext", "target": "esnext", @@ -23,9 +20,6 @@ "forceConsistentCasingInFileNames": true, "allowJs": false }, - "references": [ - { "path": "../core" } - ], "include": ["**/*.ts"], "exclude": ["node_modules"] } \ No newline at end of file diff --git a/example/bellatrix.config.ts b/example/bellatrix.config.ts index 3fd98f7..9f0d7bb 100644 --- a/example/bellatrix.config.ts +++ b/example/bellatrix.config.ts @@ -27,7 +27,7 @@ const config: BellatrixConfigurationOverride = { delayBeforeAction: 0, }, executionSettings: { - browserAutomationTool: 'playwright', // playwright, selenium + browserController: 'playwright', // playwright, selenium browser: 'edge', // chrome, firefox, safari, edge viewport: { width: 1920, height: 1080 }, startMaximized: false, diff --git a/example/package-lock.json b/example/package-lock.json new file mode 100644 index 0000000..d0b94a9 --- /dev/null +++ b/example/package-lock.json @@ -0,0 +1,137 @@ +{ + "name": "example", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "example", + "version": "0.0.1", + "dependencies": { + "@bellatrix/core": "file:../@bellatrix/core", + "@bellatrix/extras": "file:../@bellatrix/extras", + "@bellatrix/runner": "file:../@bellatrix/runner", + "@bellatrix/web": "file:../@bellatrix/web" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "../@bellatrix/core": { + "version": "0.0.1", + "dependencies": { + "@jest/globals": "^29.7.0", + "@playwright/test": "^1.41.0", + "jasmine": "^5.1.0", + "jasmine-core": "^5.1.1", + "mocha": "^10.2.0", + "reflect-metadata": "^0.2.1", + "vitest": "^1.2.1" + }, + "devDependencies": { + "c8": "^9.1.0", + "chai": "^4.4.1", + "cross-env": "^7.0.3", + "fix-esm-import-path": "^1.5.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "../@bellatrix/extras": { + "version": "0.0.1", + "dependencies": { + "@bellatrix/core": "file:../core", + "@bellatrix/web": "file:../web" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "../@bellatrix/runner": { + "version": "0.0.1", + "dependencies": { + "esm": "^3.2.25", + "jasmine": "^5.1.0", + "jasmine-reporters": "^2.5.2", + "jasmine-trx-reporter": "^2.3.0", + "jest-cli": "^29.7.0", + "jest-junit": "^16.0.0", + "jest-nunit-reporter": "^1.3.1", + "jest-trx-results-processor": "^3.0.2", + "jest-xunit": "^1.0.11", + "minimist": "^1.2.8", + "mocha": "^10.2.0", + "mocha-json-output-reporter": "^2.1.0", + "mocha-junit-reporter": "^2.2.1", + "mocha-trx-reporter": "^3.3.1", + "mocha-xunit-reporter": "^2.3.0", + "playwright": "^1.41.0", + "playwright-trx-reporter": "^1.0.10", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.2", + "ts-paths-esm-loader": "^1.4.3", + "tsconfig-paths": "^4.2.0", + "vite-tsconfig-paths": "^4.3.1" + }, + "bin": { + "bellatrix": "bellatrix.js" + }, + "devDependencies": { + "@types/jest": "^29.5.11", + "@types/jest-cli": "^24.3.0", + "@types/mocha": "^10.0.6" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "../@bellatrix/web": { + "version": "0.0.1", + "dependencies": { + "@bellatrix/core": "file:../core", + "@xmldom/xmldom": "^0.8.10", + "selenium-webdriver": "^4.16.0", + "stack-trace": "^1.0.0-pre2", + "xpath": "^0.0.34" + }, + "devDependencies": { + "@types/selenium-webdriver": "^4.1.21", + "@types/stack-trace": "^0.0.33" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, + "node_modules/@bellatrix/core": { + "resolved": "../@bellatrix/core", + "link": true + }, + "node_modules/@bellatrix/extras": { + "resolved": "../@bellatrix/extras", + "link": true + }, + "node_modules/@bellatrix/runner": { + "resolved": "../@bellatrix/runner", + "link": true + }, + "node_modules/@bellatrix/web": { + "resolved": "../@bellatrix/web", + "link": true + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/example/package.json b/example/package.json index 562ad16..d0aa806 100644 --- a/example/package.json +++ b/example/package.json @@ -3,6 +3,7 @@ "version": "0.0.1", "dependencies": { "@bellatrix/core": "file:../@bellatrix/core", + "@bellatrix/extras": "file:../@bellatrix/extras", "@bellatrix/web": "file:../@bellatrix/web", "@bellatrix/runner": "file:../@bellatrix/runner" }, diff --git a/example/tests/ProductPurchaseTests.test.ts b/example/tests/ProductPurchaseTests.test.ts index f00fa44..5112ed1 100644 --- a/example/tests/ProductPurchaseTests.test.ts +++ b/example/tests/ProductPurchaseTests.test.ts @@ -1,14 +1,20 @@ import { Test, TestClass } from '@bellatrix/web/test'; import { WebTest } from '@bellatrix/web/infrastructure'; import { Button } from '@bellatrix/web/components'; -import { DefaultWebComponentHooks } from '@bellatrix/web/components/hooks'; +import { ExtraWebHooks } from '@bellatrix/extras/hooks'; +import { LogLifecyclePlugin } from '@bellatrix/extras/plugins'; import { MainPage, CartPage, CheckoutPage, PurchaseInfo } from '../src/pages'; +import { PluginExecutionEngine } from '@bellatrix/core/infrastructure'; +import { WebServiceHooks } from '@bellatrix/web/services/utilities'; +import { NavigationService } from '@bellatrix/web/services'; @TestClass export class ProductPurchaseTests extends WebTest { override async configure(): Promise { await super.configure(); - DefaultWebComponentHooks.addComponentBDDLogging(); + ExtraWebHooks.addComponentBDDLogging(); + PluginExecutionEngine.addPlugin(LogLifecyclePlugin); + WebServiceHooks.addListenerTo(NavigationService).before('navigate', (_, url) => console.log(`navigating to ${url}`)); } override async afterEach() { diff --git a/package-lock.json b/package-lock.json index 756fab7..6f1504b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,17 @@ "typescript": "^5.0.0" } }, + "@bellatrix/extras": { + "name": "@bellatrix/web", + "version": "0.0.1", + "dependencies": { + "@bellatrix/core": "file:../core", + "@bellatrix/web": "file:../web" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } + }, "@bellatrix/runner": { "version": "0.0.1", "dependencies": { @@ -93,6 +104,7 @@ "version": "0.0.1", "dependencies": { "@bellatrix/core": "file:../@bellatrix/core", + "@bellatrix/extras": "file:../@bellatrix/extras", "@bellatrix/runner": "file:../@bellatrix/runner", "@bellatrix/web": "file:../@bellatrix/web" }, @@ -696,6 +708,10 @@ "resolved": "@bellatrix/core", "link": true }, + "node_modules/@bellatrix/extras": { + "resolved": "@bellatrix/extras", + "link": true + }, "node_modules/@bellatrix/runner": { "resolved": "@bellatrix/runner", "link": true