@emesoft_company/playwright-core 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +62 -0
  2. package/dist/api/index.cjs +248 -0
  3. package/dist/api/index.cjs.map +1 -0
  4. package/dist/api/index.d.cts +12 -0
  5. package/dist/api/index.d.ts +12 -0
  6. package/dist/api/index.js +245 -0
  7. package/dist/api/index.js.map +1 -0
  8. package/dist/api-client-C_StjpPX.d.ts +58 -0
  9. package/dist/api-client-CpXo_NOU.d.cts +58 -0
  10. package/dist/auth/index.cjs +310 -0
  11. package/dist/auth/index.cjs.map +1 -0
  12. package/dist/auth/index.d.cts +98 -0
  13. package/dist/auth/index.d.ts +98 -0
  14. package/dist/auth/index.js +304 -0
  15. package/dist/auth/index.js.map +1 -0
  16. package/dist/config/index.cjs +268 -0
  17. package/dist/config/index.cjs.map +1 -0
  18. package/dist/config/index.d.cts +105 -0
  19. package/dist/config/index.d.ts +105 -0
  20. package/dist/config/index.js +254 -0
  21. package/dist/config/index.js.map +1 -0
  22. package/dist/data/index.cjs +59 -0
  23. package/dist/data/index.cjs.map +1 -0
  24. package/dist/data/index.d.cts +25 -0
  25. package/dist/data/index.d.ts +25 -0
  26. package/dist/data/index.js +50 -0
  27. package/dist/data/index.js.map +1 -0
  28. package/dist/di/index.cjs +47 -0
  29. package/dist/di/index.cjs.map +1 -0
  30. package/dist/di/index.d.cts +19 -0
  31. package/dist/di/index.d.ts +19 -0
  32. package/dist/di/index.js +45 -0
  33. package/dist/di/index.js.map +1 -0
  34. package/dist/errors/index.cjs +90 -0
  35. package/dist/errors/index.cjs.map +1 -0
  36. package/dist/errors/index.d.cts +38 -0
  37. package/dist/errors/index.d.ts +38 -0
  38. package/dist/errors/index.js +83 -0
  39. package/dist/errors/index.js.map +1 -0
  40. package/dist/expect/index.cjs +36 -0
  41. package/dist/expect/index.cjs.map +1 -0
  42. package/dist/expect/index.d.cts +30 -0
  43. package/dist/expect/index.d.ts +30 -0
  44. package/dist/expect/index.js +34 -0
  45. package/dist/expect/index.js.map +1 -0
  46. package/dist/fixtures/index.cjs +660 -0
  47. package/dist/fixtures/index.cjs.map +1 -0
  48. package/dist/fixtures/index.d.cts +68 -0
  49. package/dist/fixtures/index.d.ts +68 -0
  50. package/dist/fixtures/index.js +651 -0
  51. package/dist/fixtures/index.js.map +1 -0
  52. package/dist/index.cjs +1322 -0
  53. package/dist/index.cjs.map +1 -0
  54. package/dist/index.d.cts +21 -0
  55. package/dist/index.d.ts +21 -0
  56. package/dist/index.js +1248 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/logging/index.cjs +32 -0
  59. package/dist/logging/index.cjs.map +1 -0
  60. package/dist/logging/index.d.cts +18 -0
  61. package/dist/logging/index.d.ts +18 -0
  62. package/dist/logging/index.js +25 -0
  63. package/dist/logging/index.js.map +1 -0
  64. package/dist/reporting/index.cjs +59 -0
  65. package/dist/reporting/index.cjs.map +1 -0
  66. package/dist/reporting/index.d.cts +63 -0
  67. package/dist/reporting/index.d.ts +63 -0
  68. package/dist/reporting/index.js +52 -0
  69. package/dist/reporting/index.js.map +1 -0
  70. package/dist/retry-XqnSC8YL.d.cts +27 -0
  71. package/dist/retry-XqnSC8YL.d.ts +27 -0
  72. package/dist/schema-y5w2hbT-.d.cts +292 -0
  73. package/dist/schema-y5w2hbT-.d.ts +292 -0
  74. package/dist/types-CbWnSYEK.d.cts +5 -0
  75. package/dist/types-CbWnSYEK.d.ts +5 -0
  76. package/dist/types-CopwwpSW.d.cts +8 -0
  77. package/dist/types-CopwwpSW.d.ts +8 -0
  78. package/dist/types-hInbkPXf.d.cts +59 -0
  79. package/dist/types-hInbkPXf.d.ts +59 -0
  80. package/dist/ui/index.cjs +366 -0
  81. package/dist/ui/index.cjs.map +1 -0
  82. package/dist/ui/index.d.cts +190 -0
  83. package/dist/ui/index.d.ts +190 -0
  84. package/dist/ui/index.js +348 -0
  85. package/dist/ui/index.js.map +1 -0
  86. package/dist/utils/index.cjs +72 -0
  87. package/dist/utils/index.cjs.map +1 -0
  88. package/dist/utils/index.d.cts +27 -0
  89. package/dist/utils/index.d.ts +27 -0
  90. package/dist/utils/index.js +66 -0
  91. package/dist/utils/index.js.map +1 -0
  92. package/package.json +133 -0
