pw-core 1.3.0 → 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 +1 -0
- 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
|
@@ -21,3 +21,19 @@
|
|
|
21
21
|
export declare function toRegistryKey(str: string, options?: {
|
|
22
22
|
roleSuffix?: string;
|
|
23
23
|
}): string;
|
|
24
|
+
/**
|
|
25
|
+
* Formats a worker fixture prefix for page keys.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* toWorkerKey('loginPage') => 'workerLoginPage'
|
|
29
|
+
* toWorkerKey('workerLoginPage') => 'workerLoginPage'
|
|
30
|
+
*/
|
|
31
|
+
export declare function toWorkerKey(key: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Strips the worker prefix and lowercases the initial character.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* toUnprefixedPageKey('workerLoginPage') => 'loginPage'
|
|
37
|
+
* toUnprefixedPageKey('loginPage') => 'loginPage'
|
|
38
|
+
*/
|
|
39
|
+
export declare function toUnprefixedPageKey(key: string): string;
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.toRegistryKey = toRegistryKey;
|
|
14
|
+
exports.toWorkerKey = toWorkerKey;
|
|
15
|
+
exports.toUnprefixedPageKey = toUnprefixedPageKey;
|
|
14
16
|
/**
|
|
15
17
|
* Convert an arbitrary string into a clean camelCase registry key.
|
|
16
18
|
*
|
|
@@ -49,10 +51,11 @@ function toRegistryKey(str, options) {
|
|
|
49
51
|
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
50
52
|
})
|
|
51
53
|
.join('');
|
|
52
|
-
// Trim leading digits
|
|
54
|
+
// Trim leading digits and ensure first char is lowercase
|
|
53
55
|
let result = camel.replace(/^\d+/, '');
|
|
54
56
|
if (!result)
|
|
55
57
|
return 'element';
|
|
58
|
+
result = result.charAt(0).toLowerCase() + result.slice(1);
|
|
56
59
|
// Append role suffix if requested (e.g. 'Btn' for buttons)
|
|
57
60
|
if (options?.roleSuffix) {
|
|
58
61
|
const lowerResult = result.toLowerCase();
|
|
@@ -66,3 +69,28 @@ function toRegistryKey(str, options) {
|
|
|
66
69
|
result = result.slice(0, 40);
|
|
67
70
|
return result;
|
|
68
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Formats a worker fixture prefix for page keys.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* toWorkerKey('loginPage') => 'workerLoginPage'
|
|
77
|
+
* toWorkerKey('workerLoginPage') => 'workerLoginPage'
|
|
78
|
+
*/
|
|
79
|
+
function toWorkerKey(key) {
|
|
80
|
+
if (key.startsWith('worker'))
|
|
81
|
+
return key;
|
|
82
|
+
return `worker${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Strips the worker prefix and lowercases the initial character.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* toUnprefixedPageKey('workerLoginPage') => 'loginPage'
|
|
89
|
+
* toUnprefixedPageKey('loginPage') => 'loginPage'
|
|
90
|
+
*/
|
|
91
|
+
function toUnprefixedPageKey(key) {
|
|
92
|
+
if (key.startsWith('worker') && key.length > 6) {
|
|
93
|
+
return key.slice(6).charAt(0).toLowerCase() + key.slice(7);
|
|
94
|
+
}
|
|
95
|
+
return key;
|
|
96
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export { toWorkerKey } from './key-utils';
|
|
2
|
+
/**
|
|
3
|
+
* Recursively searches for registry.ts / registry.js containing `createPageRegistry(`.
|
|
4
|
+
*/
|
|
5
|
+
export declare function findRegistryFile(dir: string): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* Searches upwards or downwards for playwright.config.ts / playwright.config.js.
|
|
8
|
+
*/
|
|
9
|
+
export declare function findPlaywrightConfig(dir: string): string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Parses key-value pairs from a `.env` file in the given directory.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseDotEnv(cwd: string): Record<string, string>;
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the base URL from .env, playwright.config, or env utility files.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getBaseUrl(cwd: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Resolves test directory from playwright.config or defaults to cwd.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getTestDir(cwd: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Finds the next incremental test index in the codegen output directory.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getNextTestIndex(testDir: string): number;
|
|
26
|
+
/**
|
|
27
|
+
* Formats a recording date for comments in generated test files.
|
|
28
|
+
*/
|
|
29
|
+
export declare function formatRecordingDate(date: Date): string;
|
|
@@ -0,0 +1,283 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.toWorkerKey = void 0;
|
|
37
|
+
exports.findRegistryFile = findRegistryFile;
|
|
38
|
+
exports.findPlaywrightConfig = findPlaywrightConfig;
|
|
39
|
+
exports.parseDotEnv = parseDotEnv;
|
|
40
|
+
exports.getBaseUrl = getBaseUrl;
|
|
41
|
+
exports.getTestDir = getTestDir;
|
|
42
|
+
exports.getNextTestIndex = getNextTestIndex;
|
|
43
|
+
exports.formatRecordingDate = formatRecordingDate;
|
|
44
|
+
const fs = __importStar(require("node:fs"));
|
|
45
|
+
const path = __importStar(require("node:path"));
|
|
46
|
+
var key_utils_1 = require("./key-utils");
|
|
47
|
+
Object.defineProperty(exports, "toWorkerKey", { enumerable: true, get: function () { return key_utils_1.toWorkerKey; } });
|
|
48
|
+
/**
|
|
49
|
+
* Recursively searches for registry.ts / registry.js containing `createPageRegistry(`.
|
|
50
|
+
*/
|
|
51
|
+
function findRegistryFile(dir) {
|
|
52
|
+
const queue = [dir];
|
|
53
|
+
while (queue.length > 0) {
|
|
54
|
+
const current = queue.shift();
|
|
55
|
+
try {
|
|
56
|
+
const files = fs.readdirSync(current);
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
const fullPath = path.join(current, file);
|
|
59
|
+
if (file === 'node_modules' || file === '.git' || file === 'dist')
|
|
60
|
+
continue;
|
|
61
|
+
const stat = fs.statSync(fullPath);
|
|
62
|
+
if (stat.isDirectory()) {
|
|
63
|
+
queue.push(fullPath);
|
|
64
|
+
}
|
|
65
|
+
else if (file === 'registry.ts' || file === 'registry.js') {
|
|
66
|
+
try {
|
|
67
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
68
|
+
if (content.includes('createPageRegistry(')) {
|
|
69
|
+
return fullPath;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Ignore unreadable individual files during traversal
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Ignore inaccessible directories during traversal
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
let parent = path.dirname(dir);
|
|
83
|
+
while (parent !== dir) {
|
|
84
|
+
const checkPath = path.join(parent, 'registry.ts');
|
|
85
|
+
if (fs.existsSync(checkPath)) {
|
|
86
|
+
try {
|
|
87
|
+
const content = fs.readFileSync(checkPath, 'utf8');
|
|
88
|
+
if (content.includes('createPageRegistry('))
|
|
89
|
+
return checkPath;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Ignore unreadable registry candidate
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const checkPathSrc = path.join(parent, 'src', 'pages', 'registry.ts');
|
|
96
|
+
if (fs.existsSync(checkPathSrc)) {
|
|
97
|
+
try {
|
|
98
|
+
const content = fs.readFileSync(checkPathSrc, 'utf8');
|
|
99
|
+
if (content.includes('createPageRegistry('))
|
|
100
|
+
return checkPathSrc;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Ignore unreadable registry candidate in src/pages
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
dir = parent;
|
|
107
|
+
parent = path.dirname(dir);
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Searches upwards or downwards for playwright.config.ts / playwright.config.js.
|
|
113
|
+
*/
|
|
114
|
+
function findPlaywrightConfig(dir) {
|
|
115
|
+
const queue = [dir];
|
|
116
|
+
while (queue.length > 0) {
|
|
117
|
+
const current = queue.shift();
|
|
118
|
+
try {
|
|
119
|
+
const files = fs.readdirSync(current);
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
if (file === 'node_modules' || file === '.git' || file === 'dist')
|
|
122
|
+
continue;
|
|
123
|
+
const fullPath = path.join(current, file);
|
|
124
|
+
const stat = fs.statSync(fullPath);
|
|
125
|
+
if (stat.isDirectory()) {
|
|
126
|
+
queue.push(fullPath);
|
|
127
|
+
}
|
|
128
|
+
else if (file === 'playwright.config.ts' || file === 'playwright.config.js') {
|
|
129
|
+
return fullPath;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Ignore inaccessible directories during traversal
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
let parent = path.dirname(dir);
|
|
138
|
+
while (parent !== dir) {
|
|
139
|
+
const tsPath = path.join(parent, 'playwright.config.ts');
|
|
140
|
+
const jsPath = path.join(parent, 'playwright.config.js');
|
|
141
|
+
if (fs.existsSync(tsPath))
|
|
142
|
+
return tsPath;
|
|
143
|
+
if (fs.existsSync(jsPath))
|
|
144
|
+
return jsPath;
|
|
145
|
+
dir = parent;
|
|
146
|
+
parent = path.dirname(dir);
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Parses key-value pairs from a `.env` file in the given directory.
|
|
152
|
+
*/
|
|
153
|
+
function parseDotEnv(cwd) {
|
|
154
|
+
const env = {};
|
|
155
|
+
const envPath = path.join(cwd, '.env');
|
|
156
|
+
if (fs.existsSync(envPath)) {
|
|
157
|
+
try {
|
|
158
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
159
|
+
const lines = content.split(/\r?\n/);
|
|
160
|
+
for (const line of lines) {
|
|
161
|
+
const trimmed = line.trim();
|
|
162
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
163
|
+
continue;
|
|
164
|
+
const index = trimmed.indexOf('=');
|
|
165
|
+
if (index > 0) {
|
|
166
|
+
const key = trimmed.slice(0, index).trim();
|
|
167
|
+
let val = trimmed.slice(index + 1).trim();
|
|
168
|
+
if ((val.startsWith("'") && val.endsWith("'")) || (val.startsWith('"') && val.endsWith('"'))) {
|
|
169
|
+
val = val.slice(1, -1);
|
|
170
|
+
}
|
|
171
|
+
env[key] = val;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Return partial or empty env map if file reading is interrupted
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return env;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Resolves the base URL from .env, playwright.config, or env utility files.
|
|
183
|
+
*/
|
|
184
|
+
function getBaseUrl(cwd) {
|
|
185
|
+
const env = parseDotEnv(cwd);
|
|
186
|
+
const envUrl = process.env.URL || env['URL'];
|
|
187
|
+
if (envUrl) {
|
|
188
|
+
return envUrl;
|
|
189
|
+
}
|
|
190
|
+
let configPath = path.join(cwd, 'playwright.config.ts');
|
|
191
|
+
if (!fs.existsSync(configPath)) {
|
|
192
|
+
configPath = path.join(cwd, 'playwright.config.js');
|
|
193
|
+
}
|
|
194
|
+
if (fs.existsSync(configPath)) {
|
|
195
|
+
try {
|
|
196
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
197
|
+
const stringMatch = content.match(/baseURL:\s*['"`](.*?)['"`]/);
|
|
198
|
+
if (stringMatch && stringMatch[1]) {
|
|
199
|
+
return stringMatch[1];
|
|
200
|
+
}
|
|
201
|
+
if (/baseURL:\s*(?:env|ENV)\.url/.test(content)) {
|
|
202
|
+
let envFilePath = path.join(cwd, 'src', 'utils', 'env.ts');
|
|
203
|
+
if (!fs.existsSync(envFilePath)) {
|
|
204
|
+
envFilePath = path.join(cwd, 'src', 'utils', 'env.js');
|
|
205
|
+
}
|
|
206
|
+
if (fs.existsSync(envFilePath)) {
|
|
207
|
+
const envContent = fs.readFileSync(envFilePath, 'utf8');
|
|
208
|
+
const stringMatches = [...envContent.matchAll(/['"`](https?:\/\/.*?)['"`]/g)];
|
|
209
|
+
if (stringMatches.length > 0) {
|
|
210
|
+
return stringMatches[0][1];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// Fall back to empty string if playwright config is invalid or unreadable
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return '';
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Resolves test directory from playwright.config or defaults to cwd.
|
|
223
|
+
*/
|
|
224
|
+
function getTestDir(cwd) {
|
|
225
|
+
let configPath = path.join(cwd, 'playwright.config.ts');
|
|
226
|
+
if (!fs.existsSync(configPath)) {
|
|
227
|
+
configPath = path.join(cwd, 'playwright.config.js');
|
|
228
|
+
}
|
|
229
|
+
if (fs.existsSync(configPath)) {
|
|
230
|
+
try {
|
|
231
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
232
|
+
const testDirMatch = content.match(/testDir:\s*['"`](.*?)['"`]/);
|
|
233
|
+
if (testDirMatch && testDirMatch[1]) {
|
|
234
|
+
return path.resolve(cwd, testDirMatch[1]);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// Fall back to cwd if testDir parsing fails
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return cwd;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Finds the next incremental test index in the codegen output directory.
|
|
245
|
+
*/
|
|
246
|
+
function getNextTestIndex(testDir) {
|
|
247
|
+
if (!fs.existsSync(testDir))
|
|
248
|
+
return 1;
|
|
249
|
+
try {
|
|
250
|
+
const files = fs.readdirSync(testDir);
|
|
251
|
+
let max = 0;
|
|
252
|
+
for (const file of files) {
|
|
253
|
+
const match = file.match(/^(\d+)\.recorded\.test\.(ts|js)$/);
|
|
254
|
+
if (match) {
|
|
255
|
+
const num = parseInt(match[1], 10);
|
|
256
|
+
if (num > max) {
|
|
257
|
+
max = num;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return max + 1;
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// Default to index 1 if test directory is unreadable
|
|
265
|
+
return 1;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Formats a recording date for comments in generated test files.
|
|
270
|
+
*/
|
|
271
|
+
function formatRecordingDate(date) {
|
|
272
|
+
const day = date.getDate();
|
|
273
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
274
|
+
const month = months[date.getMonth()];
|
|
275
|
+
const year = date.getFullYear();
|
|
276
|
+
let hours = date.getHours();
|
|
277
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
278
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
279
|
+
const ampm = hours >= 12 ? 'PM' : 'AM';
|
|
280
|
+
hours = hours % 12;
|
|
281
|
+
hours = hours ? hours : 12;
|
|
282
|
+
return `${day} ${month} ${year}, ${hours}:${minutes}:${seconds} ${ampm}`;
|
|
283
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type RegistryRoot } from './registry-store';
|
|
2
|
+
import type { SelectorStrategyType } from './types';
|
|
3
|
+
export interface MatchResult {
|
|
4
|
+
pageKey: string;
|
|
5
|
+
elementKey: string;
|
|
6
|
+
val: string;
|
|
7
|
+
type: string;
|
|
8
|
+
isNew: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function toCamelCase(str: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Determines the page key from a URL or title, checking existing registry configs first.
|
|
13
|
+
*/
|
|
14
|
+
export declare function findPageKey(url: string, title: string, registryObj: RegistryRoot, overrideMode?: boolean): string;
|
|
15
|
+
/**
|
|
16
|
+
* Searches a registry dictionary or array for a matching value.
|
|
17
|
+
*/
|
|
18
|
+
export declare function findMatchInDict(val: string, strategyType: string, pageKey: string, registryObj: RegistryRoot, overrideMode?: boolean): MatchResult | null;
|
|
19
|
+
/**
|
|
20
|
+
* Resolves a selector to an element key and page key in the registry.
|
|
21
|
+
*/
|
|
22
|
+
export declare function findElementKey(selector: string, pageKey: string, registryObj: RegistryRoot, overrideMode?: boolean, extra?: {
|
|
23
|
+
targetTestId?: string;
|
|
24
|
+
targetId?: string;
|
|
25
|
+
targetParentId?: string;
|
|
26
|
+
overrideType?: SelectorStrategyType;
|
|
27
|
+
}): MatchResult;
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toCamelCase = toCamelCase;
|
|
4
|
+
exports.findPageKey = findPageKey;
|
|
5
|
+
exports.findMatchInDict = findMatchInDict;
|
|
6
|
+
exports.findElementKey = findElementKey;
|
|
7
|
+
const key_utils_1 = require("./key-utils");
|
|
8
|
+
const selector_parser_1 = require("./selector-parser");
|
|
9
|
+
const registry_store_1 = require("./registry-store");
|
|
10
|
+
function toCamelCase(str) {
|
|
11
|
+
return (0, key_utils_1.toRegistryKey)(str);
|
|
12
|
+
}
|
|
13
|
+
const normalizePageKey = key_utils_1.toUnprefixedPageKey;
|
|
14
|
+
/**
|
|
15
|
+
* Determines the page key from a URL or title, checking existing registry configs first.
|
|
16
|
+
*/
|
|
17
|
+
function findPageKey(url, title, registryObj, overrideMode) {
|
|
18
|
+
let pathname = '';
|
|
19
|
+
let hashPath = '';
|
|
20
|
+
try {
|
|
21
|
+
const u = new URL(url);
|
|
22
|
+
pathname = u.pathname;
|
|
23
|
+
if (u.hash && u.hash.startsWith('#/')) {
|
|
24
|
+
hashPath = u.hash.slice(1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Fall back to raw url string if not a standard absolute URL
|
|
29
|
+
pathname = url || '';
|
|
30
|
+
}
|
|
31
|
+
const cleanPath = (p) => {
|
|
32
|
+
let result = p.split('?')[0].split('#')[0];
|
|
33
|
+
if (result.startsWith('/'))
|
|
34
|
+
result = result.substring(1);
|
|
35
|
+
if (result.endsWith('/'))
|
|
36
|
+
result = result.slice(0, -1);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
const effectivePath = hashPath && hashPath !== '/' && hashPath !== '' ? cleanPath(hashPath) : cleanPath(pathname);
|
|
40
|
+
for (const key of Object.keys(registryObj)) {
|
|
41
|
+
const pageConfig = registryObj[key];
|
|
42
|
+
if (pageConfig && pageConfig.url) {
|
|
43
|
+
let configUrl = pageConfig.url;
|
|
44
|
+
if (configUrl.startsWith('/'))
|
|
45
|
+
configUrl = configUrl.substring(1);
|
|
46
|
+
if (configUrl.endsWith('/'))
|
|
47
|
+
configUrl = configUrl.slice(0, -1);
|
|
48
|
+
if (configUrl === effectivePath) {
|
|
49
|
+
const isCodegen = key.startsWith('codegen_') || key === 'codegenPage';
|
|
50
|
+
if (!overrideMode && !isCodegen)
|
|
51
|
+
continue;
|
|
52
|
+
if (!effectivePath && isCodegen)
|
|
53
|
+
continue;
|
|
54
|
+
return key;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
let keyBase = '';
|
|
59
|
+
if (effectivePath) {
|
|
60
|
+
const segments = effectivePath.split('/').filter(Boolean);
|
|
61
|
+
keyBase = segments
|
|
62
|
+
.map((seg, idx) => {
|
|
63
|
+
const parts = seg.split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
64
|
+
if (parts.length === 0)
|
|
65
|
+
return '';
|
|
66
|
+
return parts
|
|
67
|
+
.map((part, partIdx) => {
|
|
68
|
+
if (idx === 0 && partIdx === 0) {
|
|
69
|
+
return part.charAt(0).toLowerCase() + part.slice(1);
|
|
70
|
+
}
|
|
71
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
72
|
+
})
|
|
73
|
+
.join('');
|
|
74
|
+
})
|
|
75
|
+
.join('');
|
|
76
|
+
}
|
|
77
|
+
if (!keyBase && title) {
|
|
78
|
+
const firstSegment = title.split(/[|\u2014\-:]/)[0].trim();
|
|
79
|
+
const cleanTitle = (firstSegment || title).replace(/[^a-zA-Z0-9\s]/g, '').trim();
|
|
80
|
+
const words = cleanTitle.split(/\s+/).filter(Boolean);
|
|
81
|
+
keyBase = words
|
|
82
|
+
.map((w, idx) => {
|
|
83
|
+
const clean = w.replace(/[^a-zA-Z0-9]/g, '');
|
|
84
|
+
if (idx === 0)
|
|
85
|
+
return clean.charAt(0).toLowerCase() + clean.slice(1);
|
|
86
|
+
return clean.charAt(0).toUpperCase() + clean.slice(1);
|
|
87
|
+
})
|
|
88
|
+
.join('');
|
|
89
|
+
}
|
|
90
|
+
if (!keyBase) {
|
|
91
|
+
keyBase = 'home';
|
|
92
|
+
}
|
|
93
|
+
if (overrideMode) {
|
|
94
|
+
return keyBase;
|
|
95
|
+
}
|
|
96
|
+
return `codegen_${keyBase}`;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Searches a registry dictionary or array for a matching value.
|
|
100
|
+
*/
|
|
101
|
+
function findMatchInDict(val, strategyType, pageKey, registryObj, overrideMode) {
|
|
102
|
+
const getExpandedKey = (dynamicKey, placeholder, value) => {
|
|
103
|
+
const isPurePlaceholder = dynamicKey === `{${placeholder}}`;
|
|
104
|
+
let capValue = '';
|
|
105
|
+
if (isPurePlaceholder) {
|
|
106
|
+
capValue = value;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const words = value.split(/[^a-zA-Z0-9]/).filter(Boolean);
|
|
110
|
+
capValue = words.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
|
|
111
|
+
}
|
|
112
|
+
return dynamicKey.replace(`{${placeholder}}`, capValue);
|
|
113
|
+
};
|
|
114
|
+
let targetPageKey = pageKey;
|
|
115
|
+
for (const k of Object.keys(registryObj)) {
|
|
116
|
+
if (normalizePageKey(k) === normalizePageKey(pageKey)) {
|
|
117
|
+
targetPageKey = k;
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const pageConfig = registryObj[targetPageKey] || {};
|
|
122
|
+
const searchConfig = (config, pk) => {
|
|
123
|
+
if (strategyType === 'testId') {
|
|
124
|
+
const dict = config.testId || config.testIds || {};
|
|
125
|
+
for (const k of Object.keys(dict)) {
|
|
126
|
+
const entry = dict[k];
|
|
127
|
+
if (typeof entry === 'object' && entry !== null) {
|
|
128
|
+
const { matches, matchedValue, placeholderName } = (0, registry_store_1.matchDynamicEntry)(val, entry, true);
|
|
129
|
+
if (matches && matchedValue && placeholderName) {
|
|
130
|
+
const arr = entry[placeholderName] || [];
|
|
131
|
+
if (!arr.includes(matchedValue)) {
|
|
132
|
+
entry[placeholderName] = [...arr, matchedValue];
|
|
133
|
+
}
|
|
134
|
+
const expandedKey = getExpandedKey(k, placeholderName, matchedValue);
|
|
135
|
+
return { pageKey: pk, elementKey: expandedKey, val, type: 'testId', isNew: false };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else if (entry === val) {
|
|
139
|
+
return { pageKey: pk, elementKey: k, val, type: 'testId', isNew: false };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else if (strategyType === 'selector') {
|
|
144
|
+
const dict = config.selector || config.selectors || {};
|
|
145
|
+
for (const k of Object.keys(dict)) {
|
|
146
|
+
const entry = dict[k];
|
|
147
|
+
if (typeof entry === 'object' && entry !== null) {
|
|
148
|
+
const { matches, matchedValue, placeholderName } = (0, registry_store_1.matchDynamicEntry)(val, entry, false);
|
|
149
|
+
if (matches && matchedValue && placeholderName) {
|
|
150
|
+
const arr = entry[placeholderName] || [];
|
|
151
|
+
if (!arr.includes(matchedValue)) {
|
|
152
|
+
entry[placeholderName] = [...arr, matchedValue];
|
|
153
|
+
}
|
|
154
|
+
const expandedKey = getExpandedKey(k, placeholderName, matchedValue);
|
|
155
|
+
return { pageKey: pk, elementKey: expandedKey, val, type: 'selector', isNew: false };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else if (entry === val) {
|
|
159
|
+
return { pageKey: pk, elementKey: k, val, type: 'selector', isNew: false };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
const arr = config[strategyType];
|
|
165
|
+
if (Array.isArray(arr)) {
|
|
166
|
+
if (arr.includes(val)) {
|
|
167
|
+
const elementKey = val;
|
|
168
|
+
return { pageKey: pk, elementKey, val, type: strategyType, isNew: false };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
};
|
|
174
|
+
const targetMatch = searchConfig(pageConfig, targetPageKey);
|
|
175
|
+
if (targetMatch)
|
|
176
|
+
return targetMatch;
|
|
177
|
+
for (const pk of Object.keys(registryObj)) {
|
|
178
|
+
if (normalizePageKey(pk) === normalizePageKey(pageKey))
|
|
179
|
+
continue;
|
|
180
|
+
const isCurrentCodegen = pageKey.startsWith('codegen_') || pageKey === 'codegenPage';
|
|
181
|
+
const isOtherCodegen = pk.startsWith('codegen_') || pk === 'codegenPage';
|
|
182
|
+
if (isCurrentCodegen && isOtherCodegen)
|
|
183
|
+
continue;
|
|
184
|
+
const config = registryObj[pk] || {};
|
|
185
|
+
const match = searchConfig(config, pk);
|
|
186
|
+
if (match)
|
|
187
|
+
return match;
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Resolves a selector to an element key and page key in the registry.
|
|
193
|
+
*/
|
|
194
|
+
function findElementKey(selector, pageKey, registryObj, overrideMode, extra) {
|
|
195
|
+
let { type, val } = (0, selector_parser_1.getSelectorValue)(selector);
|
|
196
|
+
if (extra?.overrideType) {
|
|
197
|
+
type = extra.overrideType;
|
|
198
|
+
}
|
|
199
|
+
// 1. Try matching with the selector itself first
|
|
200
|
+
const selectorMatch = findMatchInDict(val, type, pageKey, registryObj, overrideMode);
|
|
201
|
+
if (selectorMatch)
|
|
202
|
+
return selectorMatch;
|
|
203
|
+
// 1.5. Check if the value exists in ANY other strategy array/object under the page config to avoid duplicates and recreation
|
|
204
|
+
const pageConfig = registryObj[pageKey] || {};
|
|
205
|
+
for (const k of Object.keys(pageConfig)) {
|
|
206
|
+
if (k === 'url')
|
|
207
|
+
continue;
|
|
208
|
+
const match = findMatchInDict(val, k, pageKey, registryObj, overrideMode);
|
|
209
|
+
if (match) {
|
|
210
|
+
return match;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// 2. Try matching targetTestId (if any) against testIds in registry
|
|
214
|
+
if (extra?.targetTestId) {
|
|
215
|
+
const testIdMatch = findMatchInDict(extra.targetTestId, 'testId', pageKey, registryObj, overrideMode);
|
|
216
|
+
if (testIdMatch)
|
|
217
|
+
return testIdMatch;
|
|
218
|
+
}
|
|
219
|
+
// 3. Try matching targetId (if any) against selectors/testIds
|
|
220
|
+
if (extra?.targetId) {
|
|
221
|
+
const idSelectorMatch = findMatchInDict(extra.targetId, 'selector', pageKey, registryObj, overrideMode);
|
|
222
|
+
if (idSelectorMatch)
|
|
223
|
+
return idSelectorMatch;
|
|
224
|
+
const idTestIdMatch = findMatchInDict(extra.targetId, 'testId', pageKey, registryObj, overrideMode);
|
|
225
|
+
if (idTestIdMatch)
|
|
226
|
+
return idTestIdMatch;
|
|
227
|
+
}
|
|
228
|
+
// 4. Try matching targetParentId (if any) against selectors/testIds
|
|
229
|
+
if (extra?.targetParentId) {
|
|
230
|
+
const pIdSelectorMatch = findMatchInDict(extra.targetParentId, 'selector', pageKey, registryObj, overrideMode);
|
|
231
|
+
if (pIdSelectorMatch)
|
|
232
|
+
return pIdSelectorMatch;
|
|
233
|
+
}
|
|
234
|
+
let baseKey = type === 'testId' || type === 'selector' ? toCamelCase(val) : val;
|
|
235
|
+
if (!baseKey)
|
|
236
|
+
baseKey = 'element';
|
|
237
|
+
let finalKey = baseKey;
|
|
238
|
+
let counter = 1;
|
|
239
|
+
const existingKeys = new Set();
|
|
240
|
+
for (const key of Object.keys(pageConfig)) {
|
|
241
|
+
const entry = pageConfig[key];
|
|
242
|
+
if (Array.isArray(entry)) {
|
|
243
|
+
entry.forEach((v) => existingKeys.add(v));
|
|
244
|
+
}
|
|
245
|
+
else if (typeof entry === 'object' && entry !== null) {
|
|
246
|
+
Object.keys(entry).forEach((k) => existingKeys.add(k));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
while (existingKeys.has(finalKey)) {
|
|
250
|
+
finalKey = baseKey + counter;
|
|
251
|
+
counter++;
|
|
252
|
+
}
|
|
253
|
+
return { pageKey, elementKey: finalKey, val, type, isNew: true };
|
|
254
|
+
}
|