|
1 | 1 | import { test as report } from '@playwright/test' |
2 | 2 | import type { BaseComponent } from '@shared/components/base' |
3 | | -import type { |
4 | | - CheckOptions, |
5 | | - ClearOptions, |
6 | | - ClickOptions, |
7 | | - FillOptions, |
8 | | - HoverOptions, |
9 | | - TimeoutOption, |
10 | | - TypeOptions, |
11 | | -} from '@shared/entities' |
12 | | -import { quoteName } from '@services/utils' |
13 | | - |
14 | | -export async function descriptiveClick(component: BaseComponent, options?: ClickOptions): Promise<void> { |
15 | | - await report.step(`Click ${quoteName(component.componentName)} ${component.componentType}`, async () => { |
16 | | - await component.mainLocator.click(options) |
17 | | - }, { box: true }) |
18 | | -} |
19 | | - |
20 | | -export async function descriptiveHover(component: BaseComponent, options?: HoverOptions): Promise<void> { |
21 | | - await report.step(`Hover ${quoteName(component.componentName)} ${component.componentType}`, async () => { |
22 | | - await component.mainLocator.hover(options) |
23 | | - await component.mainLocator.page().waitForTimeout(500) //WA: wait while extra tooltips disappear |
24 | | - }, { box: true }) |
25 | | -} |
26 | | - |
27 | | -export async function descriptiveFill(component: BaseComponent, value: string, options?: FillOptions): Promise<void> { |
28 | | - await report.step(`Fill ${quoteName(component.componentName)} ${component.componentType} with "${value}"`, async () => { |
29 | | - await component.mainLocator.fill(value, options) |
30 | | - }, { box: true }) |
31 | | -} |
32 | | - |
33 | | -export async function descriptiveType(component: BaseComponent, value: string, options?: TypeOptions): Promise<void> { |
34 | | - await report.step(`Type "${value}" into ${quoteName(component.componentName)} ${component.componentType}`, async () => { |
35 | | - await component.mainLocator.type(value, options) |
36 | | - }, { box: true }) |
37 | | -} |
38 | | - |
39 | | -export async function descriptiveClear(component: BaseComponent, options?: ClearOptions): Promise<void> { |
40 | | - await report.step(`Clear ${quoteName(component.componentName)} ${component.componentType}`, async () => { |
41 | | - await component.mainLocator.clear(options) |
42 | | - }, { box: true }) |
43 | | -} |
44 | | - |
45 | | -export async function descriptiveCheck(component: BaseComponent, options?: CheckOptions): Promise<void> { |
46 | | - await report.step(`Check ${quoteName(component.componentName)} ${component.componentType}`, async () => { |
47 | | - await component.mainLocator.check(options) |
48 | | - }, { box: true }) |
49 | | -} |
50 | | - |
51 | | -export async function descriptiveUncheck(component: BaseComponent, options?: CheckOptions): Promise<void> { |
52 | | - await report.step(`Uncheck ${quoteName(component.componentName)} ${component.componentType}`, async () => { |
53 | | - await component.mainLocator.uncheck(options) |
54 | | - }, { box: true }) |
55 | | -} |
56 | | - |
57 | | -export async function descriptiveScroll(component: BaseComponent, options?: TimeoutOption): Promise<void> { |
58 | | - await report.step(`Scroll until ${quoteName(component.componentName)} ${component.componentType} to be visible`, async () => { |
59 | | - await component.mainLocator.scrollIntoViewIfNeeded(options) |
60 | | - }, { box: true }) |
61 | | -} |
62 | | - |
63 | | -export async function descriptiveFocus(component: BaseComponent, options?: TimeoutOption): Promise<void> { |
64 | | - await report.step(`Focus ${quoteName(component.componentName)} ${component.componentType}`, async () => { |
65 | | - await component.mainLocator.focus(options) |
66 | | - }, { box: true }) |
| 3 | +import { handlePlaywrightError, quoteName } from '@services/utils' |
| 4 | + |
| 5 | +const HOVER_TOOLTIP_TIMEOUT = 500 |
| 6 | + |
| 7 | +/** |
| 8 | + * A decorator factory that adds descriptive logging to methods in UI component classes. |
| 9 | + * |
| 10 | + * This decorator is designed specifically for Playwright test automation. It: |
| 11 | + * 1. Wraps the target method with Playwright's test.step() for improved test reporting |
| 12 | + * 2. Automatically generates a descriptive step title based on the action, component name, and component type |
| 13 | + * 3. Handles errors with descriptive messages that help identify the exact component and action that failed |
| 14 | + * 4. Optionally adds a wait period after actions to allow tooltips to appear (useful for hover actions) |
| 15 | + * |
| 16 | + * The decorator supports two argument patterns: |
| 17 | + * - Methods with no input value: e.g., click(), hover() |
| 18 | + * - Methods with a string value: e.g., type('text'), select('option') |
| 19 | + * |
| 20 | + * @param action - The action being performed, e.g. "Click", "Type", "Hover", etc. |
| 21 | + * @param waitForTooltip - When true, waits for HOVER_TOOLTIP_TIMEOUT milliseconds after the action |
| 22 | + * to allow UI elements like tooltips to appear. Defaults to false. |
| 23 | + * @returns A method decorator that can be applied to component class methods. |
| 24 | + */ |
| 25 | +export function descriptive(action: string, waitForTooltip = false) { |
| 26 | + return function <This extends BaseComponent, Args extends Array<unknown>>( |
| 27 | + target: (this: This, ...args: Args) => Promise<void>, |
| 28 | + ) { |
| 29 | + return async function (this: This, ...args: Args): Promise<void> { |
| 30 | + // Check if first argument is a string (used for type/fill operations) |
| 31 | + const value = typeof args[0] === 'string' ? args[0] : undefined |
| 32 | + |
| 33 | + // Create descriptive step title |
| 34 | + const componentDetails = `${quoteName(this.componentName)} ${this.componentType}` |
| 35 | + const stepTitle = value |
| 36 | + ? `${action} "${value}" into ${componentDetails}` |
| 37 | + : `${action} ${componentDetails}` |
| 38 | + |
| 39 | + await report.step(stepTitle, async () => { |
| 40 | + try { |
| 41 | + // Execute the original method |
| 42 | + await target.apply(this, args) |
| 43 | + |
| 44 | + // Wait for tooltip if needed (e.g. for hover operations) |
| 45 | + if (waitForTooltip) { |
| 46 | + await this.mainLocator.page().waitForTimeout(HOVER_TOOLTIP_TIMEOUT) |
| 47 | + } |
| 48 | + } catch (error: unknown) { |
| 49 | + // Enhance error with context information |
| 50 | + const errorPrefix = `Failed to ${action}` |
| 51 | + const errorContext = value ? ` "${value}" into ${componentDetails}` : ` ${componentDetails}` |
| 52 | + handlePlaywrightError(error, `${errorPrefix}${errorContext}`) |
| 53 | + } |
| 54 | + }, { box: true }) |
| 55 | + } |
| 56 | + } |
67 | 57 | } |
0 commit comments