@@ -0,0 +1,190 @@
1
+ import { Page, Response, Locator } from '@playwright/test';
2
+ import { Logger } from 'pino';
3
+
4
+ interface BasePageOptions {
5
+ page: Page;
6
+ baseUrl?: string;
7
+ logger?: Logger;
8
+ }
9
+ interface GotoOptions {
10
+ /** Appended as query string, e.g. `{ tab: "open" }` -> `?tab=open`. */
11
+ query?: Record<string, string | number | boolean | undefined>;
12
+ /** Path to navigate to instead of the page's own `path` (e.g. a detail page by id). */
13
+ path?: string;
14
+ waitUntil?: "load" | "domcontentloaded" | "networkidle" | "commit";
15
+ }
16
+ /**
17
+ * Generic Page Object base. Subclasses declare `path` and their own selectors/actions —
18
+ * this base only provides framework-level plumbing (navigation, safe interactions, waits),
19
+ * never project-specific selectors or business logic.
20
+ */
21
+ declare abstract class BasePage {
22
+ protected readonly page: Page;
23
+ protected readonly baseUrl?: string;
24
+ protected readonly logger?: Logger;
25
+ abstract readonly path: string;
26
+ constructor(options: BasePageOptions);
27
+ /** Absolute URL for this page, with optional path override and query string. */
28
+ url(options?: Pick<GotoOptions, "path" | "query">): string;
29
+ goto(options?: GotoOptions): Promise<Response | null>;
30
+ reload(): Promise<void>;
31
+ currentUrl(): string;
32
+ waitForUrl(url: string | RegExp, timeoutMs?: number): Promise<void>;
33
+ waitForLoadState(state?: "load" | "domcontentloaded" | "networkidle"): Promise<void>;
34
+ /**
35
+ * Waits for network activity to settle. Prefer waiting on a specific element when you can —
36
+ * `networkidle` is discouraged by Playwright for apps that poll or hold sockets open.
37
+ */
38
+ waitForNetworkIdle(timeoutMs?: number): Promise<void>;
39
+ pressKey(key: string): Promise<void>;
40
+ /**
41
+ * Screenshot as a buffer. Pair with `attachScreenshot()` from
42
+ * `@emesoft_company/playwright-core/reporting` to put it in the HTML/Allure report.
43
+ */
44
+ screenshot(fullPage?: boolean): Promise<Buffer>;
45
+ protected waitForVisible(locator: Locator, timeoutMs?: number): Promise<void>;
46
+ safeClick(locator: Locator, timeoutMs?: number): Promise<void>;
47
+ safeFill(locator: Locator, value: string): Promise<void>;
48
+ safeType(locator: Locator, value: string, delayMs?: number): Promise<void>;
49
+ safeSelect(locator: Locator, value: string | string[]): Promise<void>;
50
+ safeCheck(locator: Locator, checked?: boolean): Promise<void>;
51
+ uploadFile(locator: Locator, files: string | string[]): Promise<void>;
52
+ scrollIntoView(locator: Locator): Promise<void>;
53
+ getText(locator: Locator): Promise<string>;
54
+ getAllTexts(locator: Locator): Promise<string[]>;
55
+ /** Fills a form from a `{ selector: value }` map. Booleans check/uncheck. */
56
+ fillFields(fields: Record<string, string | number | boolean>): Promise<void>;
57
+ }
58
+
59
+ interface BaseComponentOptions {
60
+ page: Page;
61
+ /** Locator scoping this component to its root element (e.g. a modal, a nav bar). */
62
+ root: Locator;
63
+ }
64
+ /**
65
+ * Generic, reusable wrapper for a UI component scoped to a root locator.
66
+ * Intentionally contains no selectors or business logic — projects extend
67
+ * this for their own components (e.g. `class LoginFormComponent extends BaseComponent`).
68
+ *
69
+ * Every lookup here is scoped to `root`, which is what keeps two instances of the same
70
+ * component on one page (two rows, two modals) from matching each other's elements.
71
+ */
72
+ declare class BaseComponent {
73
+ protected readonly page: Page;
74
+ readonly root: Locator;
75
+ constructor(options: BaseComponentOptions);
76
+ byTestId(testId: string): Locator;
77
+ byRole(...args: Parameters<Locator["getByRole"]>): Locator;
78
+ byText(...args: Parameters<Locator["getByText"]>): Locator;
79
+ byLabel(...args: Parameters<Locator["getByLabel"]>): Locator;
80
+ locator(selector: string): Locator;
81
+ isVisible(): Promise<boolean>;
82
+ exists(): Promise<boolean>;
83
+ text(): Promise<string>;
84
+ waitForStable(timeoutMs?: number): Promise<void>;
85
+ waitForHidden(timeoutMs?: number): Promise<void>;
86
+ safeClick(locator: Locator, timeoutMs?: number): Promise<void>;
87
+ safeFill(locator: Locator, value: string): Promise<void>;
88
+ safeSelect(locator: Locator, value: string | string[]): Promise<void>;
89
+ safeCheck(locator: Locator, checked?: boolean): Promise<void>;
90
+ scrollIntoView(locator?: Locator): Promise<void>;
91
+ getText(locator: Locator): Promise<string>;
92
+ getAllTexts(locator: Locator): Promise<string[]>;
93
+ /** Fills fields *within this component*, so selectors don't collide with the rest of the page. */
94
+ fillFields(fields: Record<string, string | number | boolean>): Promise<void>;
95
+ }
96
+
97
+ interface TableComponentOptions extends BaseComponentOptions {
98
+ /** Selector for header cells, relative to the table root. Default: `"thead th"`. */
99
+ headerSelector?: string;
100
+ /** Selector for data rows, relative to the table root. Default: `"tbody tr"`. */
101
+ rowSelector?: string;
102
+ /** Selector for cells, relative to a row. Default: `"td"`. */
103
+ cellSelector?: string;
104
+ }
105
+ /**
106
+ * Generic data-table/grid wrapper: row counts, cell lookup by column *name* rather than
107
+ * brittle index, and whole-table extraction for assertions.
108
+ *
109
+ * Defaults target a standard `<table><thead><th>/<tbody><tr><td>` layout. For div-based grids
110
+ * pass the three selectors, e.g.
111
+ * `{ headerSelector: '[role=columnheader]', rowSelector: '[role=row]', cellSelector: '[role=gridcell]' }`.
112
+ *
113
+ * Contains no project selectors itself — a project subclasses it (or just constructs it with
114
+ * its own root locator) per grid.
115
+ */
116
+ declare class TableComponent extends BaseComponent {
117
+ private readonly headerSelector;
118
+ private readonly rowSelector;
119
+ private readonly cellSelector;
120
+ constructor(options: TableComponentOptions);
121
+ /** All data rows. A live locator — safe to use with auto-retrying assertions. */
122
+ get rows(): Locator;
123
+ get headerCells(): Locator;
124
+ row(index: number): Locator;
125
+ cells(rowIndex: number): Locator;
126
+ rowCount(): Promise<number>;
127
+ isEmpty(): Promise<boolean>;
128
+ /** Trimmed header labels, in order. */
129
+ headers(): Promise<string[]>;
130
+ /**
131
+ * Zero-based index of a column by its header label (exact match after trimming, falling back
132
+ * to a case-insensitive contains match). Throws listing the available headers when not found —
133
+ * a renamed column is otherwise a confusing "cell is empty" failure.
134
+ */
135
+ columnIndex(header: string): Promise<number>;
136
+ /** A cell by row index and either a column index or a header label. */
137
+ cell(rowIndex: number, column: string | number): Promise<Locator>;
138
+ cellText(rowIndex: number, column: string | number): Promise<string>;
139
+ /** Every cell value of one row, in column order. */
140
+ rowValues(rowIndex: number): Promise<string[]>;
141
+ /**
142
+ * The whole table as `{ header: cellText }` objects — the most convenient shape for
143
+ * assertions (`expect(await table.records()).toContainEqual({ Status: "Approved", ... })`).
144
+ */
145
+ records(): Promise<Record<string, string>[]>;
146
+ /** Rows containing `text` anywhere. Still a locator, so assertions on it auto-retry. */
147
+ rowsContaining(text: string | RegExp): Locator;
148
+ /**
149
+ * Rows whose cell in a given column matches `value`. Returns a locator (auto-retrying);
150
+ * only the header lookup is awaited up front.
151
+ */
152
+ rowsByColumn(column: string | number, value: string | RegExp): Promise<Locator>;
153
+ /** Waits until the table has at least `min` rows (default 1) — e.g. after a search or load. */
154
+ waitForRows(min?: number, timeoutMs?: number): Promise<void>;
155
+ }
156
+
157
+ /**
158
+ * Locator-level helpers shared by `BasePage` and `BaseComponent` (and usable standalone on any
159
+ * locator). Each one waits for the element to be actionable first, so callers don't sprinkle
160
+ * `waitFor` everywhere — Playwright auto-waits for most of this already, but being explicit
161
+ * gives far better failure messages than a bare `click()` timing out mid-action.
162
+ */
163
+ declare function waitForVisible(locator: Locator, timeoutMs?: number): Promise<void>;
164
+ declare function safeClick(locator: Locator, timeoutMs?: number): Promise<void>;
165
+ declare function safeDoubleClick(locator: Locator, timeoutMs?: number): Promise<void>;
166
+ declare function safeFill(locator: Locator, value: string, timeoutMs?: number): Promise<void>;
167
+ /** Clears then types character by character — for inputs with keystroke-driven behavior (autocomplete, masks). */
168
+ declare function safeType(locator: Locator, value: string, delayMs?: number): Promise<void>;
169
+ declare function safeSelect(locator: Locator, value: string | string[]): Promise<void>;
170
+ declare function safeCheck(locator: Locator, checked?: boolean): Promise<void>;
171
+ declare function uploadFile(locator: Locator, files: string | string[]): Promise<void>;
172
+ declare function scrollIntoView(locator: Locator): Promise<void>;
173
+ /** Trimmed visible text of an element (`""` when it has none). */
174
+ declare function getText(locator: Locator): Promise<string>;
175
+ /** Trimmed text of every element the locator matches. */
176
+ declare function getAllTexts(locator: Locator): Promise<string[]>;
177
+ declare function getInputValue(locator: Locator): Promise<string>;
178
+ /** True when the element exists in the DOM at all (regardless of visibility). */
179
+ declare function exists(locator: Locator): Promise<boolean>;
180
+ interface FillFieldsOptions {
181
+ /** Resolve each key into a locator. Defaults to `page.locator(key)`. */
182
+ resolve?: (selector: string) => Locator;
183
+ }
184
+ /**
185
+ * Fills a whole form from a `{ selector: value }` map — the single most repeated block in UI
186
+ * tests. Booleans check/uncheck, everything else is filled as text.
187
+ */
188
+ declare function fillFields(page: Page, fields: Record<string, string | number | boolean>, options?: FillFieldsOptions): Promise<void>;
189
+
190
+ export { BaseComponent, type BaseComponentOptions, BasePage, type BasePageOptions, type FillFieldsOptions, type GotoOptions, TableComponent, type TableComponentOptions, exists, fillFields, getAllTexts, getInputValue, getText, safeCheck, safeClick, safeDoubleClick, safeFill, safeSelect, safeType, scrollIntoView, uploadFile, waitForVisible };
@@ -0,0 +1,348 @@
1
+ // src/ui/actions.ts
2
+ async function waitForVisible(locator, timeoutMs) {
3
+ await locator.waitFor({ state: "visible", timeout: timeoutMs });
4
+ }
5
+ async function safeClick(locator, timeoutMs) {
6
+ await waitForVisible(locator, timeoutMs);
7
+ await locator.click();
8
+ }
9
+ async function safeDoubleClick(locator, timeoutMs) {
10
+ await waitForVisible(locator, timeoutMs);
11
+ await locator.dblclick();
12
+ }
13
+ async function safeFill(locator, value, timeoutMs) {
14
+ await waitForVisible(locator, timeoutMs);
15
+ await locator.fill(value);
16
+ }
17
+ async function safeType(locator, value, delayMs = 0) {
18
+ await waitForVisible(locator);
19
+ await locator.fill("");
20
+ await locator.pressSequentially(value, { delay: delayMs });
21
+ }
22
+ async function safeSelect(locator, value) {
23
+ await waitForVisible(locator);
24
+ await locator.selectOption(value);
25
+ }
26
+ async function safeCheck(locator, checked = true) {
27
+ await waitForVisible(locator);
28
+ if (checked) await locator.check();
29
+ else await locator.uncheck();
30
+ }
31
+ async function uploadFile(locator, files) {
32
+ await locator.setInputFiles(files);
33
+ }
34
+ async function scrollIntoView(locator) {
35
+ await locator.scrollIntoViewIfNeeded();
36
+ }
37
+ async function getText(locator) {
38
+ return (await locator.textContent())?.trim() ?? "";
39
+ }
40
+ async function getAllTexts(locator) {
41
+ const texts = await locator.allTextContents();
42
+ return texts.map((text) => text.trim());
43
+ }
44
+ async function getInputValue(locator) {
45
+ return locator.inputValue();
46
+ }
47
+ async function exists(locator) {
48
+ return await locator.count() > 0;
49
+ }
50
+ async function fillFields(page, fields, options = {}) {
51
+ const resolve = options.resolve ?? ((selector) => page.locator(selector));
52
+ for (const [selector, value] of Object.entries(fields)) {
53
+ const locator = resolve(selector);
54
+ if (typeof value === "boolean") {
55
+ await safeCheck(locator, value);
56
+ } else {
57
+ await safeFill(locator, String(value));
58
+ }
59
+ }
60
+ }
61
+
62
+ // src/ui/components/base-page.ts
63
+ var BasePage = class {
64
+ page;
65
+ baseUrl;
66
+ logger;
67
+ constructor(options) {
68
+ this.page = options.page;
69
+ this.baseUrl = options.baseUrl;
70
+ this.logger = options.logger;
71
+ }
72
+ /** Absolute URL for this page, with optional path override and query string. */
73
+ url(options = {}) {
74
+ const path = options.path ?? this.path;
75
+ const url = this.baseUrl ? new URL(path, this.baseUrl) : new URL(path, "http://localhost");
76
+ if (options.query) {
77
+ for (const [key, value] of Object.entries(options.query)) {
78
+ if (value !== void 0) url.searchParams.set(key, String(value));
79
+ }
80
+ }
81
+ return this.baseUrl ? url.toString() : `${url.pathname}${url.search}`;
82
+ }
83
+ async goto(options = {}) {
84
+ const url = this.url(options);
85
+ this.logger?.debug({ url }, "navigating");
86
+ return this.page.goto(url, options.waitUntil ? { waitUntil: options.waitUntil } : void 0);
87
+ }
88
+ async reload() {
89
+ await this.page.reload();
90
+ }
91
+ currentUrl() {
92
+ return this.page.url();
93
+ }
94
+ async waitForUrl(url, timeoutMs) {
95
+ await this.page.waitForURL(url, timeoutMs ? { timeout: timeoutMs } : void 0);
96
+ }
97
+ async waitForLoadState(state = "load") {
98
+ await this.page.waitForLoadState(state);
99
+ }
100
+ /**
101
+ * Waits for network activity to settle. Prefer waiting on a specific element when you can —
102
+ * `networkidle` is discouraged by Playwright for apps that poll or hold sockets open.
103
+ */
104
+ async waitForNetworkIdle(timeoutMs) {
105
+ await this.page.waitForLoadState("networkidle", timeoutMs ? { timeout: timeoutMs } : void 0);
106
+ }
107
+ async pressKey(key) {
108
+ await this.page.keyboard.press(key);
109
+ }
110
+ /**
111
+ * Screenshot as a buffer. Pair with `attachScreenshot()` from
112
+ * `@emesoft_company/playwright-core/reporting` to put it in the HTML/Allure report.
113
+ */
114
+ async screenshot(fullPage = false) {
115
+ return this.page.screenshot({ fullPage, caret: "hide" });
116
+ }
117
+ // --- locator helpers, delegated to the shared actions so components behave identically ---
118
+ waitForVisible(locator, timeoutMs) {
119
+ return waitForVisible(locator, timeoutMs);
120
+ }
121
+ async safeClick(locator, timeoutMs) {
122
+ await safeClick(locator, timeoutMs);
123
+ }
124
+ async safeFill(locator, value) {
125
+ await safeFill(locator, value);
126
+ }
127
+ async safeType(locator, value, delayMs) {
128
+ await safeType(locator, value, delayMs);
129
+ }
130
+ async safeSelect(locator, value) {
131
+ await safeSelect(locator, value);
132
+ }
133
+ async safeCheck(locator, checked = true) {
134
+ await safeCheck(locator, checked);
135
+ }
136
+ async uploadFile(locator, files) {
137
+ await uploadFile(locator, files);
138
+ }
139
+ async scrollIntoView(locator) {
140
+ await scrollIntoView(locator);
141
+ }
142
+ async getText(locator) {
143
+ return getText(locator);
144
+ }
145
+ async getAllTexts(locator) {
146
+ return getAllTexts(locator);
147
+ }
148
+ /** Fills a form from a `{ selector: value }` map. Booleans check/uncheck. */
149
+ async fillFields(fields) {
150
+ await fillFields(this.page, fields);
151
+ }
152
+ };
153
+
154
+ // src/ui/components/base-component.ts
155
+ var BaseComponent = class {
156
+ page;
157
+ root;
158
+ constructor(options) {
159
+ this.page = options.page;
160
+ this.root = options.root;
161
+ }
162
+ // --- scoped lookups ---
163
+ byTestId(testId) {
164
+ return this.root.getByTestId(testId);
165
+ }
166
+ byRole(...args) {
167
+ return this.root.getByRole(...args);
168
+ }
169
+ byText(...args) {
170
+ return this.root.getByText(...args);
171
+ }
172
+ byLabel(...args) {
173
+ return this.root.getByLabel(...args);
174
+ }
175
+ locator(selector) {
176
+ return this.root.locator(selector);
177
+ }
178
+ // --- state ---
179
+ async isVisible() {
180
+ return this.root.isVisible();
181
+ }
182
+ async exists() {
183
+ return exists(this.root);
184
+ }
185
+ async text() {
186
+ return getText(this.root);
187
+ }
188
+ async waitForStable(timeoutMs) {
189
+ await waitForVisible(this.root, timeoutMs);
190
+ }
191
+ async waitForHidden(timeoutMs) {
192
+ await this.root.waitFor({ state: "hidden", timeout: timeoutMs });
193
+ }
194
+ // --- actions, same implementations BasePage uses ---
195
+ async safeClick(locator, timeoutMs) {
196
+ await safeClick(locator, timeoutMs);
197
+ }
198
+ async safeFill(locator, value) {
199
+ await safeFill(locator, value);
200
+ }
201
+ async safeSelect(locator, value) {
202
+ await safeSelect(locator, value);
203
+ }
204
+ async safeCheck(locator, checked = true) {
205
+ await safeCheck(locator, checked);
206
+ }
207
+ async scrollIntoView(locator = this.root) {
208
+ await scrollIntoView(locator);
209
+ }
210
+ async getText(locator) {
211
+ return getText(locator);
212
+ }
213
+ async getAllTexts(locator) {
214
+ return getAllTexts(locator);
215
+ }
216
+ /** Fills fields *within this component*, so selectors don't collide with the rest of the page. */
217
+ async fillFields(fields) {
218
+ await fillFields(this.page, fields, { resolve: (selector) => this.root.locator(selector) });
219
+ }
220
+ };
221
+
222
+ // src/errors/app-error.ts
223
+ var AppError = class extends Error {
224
+ code;
225
+ context;
226
+ constructor(message, options) {
227
+ super(message, { cause: options.cause });
228
+ this.name = new.target.name;
229
+ this.code = options.code;
230
+ this.context = options.context;
231
+ Object.setPrototypeOf(this, new.target.prototype);
232
+ }
233
+ };
234
+
235
+ // src/ui/components/table.component.ts
236
+ var TableComponent = class extends BaseComponent {
237
+ headerSelector;
238
+ rowSelector;
239
+ cellSelector;
240
+ constructor(options) {
241
+ super(options);
242
+ this.headerSelector = options.headerSelector ?? "thead th";
243
+ this.rowSelector = options.rowSelector ?? "tbody tr";
244
+ this.cellSelector = options.cellSelector ?? "td";
245
+ }
246
+ /** All data rows. A live locator — safe to use with auto-retrying assertions. */
247
+ get rows() {
248
+ return this.root.locator(this.rowSelector);
249
+ }
250
+ get headerCells() {
251
+ return this.root.locator(this.headerSelector);
252
+ }
253
+ row(index) {
254
+ return this.rows.nth(index);
255
+ }
256
+ cells(rowIndex) {
257
+ return this.row(rowIndex).locator(this.cellSelector);
258
+ }
259
+ async rowCount() {
260
+ return this.rows.count();
261
+ }
262
+ async isEmpty() {
263
+ return await this.rowCount() === 0;
264
+ }
265
+ /** Trimmed header labels, in order. */
266
+ async headers() {
267
+ return getAllTexts(this.headerCells);
268
+ }
269
+ /**
270
+ * Zero-based index of a column by its header label (exact match after trimming, falling back
271
+ * to a case-insensitive contains match). Throws listing the available headers when not found —
272
+ * a renamed column is otherwise a confusing "cell is empty" failure.
273
+ */
274
+ async columnIndex(header) {
275
+ const headers = await this.headers();
276
+ const exact = headers.indexOf(header.trim());
277
+ if (exact !== -1) return exact;
278
+ const needle = header.trim().toLowerCase();
279
+ const loose = headers.findIndex((candidate) => candidate.toLowerCase().includes(needle));
280
+ if (loose !== -1) return loose;
281
+ throw new AppError(`Column "${header}" not found in table`, {
282
+ code: "TABLE_COLUMN_NOT_FOUND",
283
+ context: { requested: header, available: headers }
284
+ });
285
+ }
286
+ /** A cell by row index and either a column index or a header label. */
287
+ async cell(rowIndex, column) {
288
+ const index = typeof column === "number" ? column : await this.columnIndex(column);
289
+ return this.cells(rowIndex).nth(index);
290
+ }
291
+ async cellText(rowIndex, column) {
292
+ return getText(await this.cell(rowIndex, column));
293
+ }
294
+ /** Every cell value of one row, in column order. */
295
+ async rowValues(rowIndex) {
296
+ return getAllTexts(this.cells(rowIndex));
297
+ }
298
+ /**
299
+ * The whole table as `{ header: cellText }` objects — the most convenient shape for
300
+ * assertions (`expect(await table.records()).toContainEqual({ Status: "Approved", ... })`).
301
+ */
302
+ async records() {
303
+ const headers = await this.headers();
304
+ const count = await this.rowCount();
305
+ const records = [];
306
+ for (let rowIndex = 0; rowIndex < count; rowIndex++) {
307
+ const values = await this.rowValues(rowIndex);
308
+ const record = {};
309
+ values.forEach((value, columnIndex) => {
310
+ record[headers[columnIndex] ?? `column${columnIndex}`] = value;
311
+ });
312
+ records.push(record);
313
+ }
314
+ return records;
315
+ }
316
+ /** Rows containing `text` anywhere. Still a locator, so assertions on it auto-retry. */
317
+ rowsContaining(text) {
318
+ return this.rows.filter({ hasText: text });
319
+ }
320
+ /**
321
+ * Rows whose cell in a given column matches `value`. Returns a locator (auto-retrying);
322
+ * only the header lookup is awaited up front.
323
+ */
324
+ async rowsByColumn(column, value) {
325
+ const index = typeof column === "number" ? column : await this.columnIndex(column);
326
+ return this.rows.filter({
327
+ has: this.page.locator(`${this.cellSelector}:nth-child(${index + 1})`).filter({ hasText: value })
328
+ });
329
+ }
330
+ /** Waits until the table has at least `min` rows (default 1) — e.g. after a search or load. */
331
+ async waitForRows(min = 1, timeoutMs = 15e3) {
332
+ const deadline = Date.now() + timeoutMs;
333
+ for (; ; ) {
334
+ if (await this.rowCount() >= min) return;
335
+ if (Date.now() >= deadline) {
336
+ throw new AppError(`Table did not reach ${min} row(s) within ${timeoutMs}ms`, {
337
+ code: "TABLE_ROWS_TIMEOUT",
338
+ context: { min, timeoutMs, actual: await this.rowCount() }
339
+ });
340
+ }
341
+ await this.page.waitForTimeout(200);
342
+ }
343
+ }
344
+ };
345
+
346
+ export { BaseComponent, BasePage, TableComponent, exists, fillFields, getAllTexts, getInputValue, getText, safeCheck, safeClick, safeDoubleClick, safeFill, safeSelect, safeType, scrollIntoView, uploadFile, waitForVisible };
347
+ //# sourceMappingURL=index.js.map
348
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/ui/actions.ts","../../src/ui/components/base-page.ts","../../src/ui/components/base-component.ts","../../src/errors/app-error.ts","../../src/ui/components/table.component.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Locator, Page } from \"@playwright/test\";\n\n/**\n * Locator-level helpers shared by `BasePage` and `BaseComponent` (and usable standalone on any\n * locator). Each one waits for the element to be actionable first, so callers don't sprinkle\n * `waitFor` everywhere — Playwright auto-waits for most of this already, but being explicit\n * gives far better failure messages than a bare `click()` timing out mid-action.\n */\n\nexport async function waitForVisible(locator: Locator, timeoutMs?: number): Promise<void> {\n await locator.waitFor({ state: \"visible\", timeout: timeoutMs });\n}\n\nexport async function safeClick(locator: Locator, timeoutMs?: number): Promise<void> {\n await waitForVisible(locator, timeoutMs);\n await locator.click();\n}\n\nexport async function safeDoubleClick(locator: Locator, timeoutMs?: number): Promise<void> {\n await waitForVisible(locator, timeoutMs);\n await locator.dblclick();\n}\n\nexport async function safeFill(locator: Locator, value: string, timeoutMs?: number): Promise<void> {\n await waitForVisible(locator, timeoutMs);\n await locator.fill(value);\n}\n\n/** Clears then types character by character — for inputs with keystroke-driven behavior (autocomplete, masks). */\nexport async function safeType(locator: Locator, value: string, delayMs = 0): Promise<void> {\n await waitForVisible(locator);\n await locator.fill(\"\");\n await locator.pressSequentially(value, { delay: delayMs });\n}\n\nexport async function safeSelect(locator: Locator, value: string | string[]): Promise<void> {\n await waitForVisible(locator);\n await locator.selectOption(value);\n}\n\nexport async function safeCheck(locator: Locator, checked = true): Promise<void> {\n await waitForVisible(locator);\n if (checked) await locator.check();\n else await locator.uncheck();\n}\n\nexport async function uploadFile(locator: Locator, files: string | string[]): Promise<void> {\n await locator.setInputFiles(files);\n}\n\nexport async function scrollIntoView(locator: Locator): Promise<void> {\n await locator.scrollIntoViewIfNeeded();\n}\n\n/** Trimmed visible text of an element (`\"\"` when it has none). */\nexport async function getText(locator: Locator): Promise<string> {\n return (await locator.textContent())?.trim() ?? \"\";\n}\n\n/** Trimmed text of every element the locator matches. */\nexport async function getAllTexts(locator: Locator): Promise<string[]> {\n const texts = await locator.allTextContents();\n return texts.map((text) => text.trim());\n}\n\nexport async function getInputValue(locator: Locator): Promise<string> {\n return locator.inputValue();\n}\n\n/** True when the element exists in the DOM at all (regardless of visibility). */\nexport async function exists(locator: Locator): Promise<boolean> {\n return (await locator.count()) > 0;\n}\n\nexport interface FillFieldsOptions {\n /** Resolve each key into a locator. Defaults to `page.locator(key)`. */\n resolve?: (selector: string) => Locator;\n}\n\n/**\n * Fills a whole form from a `{ selector: value }` map — the single most repeated block in UI\n * tests. Booleans check/uncheck, everything else is filled as text.\n */\nexport async function fillFields(\n page: Page,\n fields: Record<string, string | number | boolean>,\n options: FillFieldsOptions = {},\n): Promise<void> {\n const resolve = options.resolve ?? ((selector: string) => page.locator(selector));\n for (const [selector, value] of Object.entries(fields)) {\n const locator = resolve(selector);\n if (typeof value === \"boolean\") {\n await safeCheck(locator, value);\n } else {\n await safeFill(locator, String(value));\n }\n }\n}\n","import type { Locator, Page, Response } from \"@playwright/test\";\nimport type { Logger } from \"../../logging/logger.js\";\nimport {\n fillFields,\n getAllTexts,\n getText,\n safeCheck,\n safeClick,\n safeFill,\n safeSelect,\n safeType,\n scrollIntoView,\n uploadFile,\n waitForVisible,\n} from \"../actions.js\";\n\nexport interface BasePageOptions {\n page: Page;\n baseUrl?: string;\n logger?: Logger;\n}\n\nexport interface GotoOptions {\n /** Appended as query string, e.g. `{ tab: \"open\" }` -> `?tab=open`. */\n query?: Record<string, string | number | boolean | undefined>;\n /** Path to navigate to instead of the page's own `path` (e.g. a detail page by id). */\n path?: string;\n waitUntil?: \"load\" | \"domcontentloaded\" | \"networkidle\" | \"commit\";\n}\n\n/**\n * Generic Page Object base. Subclasses declare `path` and their own selectors/actions —\n * this base only provides framework-level plumbing (navigation, safe interactions, waits),\n * never project-specific selectors or business logic.\n */\nexport abstract class BasePage {\n protected readonly page: Page;\n protected readonly baseUrl?: string;\n protected readonly logger?: Logger;\n\n abstract readonly path: string;\n\n constructor(options: BasePageOptions) {\n this.page = options.page;\n this.baseUrl = options.baseUrl;\n this.logger = options.logger;\n }\n\n /** Absolute URL for this page, with optional path override and query string. */\n url(options: Pick<GotoOptions, \"path\" | \"query\"> = {}): string {\n const path = options.path ?? this.path;\n const url = this.baseUrl ? new URL(path, this.baseUrl) : new URL(path, \"http://localhost\");\n if (options.query) {\n for (const [key, value] of Object.entries(options.query)) {\n if (value !== undefined) url.searchParams.set(key, String(value));\n }\n }\n return this.baseUrl ? url.toString() : `${url.pathname}${url.search}`;\n }\n\n async goto(options: GotoOptions = {}): Promise<Response | null> {\n const url = this.url(options);\n this.logger?.debug({ url }, \"navigating\");\n return this.page.goto(url, options.waitUntil ? { waitUntil: options.waitUntil } : undefined);\n }\n\n async reload(): Promise<void> {\n await this.page.reload();\n }\n\n currentUrl(): string {\n return this.page.url();\n }\n\n async waitForUrl(url: string | RegExp, timeoutMs?: number): Promise<void> {\n await this.page.waitForURL(url, timeoutMs ? { timeout: timeoutMs } : undefined);\n }\n\n async waitForLoadState(state: \"load\" | \"domcontentloaded\" | \"networkidle\" = \"load\"): Promise<void> {\n await this.page.waitForLoadState(state);\n }\n\n /**\n * Waits for network activity to settle. Prefer waiting on a specific element when you can —\n * `networkidle` is discouraged by Playwright for apps that poll or hold sockets open.\n */\n async waitForNetworkIdle(timeoutMs?: number): Promise<void> {\n await this.page.waitForLoadState(\"networkidle\", timeoutMs ? { timeout: timeoutMs } : undefined);\n }\n\n async pressKey(key: string): Promise<void> {\n await this.page.keyboard.press(key);\n }\n\n /**\n * Screenshot as a buffer. Pair with `attachScreenshot()` from\n * `@emesoft_company/playwright-core/reporting` to put it in the HTML/Allure report.\n */\n async screenshot(fullPage = false): Promise<Buffer> {\n return this.page.screenshot({ fullPage, caret: \"hide\" });\n }\n\n // --- locator helpers, delegated to the shared actions so components behave identically ---\n\n protected waitForVisible(locator: Locator, timeoutMs?: number): Promise<void> {\n return waitForVisible(locator, timeoutMs);\n }\n\n async safeClick(locator: Locator, timeoutMs?: number): Promise<void> {\n await safeClick(locator, timeoutMs);\n }\n\n async safeFill(locator: Locator, value: string): Promise<void> {\n await safeFill(locator, value);\n }\n\n async safeType(locator: Locator, value: string, delayMs?: number): Promise<void> {\n await safeType(locator, value, delayMs);\n }\n\n async safeSelect(locator: Locator, value: string | string[]): Promise<void> {\n await safeSelect(locator, value);\n }\n\n async safeCheck(locator: Locator, checked = true): Promise<void> {\n await safeCheck(locator, checked);\n }\n\n async uploadFile(locator: Locator, files: string | string[]): Promise<void> {\n await uploadFile(locator, files);\n }\n\n async scrollIntoView(locator: Locator): Promise<void> {\n await scrollIntoView(locator);\n }\n\n async getText(locator: Locator): Promise<string> {\n return getText(locator);\n }\n\n async getAllTexts(locator: Locator): Promise<string[]> {\n return getAllTexts(locator);\n }\n\n /** Fills a form from a `{ selector: value }` map. Booleans check/uncheck. */\n async fillFields(fields: Record<string, string | number | boolean>): Promise<void> {\n await fillFields(this.page, fields);\n }\n}\n","import type { Locator, Page } from \"@playwright/test\";\nimport {\n exists,\n fillFields,\n getAllTexts,\n getText,\n safeCheck,\n safeClick,\n safeFill,\n safeSelect,\n scrollIntoView,\n waitForVisible,\n} from \"../actions.js\";\n\nexport interface BaseComponentOptions {\n page: Page;\n /** Locator scoping this component to its root element (e.g. a modal, a nav bar). */\n root: Locator;\n}\n\n/**\n * Generic, reusable wrapper for a UI component scoped to a root locator.\n * Intentionally contains no selectors or business logic — projects extend\n * this for their own components (e.g. `class LoginFormComponent extends BaseComponent`).\n *\n * Every lookup here is scoped to `root`, which is what keeps two instances of the same\n * component on one page (two rows, two modals) from matching each other's elements.\n */\nexport class BaseComponent {\n protected readonly page: Page;\n readonly root: Locator;\n\n constructor(options: BaseComponentOptions) {\n this.page = options.page;\n this.root = options.root;\n }\n\n // --- scoped lookups ---\n\n byTestId(testId: string): Locator {\n return this.root.getByTestId(testId);\n }\n\n byRole(...args: Parameters<Locator[\"getByRole\"]>): Locator {\n return this.root.getByRole(...args);\n }\n\n byText(...args: Parameters<Locator[\"getByText\"]>): Locator {\n return this.root.getByText(...args);\n }\n\n byLabel(...args: Parameters<Locator[\"getByLabel\"]>): Locator {\n return this.root.getByLabel(...args);\n }\n\n locator(selector: string): Locator {\n return this.root.locator(selector);\n }\n\n // --- state ---\n\n async isVisible(): Promise<boolean> {\n return this.root.isVisible();\n }\n\n async exists(): Promise<boolean> {\n return exists(this.root);\n }\n\n async text(): Promise<string> {\n return getText(this.root);\n }\n\n async waitForStable(timeoutMs?: number): Promise<void> {\n await waitForVisible(this.root, timeoutMs);\n }\n\n async waitForHidden(timeoutMs?: number): Promise<void> {\n await this.root.waitFor({ state: \"hidden\", timeout: timeoutMs });\n }\n\n // --- actions, same implementations BasePage uses ---\n\n async safeClick(locator: Locator, timeoutMs?: number): Promise<void> {\n await safeClick(locator, timeoutMs);\n }\n\n async safeFill(locator: Locator, value: string): Promise<void> {\n await safeFill(locator, value);\n }\n\n async safeSelect(locator: Locator, value: string | string[]): Promise<void> {\n await safeSelect(locator, value);\n }\n\n async safeCheck(locator: Locator, checked = true): Promise<void> {\n await safeCheck(locator, checked);\n }\n\n async scrollIntoView(locator: Locator = this.root): Promise<void> {\n await scrollIntoView(locator);\n }\n\n async getText(locator: Locator): Promise<string> {\n return getText(locator);\n }\n\n async getAllTexts(locator: Locator): Promise<string[]> {\n return getAllTexts(locator);\n }\n\n /** Fills fields *within this component*, so selectors don't collide with the rest of the page. */\n async fillFields(fields: Record<string, string | number | boolean>): Promise<void> {\n await fillFields(this.page, fields, { resolve: (selector) => this.root.locator(selector) });\n }\n}\n","export interface AppErrorOptions {\n code: string;\n cause?: unknown;\n context?: Record<string, unknown>;\n}\n\n/**\n * Base error class for the framework. Every error thrown by core carries a\n * machine-readable `code` and optional `context`, so callers can branch on\n * `error.code` instead of parsing messages.\n */\nexport class AppError extends Error {\n readonly code: string;\n readonly context?: Record<string, unknown>;\n\n constructor(message: string, options: AppErrorOptions) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.code = options.code;\n this.context = options.context;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class ConfigValidationError extends AppError {\n constructor(message: string, issues: unknown, context?: Record<string, unknown>) {\n super(message, { code: \"CONFIG_VALIDATION_ERROR\", context: { issues, ...context } });\n }\n}\n\nexport class ApiError extends AppError {\n readonly status?: number;\n readonly method?: string;\n readonly url?: string;\n\n constructor(\n message: string,\n options: AppErrorOptions & { status?: number; method?: string; url?: string },\n ) {\n super(message, options);\n this.status = options.status;\n this.method = options.method;\n this.url = options.url;\n }\n}\n\nexport class AuthError extends AppError {\n constructor(message: string, options: Omit<AppErrorOptions, \"code\"> = {}) {\n super(message, { code: \"AUTH_ERROR\", ...options });\n }\n}\n\nexport class ConfigMissingError extends AppError {\n constructor(name: string) {\n super(`Required environment variable \"${name}\" is not set`, {\n code: \"ENV_VAR_MISSING\",\n context: { name },\n });\n }\n}\n","import type { Locator } from \"@playwright/test\";\nimport { AppError } from \"../../errors/app-error.js\";\nimport { getAllTexts, getText } from \"../actions.js\";\nimport { BaseComponent, type BaseComponentOptions } from \"./base-component.js\";\n\nexport interface TableComponentOptions extends BaseComponentOptions {\n /** Selector for header cells, relative to the table root. Default: `\"thead th\"`. */\n headerSelector?: string;\n /** Selector for data rows, relative to the table root. Default: `\"tbody tr\"`. */\n rowSelector?: string;\n /** Selector for cells, relative to a row. Default: `\"td\"`. */\n cellSelector?: string;\n}\n\n/**\n * Generic data-table/grid wrapper: row counts, cell lookup by column *name* rather than\n * brittle index, and whole-table extraction for assertions.\n *\n * Defaults target a standard `<table><thead><th>/<tbody><tr><td>` layout. For div-based grids\n * pass the three selectors, e.g.\n * `{ headerSelector: '[role=columnheader]', rowSelector: '[role=row]', cellSelector: '[role=gridcell]' }`.\n *\n * Contains no project selectors itself — a project subclasses it (or just constructs it with\n * its own root locator) per grid.\n */\nexport class TableComponent extends BaseComponent {\n private readonly headerSelector: string;\n private readonly rowSelector: string;\n private readonly cellSelector: string;\n\n constructor(options: TableComponentOptions) {\n super(options);\n this.headerSelector = options.headerSelector ?? \"thead th\";\n this.rowSelector = options.rowSelector ?? \"tbody tr\";\n this.cellSelector = options.cellSelector ?? \"td\";\n }\n\n /** All data rows. A live locator — safe to use with auto-retrying assertions. */\n get rows(): Locator {\n return this.root.locator(this.rowSelector);\n }\n\n get headerCells(): Locator {\n return this.root.locator(this.headerSelector);\n }\n\n row(index: number): Locator {\n return this.rows.nth(index);\n }\n\n cells(rowIndex: number): Locator {\n return this.row(rowIndex).locator(this.cellSelector);\n }\n\n async rowCount(): Promise<number> {\n return this.rows.count();\n }\n\n async isEmpty(): Promise<boolean> {\n return (await this.rowCount()) === 0;\n }\n\n /** Trimmed header labels, in order. */\n async headers(): Promise<string[]> {\n return getAllTexts(this.headerCells);\n }\n\n /**\n * Zero-based index of a column by its header label (exact match after trimming, falling back\n * to a case-insensitive contains match). Throws listing the available headers when not found —\n * a renamed column is otherwise a confusing \"cell is empty\" failure.\n */\n async columnIndex(header: string): Promise<number> {\n const headers = await this.headers();\n const exact = headers.indexOf(header.trim());\n if (exact !== -1) return exact;\n\n const needle = header.trim().toLowerCase();\n const loose = headers.findIndex((candidate) => candidate.toLowerCase().includes(needle));\n if (loose !== -1) return loose;\n\n throw new AppError(`Column \"${header}\" not found in table`, {\n code: \"TABLE_COLUMN_NOT_FOUND\",\n context: { requested: header, available: headers },\n });\n }\n\n /** A cell by row index and either a column index or a header label. */\n async cell(rowIndex: number, column: string | number): Promise<Locator> {\n const index = typeof column === \"number\" ? column : await this.columnIndex(column);\n return this.cells(rowIndex).nth(index);\n }\n\n async cellText(rowIndex: number, column: string | number): Promise<string> {\n return getText(await this.cell(rowIndex, column));\n }\n\n /** Every cell value of one row, in column order. */\n async rowValues(rowIndex: number): Promise<string[]> {\n return getAllTexts(this.cells(rowIndex));\n }\n\n /**\n * The whole table as `{ header: cellText }` objects — the most convenient shape for\n * assertions (`expect(await table.records()).toContainEqual({ Status: \"Approved\", ... })`).\n */\n async records(): Promise<Record<string, string>[]> {\n const headers = await this.headers();\n const count = await this.rowCount();\n const records: Record<string, string>[] = [];\n\n for (let rowIndex = 0; rowIndex < count; rowIndex++) {\n const values = await this.rowValues(rowIndex);\n const record: Record<string, string> = {};\n values.forEach((value, columnIndex) => {\n record[headers[columnIndex] ?? `column${columnIndex}`] = value;\n });\n records.push(record);\n }\n return records;\n }\n\n /** Rows containing `text` anywhere. Still a locator, so assertions on it auto-retry. */\n rowsContaining(text: string | RegExp): Locator {\n return this.rows.filter({ hasText: text });\n }\n\n /**\n * Rows whose cell in a given column matches `value`. Returns a locator (auto-retrying);\n * only the header lookup is awaited up front.\n */\n async rowsByColumn(column: string | number, value: string | RegExp): Promise<Locator> {\n const index = typeof column === \"number\" ? column : await this.columnIndex(column);\n return this.rows.filter({\n has: this.page.locator(`${this.cellSelector}:nth-child(${index + 1})`).filter({ hasText: value }),\n });\n }\n\n /** Waits until the table has at least `min` rows (default 1) — e.g. after a search or load. */\n async waitForRows(min = 1, timeoutMs = 15_000): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n if ((await this.rowCount()) >= min) return;\n if (Date.now() >= deadline) {\n throw new AppError(`Table did not reach ${min} row(s) within ${timeoutMs}ms`, {\n code: \"TABLE_ROWS_TIMEOUT\",\n context: { min, timeoutMs, actual: await this.rowCount() },\n });\n }\n await this.page.waitForTimeout(200);\n }\n }\n}\n"]}