pomwright 1.3.0 → 1.5.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.
- package/AGENTS.md +37 -0
- package/CHANGELOG.md +193 -0
- package/README.md +316 -34
- package/dist/index.d.mts +1058 -132
- package/dist/index.d.ts +1058 -132
- package/dist/index.js +2309 -185
- package/dist/index.mjs +2304 -185
- package/docs/{get-locator-methods-explanation.md → v1/get-locator-methods-explanation.md} +0 -16
- package/docs/v1-to-v2-migration/bridge-migration-guide.md +159 -0
- package/docs/v1-to-v2-migration/direct-migration-guide.md +238 -0
- package/docs/v1-to-v2-migration/v1-to-v2-comparison.md +547 -0
- package/docs/v2/PageObject.md +293 -0
- package/docs/v2/composing-locator-modules.md +93 -0
- package/docs/v2/locator-registry.md +693 -0
- package/docs/v2/logging.md +168 -0
- package/docs/v2/overview.md +515 -0
- package/docs/v2/session-storage.md +160 -0
- package/index.ts +61 -9
- package/intTestV2/.env +0 -0
- package/intTestV2/fixtures/testApp.fixtures.ts +43 -0
- package/intTestV2/package.json +22 -0
- package/intTestV2/page-object-models/testApp/pages/iframe/iframe.locatorSchema.ts +24 -0
- package/intTestV2/page-object-models/testApp/pages/iframe/iframe.page.ts +17 -0
- package/intTestV2/page-object-models/testApp/pages/testPage.locatorSchema.ts +32 -0
- package/intTestV2/page-object-models/testApp/pages/testPage.page.ts +119 -0
- package/intTestV2/page-object-models/testApp/pages/testPath/[color]/color.locatorSchema.ts +29 -0
- package/intTestV2/page-object-models/testApp/pages/testPath/[color]/color.page.ts +48 -0
- package/intTestV2/page-object-models/testApp/pages/testPath/testPath.locatorSchema.ts +9 -0
- package/intTestV2/page-object-models/testApp/pages/testPath/testPath.page.ts +23 -0
- package/intTestV2/page-object-models/testApp/pages/testfilters/testfilters.locatorSchema.ts +114 -0
- package/intTestV2/page-object-models/testApp/pages/testfilters/testfilters.page.ts +23 -0
- package/intTestV2/page-object-models/testApp/testApp.base.ts +20 -0
- package/intTestV2/playwright.config.ts +54 -0
- package/intTestV2/server.js +216 -0
- package/intTestV2/test-data/staticPage/index.html +280 -0
- package/intTestV2/test-data/staticPage/w3images/avatar2.png +0 -0
- package/intTestV2/test-data/staticPage/w3images/avatar3.png +0 -0
- package/intTestV2/test-data/staticPage/w3images/avatar5.png +0 -0
- package/intTestV2/test-data/staticPage/w3images/avatar6.png +0 -0
- package/intTestV2/test-data/staticPage/w3images/forest.jpg +0 -0
- package/intTestV2/test-data/staticPage/w3images/lights.jpg +0 -0
- package/intTestV2/test-data/staticPage/w3images/mountains.jpg +0 -0
- package/intTestV2/test-data/staticPage/w3images/nature.jpg +0 -0
- package/intTestV2/test-data/staticPage/w3images/snow.jpg +0 -0
- package/intTestV2/tests/locatorRegistry/add/add.describe.spec.ts +54 -0
- package/intTestV2/tests/locatorRegistry/add/add.filter.spec.ts +143 -0
- package/intTestV2/tests/locatorRegistry/add/add.frameLocator.spec.ts +23 -0
- package/intTestV2/tests/locatorRegistry/add/add.getByAltText.spec.ts +23 -0
- package/intTestV2/tests/locatorRegistry/add/add.getById.spec.ts +45 -0
- package/intTestV2/tests/locatorRegistry/add/add.getByLabel.spec.ts +23 -0
- package/intTestV2/tests/locatorRegistry/add/add.getByPlaceholder.spec.ts +23 -0
- package/intTestV2/tests/locatorRegistry/add/add.getByRole.spec.ts +23 -0
- package/intTestV2/tests/locatorRegistry/add/add.getByTestId.spec.ts +23 -0
- package/intTestV2/tests/locatorRegistry/add/add.getByText.spec.ts +23 -0
- package/intTestV2/tests/locatorRegistry/add/add.getByTitle.spec.ts +23 -0
- package/intTestV2/tests/locatorRegistry/add/add.locator.spec.ts +23 -0
- package/intTestV2/tests/locatorRegistry/add/add.reuseExisting.spec.ts +66 -0
- package/intTestV2/tests/locatorRegistry/add/add.reuseReusable.spec.ts +311 -0
- package/intTestV2/tests/locatorRegistry/add/add.spec.ts +159 -0
- package/intTestV2/tests/locatorRegistry/filter.cycle.spec.ts +39 -0
- package/intTestV2/tests/locatorRegistry/getLocator/getLocator.spec.ts +253 -0
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.clearSteps.spec.ts +105 -0
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.describe.spec.ts +23 -0
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.filter.spec.ts +368 -0
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.getLocator.spec.ts +56 -0
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.getNestedLocator.spec.ts +175 -0
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.nth.spec.ts +60 -0
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.remove.spec.ts +32 -0
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.replace.spec.ts +24 -0
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.spec.ts +110 -0
- package/intTestV2/tests/locatorRegistry/getLocatorSchema/getLocatorSchema.update.spec.ts +322 -0
- package/intTestV2/tests/locatorRegistry/getNestedLocator/getNestedLocator.spec.ts +412 -0
- package/intTestV2/tests/locatorRegistry/registry/registry.binding.spec.ts +50 -0
- package/intTestV2/tests/locatorRegistry/validation/validation.locatorSchemaPath.spec.ts +115 -0
- package/intTestV2/tests/locatorRegistry/validation/validation.sub-path.spec.ts +45 -0
- package/intTestV2/tests/step/step.spec.ts +49 -0
- package/intTestV2/tests/testApp/color.spec.ts +15 -0
- package/intTestV2/tests/testApp/iframe.spec.ts +57 -0
- package/intTestV2/tests/testApp/testFilters.spec.ts +24 -0
- package/intTestV2/tests/testApp/testPage.spec.ts +161 -0
- package/intTestV2/tests/testApp/testPath.spec.ts +18 -0
- package/pack-build.sh +11 -0
- package/pack-test-v2.sh +36 -0
- package/package.json +10 -3
- package/playwright.base.ts +42 -0
- package/skills/README.md +56 -0
- package/skills/pomwright-v1-5-bridge-migration/SKILL.md +40 -0
- package/skills/pomwright-v1-5-bridge-migration/references/call-site-migration.md +178 -0
- package/skills/pomwright-v1-5-bridge-migration/references/schema-translation.md +183 -0
- package/skills/pomwright-v2-migration/SKILL.md +63 -0
- package/skills/pomwright-v2-migration/references/call-site-migration.md +265 -0
- package/skills/pomwright-v2-migration/references/class-migration.md +266 -0
- package/skills/pomwright-v2-migration/references/fixture-and-helpers.md +423 -0
- package/skills/pomwright-v2-migration/references/locator-registration.md +344 -0
- package/srcV2/fixture/base.fixtures.ts +23 -0
- package/srcV2/helpers/navigation.ts +153 -0
- package/srcV2/helpers/playwrightReportLogger.ts +196 -0
- package/srcV2/helpers/sessionStorage.ts +251 -0
- package/srcV2/helpers/stepDecorator.ts +106 -0
- package/srcV2/locators/index.ts +15 -0
- package/srcV2/locators/locatorQueryBuilder.ts +427 -0
- package/srcV2/locators/locatorRegistrationBuilder.ts +558 -0
- package/srcV2/locators/locatorRegistry.ts +541 -0
- package/srcV2/locators/locatorUpdateBuilder.ts +602 -0
- package/srcV2/locators/reusableLocatorBuilder.ts +200 -0
- package/srcV2/locators/types.ts +256 -0
- package/srcV2/locators/utils.ts +309 -0
- package/srcV2/locators/v1SchemaTranslator.ts +178 -0
- package/srcV2/pageObject.ts +105 -0
- /package/docs/{BaseApi-explanation.md → v1/BaseApi-explanation.md} +0 -0
- /package/docs/{BasePage-explanation.md → v1/BasePage-explanation.md} +0 -0
- /package/docs/{LocatorSchema-explanation.md → v1/LocatorSchema-explanation.md} +0 -0
- /package/docs/{LocatorSchemaPath-explanation.md → v1/LocatorSchemaPath-explanation.md} +0 -0
- /package/docs/{PlaywrightReportLogger-explanation.md → v1/PlaywrightReportLogger-explanation.md} +0 -0
- /package/docs/{intro-to-using-pomwright.md → v1/intro-to-using-pomwright.md} +0 -0
- /package/docs/{sessionStorage-methods-explanation.md → v1/sessionStorage-methods-explanation.md} +0 -0
- /package/docs/{tips-folder-structure.md → v1/tips-folder-structure.md} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
// src/basePage.ts
|
|
2
|
-
import { selectors } from "@playwright/test";
|
|
3
|
-
|
|
4
|
-
// src/helpers/getLocatorBase.ts
|
|
5
|
-
import { test } from "@playwright/test";
|
|
6
|
-
|
|
7
1
|
// src/helpers/locatorSchema.interface.ts
|
|
8
2
|
var GetByMethod = /* @__PURE__ */ ((GetByMethod2) => {
|
|
9
3
|
GetByMethod2["role"] = "role";
|
|
@@ -76,6 +70,414 @@ function getLocatorSchemaDummy() {
|
|
|
76
70
|
return locatorSchemaDummy;
|
|
77
71
|
}
|
|
78
72
|
|
|
73
|
+
// src/helpers/deprecationWarnings.ts
|
|
74
|
+
var warnedDeprecationsByScope = /* @__PURE__ */ new WeakMap();
|
|
75
|
+
var getWarningScope = (logger) => {
|
|
76
|
+
if (!logger) {
|
|
77
|
+
return globalThis;
|
|
78
|
+
}
|
|
79
|
+
const maybeSharedLogEntries = logger.sharedLogEntry;
|
|
80
|
+
if (Array.isArray(maybeSharedLogEntries)) {
|
|
81
|
+
return maybeSharedLogEntries;
|
|
82
|
+
}
|
|
83
|
+
return logger;
|
|
84
|
+
};
|
|
85
|
+
var warnDeprecationOncePerTest = (key, message, logger) => {
|
|
86
|
+
const warningScope = getWarningScope(logger);
|
|
87
|
+
const warnedDeprecations = warnedDeprecationsByScope.get(warningScope) ?? /* @__PURE__ */ new Set();
|
|
88
|
+
if (warnedDeprecations.has(key)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
warnedDeprecations.add(key);
|
|
92
|
+
warnedDeprecationsByScope.set(warningScope, warnedDeprecations);
|
|
93
|
+
logger?.warn(message);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/api/baseApi.ts
|
|
97
|
+
var BaseApi = class {
|
|
98
|
+
baseUrl;
|
|
99
|
+
apiName;
|
|
100
|
+
log;
|
|
101
|
+
request;
|
|
102
|
+
constructor(baseUrl, apiName, context, pwrl) {
|
|
103
|
+
this.baseUrl = baseUrl;
|
|
104
|
+
this.apiName = apiName;
|
|
105
|
+
this.log = pwrl.getNewChildLogger(apiName);
|
|
106
|
+
this.request = context;
|
|
107
|
+
const classDeprecationMessage = "[POMWright] BaseApi is depricated and will be removed in 2.0.0 with no replacement. If you need a base API class, you can use the v1 pomwright/src/api/baseApi.ts implementation for reference to implement your own.";
|
|
108
|
+
warnDeprecationOncePerTest(`${this.constructor.name}-class-deprecation`, classDeprecationMessage, this.log);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/basePage.ts
|
|
113
|
+
import { selectors } from "@playwright/test";
|
|
114
|
+
|
|
115
|
+
// src/helpers/getLocatorBase.ts
|
|
116
|
+
import { test } from "@playwright/test";
|
|
117
|
+
|
|
118
|
+
// srcV2/locators/utils.ts
|
|
119
|
+
var formatLocatorSchemaPathForError = (path) => {
|
|
120
|
+
const json = JSON.stringify(path);
|
|
121
|
+
return json.slice(1, -1);
|
|
122
|
+
};
|
|
123
|
+
var RUNTIME_WHITESPACE_REGEX = /[\s\u0085]/u;
|
|
124
|
+
var validateLocatorSchemaPath = (path) => {
|
|
125
|
+
if (!path) {
|
|
126
|
+
throw new Error("LocatorSchemaPath string cannot be empty");
|
|
127
|
+
}
|
|
128
|
+
if (RUNTIME_WHITESPACE_REGEX.test(path)) {
|
|
129
|
+
const escaped = formatLocatorSchemaPathForError(path);
|
|
130
|
+
throw new Error(`LocatorSchemaPath string cannot contain whitespace chars: ${escaped}`);
|
|
131
|
+
}
|
|
132
|
+
if (path.startsWith(".")) {
|
|
133
|
+
throw new Error(`LocatorSchemaPath string cannot start with a dot: ${path}`);
|
|
134
|
+
}
|
|
135
|
+
if (path.endsWith(".")) {
|
|
136
|
+
throw new Error(`LocatorSchemaPath string cannot end with a dot: ${path}`);
|
|
137
|
+
}
|
|
138
|
+
if (path.includes("..")) {
|
|
139
|
+
throw new Error(`LocatorSchemaPath string cannot contain consecutive dots: ${path}`);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
var expandSchemaPath = (path) => {
|
|
143
|
+
validateLocatorSchemaPath(path);
|
|
144
|
+
const parts = path.split(".");
|
|
145
|
+
return parts.map((_part, index) => parts.slice(0, index + 1).join("."));
|
|
146
|
+
};
|
|
147
|
+
var cssEscape = (value) => {
|
|
148
|
+
return value.replace(/([\\"'#.:;,?*+<>{}[\\]()])/g, "\\$1");
|
|
149
|
+
};
|
|
150
|
+
var normalizeSteps = (steps) => steps ? steps.map((step2) => ({ ...step2 })) : [];
|
|
151
|
+
function normalizeIdValue(id) {
|
|
152
|
+
if (typeof id !== "string") {
|
|
153
|
+
return id;
|
|
154
|
+
}
|
|
155
|
+
if (id.startsWith("#")) {
|
|
156
|
+
return id.slice(1);
|
|
157
|
+
}
|
|
158
|
+
if (id.startsWith("id=")) {
|
|
159
|
+
return id.slice("id=".length);
|
|
160
|
+
}
|
|
161
|
+
return id;
|
|
162
|
+
}
|
|
163
|
+
var stringifyForLog = (value) => {
|
|
164
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
165
|
+
return JSON.stringify(
|
|
166
|
+
value,
|
|
167
|
+
(_key, current) => {
|
|
168
|
+
if (typeof current === "object" && current !== null) {
|
|
169
|
+
if (seen.has(current)) {
|
|
170
|
+
return "[Circular]";
|
|
171
|
+
}
|
|
172
|
+
seen.add(current);
|
|
173
|
+
}
|
|
174
|
+
if (current instanceof RegExp) {
|
|
175
|
+
return { type: "RegExp", source: current.source, flags: current.flags };
|
|
176
|
+
}
|
|
177
|
+
return current;
|
|
178
|
+
},
|
|
179
|
+
2
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
var applyIndexSelector = (locator, selector) => {
|
|
183
|
+
if (selector === void 0 || selector === null) {
|
|
184
|
+
return locator;
|
|
185
|
+
}
|
|
186
|
+
if (selector === "first") {
|
|
187
|
+
return locator.first();
|
|
188
|
+
}
|
|
189
|
+
if (selector === "last") {
|
|
190
|
+
return locator.last();
|
|
191
|
+
}
|
|
192
|
+
return locator.nth(selector);
|
|
193
|
+
};
|
|
194
|
+
var createLocator = (target, definition) => {
|
|
195
|
+
switch (definition.type) {
|
|
196
|
+
case "role":
|
|
197
|
+
return target.getByRole(definition.role, definition.options);
|
|
198
|
+
case "text":
|
|
199
|
+
return target.getByText(definition.text, definition.options);
|
|
200
|
+
case "label":
|
|
201
|
+
return target.getByLabel(definition.text, definition.options);
|
|
202
|
+
case "placeholder":
|
|
203
|
+
return target.getByPlaceholder(definition.text, definition.options);
|
|
204
|
+
case "altText":
|
|
205
|
+
return target.getByAltText(definition.text, definition.options);
|
|
206
|
+
case "title":
|
|
207
|
+
return target.getByTitle(definition.text, definition.options);
|
|
208
|
+
case "locator":
|
|
209
|
+
return target.locator(definition.selector, definition.options);
|
|
210
|
+
case "frameLocator":
|
|
211
|
+
return target.frameLocator(definition.selector);
|
|
212
|
+
case "testId":
|
|
213
|
+
return target.getByTestId(definition.testId);
|
|
214
|
+
case "id": {
|
|
215
|
+
if (typeof definition.id === "string") {
|
|
216
|
+
const normalized = normalizeIdValue(definition.id);
|
|
217
|
+
return target.locator(`#${cssEscape(normalized ?? "")}`);
|
|
218
|
+
}
|
|
219
|
+
const pattern = definition.id.source;
|
|
220
|
+
const safePattern = cssEscape(pattern);
|
|
221
|
+
return target.locator(`[id*="${safePattern}"]`);
|
|
222
|
+
}
|
|
223
|
+
default: {
|
|
224
|
+
const exhaustive = definition;
|
|
225
|
+
return exhaustive;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
var cloneLocatorStrategyDefinition = (definition) => {
|
|
230
|
+
switch (definition.type) {
|
|
231
|
+
case "role":
|
|
232
|
+
return {
|
|
233
|
+
type: "role",
|
|
234
|
+
role: definition.role,
|
|
235
|
+
...definition.options ? { options: { ...definition.options } } : {}
|
|
236
|
+
};
|
|
237
|
+
case "text":
|
|
238
|
+
return {
|
|
239
|
+
type: "text",
|
|
240
|
+
text: definition.text,
|
|
241
|
+
...definition.options ? { options: { ...definition.options } } : {}
|
|
242
|
+
};
|
|
243
|
+
case "label":
|
|
244
|
+
return {
|
|
245
|
+
type: "label",
|
|
246
|
+
text: definition.text,
|
|
247
|
+
...definition.options ? { options: { ...definition.options } } : {}
|
|
248
|
+
};
|
|
249
|
+
case "placeholder":
|
|
250
|
+
return {
|
|
251
|
+
type: "placeholder",
|
|
252
|
+
text: definition.text,
|
|
253
|
+
...definition.options ? { options: { ...definition.options } } : {}
|
|
254
|
+
};
|
|
255
|
+
case "altText":
|
|
256
|
+
return {
|
|
257
|
+
type: "altText",
|
|
258
|
+
text: definition.text,
|
|
259
|
+
...definition.options ? { options: { ...definition.options } } : {}
|
|
260
|
+
};
|
|
261
|
+
case "title":
|
|
262
|
+
return {
|
|
263
|
+
type: "title",
|
|
264
|
+
text: definition.text,
|
|
265
|
+
...definition.options ? { options: { ...definition.options } } : {}
|
|
266
|
+
};
|
|
267
|
+
case "locator":
|
|
268
|
+
return {
|
|
269
|
+
type: "locator",
|
|
270
|
+
selector: definition.selector,
|
|
271
|
+
...definition.options ? { options: { ...definition.options } } : {}
|
|
272
|
+
};
|
|
273
|
+
case "frameLocator":
|
|
274
|
+
return { type: "frameLocator", selector: definition.selector };
|
|
275
|
+
case "testId":
|
|
276
|
+
return { type: "testId", testId: definition.testId };
|
|
277
|
+
case "id":
|
|
278
|
+
return { type: "id", id: definition.id };
|
|
279
|
+
default: {
|
|
280
|
+
const exhaustive = definition;
|
|
281
|
+
return exhaustive;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
var applyDefinitionPatch = (seed, patch) => {
|
|
286
|
+
const base2 = cloneLocatorStrategyDefinition(seed);
|
|
287
|
+
switch (patch.type) {
|
|
288
|
+
case "locator": {
|
|
289
|
+
const selector = patch.selector !== void 0 ? patch.selector : base2.selector;
|
|
290
|
+
const options = patch.options || base2.options ? { ...base2.options, ...patch.options } : void 0;
|
|
291
|
+
return { type: "locator", selector, ...options ? { options } : {} };
|
|
292
|
+
}
|
|
293
|
+
case "role": {
|
|
294
|
+
const role = patch.role ?? base2.role;
|
|
295
|
+
const options = patch.options || base2.options ? { ...base2.options, ...patch.options } : void 0;
|
|
296
|
+
return { type: "role", role, ...options ? { options } : {} };
|
|
297
|
+
}
|
|
298
|
+
case "text": {
|
|
299
|
+
const text = patch.text ?? base2.text;
|
|
300
|
+
const options = patch.options || base2.options ? { ...base2.options, ...patch.options } : void 0;
|
|
301
|
+
return { type: "text", text, ...options ? { options } : {} };
|
|
302
|
+
}
|
|
303
|
+
case "label": {
|
|
304
|
+
const text = patch.text ?? base2.text;
|
|
305
|
+
const options = patch.options || base2.options ? { ...base2.options, ...patch.options } : void 0;
|
|
306
|
+
return { type: "label", text, ...options ? { options } : {} };
|
|
307
|
+
}
|
|
308
|
+
case "placeholder": {
|
|
309
|
+
const text = patch.text ?? base2.text;
|
|
310
|
+
const options = patch.options || base2.options ? { ...base2.options, ...patch.options } : void 0;
|
|
311
|
+
return { type: "placeholder", text, ...options ? { options } : {} };
|
|
312
|
+
}
|
|
313
|
+
case "altText": {
|
|
314
|
+
const text = patch.text ?? base2.text;
|
|
315
|
+
const options = patch.options || base2.options ? { ...base2.options, ...patch.options } : void 0;
|
|
316
|
+
return { type: "altText", text, ...options ? { options } : {} };
|
|
317
|
+
}
|
|
318
|
+
case "title": {
|
|
319
|
+
const text = patch.text ?? base2.text;
|
|
320
|
+
const options = patch.options || base2.options ? { ...base2.options, ...patch.options } : void 0;
|
|
321
|
+
return { type: "title", text, ...options ? { options } : {} };
|
|
322
|
+
}
|
|
323
|
+
case "frameLocator": {
|
|
324
|
+
const selector = patch.selector !== void 0 ? patch.selector : base2.selector;
|
|
325
|
+
return { type: "frameLocator", selector };
|
|
326
|
+
}
|
|
327
|
+
case "testId": {
|
|
328
|
+
const testId = patch.testId !== void 0 ? patch.testId : base2.testId;
|
|
329
|
+
return { type: "testId", testId };
|
|
330
|
+
}
|
|
331
|
+
case "id": {
|
|
332
|
+
const id = patch.id !== void 0 ? normalizeIdValue(patch.id) ?? base2.id : base2.id;
|
|
333
|
+
return { type: "id", id };
|
|
334
|
+
}
|
|
335
|
+
default: {
|
|
336
|
+
const exhaustive = patch;
|
|
337
|
+
return exhaustive;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
var isFrameLocatorDefinition = (definition) => definition.type === "frameLocator";
|
|
342
|
+
var isLocatorInstance = (value) => {
|
|
343
|
+
return !!value && typeof value === "object" && typeof value.filter === "function";
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// srcV2/locators/v1SchemaTranslator.ts
|
|
347
|
+
var getRegistryLookup = (registry) => registry;
|
|
348
|
+
var logMissingDefinition = (path, field) => {
|
|
349
|
+
console.warn(
|
|
350
|
+
`[POMWright] Skipping v2 translation for "${path}" because "${field}" is missing. Rewrite this locator in defineLocators() using the v2 registry.`
|
|
351
|
+
);
|
|
352
|
+
};
|
|
353
|
+
var logLocatorInstanceWarning = (path) => {
|
|
354
|
+
console.warn(
|
|
355
|
+
`[POMWright] Skipping v2 translation for "${path}" because v1 LocatorSchema.locator is a Locator instance. Rewrite this path in defineLocators() to avoid runtime gaps during migration.`
|
|
356
|
+
);
|
|
357
|
+
};
|
|
358
|
+
var addV1SchemaToV2Registry = (registry, locatorSchema) => {
|
|
359
|
+
const path = locatorSchema.locatorSchemaPath;
|
|
360
|
+
validateLocatorSchemaPath(path);
|
|
361
|
+
const registryWithLookup = getRegistryLookup(registry);
|
|
362
|
+
const existing = registryWithLookup.getIfExists?.(path);
|
|
363
|
+
if (existing) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
console.info(
|
|
367
|
+
`[POMWright] LocatorSchemaPath "${path}" is not registered in the v2 registry. Translating and adding v1 schema to v2 Locator Registry; update this path to use registry.add in defineLocators().`
|
|
368
|
+
);
|
|
369
|
+
const registration = registry.add(path);
|
|
370
|
+
if (!registration) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
let postDefinition = null;
|
|
374
|
+
switch (locatorSchema.locatorMethod) {
|
|
375
|
+
case "role" /* role */: {
|
|
376
|
+
if (!locatorSchema.role) {
|
|
377
|
+
logMissingDefinition(path, "role");
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
postDefinition = registration.getByRole(locatorSchema.role, locatorSchema.roleOptions);
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
case "text" /* text */: {
|
|
384
|
+
if (!locatorSchema.text) {
|
|
385
|
+
logMissingDefinition(path, "text");
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
postDefinition = registration.getByText(locatorSchema.text, locatorSchema.textOptions);
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
case "label" /* label */: {
|
|
392
|
+
if (!locatorSchema.label) {
|
|
393
|
+
logMissingDefinition(path, "label");
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
postDefinition = registration.getByLabel(locatorSchema.label, locatorSchema.labelOptions);
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
case "placeholder" /* placeholder */: {
|
|
400
|
+
if (!locatorSchema.placeholder) {
|
|
401
|
+
logMissingDefinition(path, "placeholder");
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
postDefinition = registration.getByPlaceholder(locatorSchema.placeholder, locatorSchema.placeholderOptions);
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
case "altText" /* altText */: {
|
|
408
|
+
if (!locatorSchema.altText) {
|
|
409
|
+
logMissingDefinition(path, "altText");
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
postDefinition = registration.getByAltText(locatorSchema.altText, locatorSchema.altTextOptions);
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
case "title" /* title */: {
|
|
416
|
+
if (!locatorSchema.title) {
|
|
417
|
+
logMissingDefinition(path, "title");
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
postDefinition = registration.getByTitle(locatorSchema.title, locatorSchema.titleOptions);
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
case "locator" /* locator */: {
|
|
424
|
+
if (!locatorSchema.locator) {
|
|
425
|
+
logMissingDefinition(path, "locator");
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (isLocatorInstance(locatorSchema.locator)) {
|
|
429
|
+
logLocatorInstanceWarning(path);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
postDefinition = registration.locator(locatorSchema.locator, locatorSchema.locatorOptions);
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
case "frameLocator" /* frameLocator */: {
|
|
436
|
+
if (!locatorSchema.frameLocator) {
|
|
437
|
+
logMissingDefinition(path, "frameLocator");
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
postDefinition = registration.frameLocator(locatorSchema.frameLocator);
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
case "testId" /* testId */: {
|
|
444
|
+
if (!locatorSchema.testId) {
|
|
445
|
+
logMissingDefinition(path, "testId");
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
postDefinition = registration.getByTestId(locatorSchema.testId);
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
case "dataCy" /* dataCy */: {
|
|
452
|
+
if (!locatorSchema.dataCy) {
|
|
453
|
+
logMissingDefinition(path, "dataCy");
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
postDefinition = registration.locator(`[data-cy="${locatorSchema.dataCy}"]`);
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
case "id" /* id */: {
|
|
460
|
+
if (!locatorSchema.id) {
|
|
461
|
+
logMissingDefinition(path, "id");
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
postDefinition = registration.getById(locatorSchema.id);
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
default: {
|
|
468
|
+
const exhaustive = locatorSchema.locatorMethod;
|
|
469
|
+
return exhaustive;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (!postDefinition) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (locatorSchema.filter && locatorSchema.locatorMethod !== "frameLocator" /* frameLocator */) {
|
|
476
|
+
const filter = locatorSchema.filter;
|
|
477
|
+
postDefinition.filter(filter);
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
79
481
|
// src/helpers/getBy.locator.ts
|
|
80
482
|
var GetBy = class {
|
|
81
483
|
constructor(page, pwrl) {
|
|
@@ -240,7 +642,6 @@ var GetBy = class {
|
|
|
240
642
|
// src/helpers/getLocatorBase.ts
|
|
241
643
|
var REQUIRED_PROPERTIES_FOR_LOCATOR_SCHEMA_WITH_METHODS = [
|
|
242
644
|
"update",
|
|
243
|
-
"updates",
|
|
244
645
|
"addFilter",
|
|
245
646
|
"getNestedLocator",
|
|
246
647
|
"getLocator",
|
|
@@ -347,41 +748,6 @@ var GetLocatorBase = class {
|
|
|
347
748
|
schemasMap.set(subPath, updatedSchema);
|
|
348
749
|
}
|
|
349
750
|
}
|
|
350
|
-
/**
|
|
351
|
-
* applyUpdate:
|
|
352
|
-
* Applies updates to a single schema within the schemasMap.
|
|
353
|
-
*/
|
|
354
|
-
applyUpdate(schemasMap, locatorSchemaPath, updateData) {
|
|
355
|
-
const schema = schemasMap.get(locatorSchemaPath);
|
|
356
|
-
if (schema) {
|
|
357
|
-
const updatedSchema = this.deepMerge(schema, updateData);
|
|
358
|
-
if (this.isLocatorSchemaWithMethods(schema)) {
|
|
359
|
-
Object.assign(schema, updatedSchema);
|
|
360
|
-
} else {
|
|
361
|
-
throw new Error("Invalid LocatorSchema object provided for update method.");
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* applyUpdates:
|
|
367
|
-
* Applies multiple updates to multiple schemas in the chain, identified by their path indexes.
|
|
368
|
-
*/
|
|
369
|
-
applyUpdates(schemasMap, pathIndexPairs, updatesData) {
|
|
370
|
-
for (const [index, updateAtIndex] of Object.entries(updatesData)) {
|
|
371
|
-
const path = pathIndexPairs[Number.parseInt(index)]?.path;
|
|
372
|
-
if (path && updateAtIndex) {
|
|
373
|
-
const schema = schemasMap.get(path);
|
|
374
|
-
if (schema) {
|
|
375
|
-
const updatedSchema = this.deepMerge(schema, updateAtIndex);
|
|
376
|
-
if (this.isLocatorSchemaWithMethods(schema)) {
|
|
377
|
-
Object.assign(schema, updatedSchema);
|
|
378
|
-
} else {
|
|
379
|
-
schemasMap.set(path, updatedSchema);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
751
|
/**
|
|
386
752
|
* createLocatorSchema:
|
|
387
753
|
* Creates a fresh LocatorSchema object by merging provided schemaDetails with a required locatorSchemaPath.
|
|
@@ -412,6 +778,10 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
412
778
|
);
|
|
413
779
|
}
|
|
414
780
|
this.locatorSchemas.set(locatorSchemaPath, () => newLocatorSchema);
|
|
781
|
+
const v2Registry = this.pageObjectClass.locatorRegistry;
|
|
782
|
+
if (v2Registry) {
|
|
783
|
+
addV1SchemaToV2Registry(v2Registry, newLocatorSchema);
|
|
784
|
+
}
|
|
415
785
|
}
|
|
416
786
|
/**
|
|
417
787
|
* safeGetLocatorSchema:
|
|
@@ -423,7 +793,7 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
423
793
|
/**
|
|
424
794
|
* extractPathsFromSchema:
|
|
425
795
|
* Splits a path into incremental sub-paths and associates them with optional indices.
|
|
426
|
-
* Used by
|
|
796
|
+
* Used by getNestedLocator methods.
|
|
427
797
|
*/
|
|
428
798
|
extractPathsFromSchema = (paths, indices = {}) => {
|
|
429
799
|
const schemaParts = paths.split(".");
|
|
@@ -605,27 +975,14 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
605
975
|
Note: "iFrame locators evaluation not implemented"
|
|
606
976
|
});
|
|
607
977
|
} else {
|
|
608
|
-
const
|
|
978
|
+
const elementCount = await currentLocator.count();
|
|
609
979
|
resultsArray.push({
|
|
610
980
|
currentLocatorString: `${currentLocator}`,
|
|
611
|
-
resolved:
|
|
612
|
-
elementCount
|
|
613
|
-
elementsResolvedTo: elementsData
|
|
981
|
+
resolved: elementCount > 0,
|
|
982
|
+
elementCount
|
|
614
983
|
});
|
|
615
984
|
}
|
|
616
985
|
};
|
|
617
|
-
/**
|
|
618
|
-
* evaluateAndGetAttributes:
|
|
619
|
-
* Extracts tagName and attributes from all elements matched by the locator for debugging purposes.
|
|
620
|
-
*/
|
|
621
|
-
evaluateAndGetAttributes = async (pwLocator) => {
|
|
622
|
-
return await pwLocator.evaluateAll(
|
|
623
|
-
(objects) => objects.map((el) => {
|
|
624
|
-
const elementAttributes = el.hasAttributes() ? Object.fromEntries(Array.from(el.attributes).map(({ name, value }) => [name, value])) : {};
|
|
625
|
-
return { tagName: el.tagName, attributes: elementAttributes };
|
|
626
|
-
})
|
|
627
|
-
);
|
|
628
|
-
};
|
|
629
986
|
};
|
|
630
987
|
var WithMethodsClass = class extends GetLocatorBase {
|
|
631
988
|
constructor(pageObjectClass, log, locatorSubstring, schemasMap) {
|
|
@@ -637,30 +994,18 @@ var WithMethodsClass = class extends GetLocatorBase {
|
|
|
637
994
|
locatorSchemaPath;
|
|
638
995
|
/**
|
|
639
996
|
* init:
|
|
640
|
-
* Assigns the locatorSchemaPath and binds methods (update,
|
|
997
|
+
* Assigns the locatorSchemaPath and binds methods (update, addFilter, getNestedLocator, getLocator)
|
|
641
998
|
* directly on the locatorSchemaCopy. Returns the modified copy, now fully chainable and type-safe.
|
|
642
999
|
*/
|
|
643
1000
|
init(locatorSchemaPath, locatorSchemaCopy) {
|
|
644
1001
|
this.locatorSchemaPath = locatorSchemaPath;
|
|
645
1002
|
const self = this;
|
|
646
|
-
locatorSchemaCopy.update = function(
|
|
1003
|
+
locatorSchemaCopy.update = function(subPath, updates) {
|
|
647
1004
|
const fullPath = this.locatorSchemaPath;
|
|
648
|
-
if (
|
|
649
|
-
|
|
650
|
-
self.applyUpdate(self.schemasMap, self.locatorSchemaPath, updates);
|
|
651
|
-
} else {
|
|
652
|
-
const subPath = a;
|
|
653
|
-
const updates = b;
|
|
654
|
-
if (!(subPath === fullPath || fullPath.startsWith(`${subPath}.`))) {
|
|
655
|
-
throw new Error(`Invalid sub-path: '${subPath}' is not a valid sub-path of '${fullPath}'.`);
|
|
656
|
-
}
|
|
657
|
-
self.applyUpdateToSubPath(self.schemasMap, subPath, updates);
|
|
1005
|
+
if (!(subPath === fullPath || fullPath.startsWith(`${subPath}.`))) {
|
|
1006
|
+
throw new Error(`Invalid sub-path: '${subPath}' is not a valid sub-path of '${fullPath}'.`);
|
|
658
1007
|
}
|
|
659
|
-
|
|
660
|
-
};
|
|
661
|
-
locatorSchemaCopy.updates = function(indexedUpdates) {
|
|
662
|
-
const pathIndexPairs = self.extractPathsFromSchema(self.locatorSchemaPath);
|
|
663
|
-
self.applyUpdates(self.schemasMap, pathIndexPairs, indexedUpdates);
|
|
1008
|
+
self.applyUpdateToSubPath(self.schemasMap, subPath, updates);
|
|
664
1009
|
return this;
|
|
665
1010
|
};
|
|
666
1011
|
locatorSchemaCopy.addFilter = function(subPath, filterData) {
|
|
@@ -692,46 +1037,33 @@ ${allowedPaths.join(",\n")}`
|
|
|
692
1037
|
{}
|
|
693
1038
|
);
|
|
694
1039
|
}
|
|
695
|
-
const keys = Object.keys(arg);
|
|
696
|
-
const isNumberKey = keys.every((key) => /^\d+$/.test(key));
|
|
697
1040
|
const numericIndices = {};
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
} else {
|
|
708
|
-
const pathIndexPairs = self.extractPathsFromSchema(self.locatorSchemaPath);
|
|
709
|
-
const pathToIndexMap = new Map(pathIndexPairs.map((pair, idx) => [pair.path, idx]));
|
|
710
|
-
for (const [subPath, value] of Object.entries(arg)) {
|
|
711
|
-
if (!self.schemasMap.has(subPath)) {
|
|
712
|
-
const validPaths = Array.from(self.schemasMap.keys());
|
|
713
|
-
throw new Error(
|
|
714
|
-
`Invalid sub-path '${subPath}' in getNestedLocator. Allowed sub-paths are:
|
|
1041
|
+
const pathIndexPairs = self.extractPathsFromSchema(self.locatorSchemaPath);
|
|
1042
|
+
const pathToIndexMap = new Map(pathIndexPairs.map((pair, idx) => [pair.path, idx]));
|
|
1043
|
+
for (const [subPath, value] of Object.entries(arg)) {
|
|
1044
|
+
if (!self.schemasMap.has(subPath)) {
|
|
1045
|
+
const validPaths = Array.from(self.schemasMap.keys());
|
|
1046
|
+
throw new Error(
|
|
1047
|
+
`Invalid sub-path '${subPath}' in getNestedLocator. Allowed sub-paths are:
|
|
715
1048
|
${validPaths.join(",\n")}`
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
if (!pathToIndexMap.has(subPath)) {
|
|
1052
|
+
const validPaths = pathIndexPairs.map((p) => p.path).filter((path) => self.schemasMap.has(path));
|
|
1053
|
+
throw new Error(
|
|
1054
|
+
`Invalid sub-path '${subPath}' in getNestedLocator. Allowed sub-paths are:
|
|
722
1055
|
${validPaths.join(",\n")}`
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
}
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
const numericIndex = pathToIndexMap.get(subPath);
|
|
1059
|
+
if (numericIndex === void 0) {
|
|
1060
|
+
throw new Error(`Sub-path '${subPath}' not found in pathToIndexMap.`);
|
|
1061
|
+
}
|
|
1062
|
+
if (value !== null && (typeof value !== "number" || value < 0)) {
|
|
1063
|
+
throw new Error(`Invalid index for sub-path '${subPath}': Expected a positive number or null.`);
|
|
1064
|
+
}
|
|
1065
|
+
if (value !== null) {
|
|
1066
|
+
numericIndices[numericIndex] = value;
|
|
735
1067
|
}
|
|
736
1068
|
}
|
|
737
1069
|
return await self.buildNestedLocator(
|
|
@@ -956,6 +1288,8 @@ var BasePage = class {
|
|
|
956
1288
|
this.fullUrl = this.constructFullUrl(baseUrl, urlPath);
|
|
957
1289
|
this.pocName = pocName;
|
|
958
1290
|
this.log = pwrl.getNewChildLogger(pocName);
|
|
1291
|
+
const classDeprecationMessage = "[POMWright] BasePage is depricated and will be removed in 2.0.0. Migrate to v2, preferably directly to PageObject or through the transitional bridge BasePageV1toV2 and then to PageObject.";
|
|
1292
|
+
warnDeprecationOncePerTest(`${this.constructor.name}-class-deprecation`, classDeprecationMessage, this.log);
|
|
959
1293
|
this.locators = new GetLocatorBase(
|
|
960
1294
|
this,
|
|
961
1295
|
this.log.getNewChildLogger("GetLocator"),
|
|
@@ -1076,71 +1410,1827 @@ var WithSubPathValidation = class extends GetLocatorBase {
|
|
|
1076
1410
|
}
|
|
1077
1411
|
};
|
|
1078
1412
|
|
|
1079
|
-
// src/
|
|
1080
|
-
import {
|
|
1413
|
+
// src/basePageV1toV2.ts
|
|
1414
|
+
import { selectors as selectors2 } from "@playwright/test";
|
|
1081
1415
|
|
|
1082
|
-
//
|
|
1083
|
-
var
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1416
|
+
// srcV2/locators/locatorUpdateBuilder.ts
|
|
1417
|
+
var parseUpdateArguments = (primaryOrOptions, options, optionsProvided) => {
|
|
1418
|
+
let primary;
|
|
1419
|
+
let parsedOptions;
|
|
1420
|
+
let hasOptions = optionsProvided ?? false;
|
|
1421
|
+
if (options !== void 0) {
|
|
1422
|
+
parsedOptions = options;
|
|
1423
|
+
hasOptions = true;
|
|
1089
1424
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
getNewChildLogger(prefix) {
|
|
1099
|
-
return new _PlaywrightReportLogger(this.sharedLogLevel, this.sharedLogEntry, `${this.contextName} -> ${prefix}`);
|
|
1425
|
+
if (primaryOrOptions !== void 0) {
|
|
1426
|
+
const treatAsOptions = !hasOptions && options === void 0 && typeof primaryOrOptions === "object" && !(primaryOrOptions instanceof RegExp);
|
|
1427
|
+
if (treatAsOptions) {
|
|
1428
|
+
parsedOptions = primaryOrOptions;
|
|
1429
|
+
hasOptions = true;
|
|
1430
|
+
} else {
|
|
1431
|
+
primary = primaryOrOptions;
|
|
1432
|
+
}
|
|
1100
1433
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1434
|
+
return { primary, options: parsedOptions, hasOptions };
|
|
1435
|
+
};
|
|
1436
|
+
var mergeOptions = (currentOptions, updates) => {
|
|
1437
|
+
if (updates && "options" in updates) {
|
|
1438
|
+
const updateOptions = updates.options;
|
|
1439
|
+
if (updateOptions && typeof updateOptions === "object") {
|
|
1440
|
+
return {
|
|
1441
|
+
...typeof currentOptions === "object" && currentOptions !== null ? currentOptions : {},
|
|
1442
|
+
...updateOptions
|
|
1443
|
+
};
|
|
1109
1444
|
}
|
|
1110
|
-
|
|
1111
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
1112
|
-
logLevel: level,
|
|
1113
|
-
prefix: this.contextName,
|
|
1114
|
-
message: `${message}
|
|
1115
|
-
|
|
1116
|
-
${args.join("\n\n")}`
|
|
1117
|
-
});
|
|
1445
|
+
return updateOptions;
|
|
1118
1446
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
this.log("debug", message, ...args);
|
|
1447
|
+
return currentOptions;
|
|
1448
|
+
};
|
|
1449
|
+
var mergeLocatorDefinition = (current, updates, path, preferredSource, baseline) => {
|
|
1450
|
+
if (!updates || typeof updates !== "object" || !("type" in updates)) {
|
|
1451
|
+
throw new Error(`Locator update for "${path}" requires a "type" property.`);
|
|
1125
1452
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1453
|
+
const source = (targetType) => {
|
|
1454
|
+
if (current.type === targetType) {
|
|
1455
|
+
return current;
|
|
1456
|
+
}
|
|
1457
|
+
if (preferredSource?.type === targetType) {
|
|
1458
|
+
return preferredSource;
|
|
1459
|
+
}
|
|
1460
|
+
if (baseline?.type === targetType) {
|
|
1461
|
+
return baseline;
|
|
1462
|
+
}
|
|
1463
|
+
return void 0;
|
|
1464
|
+
};
|
|
1465
|
+
switch (updates.type) {
|
|
1466
|
+
case "role": {
|
|
1467
|
+
const roleSource = source("role");
|
|
1468
|
+
const role = updates.role ?? roleSource?.role;
|
|
1469
|
+
if (role === void 0) {
|
|
1470
|
+
throw new Error(`Locator update for "${path}" of type "role" requires a "role" value.`);
|
|
1471
|
+
}
|
|
1472
|
+
const options = mergeOptions(roleSource?.options, updates);
|
|
1473
|
+
return options !== void 0 ? { type: "role", role, options } : { type: "role", role };
|
|
1474
|
+
}
|
|
1475
|
+
case "text": {
|
|
1476
|
+
const textSource = source("text");
|
|
1477
|
+
const text = updates.text ?? textSource?.text;
|
|
1478
|
+
if (text === void 0) {
|
|
1479
|
+
throw new Error(`Locator update for "${path}" of type "text" requires a "text" value.`);
|
|
1480
|
+
}
|
|
1481
|
+
const options = mergeOptions(textSource?.options, updates);
|
|
1482
|
+
return options !== void 0 ? { type: "text", text, options } : { type: "text", text };
|
|
1483
|
+
}
|
|
1484
|
+
case "label": {
|
|
1485
|
+
const textSource = source("label");
|
|
1486
|
+
const text = updates.text ?? textSource?.text;
|
|
1487
|
+
if (text === void 0) {
|
|
1488
|
+
throw new Error(`Locator update for "${path}" of type "label" requires a "text" value.`);
|
|
1489
|
+
}
|
|
1490
|
+
const options = mergeOptions(textSource?.options, updates);
|
|
1491
|
+
return options !== void 0 ? { type: "label", text, options } : { type: "label", text };
|
|
1492
|
+
}
|
|
1493
|
+
case "placeholder": {
|
|
1494
|
+
const textSource = source("placeholder");
|
|
1495
|
+
const text = updates.text ?? textSource?.text;
|
|
1496
|
+
if (text === void 0) {
|
|
1497
|
+
throw new Error(`Locator update for "${path}" of type "placeholder" requires a "text" value.`);
|
|
1498
|
+
}
|
|
1499
|
+
const options = mergeOptions(textSource?.options, updates);
|
|
1500
|
+
return options !== void 0 ? { type: "placeholder", text, options } : { type: "placeholder", text };
|
|
1501
|
+
}
|
|
1502
|
+
case "altText": {
|
|
1503
|
+
const textSource = source("altText");
|
|
1504
|
+
const text = updates.text ?? textSource?.text;
|
|
1505
|
+
if (text === void 0) {
|
|
1506
|
+
throw new Error(`Locator update for "${path}" of type "altText" requires a "text" value.`);
|
|
1507
|
+
}
|
|
1508
|
+
const options = mergeOptions(textSource?.options, updates);
|
|
1509
|
+
return options !== void 0 ? { type: "altText", text, options } : { type: "altText", text };
|
|
1510
|
+
}
|
|
1511
|
+
case "title": {
|
|
1512
|
+
const textSource = source("title");
|
|
1513
|
+
const text = updates.text ?? textSource?.text;
|
|
1514
|
+
if (text === void 0) {
|
|
1515
|
+
throw new Error(`Locator update for "${path}" of type "title" requires a "text" value.`);
|
|
1516
|
+
}
|
|
1517
|
+
const options = mergeOptions(textSource?.options, updates);
|
|
1518
|
+
return options !== void 0 ? { type: "title", text, options } : { type: "title", text };
|
|
1519
|
+
}
|
|
1520
|
+
case "locator": {
|
|
1521
|
+
const selectorSource = source("locator");
|
|
1522
|
+
const selector = updates.selector ?? selectorSource?.selector;
|
|
1523
|
+
if (selector === void 0) {
|
|
1524
|
+
throw new Error(`Locator update for "${path}" of type "locator" requires a "selector" value.`);
|
|
1525
|
+
}
|
|
1526
|
+
const options = mergeOptions(selectorSource?.options, updates);
|
|
1527
|
+
return options !== void 0 ? { type: "locator", selector, options } : { type: "locator", selector };
|
|
1528
|
+
}
|
|
1529
|
+
case "frameLocator": {
|
|
1530
|
+
const selectorSource = source("frameLocator");
|
|
1531
|
+
const selector = updates.selector ?? selectorSource?.selector;
|
|
1532
|
+
if (selector === void 0) {
|
|
1533
|
+
throw new Error(`Locator update for "${path}" of type "frameLocator" requires a "selector" value.`);
|
|
1534
|
+
}
|
|
1535
|
+
return { type: "frameLocator", selector };
|
|
1536
|
+
}
|
|
1537
|
+
case "testId": {
|
|
1538
|
+
const testIdSource = source("testId");
|
|
1539
|
+
const testId = updates.testId ?? testIdSource?.testId;
|
|
1540
|
+
if (testId === void 0) {
|
|
1541
|
+
throw new Error(`Locator update for "${path}" of type "testId" requires a "testId" value.`);
|
|
1542
|
+
}
|
|
1543
|
+
return { type: "testId", testId };
|
|
1544
|
+
}
|
|
1545
|
+
case "id": {
|
|
1546
|
+
const idSource = source("id");
|
|
1547
|
+
const rawId = updates.id ?? idSource?.id;
|
|
1548
|
+
const id = normalizeIdValue(rawId);
|
|
1549
|
+
if (id === void 0) {
|
|
1550
|
+
throw new Error(`Locator update for "${path}" of type "id" requires an "id" value.`);
|
|
1551
|
+
}
|
|
1552
|
+
return { type: "id", id };
|
|
1553
|
+
}
|
|
1554
|
+
default: {
|
|
1555
|
+
const exhaustive = updates;
|
|
1556
|
+
return exhaustive;
|
|
1557
|
+
}
|
|
1132
1558
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
warn(message, ...args) {
|
|
1138
|
-
this.log("warn", message, ...args);
|
|
1559
|
+
};
|
|
1560
|
+
var buildReplacementDefinition = (updates, path) => {
|
|
1561
|
+
if (!updates || typeof updates !== "object" || !("type" in updates)) {
|
|
1562
|
+
throw new Error(`Locator replace for "${path}" requires a "type" property.`);
|
|
1139
1563
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1564
|
+
switch (updates.type) {
|
|
1565
|
+
case "role": {
|
|
1566
|
+
const { role, options } = updates;
|
|
1567
|
+
if (role === void 0) {
|
|
1568
|
+
throw new Error(`Locator replace for "${path}" of type "role" requires a "role" value.`);
|
|
1569
|
+
}
|
|
1570
|
+
return options !== void 0 ? { type: "role", role, options } : { type: "role", role };
|
|
1571
|
+
}
|
|
1572
|
+
case "text": {
|
|
1573
|
+
const { text, options } = updates;
|
|
1574
|
+
if (text === void 0) {
|
|
1575
|
+
throw new Error(`Locator replace for "${path}" of type "text" requires a "text" value.`);
|
|
1576
|
+
}
|
|
1577
|
+
return options !== void 0 ? { type: "text", text, options } : { type: "text", text };
|
|
1578
|
+
}
|
|
1579
|
+
case "label": {
|
|
1580
|
+
const { text, options } = updates;
|
|
1581
|
+
if (text === void 0) {
|
|
1582
|
+
throw new Error(`Locator replace for "${path}" of type "label" requires a "text" value.`);
|
|
1583
|
+
}
|
|
1584
|
+
return options !== void 0 ? { type: "label", text, options } : { type: "label", text };
|
|
1585
|
+
}
|
|
1586
|
+
case "placeholder": {
|
|
1587
|
+
const { text, options } = updates;
|
|
1588
|
+
if (text === void 0) {
|
|
1589
|
+
throw new Error(`Locator replace for "${path}" of type "placeholder" requires a "text" value.`);
|
|
1590
|
+
}
|
|
1591
|
+
return options !== void 0 ? { type: "placeholder", text, options } : { type: "placeholder", text };
|
|
1592
|
+
}
|
|
1593
|
+
case "altText": {
|
|
1594
|
+
const { text, options } = updates;
|
|
1595
|
+
if (text === void 0) {
|
|
1596
|
+
throw new Error(`Locator replace for "${path}" of type "altText" requires a "text" value.`);
|
|
1597
|
+
}
|
|
1598
|
+
return options !== void 0 ? { type: "altText", text, options } : { type: "altText", text };
|
|
1599
|
+
}
|
|
1600
|
+
case "title": {
|
|
1601
|
+
const { text, options } = updates;
|
|
1602
|
+
if (text === void 0) {
|
|
1603
|
+
throw new Error(`Locator replace for "${path}" of type "title" requires a "text" value.`);
|
|
1604
|
+
}
|
|
1605
|
+
return options !== void 0 ? { type: "title", text, options } : { type: "title", text };
|
|
1606
|
+
}
|
|
1607
|
+
case "locator": {
|
|
1608
|
+
const { selector, options } = updates;
|
|
1609
|
+
if (selector === void 0) {
|
|
1610
|
+
throw new Error(`Locator replace for "${path}" of type "locator" requires a "selector" value.`);
|
|
1611
|
+
}
|
|
1612
|
+
return options !== void 0 ? { type: "locator", selector, options } : { type: "locator", selector };
|
|
1613
|
+
}
|
|
1614
|
+
case "frameLocator": {
|
|
1615
|
+
const { selector } = updates;
|
|
1616
|
+
if (selector === void 0) {
|
|
1617
|
+
throw new Error(`Locator replace for "${path}" of type "frameLocator" requires a "selector" value.`);
|
|
1618
|
+
}
|
|
1619
|
+
return { type: "frameLocator", selector };
|
|
1620
|
+
}
|
|
1621
|
+
case "testId": {
|
|
1622
|
+
const { testId } = updates;
|
|
1623
|
+
if (testId === void 0) {
|
|
1624
|
+
throw new Error(`Locator replace for "${path}" of type "testId" requires a "testId" value.`);
|
|
1625
|
+
}
|
|
1626
|
+
return { type: "testId", testId };
|
|
1627
|
+
}
|
|
1628
|
+
case "id": {
|
|
1629
|
+
const rawId = updates.id;
|
|
1630
|
+
const id = normalizeIdValue(rawId);
|
|
1631
|
+
if (id === void 0) {
|
|
1632
|
+
throw new Error(`Locator replace for "${path}" of type "id" requires an "id" value.`);
|
|
1633
|
+
}
|
|
1634
|
+
return { type: "id", id };
|
|
1635
|
+
}
|
|
1636
|
+
default: {
|
|
1637
|
+
const exhaustive = updates;
|
|
1638
|
+
return exhaustive;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
var LocatorUpdateBuilder = class {
|
|
1643
|
+
constructor(parent, subPath, mode = "update") {
|
|
1644
|
+
this.parent = parent;
|
|
1645
|
+
this.subPath = subPath;
|
|
1646
|
+
this.mode = mode;
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Defines or patches a `getByRole` locator strategy for the target subpath. In `update` mode the
|
|
1650
|
+
* `role` and `options` arguments are optional (PATCH semantics); in `replace` mode they follow
|
|
1651
|
+
* Playwright requirements (POST semantics) and `role` is required.
|
|
1652
|
+
*
|
|
1653
|
+
* @example
|
|
1654
|
+
* ```ts
|
|
1655
|
+
* getLocatorSchema("form.button")
|
|
1656
|
+
* .update("form.button")
|
|
1657
|
+
* .getByRole({ name: "Save" })
|
|
1658
|
+
* .getNestedLocator();
|
|
1659
|
+
* ```
|
|
1660
|
+
*/
|
|
1661
|
+
getByRole(...args) {
|
|
1662
|
+
const [roleOrOptions, options] = args;
|
|
1663
|
+
const {
|
|
1664
|
+
primary: role,
|
|
1665
|
+
options: parsedOptions,
|
|
1666
|
+
hasOptions
|
|
1667
|
+
} = parseUpdateArguments(
|
|
1668
|
+
roleOrOptions,
|
|
1669
|
+
options,
|
|
1670
|
+
args.length >= 2
|
|
1671
|
+
);
|
|
1672
|
+
const definition = {
|
|
1673
|
+
type: "role",
|
|
1674
|
+
...role !== void 0 ? { role } : {},
|
|
1675
|
+
...hasOptions ? { options: parsedOptions } : {}
|
|
1676
|
+
};
|
|
1677
|
+
return this.commit(definition);
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* Defines or patches a `getByText` locator strategy for the target subpath. In `update` mode,
|
|
1681
|
+
* text/options are optional; in `replace` mode text is required.
|
|
1682
|
+
*
|
|
1683
|
+
* @example
|
|
1684
|
+
* ```ts
|
|
1685
|
+
* getLocatorSchema("banner.message")
|
|
1686
|
+
* .replace("banner.message")
|
|
1687
|
+
* .getByText(/Updated/, { exact: false })
|
|
1688
|
+
* .getNestedLocator();
|
|
1689
|
+
* ```
|
|
1690
|
+
*/
|
|
1691
|
+
getByText(...args) {
|
|
1692
|
+
const [textOrOptions, options] = args;
|
|
1693
|
+
const {
|
|
1694
|
+
primary: text,
|
|
1695
|
+
options: parsedOptions,
|
|
1696
|
+
hasOptions
|
|
1697
|
+
} = parseUpdateArguments(
|
|
1698
|
+
textOrOptions,
|
|
1699
|
+
options,
|
|
1700
|
+
args.length >= 2
|
|
1701
|
+
);
|
|
1702
|
+
const definition = {
|
|
1703
|
+
type: "text",
|
|
1704
|
+
...text !== void 0 ? { text } : {},
|
|
1705
|
+
...hasOptions ? { options: parsedOptions } : {}
|
|
1706
|
+
};
|
|
1707
|
+
return this.commit(definition);
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Defines or patches a `getByLabel` locator strategy for the target subpath. In `update` mode the
|
|
1711
|
+
* label text/options are optional; in `replace` mode text is required.
|
|
1712
|
+
*
|
|
1713
|
+
* @example
|
|
1714
|
+
* ```ts
|
|
1715
|
+
* getLocatorSchema("form.email").update("form.email").getByLabel({ exact: true }).getNestedLocator();
|
|
1716
|
+
* ```
|
|
1717
|
+
*/
|
|
1718
|
+
getByLabel(...args) {
|
|
1719
|
+
const [textOrOptions, options] = args;
|
|
1720
|
+
const {
|
|
1721
|
+
primary: text,
|
|
1722
|
+
options: parsedOptions,
|
|
1723
|
+
hasOptions
|
|
1724
|
+
} = parseUpdateArguments(
|
|
1725
|
+
textOrOptions,
|
|
1726
|
+
options,
|
|
1727
|
+
args.length >= 2
|
|
1728
|
+
);
|
|
1729
|
+
const definition = {
|
|
1730
|
+
type: "label",
|
|
1731
|
+
...text !== void 0 ? { text } : {},
|
|
1732
|
+
...hasOptions ? { options: parsedOptions } : {}
|
|
1733
|
+
};
|
|
1734
|
+
return this.commit(definition);
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Defines or patches a `getByPlaceholder` locator strategy for the target subpath. In `update`
|
|
1738
|
+
* mode text/options are optional; in `replace` mode text is required.
|
|
1739
|
+
*
|
|
1740
|
+
* @example
|
|
1741
|
+
* ```ts
|
|
1742
|
+
* getLocatorSchema("form.search").update("form.search").getByPlaceholder({ exact: true }).getNestedLocator();
|
|
1743
|
+
* ```
|
|
1744
|
+
*/
|
|
1745
|
+
getByPlaceholder(...args) {
|
|
1746
|
+
const [textOrOptions, options] = args;
|
|
1747
|
+
const {
|
|
1748
|
+
primary: text,
|
|
1749
|
+
options: parsedOptions,
|
|
1750
|
+
hasOptions
|
|
1751
|
+
} = parseUpdateArguments(
|
|
1752
|
+
textOrOptions,
|
|
1753
|
+
options,
|
|
1754
|
+
args.length >= 2
|
|
1755
|
+
);
|
|
1756
|
+
const definition = {
|
|
1757
|
+
type: "placeholder",
|
|
1758
|
+
...text !== void 0 ? { text } : {},
|
|
1759
|
+
...hasOptions ? { options: parsedOptions } : {}
|
|
1760
|
+
};
|
|
1761
|
+
return this.commit(definition);
|
|
1762
|
+
}
|
|
1763
|
+
/**
|
|
1764
|
+
* Defines or patches a `getByAltText` locator strategy for the target subpath. In `update` mode
|
|
1765
|
+
* text/options are optional; in `replace` mode text is required.
|
|
1766
|
+
*
|
|
1767
|
+
* @example
|
|
1768
|
+
* ```ts
|
|
1769
|
+
* getLocatorSchema("image.logo").replace("image.logo").getByAltText("Logo").getNestedLocator();
|
|
1770
|
+
* ```
|
|
1771
|
+
*/
|
|
1772
|
+
getByAltText(...args) {
|
|
1773
|
+
const [textOrOptions, options] = args;
|
|
1774
|
+
const {
|
|
1775
|
+
primary: text,
|
|
1776
|
+
options: parsedOptions,
|
|
1777
|
+
hasOptions
|
|
1778
|
+
} = parseUpdateArguments(
|
|
1779
|
+
textOrOptions,
|
|
1780
|
+
options,
|
|
1781
|
+
args.length >= 2
|
|
1782
|
+
);
|
|
1783
|
+
const definition = {
|
|
1784
|
+
type: "altText",
|
|
1785
|
+
...text !== void 0 ? { text } : {},
|
|
1786
|
+
...hasOptions ? { options: parsedOptions } : {}
|
|
1787
|
+
};
|
|
1788
|
+
return this.commit(definition);
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Defines or patches a `getByTitle` locator strategy for the target subpath. In `update` mode
|
|
1792
|
+
* text/options are optional; in `replace` mode text is required.
|
|
1793
|
+
*
|
|
1794
|
+
* @example
|
|
1795
|
+
* ```ts
|
|
1796
|
+
* getLocatorSchema("icon.info").update("icon.info").getByTitle({ exact: true }).getNestedLocator();
|
|
1797
|
+
* ```
|
|
1798
|
+
*/
|
|
1799
|
+
getByTitle(...args) {
|
|
1800
|
+
const [textOrOptions, options] = args;
|
|
1801
|
+
const {
|
|
1802
|
+
primary: text,
|
|
1803
|
+
options: parsedOptions,
|
|
1804
|
+
hasOptions
|
|
1805
|
+
} = parseUpdateArguments(
|
|
1806
|
+
textOrOptions,
|
|
1807
|
+
options,
|
|
1808
|
+
args.length >= 2
|
|
1809
|
+
);
|
|
1810
|
+
const definition = {
|
|
1811
|
+
type: "title",
|
|
1812
|
+
...text !== void 0 ? { text } : {},
|
|
1813
|
+
...hasOptions ? { options: parsedOptions } : {}
|
|
1814
|
+
};
|
|
1815
|
+
return this.commit(definition);
|
|
1816
|
+
}
|
|
1817
|
+
/**
|
|
1818
|
+
* Defines or patches a `locator` strategy for the target subpath. In `update` mode selector and
|
|
1819
|
+
* options are optional and merge with the existing definition; in `replace` mode selector is
|
|
1820
|
+
* required.
|
|
1821
|
+
*
|
|
1822
|
+
* @example
|
|
1823
|
+
* ```ts
|
|
1824
|
+
* getLocatorSchema("list.items")
|
|
1825
|
+
* .replace("list.items")
|
|
1826
|
+
* .locator("ul > li", { hasText: /Row/ })
|
|
1827
|
+
* .getNestedLocator();
|
|
1828
|
+
* ```
|
|
1829
|
+
*/
|
|
1830
|
+
locator(...args) {
|
|
1831
|
+
const [selectorOrOptions, options] = args;
|
|
1832
|
+
const {
|
|
1833
|
+
primary: selector,
|
|
1834
|
+
options: parsedOptions,
|
|
1835
|
+
hasOptions
|
|
1836
|
+
} = parseUpdateArguments(
|
|
1837
|
+
selectorOrOptions,
|
|
1838
|
+
options,
|
|
1839
|
+
args.length >= 2
|
|
1840
|
+
);
|
|
1841
|
+
const definition = {
|
|
1842
|
+
type: "locator",
|
|
1843
|
+
...selector !== void 0 ? { selector } : {},
|
|
1844
|
+
...hasOptions ? { options: parsedOptions } : {}
|
|
1845
|
+
};
|
|
1846
|
+
return this.commit(definition);
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Defines or patches a `frameLocator` strategy for the target subpath. In `update` mode the
|
|
1850
|
+
* selector is optional and, when omitted, inherits the existing selector; in `replace` mode the
|
|
1851
|
+
* selector is required.
|
|
1852
|
+
*
|
|
1853
|
+
* @example
|
|
1854
|
+
* ```ts
|
|
1855
|
+
* getLocatorSchema("frame.login").replace("frame.login").frameLocator("iframe.auth").getNestedLocator();
|
|
1856
|
+
* ```
|
|
1857
|
+
*/
|
|
1858
|
+
frameLocator(...args) {
|
|
1859
|
+
const [selector] = args;
|
|
1860
|
+
const definition = {
|
|
1861
|
+
type: "frameLocator",
|
|
1862
|
+
...selector !== void 0 ? { selector } : {}
|
|
1863
|
+
};
|
|
1864
|
+
return this.commit(definition);
|
|
1865
|
+
}
|
|
1866
|
+
/**
|
|
1867
|
+
* Defines or patches a `getByTestId` locator strategy for the target subpath. In `update` mode
|
|
1868
|
+
* `testId` is optional and merges with existing options; in `replace` mode it is required.
|
|
1869
|
+
*
|
|
1870
|
+
* @example
|
|
1871
|
+
* ```ts
|
|
1872
|
+
* getLocatorSchema("card.title").update("card.title").getByTestId().getNestedLocator();
|
|
1873
|
+
* ```
|
|
1874
|
+
*/
|
|
1875
|
+
getByTestId(...args) {
|
|
1876
|
+
const [testId] = args;
|
|
1877
|
+
const definition = {
|
|
1878
|
+
type: "testId",
|
|
1879
|
+
...testId !== void 0 ? { testId } : {}
|
|
1880
|
+
};
|
|
1881
|
+
return this.commit(definition);
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Defines or patches an `id` locator strategy for the target subpath. In `update` mode the id is
|
|
1885
|
+
* optional and will be normalized if provided; in `replace` mode the id is required.
|
|
1886
|
+
*
|
|
1887
|
+
* @example
|
|
1888
|
+
* ```ts
|
|
1889
|
+
* getLocatorSchema("modal.close").update("modal.close").getById().getNestedLocator();
|
|
1890
|
+
* ```
|
|
1891
|
+
*/
|
|
1892
|
+
getById(...args) {
|
|
1893
|
+
const [idValue] = args;
|
|
1894
|
+
const id = idValue !== void 0 ? normalizeIdValue(idValue) : void 0;
|
|
1895
|
+
const definition = {
|
|
1896
|
+
type: "id",
|
|
1897
|
+
...id !== void 0 ? { id } : {}
|
|
1898
|
+
};
|
|
1899
|
+
return this.commit(definition);
|
|
1900
|
+
}
|
|
1901
|
+
commit(definition) {
|
|
1902
|
+
return this.mode === "replace" ? this.parent.applyReplacement(this.subPath, definition) : this.parent.applyUpdate(this.subPath, definition);
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
|
|
1906
|
+
// srcV2/locators/locatorQueryBuilder.ts
|
|
1907
|
+
var LocatorQueryBuilder = class {
|
|
1908
|
+
constructor(registry, path) {
|
|
1909
|
+
this.registry = registry;
|
|
1910
|
+
this.path = path;
|
|
1911
|
+
const chain = expandSchemaPath(path);
|
|
1912
|
+
let hasTerminal = false;
|
|
1913
|
+
for (const part of chain) {
|
|
1914
|
+
const record = this.registry.getIfExists(part);
|
|
1915
|
+
if (!record) {
|
|
1916
|
+
continue;
|
|
1917
|
+
}
|
|
1918
|
+
const clonedDefinition = cloneLocatorStrategyDefinition(record.definition);
|
|
1919
|
+
this.definitions.set(part, clonedDefinition);
|
|
1920
|
+
this.ensureTypeCache(part, clonedDefinition);
|
|
1921
|
+
const recordSteps = normalizeSteps(
|
|
1922
|
+
record.steps
|
|
1923
|
+
);
|
|
1924
|
+
this.steps.set(part, recordSteps);
|
|
1925
|
+
if (record.description !== void 0) {
|
|
1926
|
+
this.descriptions.set(part, record.description);
|
|
1927
|
+
}
|
|
1928
|
+
if (part === path) {
|
|
1929
|
+
hasTerminal = true;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
if (!hasTerminal) {
|
|
1933
|
+
throw new Error(`No locator schema registered for path "${path}".`);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
definitions = /* @__PURE__ */ new Map();
|
|
1937
|
+
perPathTypeCache = /* @__PURE__ */ new Map();
|
|
1938
|
+
steps = /* @__PURE__ */ new Map();
|
|
1939
|
+
descriptions = /* @__PURE__ */ new Map();
|
|
1940
|
+
tombstones = /* @__PURE__ */ new Set();
|
|
1941
|
+
update(subPath) {
|
|
1942
|
+
const resolvedSubPath = subPath ?? this.path;
|
|
1943
|
+
this.ensureSubPath(resolvedSubPath);
|
|
1944
|
+
return new LocatorUpdateBuilder(
|
|
1945
|
+
this,
|
|
1946
|
+
resolvedSubPath
|
|
1947
|
+
);
|
|
1948
|
+
}
|
|
1949
|
+
filter(...args) {
|
|
1950
|
+
const hasExplicitSubPath = args.length === 2;
|
|
1951
|
+
const [subPathOrFilter, maybeFilter] = args;
|
|
1952
|
+
const resolvedSubPath = hasExplicitSubPath ? subPathOrFilter : this.path;
|
|
1953
|
+
const filter = hasExplicitSubPath ? maybeFilter : subPathOrFilter;
|
|
1954
|
+
this.ensureSubPath(resolvedSubPath);
|
|
1955
|
+
const existing = this.steps.get(resolvedSubPath) ?? [];
|
|
1956
|
+
existing.push({ kind: "filter", filter });
|
|
1957
|
+
this.steps.set(resolvedSubPath, existing);
|
|
1958
|
+
return this;
|
|
1959
|
+
}
|
|
1960
|
+
clearSteps(subPath) {
|
|
1961
|
+
const resolvedSubPath = subPath ?? this.path;
|
|
1962
|
+
this.ensureSubPath(resolvedSubPath);
|
|
1963
|
+
this.steps.set(resolvedSubPath, []);
|
|
1964
|
+
return this;
|
|
1965
|
+
}
|
|
1966
|
+
nth(...args) {
|
|
1967
|
+
const hasExplicitSubPath = args.length === 2;
|
|
1968
|
+
const [subPathOrIndex, maybeIndex] = args;
|
|
1969
|
+
const resolvedSubPath = hasExplicitSubPath ? subPathOrIndex : this.path;
|
|
1970
|
+
const index = hasExplicitSubPath ? maybeIndex : subPathOrIndex;
|
|
1971
|
+
this.ensureSubPath(resolvedSubPath);
|
|
1972
|
+
const existing = this.steps.get(resolvedSubPath) ?? [];
|
|
1973
|
+
existing.push({ kind: "index", index });
|
|
1974
|
+
this.steps.set(resolvedSubPath, existing);
|
|
1975
|
+
return this;
|
|
1976
|
+
}
|
|
1977
|
+
/**
|
|
1978
|
+
* Adds or overrides the description for the terminal path of this builder. The description is
|
|
1979
|
+
* applied only to the resolved terminal locator and does not mutate registry state.
|
|
1980
|
+
*
|
|
1981
|
+
* @example
|
|
1982
|
+
* ```ts
|
|
1983
|
+
* getLocatorSchema("section.button")
|
|
1984
|
+
* .describe("Save button")
|
|
1985
|
+
* .getNestedLocator();
|
|
1986
|
+
* ```
|
|
1987
|
+
*/
|
|
1988
|
+
describe(description) {
|
|
1989
|
+
this.ensureSubPath(this.path);
|
|
1990
|
+
this.descriptions.set(this.path, description);
|
|
1991
|
+
return this;
|
|
1992
|
+
}
|
|
1993
|
+
/** @internal */
|
|
1994
|
+
applyUpdate(subPath, updates) {
|
|
1995
|
+
this.ensureSubPath(subPath);
|
|
1996
|
+
if (!this.definitions.has(subPath) && this.tombstones.has(subPath)) {
|
|
1997
|
+
const baseline2 = this.registry.get(subPath).definition;
|
|
1998
|
+
const baselineClone = cloneLocatorStrategyDefinition(baseline2);
|
|
1999
|
+
this.definitions.set(subPath, baselineClone);
|
|
2000
|
+
this.steps.set(subPath, []);
|
|
2001
|
+
this.tombstones.delete(subPath);
|
|
2002
|
+
}
|
|
2003
|
+
const current = this.definitions.get(subPath);
|
|
2004
|
+
if (!current) {
|
|
2005
|
+
throw new Error(`No locator schema registered for sub-path "${subPath}".`);
|
|
2006
|
+
}
|
|
2007
|
+
const baseline = this.registry.get(subPath).definition;
|
|
2008
|
+
const cacheForPath = this.ensureTypeCache(subPath, cloneLocatorStrategyDefinition(baseline));
|
|
2009
|
+
const cachedDefinition = cacheForPath.get(updates.type);
|
|
2010
|
+
const merged = mergeLocatorDefinition(current, updates, subPath, cachedDefinition, baseline);
|
|
2011
|
+
const mergedClone = cloneLocatorStrategyDefinition(merged);
|
|
2012
|
+
cacheForPath.set(mergedClone.type, mergedClone);
|
|
2013
|
+
this.definitions.set(subPath, mergedClone);
|
|
2014
|
+
return this;
|
|
2015
|
+
}
|
|
2016
|
+
replace(subPath) {
|
|
2017
|
+
const resolvedSubPath = subPath ?? this.path;
|
|
2018
|
+
this.ensureSubPath(resolvedSubPath);
|
|
2019
|
+
return new LocatorUpdateBuilder(
|
|
2020
|
+
this,
|
|
2021
|
+
resolvedSubPath,
|
|
2022
|
+
"replace"
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
/** @internal */
|
|
2026
|
+
applyReplacement(subPath, updates) {
|
|
2027
|
+
this.ensureSubPath(subPath);
|
|
2028
|
+
if (this.tombstones.has(subPath) && !this.definitions.has(subPath)) {
|
|
2029
|
+
this.steps.set(subPath, []);
|
|
2030
|
+
this.tombstones.delete(subPath);
|
|
2031
|
+
}
|
|
2032
|
+
const nextDefinition = buildReplacementDefinition(updates, subPath);
|
|
2033
|
+
const cloned = cloneLocatorStrategyDefinition(nextDefinition);
|
|
2034
|
+
const cacheForPath = this.ensureTypeCache(subPath, cloned);
|
|
2035
|
+
cacheForPath.set(cloned.type, cloneLocatorStrategyDefinition(cloned));
|
|
2036
|
+
this.definitions.set(subPath, cloned);
|
|
2037
|
+
return this;
|
|
2038
|
+
}
|
|
2039
|
+
remove(subPath) {
|
|
2040
|
+
const resolvedSubPath = subPath ?? this.path;
|
|
2041
|
+
this.ensureSubPath(resolvedSubPath);
|
|
2042
|
+
this.definitions.delete(resolvedSubPath);
|
|
2043
|
+
this.steps.delete(resolvedSubPath);
|
|
2044
|
+
this.perPathTypeCache.delete(resolvedSubPath);
|
|
2045
|
+
this.descriptions.delete(resolvedSubPath);
|
|
2046
|
+
this.tombstones.add(resolvedSubPath);
|
|
2047
|
+
return this;
|
|
2048
|
+
}
|
|
2049
|
+
/**
|
|
2050
|
+
* Resolves and returns the Playwright {@link Locator} for the terminal path of this builder,
|
|
2051
|
+
* applying only the terminal definition and its steps. Throws if the terminal path has been
|
|
2052
|
+
* removed or is otherwise missing.
|
|
2053
|
+
*
|
|
2054
|
+
* @example
|
|
2055
|
+
* ```ts
|
|
2056
|
+
* const locator = getLocatorSchema("form.submit").getLocator();
|
|
2057
|
+
* ```
|
|
2058
|
+
*/
|
|
2059
|
+
getLocator() {
|
|
2060
|
+
const definition = this.definitions.get(this.path);
|
|
2061
|
+
if (!definition) {
|
|
2062
|
+
throw new Error(`No locator schema registered for path "${this.path}".`);
|
|
2063
|
+
}
|
|
2064
|
+
const stepsForPath = this.steps.get(this.path) ?? [];
|
|
2065
|
+
const definitions = /* @__PURE__ */ new Map([[this.path, definition]]);
|
|
2066
|
+
const steps = /* @__PURE__ */ new Map([
|
|
2067
|
+
[
|
|
2068
|
+
this.path,
|
|
2069
|
+
normalizeSteps(stepsForPath)
|
|
2070
|
+
]
|
|
2071
|
+
]);
|
|
2072
|
+
const { locator } = this.registry.buildLocatorChain(
|
|
2073
|
+
this.path,
|
|
2074
|
+
definitions,
|
|
2075
|
+
steps,
|
|
2076
|
+
this.tombstones,
|
|
2077
|
+
this.descriptions.get(this.path)
|
|
2078
|
+
);
|
|
2079
|
+
if (!locator) {
|
|
2080
|
+
throw new Error(`Unable to resolve direct locator for path "${this.path}".`);
|
|
2081
|
+
}
|
|
2082
|
+
return locator;
|
|
2083
|
+
}
|
|
2084
|
+
/**
|
|
2085
|
+
* Resolves the chained Playwright {@link Locator} for this builder’s root path, traversing each
|
|
2086
|
+
* registered segment and applying recorded steps. Throws if any required segment is missing.
|
|
2087
|
+
*
|
|
2088
|
+
* @example
|
|
2089
|
+
* ```ts
|
|
2090
|
+
* const nested = getLocatorSchema("list.item")
|
|
2091
|
+
* .filter("list", { hasText: "List" })
|
|
2092
|
+
* .getNestedLocator();
|
|
2093
|
+
* ```
|
|
2094
|
+
*/
|
|
2095
|
+
getNestedLocator() {
|
|
2096
|
+
const { locator } = this.resolve();
|
|
2097
|
+
if (!locator) {
|
|
2098
|
+
throw new Error(`Unable to resolve nested locator for path "${this.path}".`);
|
|
2099
|
+
}
|
|
2100
|
+
return locator;
|
|
2101
|
+
}
|
|
2102
|
+
ensureSubPath(subPath) {
|
|
2103
|
+
if (!this.definitions.has(subPath) && !this.tombstones.has(subPath)) {
|
|
2104
|
+
throw new Error(`"${subPath}" is not a valid sub-path of "${this.path}".`);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
resolve() {
|
|
2108
|
+
return this.registry.buildLocatorChain(
|
|
2109
|
+
this.path,
|
|
2110
|
+
this.definitions,
|
|
2111
|
+
this.steps,
|
|
2112
|
+
this.tombstones,
|
|
2113
|
+
this.descriptions.get(this.path)
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
ensureTypeCache(subPath, baseline) {
|
|
2117
|
+
if (!this.perPathTypeCache.has(subPath)) {
|
|
2118
|
+
this.perPathTypeCache.set(
|
|
2119
|
+
subPath,
|
|
2120
|
+
/* @__PURE__ */ new Map([
|
|
2121
|
+
[baseline.type, cloneLocatorStrategyDefinition(baseline)]
|
|
2122
|
+
])
|
|
2123
|
+
);
|
|
2124
|
+
}
|
|
2125
|
+
return this.perPathTypeCache.get(subPath);
|
|
2126
|
+
}
|
|
2127
|
+
};
|
|
2128
|
+
|
|
2129
|
+
// srcV2/locators/locatorRegistrationBuilder.ts
|
|
2130
|
+
var LocatorRegistrationBuilder = class {
|
|
2131
|
+
constructor(registry, path, seed) {
|
|
2132
|
+
this.registry = registry;
|
|
2133
|
+
this.path = path;
|
|
2134
|
+
if (seed?.initialSteps) {
|
|
2135
|
+
this.steps = normalizeSteps(seed.initialSteps);
|
|
2136
|
+
}
|
|
2137
|
+
if (seed?.initialDefinition) {
|
|
2138
|
+
this.definition = seed.initialDefinition;
|
|
2139
|
+
this.seededDefinition = true;
|
|
2140
|
+
} else {
|
|
2141
|
+
this.seededDefinition = false;
|
|
2142
|
+
}
|
|
2143
|
+
this.reuseType = seed?.reuseType;
|
|
2144
|
+
this.description = seed?.initialDescription;
|
|
2145
|
+
}
|
|
2146
|
+
steps = [];
|
|
2147
|
+
definition;
|
|
2148
|
+
description;
|
|
2149
|
+
registered = false;
|
|
2150
|
+
reuseType;
|
|
2151
|
+
seededDefinition;
|
|
2152
|
+
overrideApplied = false;
|
|
2153
|
+
postDefinitionView;
|
|
2154
|
+
persistSeededDefinition() {
|
|
2155
|
+
if (this.seededDefinition && !this.registered) {
|
|
2156
|
+
this.persist();
|
|
2157
|
+
}
|
|
2158
|
+
return this;
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Records a Playwright-style filter on the locator being registered. Filters are applied in the
|
|
2162
|
+
* order they are chained and can include `has`/`hasNot` locator references. Requires that a
|
|
2163
|
+
* locator strategy has already been set for this registration.
|
|
2164
|
+
*
|
|
2165
|
+
* @example
|
|
2166
|
+
* ```ts
|
|
2167
|
+
* registry.add("list.item").locator("li").filter({ hasText: /Row/ });
|
|
2168
|
+
* ```
|
|
2169
|
+
*/
|
|
2170
|
+
filter(filter) {
|
|
2171
|
+
this.applyFilter(filter);
|
|
2172
|
+
return this.getPostDefinitionView();
|
|
2173
|
+
}
|
|
2174
|
+
/**
|
|
2175
|
+
* Adds an index selector for the locator being registered. Indices are applied in the order they
|
|
2176
|
+
* are chained and require a locator definition to have been set first.
|
|
2177
|
+
*
|
|
2178
|
+
* @example
|
|
2179
|
+
* ```ts
|
|
2180
|
+
* registry.add("table.rows").locator("tr").nth(0);
|
|
2181
|
+
* ```
|
|
2182
|
+
*/
|
|
2183
|
+
nth(index) {
|
|
2184
|
+
this.applyIndex(index);
|
|
2185
|
+
return this.getPostDefinitionView();
|
|
2186
|
+
}
|
|
2187
|
+
describe(description) {
|
|
2188
|
+
this.description = description;
|
|
2189
|
+
this.persist();
|
|
2190
|
+
return this.getPostDefinitionView();
|
|
2191
|
+
}
|
|
2192
|
+
getByRole(roleOrOptions, options) {
|
|
2193
|
+
const definition = typeof roleOrOptions === "string" ? options !== void 0 ? { type: "role", role: roleOrOptions, options } : { type: "role", role: roleOrOptions } : { type: "role", options: roleOrOptions };
|
|
2194
|
+
return this.commit(definition);
|
|
2195
|
+
}
|
|
2196
|
+
getByText(textOrOptions, options) {
|
|
2197
|
+
const definition = typeof textOrOptions === "string" || textOrOptions instanceof RegExp ? options !== void 0 ? { type: "text", text: textOrOptions, options } : { type: "text", text: textOrOptions } : { type: "text", options: textOrOptions };
|
|
2198
|
+
return this.commit(definition);
|
|
2199
|
+
}
|
|
2200
|
+
getByLabel(textOrOptions, options) {
|
|
2201
|
+
const definition = typeof textOrOptions === "string" || textOrOptions instanceof RegExp ? options !== void 0 ? { type: "label", text: textOrOptions, options } : { type: "label", text: textOrOptions } : { type: "label", options: textOrOptions };
|
|
2202
|
+
return this.commit(definition);
|
|
2203
|
+
}
|
|
2204
|
+
getByPlaceholder(textOrOptions, options) {
|
|
2205
|
+
const definition = typeof textOrOptions === "string" || textOrOptions instanceof RegExp ? options !== void 0 ? { type: "placeholder", text: textOrOptions, options } : { type: "placeholder", text: textOrOptions } : { type: "placeholder", options: textOrOptions };
|
|
2206
|
+
return this.commit(definition);
|
|
2207
|
+
}
|
|
2208
|
+
getByAltText(textOrOptions, options) {
|
|
2209
|
+
const definition = typeof textOrOptions === "string" || textOrOptions instanceof RegExp ? options !== void 0 ? { type: "altText", text: textOrOptions, options } : { type: "altText", text: textOrOptions } : { type: "altText", options: textOrOptions };
|
|
2210
|
+
return this.commit(definition);
|
|
2211
|
+
}
|
|
2212
|
+
getByTitle(textOrOptions, options) {
|
|
2213
|
+
const definition = typeof textOrOptions === "string" || textOrOptions instanceof RegExp ? options !== void 0 ? { type: "title", text: textOrOptions, options } : { type: "title", text: textOrOptions } : { type: "title", options: textOrOptions };
|
|
2214
|
+
return this.commit(definition);
|
|
2215
|
+
}
|
|
2216
|
+
locator(selectorOrOptions, options) {
|
|
2217
|
+
const definition = typeof selectorOrOptions === "string" ? options !== void 0 ? { type: "locator", selector: selectorOrOptions, options } : { type: "locator", selector: selectorOrOptions } : { type: "locator", options: selectorOrOptions };
|
|
2218
|
+
return this.commit(definition);
|
|
2219
|
+
}
|
|
2220
|
+
/**
|
|
2221
|
+
* Uses Playwright `frameLocator` semantics to define the locator strategy, entering the targeted
|
|
2222
|
+
* frame for subsequent chained locators. When seeded, `selector` may be omitted to retain the
|
|
2223
|
+
* existing selector while overriding options elsewhere.
|
|
2224
|
+
*
|
|
2225
|
+
* @example
|
|
2226
|
+
* ```ts
|
|
2227
|
+
* registry.add("frame.login").frameLocator("iframe.auth");
|
|
2228
|
+
* ```
|
|
2229
|
+
*/
|
|
2230
|
+
frameLocator(selector) {
|
|
2231
|
+
const definition = selector ? { type: "frameLocator", selector } : { type: "frameLocator" };
|
|
2232
|
+
return this.commit(definition);
|
|
2233
|
+
}
|
|
2234
|
+
/**
|
|
2235
|
+
* Uses Playwright `getByTestId` semantics to define the locator strategy. When seeded, omitting
|
|
2236
|
+
* the argument inherits the seeded `testId` while allowing options from other overrides.
|
|
2237
|
+
*
|
|
2238
|
+
* @example
|
|
2239
|
+
* ```ts
|
|
2240
|
+
* registry.add("card.title").getByTestId("card-title");
|
|
2241
|
+
* ```
|
|
2242
|
+
*/
|
|
2243
|
+
getByTestId(testId) {
|
|
2244
|
+
const definition = testId ? { type: "testId", testId } : { type: "testId" };
|
|
2245
|
+
return this.commit(definition);
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Targets elements by `id`, normalizing string or RegExp input. When seeded, the argument can be
|
|
2249
|
+
* omitted to inherit the seeded id.
|
|
2250
|
+
*
|
|
2251
|
+
* @example
|
|
2252
|
+
* ```ts
|
|
2253
|
+
* registry.add("modal.close").getById("close-modal");
|
|
2254
|
+
* ```
|
|
2255
|
+
*/
|
|
2256
|
+
getById(id) {
|
|
2257
|
+
const definition = id ? { type: "id", id: normalizeIdValue(id) } : { type: "id" };
|
|
2258
|
+
return this.commit(definition);
|
|
2259
|
+
}
|
|
2260
|
+
commit(definition) {
|
|
2261
|
+
this.ensureDefinitionAllowedWithRollback(definition);
|
|
2262
|
+
const mergedDefinition = this.seededDefinition ? applyDefinitionPatch(this.definition, definition) : definition;
|
|
2263
|
+
this.definition = mergedDefinition;
|
|
2264
|
+
if (this.reuseType && this.seededDefinition) {
|
|
2265
|
+
this.overrideApplied = true;
|
|
2266
|
+
}
|
|
2267
|
+
this.persist();
|
|
2268
|
+
return this.getPostDefinitionView();
|
|
2269
|
+
}
|
|
2270
|
+
applyFilter(filter) {
|
|
2271
|
+
this.ensureDefinition();
|
|
2272
|
+
this.steps.push({ kind: "filter", filter });
|
|
2273
|
+
this.persist();
|
|
2274
|
+
}
|
|
2275
|
+
applyIndex(index) {
|
|
2276
|
+
this.ensureDefinition();
|
|
2277
|
+
this.steps.push({ kind: "index", index });
|
|
2278
|
+
this.persist();
|
|
2279
|
+
}
|
|
2280
|
+
ensureDefinitionAllowedWithRollback(definition) {
|
|
2281
|
+
if (this.seededDefinition) {
|
|
2282
|
+
if (definition.type !== this.reuseType) {
|
|
2283
|
+
this.rollbackSeededRegistration();
|
|
2284
|
+
throw new Error(
|
|
2285
|
+
`The locator definition for "${this.path}" must use the "${this.reuseType}" strategy when reusing a locator.`
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
if (this.overrideApplied) {
|
|
2289
|
+
throw new Error(
|
|
2290
|
+
`A locator definition for "${this.path}" was already provided from reuse; only one matching override is allowed.`
|
|
2291
|
+
);
|
|
2292
|
+
}
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
if (this.definition) {
|
|
2296
|
+
throw new Error(
|
|
2297
|
+
`A locator definition for "${this.path}" has already been provided; only one locator type can be set for a registration.`
|
|
2298
|
+
);
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
ensureDefinition() {
|
|
2302
|
+
if (!this.definition) {
|
|
2303
|
+
throw new Error(`A locator definition must be provided before applying filters or indices for "${this.path}".`);
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
persist() {
|
|
2307
|
+
if (!this.definition) {
|
|
2308
|
+
throw new Error(`No locator schema definition provided for path "${this.path}".`);
|
|
2309
|
+
}
|
|
2310
|
+
const record = {
|
|
2311
|
+
locatorSchemaPath: this.path,
|
|
2312
|
+
definition: this.definition,
|
|
2313
|
+
steps: normalizeSteps(this.steps),
|
|
2314
|
+
...this.description !== void 0 ? { description: this.description } : {}
|
|
2315
|
+
};
|
|
2316
|
+
if (this.registered) {
|
|
2317
|
+
this.registry.replace(this.path, record);
|
|
2318
|
+
} else {
|
|
2319
|
+
this.registry.register(this.path, record);
|
|
2320
|
+
this.registered = true;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
rollbackSeededRegistration() {
|
|
2324
|
+
if (this.seededDefinition && this.registered) {
|
|
2325
|
+
this.registry.unregister(this.path);
|
|
2326
|
+
this.registered = false;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
getPostDefinitionView() {
|
|
2330
|
+
if (this.postDefinitionView) {
|
|
2331
|
+
return this.postDefinitionView;
|
|
2332
|
+
}
|
|
2333
|
+
const view = {
|
|
2334
|
+
filter: (filter) => {
|
|
2335
|
+
this.applyFilter(filter);
|
|
2336
|
+
return view;
|
|
2337
|
+
},
|
|
2338
|
+
nth: (index) => {
|
|
2339
|
+
this.applyIndex(index);
|
|
2340
|
+
return view;
|
|
2341
|
+
},
|
|
2342
|
+
describe: (description) => {
|
|
2343
|
+
this.description = description;
|
|
2344
|
+
this.persist();
|
|
2345
|
+
return view;
|
|
2346
|
+
}
|
|
2347
|
+
};
|
|
2348
|
+
this.postDefinitionView = view;
|
|
2349
|
+
return view;
|
|
2350
|
+
}
|
|
2351
|
+
};
|
|
2352
|
+
|
|
2353
|
+
// srcV2/locators/reusableLocatorBuilder.ts
|
|
2354
|
+
var ReusableLocatorBuilder = class {
|
|
2355
|
+
stepsList;
|
|
2356
|
+
definitionValue;
|
|
2357
|
+
descriptionValue;
|
|
2358
|
+
type;
|
|
2359
|
+
constructor(definition, steps = []) {
|
|
2360
|
+
this.definitionValue = definition;
|
|
2361
|
+
this.type = definition.type;
|
|
2362
|
+
this.stepsList = normalizeSteps(steps);
|
|
2363
|
+
}
|
|
2364
|
+
filter(filter) {
|
|
2365
|
+
this.stepsList.push({ kind: "filter", filter });
|
|
2366
|
+
return this;
|
|
2367
|
+
}
|
|
2368
|
+
nth(index) {
|
|
2369
|
+
this.stepsList.push({ kind: "index", index });
|
|
2370
|
+
return this;
|
|
2371
|
+
}
|
|
2372
|
+
describe(description) {
|
|
2373
|
+
this.descriptionValue = description;
|
|
2374
|
+
return this;
|
|
2375
|
+
}
|
|
2376
|
+
get definition() {
|
|
2377
|
+
return this.definitionValue;
|
|
2378
|
+
}
|
|
2379
|
+
get steps() {
|
|
2380
|
+
return normalizeSteps(this.stepsList);
|
|
2381
|
+
}
|
|
2382
|
+
get description() {
|
|
2383
|
+
return this.descriptionValue;
|
|
2384
|
+
}
|
|
2385
|
+
};
|
|
2386
|
+
var ReusableLocatorFactory = class {
|
|
2387
|
+
getByRole(role, options) {
|
|
2388
|
+
const definition = options !== void 0 ? { type: "role", role, options } : { type: "role", role };
|
|
2389
|
+
return this.create(definition);
|
|
2390
|
+
}
|
|
2391
|
+
getByText(text, options) {
|
|
2392
|
+
const definition = options !== void 0 ? { type: "text", text, options } : { type: "text", text };
|
|
2393
|
+
return this.create(definition);
|
|
2394
|
+
}
|
|
2395
|
+
getByLabel(text, options) {
|
|
2396
|
+
const definition = options !== void 0 ? { type: "label", text, options } : { type: "label", text };
|
|
2397
|
+
return this.create(definition);
|
|
2398
|
+
}
|
|
2399
|
+
getByPlaceholder(text, options) {
|
|
2400
|
+
const definition = options !== void 0 ? { type: "placeholder", text, options } : { type: "placeholder", text };
|
|
2401
|
+
return this.create(definition);
|
|
2402
|
+
}
|
|
2403
|
+
getByAltText(text, options) {
|
|
2404
|
+
const definition = options !== void 0 ? { type: "altText", text, options } : { type: "altText", text };
|
|
2405
|
+
return this.create(definition);
|
|
2406
|
+
}
|
|
2407
|
+
getByTitle(text, options) {
|
|
2408
|
+
const definition = options !== void 0 ? { type: "title", text, options } : { type: "title", text };
|
|
2409
|
+
return this.create(definition);
|
|
2410
|
+
}
|
|
2411
|
+
locator(selector, options) {
|
|
2412
|
+
const definition = options !== void 0 ? { type: "locator", selector, options } : { type: "locator", selector };
|
|
2413
|
+
return this.create(definition);
|
|
2414
|
+
}
|
|
2415
|
+
frameLocator(selector) {
|
|
2416
|
+
return this.create({ type: "frameLocator", selector });
|
|
2417
|
+
}
|
|
2418
|
+
getByTestId(testId) {
|
|
2419
|
+
return this.create({ type: "testId", testId });
|
|
2420
|
+
}
|
|
2421
|
+
getById(id) {
|
|
2422
|
+
return this.create({ type: "id", id: normalizeIdValue(id) });
|
|
2423
|
+
}
|
|
2424
|
+
create(definition) {
|
|
2425
|
+
return new ReusableLocatorBuilder(definition);
|
|
2426
|
+
}
|
|
2427
|
+
};
|
|
2428
|
+
|
|
2429
|
+
// srcV2/locators/locatorRegistry.ts
|
|
2430
|
+
var LocatorRegistryInternal = class {
|
|
2431
|
+
constructor(page) {
|
|
2432
|
+
this.page = page;
|
|
2433
|
+
this.createReusable = new ReusableLocatorFactory();
|
|
2434
|
+
}
|
|
2435
|
+
schemas = /* @__PURE__ */ new Map();
|
|
2436
|
+
/**
|
|
2437
|
+
* Factory for reusable locator seeds that capture a locator strategy plus any chained
|
|
2438
|
+
* `filter`/`nth` steps without registering them. Pass the resulting seed to
|
|
2439
|
+
* {@link LocatorRegistryInternal.add} via `{ reuse }` to register a path that inherits the
|
|
2440
|
+
* stored definition and steps.
|
|
2441
|
+
*
|
|
2442
|
+
* @example
|
|
2443
|
+
* ```ts
|
|
2444
|
+
* const seed = registry.createReusable.getByRole("heading", { level: 2 }).filter({ hasText: /Summary/ });
|
|
2445
|
+
* registry.add("hero.title", { reuse: seed }).getByRole({ name: "Summary" });
|
|
2446
|
+
* ```
|
|
2447
|
+
*/
|
|
2448
|
+
createReusable;
|
|
2449
|
+
normalizeRecord(record) {
|
|
2450
|
+
return {
|
|
2451
|
+
locatorSchemaPath: record.locatorSchemaPath,
|
|
2452
|
+
definition: record.definition,
|
|
2453
|
+
steps: normalizeSteps(record.steps),
|
|
2454
|
+
...record.description !== void 0 ? { description: record.description } : {}
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
cloneRecordForReuse(record, path) {
|
|
2458
|
+
return {
|
|
2459
|
+
locatorSchemaPath: path,
|
|
2460
|
+
definition: cloneLocatorStrategyDefinition(record.definition),
|
|
2461
|
+
steps: normalizeSteps(record.steps),
|
|
2462
|
+
...record.description !== void 0 ? { description: record.description } : {}
|
|
2463
|
+
};
|
|
2464
|
+
}
|
|
2465
|
+
add(path, options) {
|
|
2466
|
+
const reuse = options?.reuse;
|
|
2467
|
+
if (!reuse) {
|
|
2468
|
+
return new LocatorRegistrationBuilder(
|
|
2469
|
+
this,
|
|
2470
|
+
path
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
if (typeof reuse === "string") {
|
|
2474
|
+
const sourceRecord = this.get(reuse);
|
|
2475
|
+
const cloned = this.cloneRecordForReuse(sourceRecord, path);
|
|
2476
|
+
this.register(path, cloned);
|
|
2477
|
+
return void 0;
|
|
2478
|
+
}
|
|
2479
|
+
const reusedRecord = this.cloneRecordForReuse(
|
|
2480
|
+
{
|
|
2481
|
+
locatorSchemaPath: path,
|
|
2482
|
+
definition: reuse.definition,
|
|
2483
|
+
steps: reuse.steps,
|
|
2484
|
+
description: reuse.description
|
|
2485
|
+
},
|
|
2486
|
+
path
|
|
2487
|
+
);
|
|
2488
|
+
return new LocatorRegistrationBuilder(
|
|
2489
|
+
this,
|
|
2490
|
+
path,
|
|
2491
|
+
{
|
|
2492
|
+
initialDefinition: reusedRecord.definition,
|
|
2493
|
+
initialSteps: reusedRecord.steps,
|
|
2494
|
+
reuseType: reusedRecord.definition.type,
|
|
2495
|
+
initialDescription: reusedRecord.description
|
|
2496
|
+
}
|
|
2497
|
+
).persistSeededDefinition();
|
|
2498
|
+
}
|
|
2499
|
+
register(path, record) {
|
|
2500
|
+
validateLocatorSchemaPath(path);
|
|
2501
|
+
if (this.schemas.has(path)) {
|
|
2502
|
+
const existing = this.schemas.get(path);
|
|
2503
|
+
if (!existing) {
|
|
2504
|
+
throw new Error(`A locator schema with the path "${path}" already exists.`);
|
|
2505
|
+
}
|
|
2506
|
+
const errorDetails = stringifyForLog({
|
|
2507
|
+
existing,
|
|
2508
|
+
attempted: record
|
|
2509
|
+
});
|
|
2510
|
+
throw new Error(`A locator schema with the path "${path}" already exists.
|
|
2511
|
+
Existing Schema: ${errorDetails}`);
|
|
2512
|
+
}
|
|
2513
|
+
this.schemas.set(path, this.normalizeRecord(record));
|
|
2514
|
+
}
|
|
2515
|
+
replace(path, record) {
|
|
2516
|
+
validateLocatorSchemaPath(path);
|
|
2517
|
+
if (!this.schemas.has(path)) {
|
|
2518
|
+
throw new Error(`No locator schema registered for path "${path}".`);
|
|
2519
|
+
}
|
|
2520
|
+
this.schemas.set(path, this.normalizeRecord(record));
|
|
2521
|
+
}
|
|
2522
|
+
get(path) {
|
|
2523
|
+
const record = this.schemas.get(path);
|
|
2524
|
+
if (!record) {
|
|
2525
|
+
throw new Error(`No locator schema registered for path "${path}".`);
|
|
2526
|
+
}
|
|
2527
|
+
return {
|
|
2528
|
+
locatorSchemaPath: record.locatorSchemaPath,
|
|
2529
|
+
definition: record.definition,
|
|
2530
|
+
steps: normalizeSteps(record.steps),
|
|
2531
|
+
...record.description !== void 0 ? { description: record.description } : {}
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
unregister(path) {
|
|
2535
|
+
this.schemas.delete(path);
|
|
2536
|
+
}
|
|
2537
|
+
getIfExists(path) {
|
|
2538
|
+
const record = this.schemas.get(path);
|
|
2539
|
+
if (!record) {
|
|
2540
|
+
return void 0;
|
|
2541
|
+
}
|
|
2542
|
+
return {
|
|
2543
|
+
locatorSchemaPath: record.locatorSchemaPath,
|
|
2544
|
+
definition: record.definition,
|
|
2545
|
+
steps: normalizeSteps(record.steps),
|
|
2546
|
+
...record.description !== void 0 ? { description: record.description } : {}
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
resolveFilterLocator(locatorReference, context) {
|
|
2550
|
+
if (isLocatorInstance(locatorReference)) {
|
|
2551
|
+
return locatorReference;
|
|
2552
|
+
}
|
|
2553
|
+
if (typeof locatorReference === "string") {
|
|
2554
|
+
return this.getLocatorWithContext(locatorReference, context);
|
|
2555
|
+
}
|
|
2556
|
+
throw new Error(
|
|
2557
|
+
`Unsupported filter reference while resolving "${context.rootPath}". Filter has/hasNot supports Playwright Locator instances or registry path strings.`
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
createResolutionContext(rootPath) {
|
|
2561
|
+
return {
|
|
2562
|
+
rootPath,
|
|
2563
|
+
resolvingPaths: /* @__PURE__ */ new Set()
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
getLocatorWithContext(path, context) {
|
|
2567
|
+
if (context.resolvingPaths.has(path)) {
|
|
2568
|
+
throw new Error(`Detected cyclic filter reference while resolving "${context.rootPath}": "${path}".`);
|
|
2569
|
+
}
|
|
2570
|
+
context.resolvingPaths.add(path);
|
|
2571
|
+
try {
|
|
2572
|
+
const record = this.get(path);
|
|
2573
|
+
const definitions = /* @__PURE__ */ new Map([[path, record.definition]]);
|
|
2574
|
+
const steps = /* @__PURE__ */ new Map([[path, normalizeSteps(record.steps)]]);
|
|
2575
|
+
const { locator } = this.buildLocatorChain(path, definitions, steps, void 0, record.description, context);
|
|
2576
|
+
if (!locator) {
|
|
2577
|
+
throw new Error(`Unable to resolve direct locator for path "${path}".`);
|
|
2578
|
+
}
|
|
2579
|
+
return locator;
|
|
2580
|
+
} finally {
|
|
2581
|
+
context.resolvingPaths.delete(path);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
resolveFiltersForTarget(filters, context) {
|
|
2585
|
+
if (!filters || filters.length === 0) {
|
|
2586
|
+
return [];
|
|
2587
|
+
}
|
|
2588
|
+
const resolved = [];
|
|
2589
|
+
for (const filter of filters) {
|
|
2590
|
+
if (filter && typeof filter === "object" && ("has" in filter || "hasNot" in filter)) {
|
|
2591
|
+
const { has, hasNot, ...rest } = filter;
|
|
2592
|
+
const normalizedFilter = {
|
|
2593
|
+
...rest
|
|
2594
|
+
};
|
|
2595
|
+
if (has !== void 0) {
|
|
2596
|
+
normalizedFilter.has = this.resolveFilterLocator(has, context);
|
|
2597
|
+
}
|
|
2598
|
+
if (hasNot !== void 0) {
|
|
2599
|
+
normalizedFilter.hasNot = this.resolveFilterLocator(hasNot, context);
|
|
2600
|
+
}
|
|
2601
|
+
resolved.push(normalizedFilter);
|
|
2602
|
+
continue;
|
|
2603
|
+
}
|
|
2604
|
+
resolved.push(filter);
|
|
2605
|
+
}
|
|
2606
|
+
return resolved;
|
|
2607
|
+
}
|
|
2608
|
+
/**
|
|
2609
|
+
* Returns a mutable query builder clone for the provided path. Use the builder to add or clear
|
|
2610
|
+
* `filter`/`nth` steps, or to `update`/`replace` locator definitions before resolving locators.
|
|
2611
|
+
* Changes affect only the builder clone; the registry’s stored schema remains untouched.
|
|
2612
|
+
*
|
|
2613
|
+
* @example
|
|
2614
|
+
* ```ts
|
|
2615
|
+
* const builder = registry
|
|
2616
|
+
* .getLocatorSchema("section.button")
|
|
2617
|
+
* .filter("section.button", { hasText: /Save/ })
|
|
2618
|
+
* .nth("section", 0);
|
|
2619
|
+
* const locator = builder.getNestedLocator();
|
|
2620
|
+
* ```
|
|
2621
|
+
*/
|
|
2622
|
+
getLocatorSchema(path) {
|
|
2623
|
+
return new LocatorQueryBuilder(this, path);
|
|
2624
|
+
}
|
|
2625
|
+
/**
|
|
2626
|
+
* Resolves the Playwright {@link Locator} for the provided path, applying only the terminal
|
|
2627
|
+
* definition and its recorded steps (no ancestor chaining). Throws if the path is not registered.
|
|
2628
|
+
*
|
|
2629
|
+
* @example
|
|
2630
|
+
* ```ts
|
|
2631
|
+
* const button = registry.getLocator("form.submit");
|
|
2632
|
+
* await button.click();
|
|
2633
|
+
* ```
|
|
2634
|
+
*/
|
|
2635
|
+
getLocator(path) {
|
|
2636
|
+
return this.getLocatorSchema(path).getLocator();
|
|
2637
|
+
}
|
|
2638
|
+
/**
|
|
2639
|
+
* Resolves a chained Playwright {@link Locator} for a nested path, traversing each registered
|
|
2640
|
+
* segment and applying their steps in order. Throws if any segment in the chain is missing.
|
|
2641
|
+
*
|
|
2642
|
+
* @example
|
|
2643
|
+
* ```ts
|
|
2644
|
+
* const nested = registry.getNestedLocator("list.item");
|
|
2645
|
+
* await expect(nested).toBeVisible();
|
|
2646
|
+
* ```
|
|
2647
|
+
*/
|
|
2648
|
+
getNestedLocator(path) {
|
|
2649
|
+
return this.getLocatorSchema(path).getNestedLocator();
|
|
2650
|
+
}
|
|
2651
|
+
buildLocatorChain(path, definitions, steps, tombstones, terminalDescription, context) {
|
|
2652
|
+
const resolutionContext = context ?? this.createResolutionContext(path);
|
|
2653
|
+
const chain = expandSchemaPath(path);
|
|
2654
|
+
const registeredChain = chain.filter((part) => definitions.has(part));
|
|
2655
|
+
if (tombstones?.has(path) || !definitions.has(path)) {
|
|
2656
|
+
throw new Error(`No locator schema registered for path "${path}".`);
|
|
2657
|
+
}
|
|
2658
|
+
let currentTarget = this.page;
|
|
2659
|
+
let lastLocator = null;
|
|
2660
|
+
const debugSteps = [];
|
|
2661
|
+
const lastIndex = registeredChain.length - 1;
|
|
2662
|
+
for (const [index, part] of registeredChain.entries()) {
|
|
2663
|
+
const isTerminalStep = index === lastIndex;
|
|
2664
|
+
const definition = definitions.get(part);
|
|
2665
|
+
if (tombstones?.has(part)) {
|
|
2666
|
+
if (isTerminalStep) {
|
|
2667
|
+
throw new Error(`No locator schema registered for path "${path}".`);
|
|
2668
|
+
}
|
|
2669
|
+
continue;
|
|
2670
|
+
}
|
|
2671
|
+
if (!definition) {
|
|
2672
|
+
throw new Error(`Missing locator definition for "${part}" while resolving "${path}".`);
|
|
2673
|
+
}
|
|
2674
|
+
if (isFrameLocatorDefinition(definition)) {
|
|
2675
|
+
const frameLocator = createLocator(currentTarget, definition);
|
|
2676
|
+
const isTerminalStep2 = index === lastIndex;
|
|
2677
|
+
if (isTerminalStep2) {
|
|
2678
|
+
const ownerLocator = frameLocator.owner();
|
|
2679
|
+
currentTarget = ownerLocator;
|
|
2680
|
+
lastLocator = ownerLocator;
|
|
2681
|
+
} else {
|
|
2682
|
+
currentTarget = frameLocator;
|
|
2683
|
+
lastLocator = frameLocator;
|
|
2684
|
+
}
|
|
2685
|
+
if (isTerminalStep2 && terminalDescription && isLocatorInstance(lastLocator)) {
|
|
2686
|
+
lastLocator = lastLocator.describe(terminalDescription);
|
|
2687
|
+
currentTarget = lastLocator;
|
|
2688
|
+
}
|
|
2689
|
+
debugSteps.push({ path: part, definition, appliedFilters: [], recordedSteps: [] });
|
|
2690
|
+
continue;
|
|
2691
|
+
}
|
|
2692
|
+
const locatorResult = createLocator(currentTarget, definition);
|
|
2693
|
+
const recordedSteps = steps.get(part) ?? [];
|
|
2694
|
+
let resolvedLocator = locatorResult;
|
|
2695
|
+
const appliedFilters = [];
|
|
2696
|
+
let appliedIndex;
|
|
2697
|
+
for (const step2 of recordedSteps) {
|
|
2698
|
+
if (step2.kind === "filter") {
|
|
2699
|
+
const [resolvedFilter] = this.resolveFiltersForTarget([step2.filter], resolutionContext);
|
|
2700
|
+
if (resolvedFilter) {
|
|
2701
|
+
resolvedLocator = resolvedLocator.filter(resolvedFilter);
|
|
2702
|
+
appliedFilters.push(resolvedFilter);
|
|
2703
|
+
}
|
|
2704
|
+
} else {
|
|
2705
|
+
appliedIndex = step2.index ?? null;
|
|
2706
|
+
resolvedLocator = applyIndexSelector(resolvedLocator, appliedIndex ?? void 0);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
if (isTerminalStep && terminalDescription) {
|
|
2710
|
+
resolvedLocator = resolvedLocator.describe(terminalDescription);
|
|
2711
|
+
}
|
|
2712
|
+
currentTarget = resolvedLocator;
|
|
2713
|
+
lastLocator = resolvedLocator;
|
|
2714
|
+
debugSteps.push({
|
|
2715
|
+
path: part,
|
|
2716
|
+
definition,
|
|
2717
|
+
appliedFilters,
|
|
2718
|
+
index: appliedIndex,
|
|
2719
|
+
recordedSteps
|
|
2720
|
+
});
|
|
2721
|
+
}
|
|
2722
|
+
return { locator: lastLocator, steps: debugSteps };
|
|
2723
|
+
}
|
|
2724
|
+
};
|
|
2725
|
+
var createRegistryWithAccessors = (page) => {
|
|
2726
|
+
const _assertValidPaths = true;
|
|
2727
|
+
const registryInstance = new LocatorRegistryInternal(page);
|
|
2728
|
+
const add = registryInstance.add.bind(registryInstance);
|
|
2729
|
+
const getLocator = registryInstance.getLocator.bind(registryInstance);
|
|
2730
|
+
const getNestedLocator = registryInstance.getNestedLocator.bind(registryInstance);
|
|
2731
|
+
const getLocatorSchema = registryInstance.getLocatorSchema.bind(registryInstance);
|
|
2732
|
+
const registry = registryInstance;
|
|
2733
|
+
return { registry, add, getLocator, getNestedLocator, getLocatorSchema };
|
|
2734
|
+
};
|
|
2735
|
+
|
|
2736
|
+
// src/basePageV1toV2.ts
|
|
2737
|
+
var BasePageV1toV2 = class {
|
|
2738
|
+
/** Provides Playwright page methods */
|
|
2739
|
+
page;
|
|
2740
|
+
/** Playwright TestInfo contains information about currently running test, available to any test function */
|
|
2741
|
+
testInfo;
|
|
2742
|
+
/** Selectors can be used to install custom selector engines.*/
|
|
2743
|
+
selector;
|
|
2744
|
+
/** The base URL of the Page Object Class */
|
|
2745
|
+
baseUrl;
|
|
2746
|
+
/** The URL path of the Page Object Class */
|
|
2747
|
+
urlPath;
|
|
2748
|
+
/** The full URL of the Page Object Class */
|
|
2749
|
+
fullUrl;
|
|
2750
|
+
/** The name of the Page Object Class */
|
|
2751
|
+
pocName;
|
|
2752
|
+
/** The Page Object Class' PlaywrightReportLogger instance, prefixed with its name. Log levels: debug, info, warn, and error. */
|
|
2753
|
+
log;
|
|
2754
|
+
/** The SessionStorage class provides methods for setting and getting session storage data in Playwright.*/
|
|
2755
|
+
sessionStorage;
|
|
2756
|
+
/**
|
|
2757
|
+
* locators:
|
|
2758
|
+
* An instance of GetLocatorBase that handles schema management and provides getLocatorSchema calls.
|
|
2759
|
+
* Initially, LocatorSubstring is undefined. Once getLocatorSchema(path) is called,
|
|
2760
|
+
* we get a chainable object typed with LocatorSubstring = P.
|
|
2761
|
+
*/
|
|
2762
|
+
locators;
|
|
2763
|
+
/**
|
|
2764
|
+
* v2 locator registry and accessors, used for migration to the fluent registry DSL.
|
|
2765
|
+
*/
|
|
2766
|
+
locatorRegistry;
|
|
2767
|
+
add;
|
|
2768
|
+
getLocator;
|
|
2769
|
+
getLocatorSchema;
|
|
2770
|
+
getNestedLocator;
|
|
2771
|
+
constructor(page, testInfo, baseUrl, urlPath, pocName, pwrl, locatorSubstring) {
|
|
2772
|
+
this.page = page;
|
|
2773
|
+
this.testInfo = testInfo;
|
|
2774
|
+
this.selector = selectors2;
|
|
2775
|
+
this.baseUrl = baseUrl;
|
|
2776
|
+
this.urlPath = urlPath;
|
|
2777
|
+
this.fullUrl = this.constructFullUrl(baseUrl, urlPath);
|
|
2778
|
+
this.pocName = pocName;
|
|
2779
|
+
this.log = pwrl.getNewChildLogger(pocName);
|
|
2780
|
+
const classDeprecationMessage = "[POMWright] BasePageV1toV2 is a transitional bridge and will be removed in 2.0.0. Prefer PageObject for new work and migrate existing classes to PageObject.";
|
|
2781
|
+
warnDeprecationOncePerTest(`${this.constructor.name}-class-deprecation`, classDeprecationMessage, this.log);
|
|
2782
|
+
const { registry, add, getLocator, getNestedLocator, getLocatorSchema } = createRegistryWithAccessors(page);
|
|
2783
|
+
this.locatorRegistry = registry;
|
|
2784
|
+
this.add = add;
|
|
2785
|
+
this.getLocator = getLocator;
|
|
2786
|
+
this.getLocatorSchema = getLocatorSchema;
|
|
2787
|
+
this.getNestedLocator = getNestedLocator;
|
|
2788
|
+
this.defineLocators();
|
|
2789
|
+
this.locators = new GetLocatorBase(
|
|
2790
|
+
this,
|
|
2791
|
+
this.log.getNewChildLogger("GetLocator"),
|
|
2792
|
+
locatorSubstring
|
|
2793
|
+
);
|
|
2794
|
+
const initLocatorSchemasDeprecationMessage = "[POMWright] initLocatorSchemas is deprecated and will be removed in 2.0.0. Define locators with the v2 registry DSL in defineLocators instead.";
|
|
2795
|
+
warnDeprecationOncePerTest(
|
|
2796
|
+
`${this.constructor.name}-initLocatorSchemas-deprecation`,
|
|
2797
|
+
initLocatorSchemasDeprecationMessage,
|
|
2798
|
+
this.log
|
|
2799
|
+
);
|
|
2800
|
+
this.initLocatorSchemas();
|
|
2801
|
+
this.sessionStorage = new SessionStorage(this.page, this.pocName);
|
|
2802
|
+
}
|
|
2803
|
+
/**
|
|
2804
|
+
* constructFullUrl:
|
|
2805
|
+
* Combines baseUrl and urlPath, handling both strings and RegExps.
|
|
2806
|
+
* Ensures a flexible approach to URL matching (string or regex-based).
|
|
2807
|
+
*/
|
|
2808
|
+
constructFullUrl(baseUrl, urlPath) {
|
|
2809
|
+
const escapeStringForRegExp = (str) => str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
2810
|
+
if (typeof baseUrl === "string" && typeof urlPath === "string") {
|
|
2811
|
+
return `${baseUrl}${urlPath}`;
|
|
2812
|
+
}
|
|
2813
|
+
if (typeof baseUrl === "string" && urlPath instanceof RegExp) {
|
|
2814
|
+
return new RegExp(`^${escapeStringForRegExp(baseUrl)}${urlPath.source}`);
|
|
2815
|
+
}
|
|
2816
|
+
if (baseUrl instanceof RegExp && typeof urlPath === "string") {
|
|
2817
|
+
return new RegExp(`${baseUrl.source}${escapeStringForRegExp(urlPath)}$`);
|
|
2818
|
+
}
|
|
2819
|
+
if (baseUrl instanceof RegExp && urlPath instanceof RegExp) {
|
|
2820
|
+
return new RegExp(`${baseUrl.source}${urlPath.source}`);
|
|
2821
|
+
}
|
|
2822
|
+
throw new Error("Invalid baseUrl or urlPath types. Expected string or RegExp.");
|
|
2823
|
+
}
|
|
2824
|
+
};
|
|
2825
|
+
|
|
2826
|
+
// srcV2/helpers/navigation.ts
|
|
2827
|
+
import { expect, test as test3 } from "@playwright/test";
|
|
2828
|
+
var DEFAULT_WAIT_UNTIL = "load";
|
|
2829
|
+
var DEFAULT_LOAD_STATE = "load";
|
|
2830
|
+
var Navigation = class {
|
|
2831
|
+
constructor(page, baseUrl, urlPath, fullUrl, label, actions = null, defaultOptions) {
|
|
2832
|
+
this.page = page;
|
|
2833
|
+
this.baseUrl = baseUrl;
|
|
2834
|
+
this.urlPath = urlPath;
|
|
2835
|
+
this.fullUrl = fullUrl;
|
|
2836
|
+
this.label = label;
|
|
2837
|
+
this.pageActionsToPerform = actions ?? [];
|
|
2838
|
+
this.defaultOptions = defaultOptions;
|
|
2839
|
+
}
|
|
2840
|
+
pageActionsToPerform;
|
|
2841
|
+
defaultOptions;
|
|
2842
|
+
resolveWaitUntil(options) {
|
|
2843
|
+
return options?.waitUntil ?? this.defaultOptions?.waitUntil ?? DEFAULT_WAIT_UNTIL;
|
|
2844
|
+
}
|
|
2845
|
+
resolveWaitForLoadState(options) {
|
|
2846
|
+
return options?.waitForLoadState ?? this.defaultOptions?.waitForLoadState ?? DEFAULT_LOAD_STATE;
|
|
2847
|
+
}
|
|
2848
|
+
async executeActions() {
|
|
2849
|
+
for (const action of this.pageActionsToPerform) {
|
|
2850
|
+
await action();
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
/**
|
|
2854
|
+
* Navigate to a provided URL or URL path. If the input starts with "/", the POC's baseUrl is used as a prefix.
|
|
2855
|
+
* Available only when baseUrl and urlPath are strings.
|
|
2856
|
+
*/
|
|
2857
|
+
async goto(urlPathOrUrl, options) {
|
|
2858
|
+
const waitUntil = this.resolveWaitUntil(options);
|
|
2859
|
+
if (typeof this.baseUrl !== "string" || typeof this.urlPath !== "string") {
|
|
2860
|
+
throw new Error("goto() is not supported when baseUrl or urlPath is a RegExp.");
|
|
2861
|
+
}
|
|
2862
|
+
await test3.step(`${this.label}: Navigate to the provided URL or URL Path`, async () => {
|
|
2863
|
+
const targetUrl = urlPathOrUrl.startsWith("/") ? `${this.baseUrl}${urlPathOrUrl}` : urlPathOrUrl;
|
|
2864
|
+
await this.page.goto(targetUrl, { waitUntil });
|
|
2865
|
+
});
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* Navigate to this page's fullUrl and run any post-navigation actions.
|
|
2869
|
+
* Available only when fullUrl is a string.
|
|
2870
|
+
*/
|
|
2871
|
+
async gotoThisPage(options) {
|
|
2872
|
+
if (typeof this.fullUrl !== "string") {
|
|
2873
|
+
throw new Error("gotoThisPage() is not supported when fullUrl is a RegExp.");
|
|
2874
|
+
}
|
|
2875
|
+
const waitUntil = this.resolveWaitUntil(options);
|
|
2876
|
+
const fullUrl = this.fullUrl;
|
|
2877
|
+
await test3.step(`${this.label}: Navigate to this Page`, async () => {
|
|
2878
|
+
await this.page.goto(fullUrl, { waitUntil });
|
|
2879
|
+
await this.executeActions();
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2882
|
+
/**
|
|
2883
|
+
* Expect to be on this page. Works with both string and RegExp fullUrl values.
|
|
2884
|
+
* Uses waitUntil from navigation options when waiting for URL.
|
|
2885
|
+
*/
|
|
2886
|
+
async expectThisPage(options) {
|
|
2887
|
+
const waitUntil = this.resolveWaitUntil(options);
|
|
2888
|
+
await test3.step(`${this.label}: Expect this Page`, async () => {
|
|
2889
|
+
await this.page.waitForURL(this.fullUrl, { waitUntil });
|
|
2890
|
+
await expect(async () => {
|
|
2891
|
+
if (this.fullUrl instanceof RegExp) {
|
|
2892
|
+
expect(this.page.url(), `expected '${this.fullUrl}', found '${this.page.url()}'`).toMatch(this.fullUrl);
|
|
2893
|
+
} else {
|
|
2894
|
+
expect(this.page.url(), `expected '${this.fullUrl}', found '${this.page.url()}'`).toBe(this.fullUrl);
|
|
2895
|
+
}
|
|
2896
|
+
}).toPass();
|
|
2897
|
+
await this.executeActions();
|
|
2898
|
+
});
|
|
2899
|
+
}
|
|
2900
|
+
/**
|
|
2901
|
+
* Expect to be on any other page (i.e. not this page).
|
|
2902
|
+
* Uses waitForLoadState from navigation options before validating URL.
|
|
2903
|
+
*/
|
|
2904
|
+
async expectAnotherPage(options) {
|
|
2905
|
+
const waitForLoadState = this.resolveWaitForLoadState(options);
|
|
2906
|
+
await test3.step(`${this.label}: Expect any other Page`, async () => {
|
|
2907
|
+
await this.page.waitForLoadState(waitForLoadState);
|
|
2908
|
+
if (this.fullUrl instanceof RegExp) {
|
|
2909
|
+
await expect.poll(async () => this.page.url(), {
|
|
2910
|
+
message: `expected url to not match '${this.fullUrl}'`
|
|
2911
|
+
}).not.toMatch(this.fullUrl);
|
|
2912
|
+
} else {
|
|
2913
|
+
await expect.poll(async () => this.page.url(), {
|
|
2914
|
+
message: `expected url to not be '${this.fullUrl}', found '${this.page.url()}'`
|
|
2915
|
+
}).not.toBe(this.fullUrl);
|
|
2916
|
+
}
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2919
|
+
};
|
|
2920
|
+
function createNavigation(page, baseUrl, urlPath, fullUrl, label, actions = null, defaultOptions) {
|
|
2921
|
+
const navigation = new Navigation(page, baseUrl, urlPath, fullUrl, label, actions, defaultOptions);
|
|
2922
|
+
return navigation;
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
// srcV2/helpers/sessionStorage.ts
|
|
2926
|
+
import { test as test4 } from "@playwright/test";
|
|
2927
|
+
var SessionStorage2 = class {
|
|
2928
|
+
// Initializes the class with a Playwright Page object and an optional label for step titles.
|
|
2929
|
+
constructor(page, options = {}) {
|
|
2930
|
+
this.page = page;
|
|
2931
|
+
this.options = options;
|
|
2932
|
+
}
|
|
2933
|
+
// Defines an object to hold states to be set in session storage, allowing any value type.
|
|
2934
|
+
queuedStates = {};
|
|
2935
|
+
// Indicates if the session storage manipulation has been initiated.
|
|
2936
|
+
isInitiated = false;
|
|
2937
|
+
getStepLabel(methodName) {
|
|
2938
|
+
const prefix = this.options.label ? `${this.options.label}.` : "";
|
|
2939
|
+
return `${prefix}SessionStorage.${methodName}:`;
|
|
2940
|
+
}
|
|
2941
|
+
async hasContext() {
|
|
2942
|
+
return await this.page.evaluate(() => {
|
|
2943
|
+
return typeof window !== "undefined" && window.sessionStorage !== void 0;
|
|
2944
|
+
});
|
|
2945
|
+
}
|
|
2946
|
+
async waitForContextAvailability() {
|
|
2947
|
+
try {
|
|
2948
|
+
const contextExists = await this.hasContext();
|
|
2949
|
+
if (contextExists) {
|
|
2950
|
+
return;
|
|
2951
|
+
}
|
|
2952
|
+
} catch (_e) {
|
|
2953
|
+
}
|
|
2954
|
+
await new Promise((resolve) => {
|
|
2955
|
+
const handler = async (frame) => {
|
|
2956
|
+
if (frame !== this.page.mainFrame()) {
|
|
2957
|
+
return;
|
|
2958
|
+
}
|
|
2959
|
+
try {
|
|
2960
|
+
const contextExists = await this.hasContext();
|
|
2961
|
+
if (!contextExists) {
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
} catch (_e) {
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2967
|
+
this.page.off("framenavigated", handler);
|
|
2968
|
+
resolve();
|
|
2969
|
+
};
|
|
2970
|
+
this.page.on("framenavigated", handler);
|
|
2971
|
+
});
|
|
2972
|
+
}
|
|
2973
|
+
async ensureContext({ waitForContext = false } = {}) {
|
|
2974
|
+
try {
|
|
2975
|
+
const contextExists = await this.hasContext();
|
|
2976
|
+
if (contextExists) {
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
} catch (_e) {
|
|
2980
|
+
}
|
|
2981
|
+
if (!waitForContext) {
|
|
2982
|
+
throw new Error("SessionStorage context is not available.");
|
|
2983
|
+
}
|
|
2984
|
+
await this.waitForContextAvailability();
|
|
2985
|
+
}
|
|
2986
|
+
/** Writes states to session storage. Accepts an object with key-value pairs representing the states. */
|
|
2987
|
+
async writeToSessionStorage(states) {
|
|
2988
|
+
await this.page.evaluate((storage) => {
|
|
2989
|
+
for (const [key, value] of Object.entries(storage)) {
|
|
2990
|
+
window.sessionStorage.setItem(key, JSON.stringify(value));
|
|
2991
|
+
}
|
|
2992
|
+
}, states);
|
|
2993
|
+
}
|
|
2994
|
+
/** Reads all states from session storage and returns them as an object. */
|
|
2995
|
+
async readFromSessionStorage() {
|
|
2996
|
+
const storage = await this.page.evaluate(() => {
|
|
2997
|
+
const storage2 = {};
|
|
2998
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
2999
|
+
const key = sessionStorage.key(i);
|
|
3000
|
+
if (key !== null) {
|
|
3001
|
+
const item = sessionStorage.getItem(key);
|
|
3002
|
+
try {
|
|
3003
|
+
storage2[key] = item ? JSON.parse(item) : null;
|
|
3004
|
+
} catch (_e) {
|
|
3005
|
+
storage2[key] = item;
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
return storage2;
|
|
3010
|
+
});
|
|
3011
|
+
return storage;
|
|
3012
|
+
}
|
|
3013
|
+
/**
|
|
3014
|
+
* Sets the specified states in session storage.
|
|
3015
|
+
* Optionally waits for the next main-frame navigation to establish a valid context before writing,
|
|
3016
|
+
* and reloads the page after setting the data.
|
|
3017
|
+
*
|
|
3018
|
+
* Parameters:
|
|
3019
|
+
* states: Object representing the states to set in session storage.
|
|
3020
|
+
* reload: Boolean indicating whether to reload the page after setting the session storage data.
|
|
3021
|
+
* waitForContext: Boolean indicating whether to wait for a main-frame navigation and a valid context.
|
|
3022
|
+
*/
|
|
3023
|
+
async set(states, options = {}) {
|
|
3024
|
+
await test4.step(this.getStepLabel("set"), async () => {
|
|
3025
|
+
await this.ensureContext({ waitForContext: options.waitForContext });
|
|
3026
|
+
await this.writeToSessionStorage(states);
|
|
3027
|
+
if (options.reload) {
|
|
3028
|
+
await this.page.reload();
|
|
3029
|
+
}
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
3032
|
+
/**
|
|
3033
|
+
* Queues states to be set in the sessionStorage before the next navigation occurs.
|
|
3034
|
+
* Handles different scenarios based on multiple calls made before the navigation occurs.
|
|
3035
|
+
*
|
|
3036
|
+
* 1. No Context, Single Call: Queues and sets states upon the next navigation.
|
|
3037
|
+
* 2. No Context, Multiple Calls: Merges states from multiple calls and sets them upon the next navigation.
|
|
3038
|
+
* 3. With Context: Still queues until the next navigation.
|
|
3039
|
+
*
|
|
3040
|
+
* Parameters:
|
|
3041
|
+
* states: Object representing the states to queue for setting in session storage.
|
|
3042
|
+
*/
|
|
3043
|
+
async setOnNextNavigation(states) {
|
|
3044
|
+
this.queuedStates = { ...this.queuedStates, ...states };
|
|
3045
|
+
const populateStorage = async () => {
|
|
3046
|
+
await test4.step(this.getStepLabel("setOnNextNavigation"), async () => {
|
|
3047
|
+
await this.writeToSessionStorage(this.queuedStates);
|
|
3048
|
+
});
|
|
3049
|
+
this.queuedStates = {};
|
|
3050
|
+
};
|
|
3051
|
+
if (!this.isInitiated) {
|
|
3052
|
+
this.isInitiated = true;
|
|
3053
|
+
const handler = async (frame) => {
|
|
3054
|
+
if (frame !== this.page.mainFrame()) {
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
3057
|
+
await populateStorage();
|
|
3058
|
+
this.page.off("framenavigated", handler);
|
|
3059
|
+
this.isInitiated = false;
|
|
3060
|
+
};
|
|
3061
|
+
this.page.on("framenavigated", handler);
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
async get(keys, options = {}) {
|
|
3065
|
+
let result = {};
|
|
3066
|
+
await test4.step(this.getStepLabel("get"), async () => {
|
|
3067
|
+
await this.ensureContext(options);
|
|
3068
|
+
const allData = await this.readFromSessionStorage();
|
|
3069
|
+
if (keys && keys.length > 0) {
|
|
3070
|
+
for (const key of keys) {
|
|
3071
|
+
if (Object.hasOwn(allData, key)) {
|
|
3072
|
+
const value = allData[key];
|
|
3073
|
+
if (value !== void 0) {
|
|
3074
|
+
result[key] = value;
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
} else {
|
|
3079
|
+
result = allData;
|
|
3080
|
+
}
|
|
3081
|
+
});
|
|
3082
|
+
return result;
|
|
3083
|
+
}
|
|
3084
|
+
async clear(keyOrOptions, options = {}) {
|
|
3085
|
+
const { keys, waitForContext } = (() => {
|
|
3086
|
+
if (Array.isArray(keyOrOptions)) {
|
|
3087
|
+
return { keys: keyOrOptions, waitForContext: options.waitForContext };
|
|
3088
|
+
}
|
|
3089
|
+
if (typeof keyOrOptions === "string") {
|
|
3090
|
+
return { keys: [keyOrOptions], waitForContext: options.waitForContext };
|
|
3091
|
+
}
|
|
3092
|
+
if (keyOrOptions) {
|
|
3093
|
+
return { keys: void 0, waitForContext: keyOrOptions.waitForContext };
|
|
3094
|
+
}
|
|
3095
|
+
return { keys: void 0, waitForContext: options.waitForContext };
|
|
3096
|
+
})();
|
|
3097
|
+
await test4.step(this.getStepLabel("clear"), async () => {
|
|
3098
|
+
await this.ensureContext({ waitForContext });
|
|
3099
|
+
if (!keys || keys.length === 0) {
|
|
3100
|
+
await this.page.evaluate(() => sessionStorage.clear());
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
await this.page.evaluate((keysToClear) => {
|
|
3104
|
+
for (const key of keysToClear) {
|
|
3105
|
+
sessionStorage.removeItem(key);
|
|
3106
|
+
}
|
|
3107
|
+
}, keys);
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
};
|
|
3111
|
+
|
|
3112
|
+
// srcV2/pageObject.ts
|
|
3113
|
+
var PageObject = class {
|
|
3114
|
+
page;
|
|
3115
|
+
baseUrl;
|
|
3116
|
+
urlPath;
|
|
3117
|
+
fullUrl;
|
|
3118
|
+
label;
|
|
3119
|
+
sessionStorage;
|
|
3120
|
+
navigation;
|
|
3121
|
+
locatorRegistry;
|
|
3122
|
+
add;
|
|
3123
|
+
getLocator;
|
|
3124
|
+
getLocatorSchema;
|
|
3125
|
+
getNestedLocator;
|
|
3126
|
+
constructor(page, baseUrl, urlPath, options) {
|
|
3127
|
+
this.page = page;
|
|
3128
|
+
this.baseUrl = baseUrl;
|
|
3129
|
+
this.urlPath = urlPath;
|
|
3130
|
+
this.fullUrl = this.composeFullUrl(baseUrl, urlPath);
|
|
3131
|
+
const label = options?.label ?? this.constructor.name;
|
|
3132
|
+
this.label = label;
|
|
3133
|
+
const { registry, add, getLocator, getNestedLocator, getLocatorSchema } = createRegistryWithAccessors(page);
|
|
3134
|
+
this.locatorRegistry = registry;
|
|
3135
|
+
this.add = add;
|
|
3136
|
+
this.getLocator = getLocator;
|
|
3137
|
+
this.getLocatorSchema = getLocatorSchema;
|
|
3138
|
+
this.getNestedLocator = getNestedLocator;
|
|
3139
|
+
this.sessionStorage = new SessionStorage2(page, { label });
|
|
3140
|
+
this.defineLocators();
|
|
3141
|
+
this.navigation = createNavigation(
|
|
3142
|
+
this.page,
|
|
3143
|
+
this.baseUrl,
|
|
3144
|
+
this.urlPath,
|
|
3145
|
+
this.fullUrl,
|
|
3146
|
+
this.label,
|
|
3147
|
+
this.pageActionsToPerformAfterNavigation(),
|
|
3148
|
+
options?.navOptions
|
|
3149
|
+
);
|
|
3150
|
+
}
|
|
3151
|
+
composeFullUrl(baseUrl, urlPath) {
|
|
3152
|
+
const escapeRegex = (value) => value.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
3153
|
+
if (typeof baseUrl === "string" && typeof urlPath === "string") {
|
|
3154
|
+
return `${baseUrl}${urlPath}`;
|
|
3155
|
+
}
|
|
3156
|
+
if (typeof baseUrl === "string" && urlPath instanceof RegExp) {
|
|
3157
|
+
return new RegExp(`^${escapeRegex(baseUrl)}${urlPath.source}`);
|
|
3158
|
+
}
|
|
3159
|
+
if (baseUrl instanceof RegExp && typeof urlPath === "string") {
|
|
3160
|
+
return new RegExp(`${baseUrl.source}${escapeRegex(urlPath)}$`);
|
|
3161
|
+
}
|
|
3162
|
+
if (baseUrl instanceof RegExp && urlPath instanceof RegExp) {
|
|
3163
|
+
return new RegExp(`${baseUrl.source}${urlPath.source}`);
|
|
3164
|
+
}
|
|
3165
|
+
throw new Error("Invalid baseUrl or urlPath types. Expected string or RegExp.");
|
|
3166
|
+
}
|
|
3167
|
+
};
|
|
3168
|
+
|
|
3169
|
+
// srcV2/fixture/base.fixtures.ts
|
|
3170
|
+
import { test as base } from "@playwright/test";
|
|
3171
|
+
|
|
3172
|
+
// srcV2/helpers/playwrightReportLogger.ts
|
|
3173
|
+
var PlaywrightReportLogger = class _PlaywrightReportLogger {
|
|
3174
|
+
// Initializes the logger with shared log level, log entries, and a context name.
|
|
3175
|
+
constructor(sharedLogLevel, sharedLogEntry, contextName) {
|
|
3176
|
+
this.sharedLogLevel = sharedLogLevel;
|
|
3177
|
+
this.sharedLogEntry = sharedLogEntry;
|
|
3178
|
+
this.contextName = contextName;
|
|
3179
|
+
}
|
|
3180
|
+
contextName;
|
|
3181
|
+
logLevels = ["debug", "info", "warn", "error"];
|
|
3182
|
+
/**
|
|
3183
|
+
* Creates a child logger with a new contextual name, sharing the same log level and log entries with the parent logger.
|
|
3184
|
+
*
|
|
3185
|
+
* The root loggers log "level" is referenced by all child loggers and their child loggers and so on...
|
|
3186
|
+
* Changing the log "level" of one, will change it for all.
|
|
3187
|
+
*/
|
|
3188
|
+
getNewChildLogger(prefix) {
|
|
3189
|
+
return new _PlaywrightReportLogger(this.sharedLogLevel, this.sharedLogEntry, `${this.contextName} -> ${prefix}`);
|
|
3190
|
+
}
|
|
3191
|
+
/**
|
|
3192
|
+
* Logs a message with the specified log level, prefix, and additional arguments if the current log level permits.
|
|
3193
|
+
*/
|
|
3194
|
+
// biome-ignore lint/suspicious/noExplicitAny: logger accepts arbitrary payloads for debug output.
|
|
3195
|
+
log(level, message, ...args) {
|
|
3196
|
+
const logLevelIndex = this.logLevels.indexOf(level);
|
|
3197
|
+
if (logLevelIndex < this.getCurrentLogLevelIndex()) {
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
this.sharedLogEntry.push({
|
|
3201
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
3202
|
+
logLevel: level,
|
|
3203
|
+
prefix: this.contextName,
|
|
3204
|
+
message: `${message}
|
|
3205
|
+
|
|
3206
|
+
${args.join("\n\n")}`
|
|
3207
|
+
});
|
|
3208
|
+
}
|
|
3209
|
+
/**
|
|
3210
|
+
* Logs a debug-level message with the specified message and arguments.
|
|
3211
|
+
*/
|
|
3212
|
+
// biome-ignore lint/suspicious/noExplicitAny: logger accepts arbitrary payloads for debug output.
|
|
3213
|
+
debug(message, ...args) {
|
|
3214
|
+
this.log("debug", message, ...args);
|
|
3215
|
+
}
|
|
3216
|
+
/**
|
|
3217
|
+
* Logs a info-level message with the specified message and arguments.
|
|
3218
|
+
*/
|
|
3219
|
+
// biome-ignore lint/suspicious/noExplicitAny: logger accepts arbitrary payloads for debug output.
|
|
3220
|
+
info(message, ...args) {
|
|
3221
|
+
this.log("info", message, ...args);
|
|
3222
|
+
}
|
|
3223
|
+
/**
|
|
3224
|
+
* Logs a warn-level message with the specified message and arguments.
|
|
3225
|
+
*/
|
|
3226
|
+
// biome-ignore lint/suspicious/noExplicitAny: logger accepts arbitrary payloads for debug output.
|
|
3227
|
+
warn(message, ...args) {
|
|
3228
|
+
this.log("warn", message, ...args);
|
|
3229
|
+
}
|
|
3230
|
+
/**
|
|
3231
|
+
* Logs a error-level message with the specified message and arguments.
|
|
3232
|
+
*/
|
|
3233
|
+
// biome-ignore lint/suspicious/noExplicitAny: logger accepts arbitrary payloads for debug output.
|
|
1144
3234
|
error(message, ...args) {
|
|
1145
3235
|
this.log("error", message, ...args);
|
|
1146
3236
|
}
|
|
@@ -1220,9 +3310,9 @@ ${args.join("\n\n")}`
|
|
|
1220
3310
|
}
|
|
1221
3311
|
};
|
|
1222
3312
|
|
|
1223
|
-
//
|
|
1224
|
-
var
|
|
1225
|
-
// biome-ignore lint/correctness/noEmptyPattern:
|
|
3313
|
+
// srcV2/fixture/base.fixtures.ts
|
|
3314
|
+
var test5 = base.extend({
|
|
3315
|
+
// biome-ignore lint/correctness/noEmptyPattern: Playwright does not support the use of _
|
|
1226
3316
|
log: async ({}, use, testInfo) => {
|
|
1227
3317
|
const contextName = "TestCase";
|
|
1228
3318
|
const sharedLogEntry = [];
|
|
@@ -1233,24 +3323,53 @@ var test3 = base.extend({
|
|
|
1233
3323
|
}
|
|
1234
3324
|
});
|
|
1235
3325
|
|
|
1236
|
-
//
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
3326
|
+
// srcV2/helpers/stepDecorator.ts
|
|
3327
|
+
import { test as test6 } from "@playwright/test";
|
|
3328
|
+
var isMethodDecoratorArgs = (args) => args.length === 3 && typeof args[0] === "object" && (typeof args[1] === "string" || typeof args[1] === "symbol");
|
|
3329
|
+
var isStage3MethodDecoratorArgs = (args) => args.length === 2 && typeof args[0] === "function" && args[1] !== null && typeof args[1] === "object";
|
|
3330
|
+
var normalizeStepArguments = (args) => {
|
|
3331
|
+
const [titleOrOptions, maybeOptions] = args;
|
|
3332
|
+
const title = typeof titleOrOptions === "string" ? titleOrOptions : void 0;
|
|
3333
|
+
const options = typeof titleOrOptions === "string" ? maybeOptions : titleOrOptions;
|
|
3334
|
+
return { title, options };
|
|
3335
|
+
};
|
|
3336
|
+
var createWrappedMethod = (original, methodName, title, options) => function(...methodArgs) {
|
|
3337
|
+
const rawClassName = this.constructor?.name ?? "";
|
|
3338
|
+
const className = rawClassName && rawClassName !== "Object" ? rawClassName : "Anonymous";
|
|
3339
|
+
const resolvedTitle = title ?? `${className}.${String(methodName)}`;
|
|
3340
|
+
return test6.step(resolvedTitle, () => original.apply(this, methodArgs), options);
|
|
3341
|
+
};
|
|
3342
|
+
var createStepDecorator = ({ title, options }) => (valueOrTarget, contextOrKey, descriptor) => {
|
|
3343
|
+
if (typeof valueOrTarget === "function" && isStage3MethodDecoratorArgs([valueOrTarget, contextOrKey])) {
|
|
3344
|
+
const [original2, context] = [valueOrTarget, contextOrKey];
|
|
3345
|
+
return createWrappedMethod(original2, context.name, title, options);
|
|
1247
3346
|
}
|
|
3347
|
+
if (!descriptor || typeof descriptor.value !== "function") {
|
|
3348
|
+
throw new Error("@step decorator can only be applied to methods.");
|
|
3349
|
+
}
|
|
3350
|
+
const original = descriptor.value;
|
|
3351
|
+
descriptor.value = createWrappedMethod(original, contextOrKey, title, options);
|
|
3352
|
+
return descriptor;
|
|
1248
3353
|
};
|
|
3354
|
+
function step(...args) {
|
|
3355
|
+
if (isStage3MethodDecoratorArgs(args)) {
|
|
3356
|
+
return createStepDecorator(normalizeStepArguments([]))(...args);
|
|
3357
|
+
}
|
|
3358
|
+
if (isMethodDecoratorArgs(args)) {
|
|
3359
|
+
return createStepDecorator(normalizeStepArguments([]))(...args);
|
|
3360
|
+
}
|
|
3361
|
+
return createStepDecorator(normalizeStepArguments(args));
|
|
3362
|
+
}
|
|
1249
3363
|
export {
|
|
1250
3364
|
BaseApi,
|
|
1251
3365
|
BasePage,
|
|
3366
|
+
BasePageV1toV2,
|
|
1252
3367
|
GetByMethod,
|
|
1253
3368
|
GetLocatorBase,
|
|
3369
|
+
PageObject,
|
|
1254
3370
|
PlaywrightReportLogger,
|
|
1255
|
-
|
|
3371
|
+
SessionStorage2 as SessionStorage,
|
|
3372
|
+
createRegistryWithAccessors,
|
|
3373
|
+
step,
|
|
3374
|
+
test5 as test
|
|
1256
3375
|
};
|