pw-core 1.2.1 → 1.3.1
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.
- package/README.md +2 -1
- package/dist/cli.js +145 -421
- package/dist/codegen/action-formatter.d.ts +19 -0
- package/dist/codegen/action-formatter.js +160 -0
- package/dist/codegen/action-processor.d.ts +23 -0
- package/dist/codegen/action-processor.js +112 -0
- package/dist/codegen/floating-panel/client.d.ts +14 -1
- package/dist/codegen/floating-panel/client.js +20 -25
- package/dist/codegen/floating-panel/manager.js +16 -22
- package/dist/codegen/generator/action.validator.d.ts +2 -1
- package/dist/codegen/generator/action.validator.js +2 -1
- package/dist/codegen/generator/candidate-scorer.d.ts +7 -0
- package/dist/codegen/generator/candidate-scorer.js +206 -0
- package/dist/codegen/generator/dom-scanner.d.ts +39 -0
- package/dist/codegen/generator/dom-scanner.js +429 -0
- package/dist/codegen/generator/id-stability.d.ts +6 -0
- package/dist/codegen/generator/id-stability.js +38 -0
- package/dist/codegen/generator/index.d.ts +8 -10
- package/dist/codegen/generator/index.js +23 -678
- package/dist/codegen/generator/key-builder.d.ts +13 -0
- package/dist/codegen/generator/key-builder.js +32 -0
- package/dist/codegen/hover-tracker/client.d.ts +5 -0
- package/dist/codegen/hover-tracker/client.js +14 -15
- package/dist/codegen/hover-tracker/manager.js +2 -6
- package/dist/codegen/index.d.ts +9 -32
- package/dist/codegen/index.js +9 -1128
- package/dist/codegen/key-utils.d.ts +16 -0
- package/dist/codegen/key-utils.js +29 -1
- package/dist/codegen/project-finder.d.ts +29 -0
- package/dist/codegen/project-finder.js +283 -0
- package/dist/codegen/registry-matcher.d.ts +27 -0
- package/dist/codegen/registry-matcher.js +254 -0
- package/dist/codegen/registry-store.d.ts +52 -0
- package/dist/codegen/registry-store.js +652 -0
- package/dist/codegen/selector-parser.d.ts +16 -0
- package/dist/codegen/selector-parser.js +121 -0
- package/dist/codegen/types.d.ts +18 -1
- package/dist/codegen/uniqueness.d.ts +3 -0
- package/dist/codegen/uniqueness.js +15 -0
- package/dist/component/table.d.ts +2 -2
- package/dist/component/table.js +7 -6
- package/dist/index.d.ts +3 -0
- package/dist/index.js +19 -0
- package/dist/page/actions/locator-actions.d.ts +6 -10
- package/dist/page/actions/locator-actions.js +24 -23
- package/dist/page/assertions/verify-chain.d.ts +11 -13
- package/dist/page/assertions/verify-chain.js +26 -7
- package/dist/page/assertions/verify-helpers.d.ts +5 -15
- package/dist/page/config.d.ts +25 -25
- package/dist/page/locators/dynamic-locator-resolver.js +22 -9
- package/dist/page/locators/resolver.d.ts +17 -11
- package/dist/page/locators/resolver.js +84 -78
- package/dist/page/registry.d.ts +35 -35
- package/dist/page/registry.js +86 -82
- package/dist/page/typed-page.d.ts +1 -0
- package/dist/page/typed-page.js +22 -12
- package/dist/page/types/proxy-methods.d.ts +11 -19
- package/dist/page/types/validation.d.ts +1 -1
- package/dist/page/utils/formatter.d.ts +3 -3
- package/dist/page/utils/formatter.js +5 -2
- package/package.json +6 -3
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSelectorValue = getSelectorValue;
|
|
4
|
+
exports.extractNthFromSelector = extractNthFromSelector;
|
|
5
|
+
/**
|
|
6
|
+
* Parses a Playwright or CSS selector string and extracts its semantic strategy type and value.
|
|
7
|
+
*/
|
|
8
|
+
function getSelectorValue(selector) {
|
|
9
|
+
// 1. Try to find data-testid first
|
|
10
|
+
const testIdRegexes = [
|
|
11
|
+
/internal:testid=\[data-testid="([^"]+)"[si]?\]/,
|
|
12
|
+
/data-testid="([^"]+)"[si]?/,
|
|
13
|
+
/\[data-testid="([^"]+)"[si]?\]/,
|
|
14
|
+
/data-testid=([^\[\]\s]+)/
|
|
15
|
+
];
|
|
16
|
+
for (const regex of testIdRegexes) {
|
|
17
|
+
const match = selector.match(regex);
|
|
18
|
+
if (match) {
|
|
19
|
+
let val = match[1];
|
|
20
|
+
val = val.replace(/^["']|["']$/g, '');
|
|
21
|
+
return { type: 'testId', val };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// 2. Playwright getByRole
|
|
25
|
+
const roleRegex = /internal:role=([a-zA-Z0-9_-]+)\[name="([^"]+)"i?\]/;
|
|
26
|
+
const roleMatch = selector.match(roleRegex);
|
|
27
|
+
if (roleMatch) {
|
|
28
|
+
let type = roleMatch[1];
|
|
29
|
+
if (type === 'img')
|
|
30
|
+
type = 'altText';
|
|
31
|
+
return { type: type, val: roleMatch[2] };
|
|
32
|
+
}
|
|
33
|
+
const roleSimpleRegex = /role=([a-zA-Z0-9_-]+)/;
|
|
34
|
+
const roleSimpleMatch = selector.match(roleSimpleRegex);
|
|
35
|
+
if (roleSimpleMatch) {
|
|
36
|
+
return { type: roleSimpleMatch[1], val: roleSimpleMatch[1] };
|
|
37
|
+
}
|
|
38
|
+
// 3. Playwright getByLabel
|
|
39
|
+
const labelRegex = /internal:label="([^"]+)"i?/;
|
|
40
|
+
const labelMatch = selector.match(labelRegex);
|
|
41
|
+
if (labelMatch) {
|
|
42
|
+
return { type: 'label', val: labelMatch[1] };
|
|
43
|
+
}
|
|
44
|
+
// 4. Playwright getByPlaceholder
|
|
45
|
+
const placeholderRegexes = [
|
|
46
|
+
/internal:placeholder="([^"]+)"i?/,
|
|
47
|
+
/internal:attr=\[placeholder="([^"]+)"[si]?\]/
|
|
48
|
+
];
|
|
49
|
+
for (const regex of placeholderRegexes) {
|
|
50
|
+
const placeholderMatch = selector.match(regex);
|
|
51
|
+
if (placeholderMatch) {
|
|
52
|
+
return { type: 'placeholder', val: placeholderMatch[1] };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// 5. Playwright getByAltText
|
|
56
|
+
const altRegexes = [
|
|
57
|
+
/internal:alt="([^"]+)"i?/,
|
|
58
|
+
/internal:attr=\[alt="([^"]+)"[si]?\]/
|
|
59
|
+
];
|
|
60
|
+
for (const regex of altRegexes) {
|
|
61
|
+
const altMatch = selector.match(regex);
|
|
62
|
+
if (altMatch) {
|
|
63
|
+
return { type: 'altText', val: altMatch[1] };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// 6. Playwright getByTitle
|
|
67
|
+
const titleRegexes = [
|
|
68
|
+
/internal:title="([^"]+)"i?/,
|
|
69
|
+
/internal:attr=\[title="([^"]+)"[si]?\]/
|
|
70
|
+
];
|
|
71
|
+
for (const regex of titleRegexes) {
|
|
72
|
+
const titleMatch = selector.match(regex);
|
|
73
|
+
if (titleMatch) {
|
|
74
|
+
return { type: 'title', val: titleMatch[1] };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// 7. Playwright getByText
|
|
78
|
+
const textRegex = /(?:internal:text|text)="([^"]+)"i?/;
|
|
79
|
+
const textMatch = selector.match(textRegex);
|
|
80
|
+
if (textMatch) {
|
|
81
|
+
return { type: 'text', val: textMatch[1] };
|
|
82
|
+
}
|
|
83
|
+
const textSimpleRegex = /(?:internal:text|text)=([^"\s]+)/;
|
|
84
|
+
const textSimpleMatch = selector.match(textSimpleRegex);
|
|
85
|
+
if (textSimpleMatch) {
|
|
86
|
+
return { type: 'text', val: textSimpleMatch[1] };
|
|
87
|
+
}
|
|
88
|
+
// 8. Try to find ID selector
|
|
89
|
+
const idRegexes = [/id=([a-zA-Z0-9_-]+)/, /#([a-zA-Z0-9_-]+)/];
|
|
90
|
+
for (const regex of idRegexes) {
|
|
91
|
+
const match = selector.match(regex);
|
|
92
|
+
if (match) {
|
|
93
|
+
return { type: 'selector', val: `#${match[1]}` };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// 9. Try to find class selectors
|
|
97
|
+
const classRegexes = [/\.([a-zA-Z0-9_-]+)/];
|
|
98
|
+
for (const regex of classRegexes) {
|
|
99
|
+
const match = selector.match(regex);
|
|
100
|
+
if (match) {
|
|
101
|
+
return { type: 'selector', val: `.${match[1]}` };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// 10. Fallback to raw selector
|
|
105
|
+
return { type: 'selector', val: selector };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Extracts any `>> nth=N` or `>> first()` suffix from a Playwright selector.
|
|
109
|
+
*/
|
|
110
|
+
function extractNthFromSelector(selector) {
|
|
111
|
+
const nthMatch = selector.match(/\s*>>\s*nth=(\d+)/i);
|
|
112
|
+
if (nthMatch) {
|
|
113
|
+
const baseSelector = selector.replace(/\s*>>\s*nth=\d+/i, '').trim();
|
|
114
|
+
return { baseSelector, nth: parseInt(nthMatch[1], 10) };
|
|
115
|
+
}
|
|
116
|
+
if (selector.endsWith(' >> first()')) {
|
|
117
|
+
const baseSelector = selector.substring(0, selector.length - ' >> first()'.length).trim();
|
|
118
|
+
return { baseSelector, nth: 0 };
|
|
119
|
+
}
|
|
120
|
+
return { baseSelector: selector };
|
|
121
|
+
}
|
package/dist/codegen/types.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { StrategyType } from '../types/strategies';
|
|
2
|
+
export type { DynamicRegistryEntry, RegistryLocatorDictionary, RegistryPageConfig, RegistryRoot } from './registry-store';
|
|
1
3
|
export interface DOMContext {
|
|
2
4
|
parentText?: string;
|
|
3
5
|
siblingText?: string;
|
|
@@ -6,10 +8,11 @@ export interface DOMContext {
|
|
|
6
8
|
parentAttributes?: Record<string, string>;
|
|
7
9
|
siblingAttributes?: Record<string, string>;
|
|
8
10
|
}
|
|
11
|
+
export type SelectorStrategyType = StrategyType | 'role' | 'ariaLabel' | 'selector' | 'id' | 'class' | 'css' | 'xpath' | 'dataAttribute';
|
|
9
12
|
export interface Candidate {
|
|
10
13
|
selector: string;
|
|
11
14
|
source: 'target' | 'child' | 'sibling' | 'parent' | 'ancestor';
|
|
12
|
-
strategy:
|
|
15
|
+
strategy: SelectorStrategyType;
|
|
13
16
|
depth: number;
|
|
14
17
|
baseScore: number;
|
|
15
18
|
semanticScore: number;
|
|
@@ -19,6 +22,16 @@ export interface Candidate {
|
|
|
19
22
|
totalScore: number;
|
|
20
23
|
unique: boolean;
|
|
21
24
|
valueToScore?: string;
|
|
25
|
+
/** Attribute used to derive a data-attribute candidate. */
|
|
26
|
+
attrName?: string;
|
|
27
|
+
/** ARIA role used to derive a role candidate. */
|
|
28
|
+
roleVal?: string;
|
|
29
|
+
/** Additional score awarded for accessibility-oriented locators. */
|
|
30
|
+
accessibilityBonus?: number;
|
|
31
|
+
/** Additional score awarded for stable locator values. */
|
|
32
|
+
stabilityBonus?: number;
|
|
33
|
+
/** Penalties applied to unstable or non-semantic locator values. */
|
|
34
|
+
penalties?: number;
|
|
22
35
|
}
|
|
23
36
|
export interface LocatorCandidate extends Candidate {
|
|
24
37
|
locator: string;
|
|
@@ -27,6 +40,10 @@ export interface LocatorCandidate extends Candidate {
|
|
|
27
40
|
parentText?: string;
|
|
28
41
|
accessibleName?: string;
|
|
29
42
|
generatedKey?: string;
|
|
43
|
+
/** Stable target metadata consumed by registry matching. */
|
|
44
|
+
targetTestId?: string;
|
|
45
|
+
targetId?: string;
|
|
46
|
+
targetParentId?: string;
|
|
30
47
|
}
|
|
31
48
|
export interface LocatorStrategy {
|
|
32
49
|
name: string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkUniqueness = checkUniqueness;
|
|
4
|
+
async function checkUniqueness(page, candidates) {
|
|
5
|
+
for (const candidate of candidates) {
|
|
6
|
+
try {
|
|
7
|
+
const count = await page.locator(candidate.selector).count();
|
|
8
|
+
candidate.unique = count === 1;
|
|
9
|
+
}
|
|
10
|
+
catch (e) {
|
|
11
|
+
candidate.unique = false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return candidates;
|
|
15
|
+
}
|
|
@@ -3,7 +3,7 @@ import { Locator } from '@playwright/test';
|
|
|
3
3
|
* A custom Array subclass representing a list of table rows.
|
|
4
4
|
* Inherits all standard Array methods while adding type-safe get and getAll capabilities.
|
|
5
5
|
*/
|
|
6
|
-
export declare class TableRows<T extends
|
|
6
|
+
export declare class TableRows<T extends object> extends Array<T> {
|
|
7
7
|
constructor(...items: T[]);
|
|
8
8
|
/**
|
|
9
9
|
* Overloaded get method:
|
|
@@ -20,7 +20,7 @@ export declare class TableRows<T extends Record<string, any>> extends Array<T> {
|
|
|
20
20
|
getAll<K extends keyof T>(key: K): T[K][];
|
|
21
21
|
getAll<K extends keyof T>(key: K, value: T[K]): T[];
|
|
22
22
|
}
|
|
23
|
-
export declare class Table<T extends
|
|
23
|
+
export declare class Table<T extends object> {
|
|
24
24
|
readonly root: Locator;
|
|
25
25
|
constructor(root: Locator);
|
|
26
26
|
/**
|
package/dist/component/table.js
CHANGED
|
@@ -32,6 +32,7 @@ class TableRows extends Array {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
exports.TableRows = TableRows;
|
|
35
|
+
const TABLE_DATA_ROW_SELECTOR = 'tbody tr, tr[data-testid="transaction-row"]';
|
|
35
36
|
class Table {
|
|
36
37
|
root;
|
|
37
38
|
constructor(root) {
|
|
@@ -56,7 +57,7 @@ class Table {
|
|
|
56
57
|
return test_1.test.step('Get table rows', async () => {
|
|
57
58
|
const headers = await this.getHeaders();
|
|
58
59
|
const rowsData = await this.root
|
|
59
|
-
.locator(
|
|
60
|
+
.locator(TABLE_DATA_ROW_SELECTOR)
|
|
60
61
|
.evaluateAll((trs, headers) => {
|
|
61
62
|
return trs.map((tr) => {
|
|
62
63
|
const cells = Array.from(tr.querySelectorAll('td')).map((td) => td.textContent?.trim() || '');
|
|
@@ -71,7 +72,7 @@ class Table {
|
|
|
71
72
|
return rowData;
|
|
72
73
|
});
|
|
73
74
|
}, headers);
|
|
74
|
-
return rowsData.filter((
|
|
75
|
+
return rowsData.filter((row) => row !== null);
|
|
75
76
|
}, { box: true, location: (0, caller_location_1.getCallerLocation)() });
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
@@ -87,7 +88,7 @@ class Table {
|
|
|
87
88
|
*/
|
|
88
89
|
async getRowCount() {
|
|
89
90
|
return test_1.test.step('Get table row count', async () => {
|
|
90
|
-
return await this.root.locator(
|
|
91
|
+
return await this.root.locator(TABLE_DATA_ROW_SELECTOR).evaluateAll((trs) => {
|
|
91
92
|
return trs.filter((tr) => tr.querySelectorAll('td').length > 0).length;
|
|
92
93
|
});
|
|
93
94
|
}, { box: true, location: (0, caller_location_1.getCallerLocation)() });
|
|
@@ -102,15 +103,15 @@ class Table {
|
|
|
102
103
|
if (columnIndex === -1) {
|
|
103
104
|
throw new Error(`Column "${String(column)}" not found in table headers: ${headers.join(', ')}`);
|
|
104
105
|
}
|
|
105
|
-
return await this.root.evaluate((tableEl, { rowIndex, columnIndex }) => {
|
|
106
|
-
const trs = Array.from(tableEl.querySelectorAll(
|
|
106
|
+
return await this.root.evaluate((tableEl, { rowIndex, columnIndex, rowSelector }) => {
|
|
107
|
+
const trs = Array.from(tableEl.querySelectorAll(rowSelector));
|
|
107
108
|
const dataRows = trs.filter((tr) => tr.querySelectorAll('td').length > 0);
|
|
108
109
|
if (rowIndex < 0 || rowIndex >= dataRows.length) {
|
|
109
110
|
throw new Error(`Row index ${rowIndex} is out of bounds (0 to ${dataRows.length - 1})`);
|
|
110
111
|
}
|
|
111
112
|
const cell = dataRows[rowIndex].querySelectorAll('td')[columnIndex];
|
|
112
113
|
return cell?.textContent?.trim() ?? '';
|
|
113
|
-
}, { rowIndex, columnIndex });
|
|
114
|
+
}, { rowIndex, columnIndex, rowSelector: TABLE_DATA_ROW_SELECTOR });
|
|
114
115
|
}, { box: true, location: (0, caller_location_1.getCallerLocation)() });
|
|
115
116
|
}
|
|
116
117
|
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./page/index"), exports);
|
|
18
|
+
__exportStar(require("./component/table"), exports);
|
|
19
|
+
__exportStar(require("./helpers"), exports);
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { Locator } from '@playwright/test';
|
|
2
2
|
import { AllowedMethodKeys } from '../config';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export
|
|
9
|
-
nth?: number;
|
|
10
|
-
hasText?: string | RegExp;
|
|
11
|
-
raw?: boolean;
|
|
12
|
-
}) => Locator, timeout: number | undefined): void;
|
|
3
|
+
import { LocatorResolutionOptions, LocatorTarget } from '../locators/resolver';
|
|
4
|
+
type LocatorResolver = (target: LocatorTarget, options?: LocatorResolutionOptions) => Locator;
|
|
5
|
+
type ActionArguments = unknown[];
|
|
6
|
+
export declare function executeAction(prop: AllowedMethodKeys, resolveLocatorFn: LocatorResolver, timeout: number | undefined, args: ActionArguments): Promise<unknown>;
|
|
7
|
+
export declare function defineActionMethods(instance: object, resolveLocatorFn: LocatorResolver, timeout: number | undefined): void;
|
|
8
|
+
export {};
|
|
@@ -6,31 +6,33 @@ const test_1 = require("@playwright/test");
|
|
|
6
6
|
const config_1 = require("../config");
|
|
7
7
|
const formatter_1 = require("../utils/formatter");
|
|
8
8
|
const caller_location_1 = require("../utils/caller-location");
|
|
9
|
+
function isActionOptions(value) {
|
|
10
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
function getResolutionOptions(value) {
|
|
13
|
+
if (!isActionOptions(value))
|
|
14
|
+
return {};
|
|
15
|
+
return {
|
|
16
|
+
nth: typeof value.nth === 'number' ? value.nth : undefined,
|
|
17
|
+
hasText: typeof value.hasText === 'string' || value.hasText instanceof RegExp ? value.hasText : undefined
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function removeLocatorStrategyOptions(options) {
|
|
21
|
+
const { exact, checked, disabled, expanded, includeHidden, level, pressed, selected, ...playwrightOptions } = options;
|
|
22
|
+
return playwrightOptions;
|
|
23
|
+
}
|
|
9
24
|
function executeAction(prop, resolveLocatorFn, timeout, args) {
|
|
10
25
|
const [locatorKey, ...methodArgs] = args;
|
|
11
|
-
let optNth = undefined;
|
|
12
|
-
let optHasText = undefined;
|
|
13
26
|
const optionsIndex = (0, config_1.getOptionsArgumentIndex)(prop);
|
|
14
|
-
if (optionsIndex !== -1 && methodArgs.length > optionsIndex) {
|
|
15
|
-
const opts = methodArgs[optionsIndex];
|
|
16
|
-
if (opts && typeof opts === 'object') {
|
|
17
|
-
if ('nth' in opts) {
|
|
18
|
-
optNth = opts.nth;
|
|
19
|
-
}
|
|
20
|
-
if ('hasText' in opts) {
|
|
21
|
-
optHasText = opts.hasText;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
const isCount = prop === 'count';
|
|
26
27
|
const userOptions = optionsIndex !== -1 && methodArgs.length > optionsIndex ? methodArgs[optionsIndex] : undefined;
|
|
28
|
+
const resolutionOptions = getResolutionOptions(userOptions);
|
|
29
|
+
const isCount = prop === 'count';
|
|
27
30
|
const locator = resolveLocatorFn(locatorKey, {
|
|
28
|
-
|
|
29
|
-
hasText: optHasText,
|
|
31
|
+
...resolutionOptions,
|
|
30
32
|
raw: isCount,
|
|
31
|
-
...userOptions
|
|
33
|
+
...(isActionOptions(userOptions) ? userOptions : {})
|
|
32
34
|
});
|
|
33
|
-
const method = locator
|
|
35
|
+
const method = Reflect.get(locator, prop);
|
|
34
36
|
if (typeof method !== 'function') {
|
|
35
37
|
throw new Error(`Property '${prop}' does not exist on Locator.`);
|
|
36
38
|
}
|
|
@@ -39,9 +41,8 @@ function executeAction(prop, resolveLocatorFn, timeout, args) {
|
|
|
39
41
|
}
|
|
40
42
|
if (optionsIndex !== -1 && methodArgs.length > optionsIndex) {
|
|
41
43
|
const opts = methodArgs[optionsIndex];
|
|
42
|
-
if (opts
|
|
43
|
-
|
|
44
|
-
methodArgs[optionsIndex] = rest;
|
|
44
|
+
if (isActionOptions(opts)) {
|
|
45
|
+
methodArgs[optionsIndex] = removeLocatorStrategyOptions(opts);
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
if (timeout !== undefined) {
|
|
@@ -56,7 +57,7 @@ function executeAction(prop, resolveLocatorFn, timeout, args) {
|
|
|
56
57
|
const stepName = (0, formatter_1.formatStepDescription)(prop, locatorKey, methodArgs);
|
|
57
58
|
if (prop === 'fill' && methodArgs.length > 1) {
|
|
58
59
|
const opts = methodArgs[1];
|
|
59
|
-
if (opts &&
|
|
60
|
+
if (isActionOptions(opts) && 'mask' in opts) {
|
|
60
61
|
const { mask, ...playwrightOpts } = opts;
|
|
61
62
|
methodArgs[1] = playwrightOpts;
|
|
62
63
|
}
|
|
@@ -64,7 +65,7 @@ function executeAction(prop, resolveLocatorFn, timeout, args) {
|
|
|
64
65
|
let shouldMask = false;
|
|
65
66
|
if (prop === 'fill' && methodArgs.length > 0) {
|
|
66
67
|
const opts = methodArgs[1];
|
|
67
|
-
if (opts &&
|
|
68
|
+
if (isActionOptions(opts) && opts.mask !== undefined) {
|
|
68
69
|
shouldMask = opts.mask === true;
|
|
69
70
|
}
|
|
70
71
|
else {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Locator, expect as playwrightExpect } from '@playwright/test';
|
|
2
2
|
import { PageKeys, GetStrategyOfKey } from '../config';
|
|
3
|
+
import { LocatorResolutionOptions, LocatorTarget } from '../locators/resolver';
|
|
3
4
|
export type VerifyOptions = {
|
|
4
5
|
timeout?: number;
|
|
5
6
|
nth?: number;
|
|
@@ -7,25 +8,25 @@ export type VerifyOptions = {
|
|
|
7
8
|
message?: string;
|
|
8
9
|
};
|
|
9
10
|
type PlaywrightLocatorMatchers = ReturnType<typeof playwrightExpect<Locator>>;
|
|
10
|
-
type ModifyMatcherArgs<Args extends
|
|
11
|
+
type ModifyMatcherArgs<Args extends readonly unknown[]> = Args extends [] ? [options?: {
|
|
11
12
|
nth?: number;
|
|
12
13
|
hasText?: string | RegExp;
|
|
13
14
|
message?: string;
|
|
14
|
-
}] : Args extends [
|
|
15
|
-
|
|
16
|
-
(Exclude<
|
|
15
|
+
}] : Args extends [infer First, (infer Second)?] ? [
|
|
16
|
+
First,
|
|
17
|
+
(Exclude<Second, undefined> & {
|
|
17
18
|
nth?: number;
|
|
18
19
|
hasText?: string | RegExp;
|
|
19
20
|
message?: string;
|
|
20
21
|
})?
|
|
21
|
-
] : Args extends [
|
|
22
|
-
(Exclude<
|
|
22
|
+
] : Args extends [(infer First)?] ? Exclude<First, undefined> extends object ? [
|
|
23
|
+
(Exclude<First, undefined> & {
|
|
23
24
|
nth?: number;
|
|
24
25
|
hasText?: string | RegExp;
|
|
25
26
|
message?: string;
|
|
26
27
|
})?
|
|
27
28
|
] : [
|
|
28
|
-
Exclude<
|
|
29
|
+
Exclude<First, undefined>,
|
|
29
30
|
options?: {
|
|
30
31
|
nth?: number;
|
|
31
32
|
hasText?: string | RegExp;
|
|
@@ -39,7 +40,7 @@ type ModifyMatcherArgs<Args extends any[]> = Args extends [] ? [options?: {
|
|
|
39
40
|
}
|
|
40
41
|
];
|
|
41
42
|
type DynamicallyModifiedMatchers<T, Target> = {
|
|
42
|
-
[K in keyof PlaywrightLocatorMatchers]: K extends 'not' ? Omit<VerifyMatchers<T, Target>, 'not'> : K extends 'toBeChecked' ? Target extends Locator ? (...args: ModifyMatcherArgs<Parameters<PlaywrightLocatorMatchers[K]>>) => Promise<void> : Target extends string ? GetStrategyOfKey<T, Target> extends 'checkbox' | 'radio' ? (...args: ModifyMatcherArgs<Parameters<PlaywrightLocatorMatchers[K]>>) => Promise<void> : never : never : PlaywrightLocatorMatchers[K] extends (...args: infer Args) =>
|
|
43
|
+
[K in keyof PlaywrightLocatorMatchers]: K extends 'not' ? Omit<VerifyMatchers<T, Target>, 'not'> : K extends 'toBeChecked' ? Target extends Locator ? (...args: ModifyMatcherArgs<Parameters<PlaywrightLocatorMatchers[K]>>) => Promise<void> : Target extends string ? GetStrategyOfKey<T, Target> extends 'checkbox' | 'radio' ? (...args: ModifyMatcherArgs<Parameters<PlaywrightLocatorMatchers[K]>>) => Promise<void> : never : never : PlaywrightLocatorMatchers[K] extends (...args: infer Args) => unknown ? (...args: ModifyMatcherArgs<Args>) => Promise<void> : PlaywrightLocatorMatchers[K];
|
|
43
44
|
};
|
|
44
45
|
export type VerifyMatchers<T, Target> = DynamicallyModifiedMatchers<T, Target> & PromiseLike<void> & {
|
|
45
46
|
(options?: Parameters<PlaywrightLocatorMatchers['toBeVisible']>[0] & {
|
|
@@ -73,9 +74,6 @@ export type AssertionsMethod<T> = {
|
|
|
73
74
|
nth?: number;
|
|
74
75
|
}): Locator;
|
|
75
76
|
};
|
|
76
|
-
export
|
|
77
|
-
|
|
78
|
-
hasText?: string | RegExp;
|
|
79
|
-
raw?: boolean;
|
|
80
|
-
}) => Locator, target: PageKeys<T> | Locator, verifyOptions: VerifyOptions | undefined, isSoft: boolean): VerifyMatchers<T, any>;
|
|
77
|
+
export type VerifyResolver = (target: LocatorTarget, options?: LocatorResolutionOptions) => Locator;
|
|
78
|
+
export declare function createVerifyChain<T, Target extends PageKeys<T> | Locator>(resolveLocator: VerifyResolver, target: Target, verifyOptions: VerifyOptions | undefined, isSoft: boolean): VerifyMatchers<T, Target>;
|
|
81
79
|
export {};
|
|
@@ -4,6 +4,21 @@ exports.createVerifyChain = createVerifyChain;
|
|
|
4
4
|
const test_1 = require("@playwright/test");
|
|
5
5
|
const formatter_1 = require("../utils/formatter");
|
|
6
6
|
const caller_location_1 = require("../utils/caller-location");
|
|
7
|
+
function isAssertionOptions(value) {
|
|
8
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof RegExp);
|
|
9
|
+
}
|
|
10
|
+
function getOptionNumber(options, key) {
|
|
11
|
+
const value = options?.[key];
|
|
12
|
+
return typeof value === 'number' ? value : undefined;
|
|
13
|
+
}
|
|
14
|
+
function getOptionText(options) {
|
|
15
|
+
const value = options?.hasText;
|
|
16
|
+
return typeof value === 'string' || value instanceof RegExp ? value : undefined;
|
|
17
|
+
}
|
|
18
|
+
function getOptionMessage(options) {
|
|
19
|
+
const value = options?.message;
|
|
20
|
+
return typeof value === 'string' ? value : undefined;
|
|
21
|
+
}
|
|
7
22
|
function createVerifyChain(resolveLocator, target, verifyOptions, isSoft) {
|
|
8
23
|
const defaultNth = verifyOptions?.nth;
|
|
9
24
|
const defaultHasText = verifyOptions?.hasText;
|
|
@@ -24,7 +39,7 @@ function createVerifyChain(resolveLocator, target, verifyOptions, isSoft) {
|
|
|
24
39
|
return new Proxy(baseFn, {
|
|
25
40
|
get(targetObj, prop) {
|
|
26
41
|
if (typeof prop === 'symbol') {
|
|
27
|
-
return targetObj
|
|
42
|
+
return Reflect.get(targetObj, prop);
|
|
28
43
|
}
|
|
29
44
|
if (prop === 'not') {
|
|
30
45
|
if (isNegated)
|
|
@@ -48,17 +63,17 @@ function createVerifyChain(resolveLocator, target, verifyOptions, isSoft) {
|
|
|
48
63
|
'toLocaleString'
|
|
49
64
|
]);
|
|
50
65
|
if (skippedProps.has(prop)) {
|
|
51
|
-
return targetObj
|
|
66
|
+
return Reflect.get(targetObj, prop);
|
|
52
67
|
}
|
|
53
68
|
return async (...args) => {
|
|
54
69
|
const lastArg = args[args.length - 1];
|
|
55
|
-
const lastIsOptions =
|
|
70
|
+
const lastIsOptions = isAssertionOptions(lastArg);
|
|
56
71
|
const valueArgs = lastIsOptions ? args.slice(0, args.length - 1) : args;
|
|
57
72
|
const options = lastIsOptions ? lastArg : undefined;
|
|
58
|
-
const nth = options
|
|
59
|
-
const hasText = options
|
|
73
|
+
const nth = getOptionNumber(options, 'nth') ?? defaultNth;
|
|
74
|
+
const hasText = getOptionText(options) ?? defaultHasText;
|
|
60
75
|
const isHaveCount = prop === 'toHaveCount';
|
|
61
|
-
const stepName = options
|
|
76
|
+
const stepName = getOptionMessage(options) ?? defaultMessage ?? (0, formatter_1.formatAssertionDescription)(target, prop, isNegated, valueArgs);
|
|
62
77
|
await test_1.test.step(stepName, async () => {
|
|
63
78
|
const locator = resolveLocator(target, {
|
|
64
79
|
nth,
|
|
@@ -67,7 +82,11 @@ function createVerifyChain(resolveLocator, target, verifyOptions, isSoft) {
|
|
|
67
82
|
});
|
|
68
83
|
const expectation = expectFn(locator, stepName);
|
|
69
84
|
const match = isNegated ? expectation.not : expectation;
|
|
70
|
-
|
|
85
|
+
const matcher = Reflect.get(match, prop);
|
|
86
|
+
if (typeof matcher !== 'function') {
|
|
87
|
+
throw new Error(`Matcher '${prop}' does not exist on Playwright expectations.`);
|
|
88
|
+
}
|
|
89
|
+
await matcher.apply(match, args);
|
|
71
90
|
}, { box: true, location: (0, caller_location_1.getCallerLocation)() });
|
|
72
91
|
};
|
|
73
92
|
}
|
|
@@ -1,32 +1,22 @@
|
|
|
1
1
|
import { Locator, expect as playwrightExpect } from '@playwright/test';
|
|
2
|
+
import { VerifyResolver } from './verify-chain';
|
|
3
|
+
import { LocatorTarget } from '../locators/resolver';
|
|
2
4
|
type StepLocation = {
|
|
3
5
|
file: string;
|
|
4
6
|
line: number;
|
|
5
7
|
column: number;
|
|
6
8
|
};
|
|
7
|
-
export declare function verifyHidden(resolveLocator:
|
|
8
|
-
nth?: number;
|
|
9
|
-
hasText?: string | RegExp;
|
|
10
|
-
raw?: boolean;
|
|
11
|
-
}) => Locator, target: any, options?: Parameters<ReturnType<typeof playwrightExpect<Locator>>['toBeHidden']>[0] & {
|
|
9
|
+
export declare function verifyHidden(resolveLocator: VerifyResolver, target: LocatorTarget, options?: Parameters<ReturnType<typeof playwrightExpect<Locator>>['toBeHidden']>[0] & {
|
|
12
10
|
nth?: number;
|
|
13
11
|
hasText?: string | RegExp;
|
|
14
12
|
message?: string;
|
|
15
13
|
}, location?: StepLocation): Promise<void>;
|
|
16
|
-
export declare function verifyEnabled(resolveLocator:
|
|
17
|
-
nth?: number;
|
|
18
|
-
hasText?: string | RegExp;
|
|
19
|
-
raw?: boolean;
|
|
20
|
-
}) => Locator, target: any, options?: Parameters<ReturnType<typeof playwrightExpect<Locator>>['toBeEnabled']>[0] & {
|
|
14
|
+
export declare function verifyEnabled(resolveLocator: VerifyResolver, target: LocatorTarget, options?: Parameters<ReturnType<typeof playwrightExpect<Locator>>['toBeEnabled']>[0] & {
|
|
21
15
|
nth?: number;
|
|
22
16
|
hasText?: string | RegExp;
|
|
23
17
|
message?: string;
|
|
24
18
|
}, location?: StepLocation): Promise<void>;
|
|
25
|
-
export declare function verifyDisabled(resolveLocator:
|
|
26
|
-
nth?: number;
|
|
27
|
-
hasText?: string | RegExp;
|
|
28
|
-
raw?: boolean;
|
|
29
|
-
}) => Locator, target: any, options?: Parameters<ReturnType<typeof playwrightExpect<Locator>>['toBeDisabled']>[0] & {
|
|
19
|
+
export declare function verifyDisabled(resolveLocator: VerifyResolver, target: LocatorTarget, options?: Parameters<ReturnType<typeof playwrightExpect<Locator>>['toBeDisabled']>[0] & {
|
|
30
20
|
nth?: number;
|
|
31
21
|
hasText?: string | RegExp;
|
|
32
22
|
message?: string;
|