pomwright 1.1.1 → 1.3.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/CHANGELOG.md +612 -0
- package/README.md +41 -312
- package/dist/index.d.mts +528 -128
- package/dist/index.d.ts +528 -128
- package/dist/index.js +333 -125
- package/dist/index.mjs +330 -122
- package/docs/BaseApi-explanation.md +63 -0
- package/docs/BasePage-explanation.md +96 -0
- package/docs/LocatorSchema-explanation.md +271 -0
- package/docs/LocatorSchemaPath-explanation.md +165 -0
- package/docs/PlaywrightReportLogger-explanation.md +56 -0
- package/docs/get-locator-methods-explanation.md +266 -0
- package/docs/intro-to-using-pomwright.md +899 -0
- package/docs/sessionStorage-methods-explanation.md +38 -0
- package/docs/tips-folder-structure.md +38 -0
- package/index.ts +2 -2
- package/package.json +8 -8
package/dist/index.mjs
CHANGED
|
@@ -63,6 +63,12 @@ var locatorSchemaDummy = {
|
|
|
63
63
|
testId: void 0,
|
|
64
64
|
dataCy: void 0,
|
|
65
65
|
id: void 0,
|
|
66
|
+
filter: {
|
|
67
|
+
has: void 0,
|
|
68
|
+
hasNot: void 0,
|
|
69
|
+
hasNotText: void 0,
|
|
70
|
+
hasText: void 0
|
|
71
|
+
},
|
|
66
72
|
locatorMethod: void 0,
|
|
67
73
|
locatorSchemaPath: void 0
|
|
68
74
|
};
|
|
@@ -235,52 +241,72 @@ var GetBy = class {
|
|
|
235
241
|
var REQUIRED_PROPERTIES_FOR_LOCATOR_SCHEMA_WITH_METHODS = [
|
|
236
242
|
"update",
|
|
237
243
|
"updates",
|
|
244
|
+
"addFilter",
|
|
238
245
|
"getNestedLocator",
|
|
239
246
|
"getLocator",
|
|
240
247
|
"locatorSchemaPath",
|
|
241
248
|
"locatorMethod",
|
|
242
|
-
"schemasMap"
|
|
249
|
+
"schemasMap",
|
|
250
|
+
"filterMap"
|
|
243
251
|
];
|
|
252
|
+
var safeStringifyOfNestedLocatorResults = (obj) => {
|
|
253
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
254
|
+
return JSON.stringify(
|
|
255
|
+
obj,
|
|
256
|
+
(key, value) => {
|
|
257
|
+
if (value instanceof Map) {
|
|
258
|
+
return Array.from(value.entries());
|
|
259
|
+
}
|
|
260
|
+
if (value instanceof RegExp) {
|
|
261
|
+
return { type: "RegExp", source: value.source, flags: value.flags };
|
|
262
|
+
}
|
|
263
|
+
if (value && typeof value === "object" && value.constructor && value.constructor.name === "Locator") {
|
|
264
|
+
return { type: "Locator", note: "Custom placeholder - Locators are complex." };
|
|
265
|
+
}
|
|
266
|
+
if (typeof value === "object" && value !== null) {
|
|
267
|
+
if (seen.has(value)) return "[Circular]";
|
|
268
|
+
seen.add(value);
|
|
269
|
+
}
|
|
270
|
+
return value;
|
|
271
|
+
},
|
|
272
|
+
2
|
|
273
|
+
);
|
|
274
|
+
};
|
|
244
275
|
var GetLocatorBase = class {
|
|
245
|
-
|
|
246
|
-
* Initializes the GetLocatorBase class with a page object class and a logger.
|
|
247
|
-
*/
|
|
248
|
-
constructor(pageObjectClass, log) {
|
|
276
|
+
constructor(pageObjectClass, log, locatorSubstring) {
|
|
249
277
|
this.pageObjectClass = pageObjectClass;
|
|
250
278
|
this.log = log;
|
|
279
|
+
this.locatorSubstring = locatorSubstring;
|
|
251
280
|
this.locatorSchemas = /* @__PURE__ */ new Map();
|
|
252
281
|
this.getBy = new GetBy(this.pageObjectClass.page, this.log.getNewChildLogger("GetBy"));
|
|
253
282
|
}
|
|
254
283
|
getBy;
|
|
255
284
|
locatorSchemas;
|
|
256
285
|
/**
|
|
257
|
-
*
|
|
286
|
+
* getLocatorSchema:
|
|
287
|
+
* Given a path P, we:
|
|
288
|
+
* 1. Collect deep copies of the schemas involved.
|
|
289
|
+
* 2. Create a WithMethodsClass instance with LocatorSubstring = P.
|
|
290
|
+
* 3. Return a locator schema copy enriched with chainable methods.
|
|
258
291
|
*/
|
|
259
292
|
getLocatorSchema(locatorSchemaPath) {
|
|
260
293
|
const pathIndexPairs = this.extractPathsFromSchema(locatorSchemaPath);
|
|
261
294
|
const schemasMap = this.collectDeepCopies(locatorSchemaPath, pathIndexPairs);
|
|
262
295
|
const locatorSchemaCopy = schemasMap.get(locatorSchemaPath);
|
|
263
296
|
locatorSchemaCopy.schemasMap = schemasMap;
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
};
|
|
273
|
-
locatorSchemaCopy.getNestedLocator = async (indices) => {
|
|
274
|
-
return await this.buildNestedLocator(locatorSchemaPath, schemasMap, indices);
|
|
275
|
-
};
|
|
276
|
-
locatorSchemaCopy.getLocator = async () => {
|
|
277
|
-
return this.getBy.getLocator(locatorSchemaCopy);
|
|
278
|
-
};
|
|
279
|
-
return locatorSchemaCopy;
|
|
297
|
+
locatorSchemaCopy.filterMap = /* @__PURE__ */ new Map();
|
|
298
|
+
const wrapper = new WithMethodsClass(
|
|
299
|
+
this.pageObjectClass,
|
|
300
|
+
this.log,
|
|
301
|
+
locatorSchemaPath,
|
|
302
|
+
schemasMap
|
|
303
|
+
);
|
|
304
|
+
return wrapper.init(locatorSchemaPath, locatorSchemaCopy);
|
|
280
305
|
}
|
|
281
306
|
/**
|
|
282
|
-
*
|
|
283
|
-
*
|
|
307
|
+
* collectDeepCopies:
|
|
308
|
+
* Clones and stores all schemas related to the chosen path and its sub-paths.
|
|
309
|
+
* Ensures updates and filters don't affect original schema definitions.
|
|
284
310
|
*/
|
|
285
311
|
collectDeepCopies(locatorSchemaPath, pathIndexPairs) {
|
|
286
312
|
const schemasMap = /* @__PURE__ */ new Map();
|
|
@@ -305,8 +331,25 @@ var GetLocatorBase = class {
|
|
|
305
331
|
return REQUIRED_PROPERTIES_FOR_LOCATOR_SCHEMA_WITH_METHODS.every((p) => p in schema);
|
|
306
332
|
}
|
|
307
333
|
/**
|
|
308
|
-
*
|
|
309
|
-
*
|
|
334
|
+
* applyUpdateToSubPath:
|
|
335
|
+
* Applies updates to a specific sub-path schema within schemasMap.
|
|
336
|
+
* Similar to applyUpdate, but we locate the sub-path schema directly by its path.
|
|
337
|
+
*/
|
|
338
|
+
applyUpdateToSubPath(schemasMap, subPath, updates) {
|
|
339
|
+
const schema = schemasMap.get(subPath);
|
|
340
|
+
if (!schema) {
|
|
341
|
+
throw new Error(`No schema found for sub-path: '${subPath}'`);
|
|
342
|
+
}
|
|
343
|
+
const updatedSchema = this.deepMerge(schema, updates);
|
|
344
|
+
if (this.isLocatorSchemaWithMethods(schema)) {
|
|
345
|
+
Object.assign(schema, updatedSchema);
|
|
346
|
+
} else {
|
|
347
|
+
schemasMap.set(subPath, updatedSchema);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* applyUpdate:
|
|
352
|
+
* Applies updates to a single schema within the schemasMap.
|
|
310
353
|
*/
|
|
311
354
|
applyUpdate(schemasMap, locatorSchemaPath, updateData) {
|
|
312
355
|
const schema = schemasMap.get(locatorSchemaPath);
|
|
@@ -320,8 +363,8 @@ var GetLocatorBase = class {
|
|
|
320
363
|
}
|
|
321
364
|
}
|
|
322
365
|
/**
|
|
323
|
-
*
|
|
324
|
-
*
|
|
366
|
+
* applyUpdates:
|
|
367
|
+
* Applies multiple updates to multiple schemas in the chain, identified by their path indexes.
|
|
325
368
|
*/
|
|
326
369
|
applyUpdates(schemasMap, pathIndexPairs, updatesData) {
|
|
327
370
|
for (const [index, updateAtIndex] of Object.entries(updatesData)) {
|
|
@@ -340,18 +383,24 @@ var GetLocatorBase = class {
|
|
|
340
383
|
}
|
|
341
384
|
}
|
|
342
385
|
/**
|
|
343
|
-
*
|
|
344
|
-
*
|
|
386
|
+
* createLocatorSchema:
|
|
387
|
+
* Creates a fresh LocatorSchema object by merging provided schemaDetails with a required locatorSchemaPath.
|
|
345
388
|
*/
|
|
346
389
|
createLocatorSchema(schemaDetails, locatorSchemaPath) {
|
|
347
390
|
const schema = { ...schemaDetails, locatorSchemaPath };
|
|
348
391
|
return schema;
|
|
349
392
|
}
|
|
350
393
|
/**
|
|
351
|
-
*
|
|
352
|
-
*
|
|
394
|
+
* addSchema:
|
|
395
|
+
* Registers a new LocatorSchema under the given locatorSchemaPath.
|
|
396
|
+
* Throws an error if a schema already exists at that path.
|
|
353
397
|
*/
|
|
354
398
|
addSchema(locatorSchemaPath, schemaDetails) {
|
|
399
|
+
if (locatorSchemaPath.length === 0 || locatorSchemaPath.startsWith(".") || locatorSchemaPath.endsWith(".") || locatorSchemaPath.includes("..")) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
`[${this.pageObjectClass.pocName}] Invalid LocatorSchemaPath '${locatorSchemaPath}'. LocatorSchemaPath must not be empty, start or end with a '.', or contain consecutive '.'.`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
355
404
|
const newLocatorSchema = this.createLocatorSchema(schemaDetails, locatorSchemaPath);
|
|
356
405
|
const existingSchemaFunc = this.safeGetLocatorSchema(locatorSchemaPath);
|
|
357
406
|
if (existingSchemaFunc) {
|
|
@@ -365,16 +414,16 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
365
414
|
this.locatorSchemas.set(locatorSchemaPath, () => newLocatorSchema);
|
|
366
415
|
}
|
|
367
416
|
/**
|
|
368
|
-
*
|
|
369
|
-
*
|
|
417
|
+
* safeGetLocatorSchema:
|
|
418
|
+
* Safely retrieves a schema function if available for the given path.
|
|
370
419
|
*/
|
|
371
420
|
safeGetLocatorSchema(path) {
|
|
372
421
|
return this.locatorSchemas.get(path);
|
|
373
422
|
}
|
|
374
423
|
/**
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
424
|
+
* extractPathsFromSchema:
|
|
425
|
+
* Splits a path into incremental sub-paths and associates them with optional indices.
|
|
426
|
+
* Used by updates and getNestedLocator methods.
|
|
378
427
|
*/
|
|
379
428
|
extractPathsFromSchema = (paths, indices = {}) => {
|
|
380
429
|
const schemaParts = paths.split(".");
|
|
@@ -388,8 +437,8 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
388
437
|
});
|
|
389
438
|
};
|
|
390
439
|
/**
|
|
391
|
-
* logError
|
|
392
|
-
*
|
|
440
|
+
* logError:
|
|
441
|
+
* Logs detailed error information and re-throws the error to ensure tests fail as expected.
|
|
393
442
|
*/
|
|
394
443
|
logError = (error, locatorSchemaPath, currentLocator, currentPath, pathIndexPairs, nestedLocatorResults) => {
|
|
395
444
|
const errorDetails = {
|
|
@@ -401,7 +450,7 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
401
450
|
locatorString: currentLocator,
|
|
402
451
|
isNotNull: true
|
|
403
452
|
} : { isNotNull: false },
|
|
404
|
-
nestedLocatorResults
|
|
453
|
+
nestedLocatorResults: safeStringifyOfNestedLocatorResults(nestedLocatorResults)
|
|
405
454
|
};
|
|
406
455
|
this.log.error(
|
|
407
456
|
"An error occurred during nested locator construction.\n",
|
|
@@ -410,7 +459,11 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
410
459
|
);
|
|
411
460
|
throw error;
|
|
412
461
|
};
|
|
413
|
-
/**
|
|
462
|
+
/**
|
|
463
|
+
* deepMerge:
|
|
464
|
+
* Recursively merges source properties into target, validating them against LocatorSchema to ensure no invalid keys.
|
|
465
|
+
* Ensures immutability by creating a new object rather than modifying in place.
|
|
466
|
+
*/
|
|
414
467
|
deepMerge(target, source, schema = getLocatorSchemaDummy()) {
|
|
415
468
|
const merged = { ...target };
|
|
416
469
|
for (const key of Object.keys(source)) {
|
|
@@ -435,7 +488,6 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
435
488
|
} else {
|
|
436
489
|
merged[key] = this.deepMerge(
|
|
437
490
|
{},
|
|
438
|
-
// Updated type here
|
|
439
491
|
sourceValue,
|
|
440
492
|
schema[key]
|
|
441
493
|
);
|
|
@@ -456,18 +508,17 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
456
508
|
return merged;
|
|
457
509
|
}
|
|
458
510
|
/**
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
511
|
+
* buildNestedLocator:
|
|
512
|
+
* Constructs a nested locator by iterating through each sub-path of locatorSchemaPath and chaining locators.
|
|
513
|
+
* Applies filters, indexing (nth), and logs details for debugging during test retries.
|
|
462
514
|
*/
|
|
463
|
-
buildNestedLocator = async (locatorSchemaPath, schemasMap, indices = {}) => {
|
|
515
|
+
buildNestedLocator = async (locatorSchemaPath, schemasMap, filterMap, indices = {}) => {
|
|
464
516
|
return await test.step(`${this.pageObjectClass.pocName}: Build Nested Locator`, async () => {
|
|
465
517
|
const pathIndexPairs = this.extractPathsFromSchema(locatorSchemaPath, indices);
|
|
466
518
|
let currentLocator = null;
|
|
467
519
|
let currentIFrame = null;
|
|
468
520
|
const nestedLocatorResults = {
|
|
469
521
|
LocatorSchema: null,
|
|
470
|
-
// Initialize as an empty object
|
|
471
522
|
NestingSteps: []
|
|
472
523
|
};
|
|
473
524
|
for (const { path, index } of pathIndexPairs) {
|
|
@@ -475,32 +526,42 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
475
526
|
if (!currentSchema) continue;
|
|
476
527
|
try {
|
|
477
528
|
const nextLocator = this.getBy.getLocator(currentSchema);
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
529
|
+
currentLocator = currentLocator ? currentLocator.locator(nextLocator) : nextLocator;
|
|
530
|
+
if (currentSchema.locatorMethod !== "frameLocator" /* frameLocator */ && currentSchema.filter) {
|
|
531
|
+
currentLocator = currentLocator.filter({
|
|
532
|
+
has: currentSchema.filter.has,
|
|
533
|
+
hasNot: currentSchema.filter.hasNot,
|
|
534
|
+
hasNotText: currentSchema.filter.hasNotText,
|
|
535
|
+
hasText: currentSchema.filter.hasText
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
const filterEntries = filterMap.get(path);
|
|
539
|
+
if (filterEntries) {
|
|
540
|
+
for (const filterData of filterEntries) {
|
|
541
|
+
currentLocator = currentLocator.filter({
|
|
542
|
+
has: filterData.has,
|
|
543
|
+
hasNot: filterData.hasNot,
|
|
544
|
+
hasNotText: filterData.hasNotText,
|
|
545
|
+
hasText: filterData.hasText
|
|
546
|
+
});
|
|
487
547
|
}
|
|
488
548
|
}
|
|
549
|
+
if (index != null) {
|
|
550
|
+
currentLocator = currentLocator.nth(index);
|
|
551
|
+
}
|
|
489
552
|
if (this.log.isLogLevelEnabled("debug")) {
|
|
490
553
|
if (!nestedLocatorResults.LocatorSchema) {
|
|
491
|
-
const
|
|
492
|
-
if (
|
|
493
|
-
nestedLocatorResults.LocatorSchema =
|
|
554
|
+
const schemaFromMap = schemasMap.get(locatorSchemaPath);
|
|
555
|
+
if (schemaFromMap) {
|
|
556
|
+
nestedLocatorResults.LocatorSchema = schemaFromMap;
|
|
494
557
|
}
|
|
495
558
|
}
|
|
496
559
|
if (currentSchema.locatorMethod === "frameLocator" /* frameLocator */) {
|
|
497
560
|
if (!currentIFrame) {
|
|
498
561
|
currentIFrame = currentSchema.frameLocator;
|
|
499
562
|
}
|
|
500
|
-
if (currentIFrame
|
|
501
|
-
|
|
502
|
-
currentIFrame += ` -> ${currentSchema.frameLocator}`;
|
|
503
|
-
}
|
|
563
|
+
if (currentIFrame && currentSchema.frameLocator && currentIFrame.endsWith(currentSchema.frameLocator)) {
|
|
564
|
+
currentIFrame += ` -> ${currentSchema.frameLocator}`;
|
|
504
565
|
}
|
|
505
566
|
}
|
|
506
567
|
if (currentIFrame !== void 0) {
|
|
@@ -524,7 +585,7 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
524
585
|
);
|
|
525
586
|
}
|
|
526
587
|
if (this.log.isLogLevelEnabled("debug")) {
|
|
527
|
-
this.log.debug("Nested locator evaluation results:",
|
|
588
|
+
this.log.debug("Nested locator evaluation results:", safeStringifyOfNestedLocatorResults(nestedLocatorResults));
|
|
528
589
|
}
|
|
529
590
|
if (currentLocator != null) {
|
|
530
591
|
return currentLocator;
|
|
@@ -532,7 +593,9 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
532
593
|
});
|
|
533
594
|
};
|
|
534
595
|
/**
|
|
535
|
-
*
|
|
596
|
+
* evaluateCurrentLocator:
|
|
597
|
+
* Gathers debug information about the current locator's resolved elements.
|
|
598
|
+
* Helps with logging and debugging complex locator chains.
|
|
536
599
|
*/
|
|
537
600
|
evaluateCurrentLocator = async (currentLocator, resultsArray, currentIFrame) => {
|
|
538
601
|
if (currentIFrame) {
|
|
@@ -544,7 +607,7 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
544
607
|
} else {
|
|
545
608
|
const elementsData = await this.evaluateAndGetAttributes(currentLocator);
|
|
546
609
|
resultsArray.push({
|
|
547
|
-
currentLocatorString: currentLocator
|
|
610
|
+
currentLocatorString: `${currentLocator}`,
|
|
548
611
|
resolved: elementsData.length > 0,
|
|
549
612
|
elementCount: elementsData.length,
|
|
550
613
|
elementsResolvedTo: elementsData
|
|
@@ -552,8 +615,8 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
552
615
|
}
|
|
553
616
|
};
|
|
554
617
|
/**
|
|
555
|
-
*
|
|
556
|
-
*
|
|
618
|
+
* evaluateAndGetAttributes:
|
|
619
|
+
* Extracts tagName and attributes from all elements matched by the locator for debugging purposes.
|
|
557
620
|
*/
|
|
558
621
|
evaluateAndGetAttributes = async (pwLocator) => {
|
|
559
622
|
return await pwLocator.evaluateAll(
|
|
@@ -564,6 +627,126 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
564
627
|
);
|
|
565
628
|
};
|
|
566
629
|
};
|
|
630
|
+
var WithMethodsClass = class extends GetLocatorBase {
|
|
631
|
+
constructor(pageObjectClass, log, locatorSubstring, schemasMap) {
|
|
632
|
+
super(pageObjectClass, log, locatorSubstring);
|
|
633
|
+
this.pageObjectClass = pageObjectClass;
|
|
634
|
+
this.log = log;
|
|
635
|
+
this.schemasMap = schemasMap;
|
|
636
|
+
}
|
|
637
|
+
locatorSchemaPath;
|
|
638
|
+
/**
|
|
639
|
+
* init:
|
|
640
|
+
* Assigns the locatorSchemaPath and binds methods (update, updates, addFilter, getNestedLocator, getLocator)
|
|
641
|
+
* directly on the locatorSchemaCopy. Returns the modified copy, now fully chainable and type-safe.
|
|
642
|
+
*/
|
|
643
|
+
init(locatorSchemaPath, locatorSchemaCopy) {
|
|
644
|
+
this.locatorSchemaPath = locatorSchemaPath;
|
|
645
|
+
const self = this;
|
|
646
|
+
locatorSchemaCopy.update = function(a, b) {
|
|
647
|
+
const fullPath = this.locatorSchemaPath;
|
|
648
|
+
if (b === void 0) {
|
|
649
|
+
const updates = a;
|
|
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);
|
|
658
|
+
}
|
|
659
|
+
return this;
|
|
660
|
+
};
|
|
661
|
+
locatorSchemaCopy.updates = function(indexedUpdates) {
|
|
662
|
+
const pathIndexPairs = self.extractPathsFromSchema(self.locatorSchemaPath);
|
|
663
|
+
self.applyUpdates(self.schemasMap, pathIndexPairs, indexedUpdates);
|
|
664
|
+
return this;
|
|
665
|
+
};
|
|
666
|
+
locatorSchemaCopy.addFilter = function(subPath, filterData) {
|
|
667
|
+
const fullPath = this.locatorSchemaPath;
|
|
668
|
+
if (!self.schemasMap.has(subPath)) {
|
|
669
|
+
const allowedPaths = self.extractPathsFromSchema(fullPath).map((p) => p.path).filter((path) => self.schemasMap.has(path));
|
|
670
|
+
throw new Error(
|
|
671
|
+
`Invalid sub-path '${subPath}' in addFilter. Allowed sub-paths are:
|
|
672
|
+
${allowedPaths.join(",\n")}`
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
if (!this.filterMap) {
|
|
676
|
+
this.filterMap = /* @__PURE__ */ new Map();
|
|
677
|
+
}
|
|
678
|
+
const existingFilters = this.filterMap.get(subPath) || [];
|
|
679
|
+
existingFilters.push(filterData);
|
|
680
|
+
this.filterMap.set(subPath, existingFilters);
|
|
681
|
+
return this;
|
|
682
|
+
};
|
|
683
|
+
locatorSchemaCopy.getNestedLocator = async function(arg) {
|
|
684
|
+
if (arg !== void 0 && arg !== null && typeof arg !== "object") {
|
|
685
|
+
throw new Error("Invalid argument passed to getNestedLocator: Expected an object or null.");
|
|
686
|
+
}
|
|
687
|
+
if (!arg || Object.keys(arg).length === 0) {
|
|
688
|
+
return await self.buildNestedLocator(
|
|
689
|
+
self.locatorSchemaPath,
|
|
690
|
+
self.schemasMap,
|
|
691
|
+
this.filterMap,
|
|
692
|
+
{}
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
const keys = Object.keys(arg);
|
|
696
|
+
const isNumberKey = keys.every((key) => /^\d+$/.test(key));
|
|
697
|
+
const numericIndices = {};
|
|
698
|
+
if (isNumberKey) {
|
|
699
|
+
for (const [key, value] of Object.entries(arg)) {
|
|
700
|
+
const index = Number(key);
|
|
701
|
+
if (typeof value === "number" && value >= 0) {
|
|
702
|
+
numericIndices[index] = value;
|
|
703
|
+
} else if (value !== null) {
|
|
704
|
+
throw new Error(`Invalid index value at key '${key}': Expected a positive number or null.`);
|
|
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:
|
|
715
|
+
${validPaths.join(",\n")}`
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
if (!pathToIndexMap.has(subPath)) {
|
|
719
|
+
const validPaths = pathIndexPairs.map((p) => p.path).filter((path) => self.schemasMap.has(path));
|
|
720
|
+
throw new Error(
|
|
721
|
+
`Invalid sub-path '${subPath}' in getNestedLocator. Allowed sub-paths are:
|
|
722
|
+
${validPaths.join(",\n")}`
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
const numericIndex = pathToIndexMap.get(subPath);
|
|
726
|
+
if (numericIndex === void 0) {
|
|
727
|
+
throw new Error(`Sub-path '${subPath}' not found in pathToIndexMap.`);
|
|
728
|
+
}
|
|
729
|
+
if (value !== null && (typeof value !== "number" || value < 0)) {
|
|
730
|
+
throw new Error(`Invalid index for sub-path '${subPath}': Expected a positive number or null.`);
|
|
731
|
+
}
|
|
732
|
+
if (value !== null) {
|
|
733
|
+
numericIndices[numericIndex] = value;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return await self.buildNestedLocator(
|
|
738
|
+
self.locatorSchemaPath,
|
|
739
|
+
self.schemasMap,
|
|
740
|
+
this.filterMap,
|
|
741
|
+
numericIndices
|
|
742
|
+
);
|
|
743
|
+
};
|
|
744
|
+
locatorSchemaCopy.getLocator = async () => {
|
|
745
|
+
return self.getBy.getLocator(locatorSchemaCopy);
|
|
746
|
+
};
|
|
747
|
+
return locatorSchemaCopy;
|
|
748
|
+
}
|
|
749
|
+
};
|
|
567
750
|
|
|
568
751
|
// src/helpers/sessionStorage.actions.ts
|
|
569
752
|
import { test as test2 } from "@playwright/test";
|
|
@@ -598,7 +781,7 @@ var SessionStorage = class {
|
|
|
598
781
|
const item = sessionStorage.getItem(key);
|
|
599
782
|
try {
|
|
600
783
|
storage[key] = item ? JSON.parse(item) : null;
|
|
601
|
-
} catch (
|
|
784
|
+
} catch (_e) {
|
|
602
785
|
storage[key] = item;
|
|
603
786
|
}
|
|
604
787
|
}
|
|
@@ -648,7 +831,7 @@ var SessionStorage = class {
|
|
|
648
831
|
contextExists = await this.page.evaluate(() => {
|
|
649
832
|
return typeof window !== "undefined" && window.sessionStorage !== void 0;
|
|
650
833
|
});
|
|
651
|
-
} catch (
|
|
834
|
+
} catch (_e) {
|
|
652
835
|
contextExists = false;
|
|
653
836
|
}
|
|
654
837
|
if (contextExists) {
|
|
@@ -679,7 +862,7 @@ var SessionStorage = class {
|
|
|
679
862
|
const allData = await this.readFromSessionStorage();
|
|
680
863
|
if (keys && keys.length > 0) {
|
|
681
864
|
for (const key of keys) {
|
|
682
|
-
if (Object.
|
|
865
|
+
if (Object.hasOwn(allData, key)) {
|
|
683
866
|
result[key] = allData[key];
|
|
684
867
|
}
|
|
685
868
|
}
|
|
@@ -746,12 +929,10 @@ var BasePage = class {
|
|
|
746
929
|
/** Selectors can be used to install custom selector engines.*/
|
|
747
930
|
selector;
|
|
748
931
|
/** The base URL of the Page Object Class */
|
|
749
|
-
// baseUrl: string;
|
|
750
932
|
baseUrl;
|
|
751
933
|
/** The URL path of the Page Object Class */
|
|
752
|
-
// urlPath: string;
|
|
753
934
|
urlPath;
|
|
754
|
-
|
|
935
|
+
/** The full URL of the Page Object Class */
|
|
755
936
|
fullUrl;
|
|
756
937
|
/** The name of the Page Object Class */
|
|
757
938
|
pocName;
|
|
@@ -759,8 +940,14 @@ var BasePage = class {
|
|
|
759
940
|
log;
|
|
760
941
|
/** The SessionStorage class provides methods for setting and getting session storage data in Playwright.*/
|
|
761
942
|
sessionStorage;
|
|
943
|
+
/**
|
|
944
|
+
* locators:
|
|
945
|
+
* An instance of GetLocatorBase that handles schema management and provides getLocatorSchema calls.
|
|
946
|
+
* Initially, LocatorSubstring is undefined. Once getLocatorSchema(path) is called,
|
|
947
|
+
* we get a chainable object typed with LocatorSubstring = P.
|
|
948
|
+
*/
|
|
762
949
|
locators;
|
|
763
|
-
constructor(page, testInfo, baseUrl, urlPath, pocName, pwrl) {
|
|
950
|
+
constructor(page, testInfo, baseUrl, urlPath, pocName, pwrl, locatorSubstring) {
|
|
764
951
|
this.page = page;
|
|
765
952
|
this.testInfo = testInfo;
|
|
766
953
|
this.selector = selectors;
|
|
@@ -769,7 +956,11 @@ var BasePage = class {
|
|
|
769
956
|
this.fullUrl = this.constructFullUrl(baseUrl, urlPath);
|
|
770
957
|
this.pocName = pocName;
|
|
771
958
|
this.log = pwrl.getNewChildLogger(pocName);
|
|
772
|
-
this.locators = new GetLocatorBase(
|
|
959
|
+
this.locators = new GetLocatorBase(
|
|
960
|
+
this,
|
|
961
|
+
this.log.getNewChildLogger("GetLocator"),
|
|
962
|
+
locatorSubstring
|
|
963
|
+
);
|
|
773
964
|
this.initLocatorSchemas();
|
|
774
965
|
this.sessionStorage = new SessionStorage(this.page, this.pocName);
|
|
775
966
|
if (!selectorRegistered) {
|
|
@@ -777,8 +968,13 @@ var BasePage = class {
|
|
|
777
968
|
selectorRegistered = true;
|
|
778
969
|
}
|
|
779
970
|
}
|
|
971
|
+
/**
|
|
972
|
+
* constructFullUrl:
|
|
973
|
+
* Combines baseUrl and urlPath, handling both strings and RegExps.
|
|
974
|
+
* Ensures a flexible approach to URL matching (string or regex-based).
|
|
975
|
+
*/
|
|
780
976
|
constructFullUrl(baseUrl, urlPath) {
|
|
781
|
-
const escapeStringForRegExp = (str) => str.replace(/[
|
|
977
|
+
const escapeStringForRegExp = (str) => str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
782
978
|
if (typeof baseUrl === "string" && typeof urlPath === "string") {
|
|
783
979
|
return `${baseUrl}${urlPath}`;
|
|
784
980
|
}
|
|
@@ -794,64 +990,67 @@ var BasePage = class {
|
|
|
794
990
|
throw new Error("Invalid baseUrl or urlPath types. Expected string or RegExp.");
|
|
795
991
|
}
|
|
796
992
|
/**
|
|
797
|
-
*
|
|
798
|
-
* - Asynchronously retrieves a nested locator based on the LocatorSchemaPath provided by getLocatorSchema("...")
|
|
799
|
-
* - Can be chained after the update and updates methods, getNestedLocator will end the chain.
|
|
800
|
-
* - The optional parameter of the method takes an object with 0-based indices "{0: 0, 3: 1}" for one or more locators
|
|
801
|
-
* to be nested given by sub-paths (indices correspond to last "word" of a sub-path).
|
|
802
|
-
* - Returns a promise that resolves to the nested locator.
|
|
993
|
+
* Implementation of getNestedLocator.
|
|
803
994
|
*/
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
995
|
+
async getNestedLocator(locatorSchemaPath, subPathIndices) {
|
|
996
|
+
const withValidation = new WithSubPathValidation(
|
|
997
|
+
this,
|
|
998
|
+
this.log.getNewChildLogger("SubPathValidation"),
|
|
999
|
+
locatorSchemaPath
|
|
1000
|
+
);
|
|
1001
|
+
return await withValidation.getNestedLocator(subPathIndices);
|
|
1002
|
+
}
|
|
807
1003
|
/**
|
|
808
|
-
* getLocator()
|
|
809
|
-
*
|
|
810
|
-
* and will return the locator for which the full LocatorSchemaPath resolves to,
|
|
811
|
-
*
|
|
812
|
-
*
|
|
1004
|
+
* Short-hand wrapper method for calling .getLocatorSchema(LocatorSchemaPath).getLocator()
|
|
1005
|
+
*
|
|
1006
|
+
* This method does not perform nesting,and will return the locator for which the full LocatorSchemaPath resolves to,
|
|
1007
|
+
* provided by getLocatorSchema("...")
|
|
1008
|
+
*
|
|
1009
|
+
* Note: This short-hand wrapper method is useful for quickly getting a locator without having to call
|
|
1010
|
+
* getLocatorSchema("...") first. On the other hand, it can't be used to update or add filters to the LocatorSchema.
|
|
1011
|
+
*
|
|
1012
|
+
* @example
|
|
1013
|
+
* // Usage:
|
|
1014
|
+
* const submitButton = await poc.getLocator("main.form.button@submit");
|
|
1015
|
+
* await expect(submitButton, "should only exist one submit button").toHaveCount(1);
|
|
813
1016
|
*/
|
|
814
1017
|
getLocator = async (locatorSchemaPath) => {
|
|
815
1018
|
return await this.getLocatorSchema(locatorSchemaPath).getLocator();
|
|
816
1019
|
};
|
|
817
1020
|
/**
|
|
1021
|
+
* getLocatorSchema:
|
|
1022
|
+
* Delegates to this.locators.getLocatorSchema.
|
|
1023
|
+
* Returns a chainable schema object for the given path.
|
|
1024
|
+
* Once called with a specific path P, the update and addFilter methods are restricted to sub-paths of P.
|
|
1025
|
+
*
|
|
818
1026
|
* The "getLocatorSchema" method is used to retrieve an updatable deep copy of a LocatorSchema defined in the
|
|
819
1027
|
* GetLocatorBase class. It enriches the returned schema with additional methods to handle updates and retrieval of
|
|
820
1028
|
* deep copy locators.
|
|
821
1029
|
*
|
|
822
1030
|
* getLocatorSchema adds the following chainable methods to the returned LocatorSchemaWithMethods object:
|
|
823
1031
|
*
|
|
824
|
-
* update
|
|
825
|
-
* - Allows updating
|
|
826
|
-
* -
|
|
827
|
-
* -
|
|
828
|
-
* will overwrite the corresponding property in the current schema.
|
|
829
|
-
* - Returns the updated deep copy of the "LocatorSchema" with methods.
|
|
830
|
-
* - Can be chained with the update and updates methods, and the getLocator or getNestedLocator method.
|
|
1032
|
+
* update
|
|
1033
|
+
* - Allows updating any schema in the chain by specifying the subPath directly.
|
|
1034
|
+
* - Gives compile-time suggestions for valid sub-paths of the LocatorSchemaPath provided to .getLocatorSchema().
|
|
1035
|
+
* - If you want to update multiple schemas, chain multiple .update() calls.
|
|
831
1036
|
*
|
|
832
|
-
*
|
|
833
|
-
* -
|
|
834
|
-
* - This method
|
|
835
|
-
*
|
|
836
|
-
* - Takes an object where keys represent the index of the last "word" of a sub-path, where the value per key is a
|
|
837
|
-
* "LocatorSchema" object which omits the locatorSchemaPath parameter as input, the parameters provided will overwrite
|
|
838
|
-
* the corresponding property in the given schema.
|
|
839
|
-
* - Returns the updated deep copy of the LocatorSchema object with methods and its own updated deep copies for all
|
|
840
|
-
* LocatorSchema each sub-path resolved to.
|
|
841
|
-
* - Can be chained with the update and updates methods, and the getLocator or getNestedLocator method.
|
|
1037
|
+
* addFilter(subPath: SubPaths<LocatorSchemaPathType, LocatorSubstring>, filterData: FilterEntry)
|
|
1038
|
+
* - The equivalent of the Playwright locator.filter() method
|
|
1039
|
+
* - This method is used for filtering the specified locator based on the provided filterData.
|
|
1040
|
+
* - Can be chained multiple times to add multiple filters to the same or different LocatorSchema.
|
|
842
1041
|
*
|
|
843
|
-
* getNestedLocator
|
|
844
|
-
* - Asynchronously
|
|
845
|
-
* - Can be chained after the update and
|
|
846
|
-
* -
|
|
847
|
-
*
|
|
848
|
-
*
|
|
1042
|
+
* getNestedLocator
|
|
1043
|
+
* - Asynchronously builds a nested locator based on the LocatorSchemaPath provided by getLocatorSchema("...")
|
|
1044
|
+
* - Can be chained once after the update and addFilter methods or directly on the .getLocatorSchema method.
|
|
1045
|
+
* - getNestedLocator will end the method chain and return a nested Playwright Locator.
|
|
1046
|
+
* - Optionally parameter takes a list of key(subPath)-value(index) pairs, the locator constructed from the LocatorSchema
|
|
1047
|
+
* with the specified subPath will resolve to the .nth(n) occurrence of the element, within the chain.
|
|
849
1048
|
*
|
|
850
1049
|
* getLocator()
|
|
851
|
-
* - Asynchronously retrieves a locator based on the current
|
|
852
|
-
* and will return the locator for which the full LocatorSchemaPath resolves to, provided by getLocatorSchema("...")
|
|
853
|
-
* - Can be chained after the update and
|
|
854
|
-
* -
|
|
1050
|
+
* - Asynchronously retrieves a locator based on the current LocatorSchemaPath.
|
|
1051
|
+
* - This method does not perform nesting and will return the locator for which the full LocatorSchemaPath resolves to, provided by getLocatorSchema("...")
|
|
1052
|
+
* - Can be chained once after the update and addFilter methods or directly on the .getLocatorSchema method.
|
|
1053
|
+
* - getLocator will end the method chain and return a Playwright Locator.
|
|
855
1054
|
*
|
|
856
1055
|
* Note: Calling getLocator() and getNestedLocator() on the same LocatorSchemaPath will return a Locator for the same
|
|
857
1056
|
* element, but the Locator returned by getNestedLocator() will be a locator resolving to said same element through
|
|
@@ -861,10 +1060,19 @@ var BasePage = class {
|
|
|
861
1060
|
* That said, for certain use cases, getLocator() can be useful, and you could use it to manually chain locators
|
|
862
1061
|
* yourself if some edge case required it. Though, it would be likely be more prudent to expand your LocatorSchemaPath
|
|
863
1062
|
* type and initLocatorSchemas() method to include the additional locators you need for the given POC, and then use
|
|
864
|
-
* getNestedLocator() instead.
|
|
1063
|
+
* getNestedLocator() instead, or by implementing a helper method on your Page Object Class.
|
|
865
1064
|
*/
|
|
866
|
-
getLocatorSchema(
|
|
867
|
-
return this.locators.getLocatorSchema(
|
|
1065
|
+
getLocatorSchema(path) {
|
|
1066
|
+
return this.locators.getLocatorSchema(path);
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
var WithSubPathValidation = class extends GetLocatorBase {
|
|
1070
|
+
constructor(pageObjectClass, log, locatorSchemaPath) {
|
|
1071
|
+
super(pageObjectClass, log, locatorSchemaPath);
|
|
1072
|
+
this.locatorSchemaPath = locatorSchemaPath;
|
|
1073
|
+
}
|
|
1074
|
+
async getNestedLocator(arg) {
|
|
1075
|
+
return await this.pageObjectClass.getLocatorSchema(this.locatorSchemaPath).getNestedLocator(arg);
|
|
868
1076
|
}
|
|
869
1077
|
};
|
|
870
1078
|
|
|
@@ -1000,7 +1208,7 @@ ${args.join("\n\n")}`
|
|
|
1000
1208
|
const parsedMessage = JSON.parse(log.message);
|
|
1001
1209
|
messageContentType = "application/json";
|
|
1002
1210
|
messageBody = JSON.stringify(parsedMessage, null, 2);
|
|
1003
|
-
} catch (
|
|
1211
|
+
} catch (_error) {
|
|
1004
1212
|
messageContentType = "text/plain";
|
|
1005
1213
|
messageBody = log.message;
|
|
1006
1214
|
}
|