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.js
CHANGED
|
@@ -18,8 +18,8 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
20
|
// index.ts
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
23
|
BaseApi: () => BaseApi,
|
|
24
24
|
BasePage: () => BasePage,
|
|
25
25
|
GetByMethod: () => GetByMethod,
|
|
@@ -27,7 +27,7 @@ __export(POMWright_exports, {
|
|
|
27
27
|
PlaywrightReportLogger: () => PlaywrightReportLogger,
|
|
28
28
|
test: () => test3
|
|
29
29
|
});
|
|
30
|
-
module.exports = __toCommonJS(
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
31
|
|
|
32
32
|
// src/basePage.ts
|
|
33
33
|
var import_test3 = require("@playwright/test");
|
|
@@ -94,6 +94,12 @@ var locatorSchemaDummy = {
|
|
|
94
94
|
testId: void 0,
|
|
95
95
|
dataCy: void 0,
|
|
96
96
|
id: void 0,
|
|
97
|
+
filter: {
|
|
98
|
+
has: void 0,
|
|
99
|
+
hasNot: void 0,
|
|
100
|
+
hasNotText: void 0,
|
|
101
|
+
hasText: void 0
|
|
102
|
+
},
|
|
97
103
|
locatorMethod: void 0,
|
|
98
104
|
locatorSchemaPath: void 0
|
|
99
105
|
};
|
|
@@ -266,52 +272,72 @@ var GetBy = class {
|
|
|
266
272
|
var REQUIRED_PROPERTIES_FOR_LOCATOR_SCHEMA_WITH_METHODS = [
|
|
267
273
|
"update",
|
|
268
274
|
"updates",
|
|
275
|
+
"addFilter",
|
|
269
276
|
"getNestedLocator",
|
|
270
277
|
"getLocator",
|
|
271
278
|
"locatorSchemaPath",
|
|
272
279
|
"locatorMethod",
|
|
273
|
-
"schemasMap"
|
|
280
|
+
"schemasMap",
|
|
281
|
+
"filterMap"
|
|
274
282
|
];
|
|
283
|
+
var safeStringifyOfNestedLocatorResults = (obj) => {
|
|
284
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
285
|
+
return JSON.stringify(
|
|
286
|
+
obj,
|
|
287
|
+
(key, value) => {
|
|
288
|
+
if (value instanceof Map) {
|
|
289
|
+
return Array.from(value.entries());
|
|
290
|
+
}
|
|
291
|
+
if (value instanceof RegExp) {
|
|
292
|
+
return { type: "RegExp", source: value.source, flags: value.flags };
|
|
293
|
+
}
|
|
294
|
+
if (value && typeof value === "object" && value.constructor && value.constructor.name === "Locator") {
|
|
295
|
+
return { type: "Locator", note: "Custom placeholder - Locators are complex." };
|
|
296
|
+
}
|
|
297
|
+
if (typeof value === "object" && value !== null) {
|
|
298
|
+
if (seen.has(value)) return "[Circular]";
|
|
299
|
+
seen.add(value);
|
|
300
|
+
}
|
|
301
|
+
return value;
|
|
302
|
+
},
|
|
303
|
+
2
|
|
304
|
+
);
|
|
305
|
+
};
|
|
275
306
|
var GetLocatorBase = class {
|
|
276
|
-
|
|
277
|
-
* Initializes the GetLocatorBase class with a page object class and a logger.
|
|
278
|
-
*/
|
|
279
|
-
constructor(pageObjectClass, log) {
|
|
307
|
+
constructor(pageObjectClass, log, locatorSubstring) {
|
|
280
308
|
this.pageObjectClass = pageObjectClass;
|
|
281
309
|
this.log = log;
|
|
310
|
+
this.locatorSubstring = locatorSubstring;
|
|
282
311
|
this.locatorSchemas = /* @__PURE__ */ new Map();
|
|
283
312
|
this.getBy = new GetBy(this.pageObjectClass.page, this.log.getNewChildLogger("GetBy"));
|
|
284
313
|
}
|
|
285
314
|
getBy;
|
|
286
315
|
locatorSchemas;
|
|
287
316
|
/**
|
|
288
|
-
*
|
|
317
|
+
* getLocatorSchema:
|
|
318
|
+
* Given a path P, we:
|
|
319
|
+
* 1. Collect deep copies of the schemas involved.
|
|
320
|
+
* 2. Create a WithMethodsClass instance with LocatorSubstring = P.
|
|
321
|
+
* 3. Return a locator schema copy enriched with chainable methods.
|
|
289
322
|
*/
|
|
290
323
|
getLocatorSchema(locatorSchemaPath) {
|
|
291
324
|
const pathIndexPairs = this.extractPathsFromSchema(locatorSchemaPath);
|
|
292
325
|
const schemasMap = this.collectDeepCopies(locatorSchemaPath, pathIndexPairs);
|
|
293
326
|
const locatorSchemaCopy = schemasMap.get(locatorSchemaPath);
|
|
294
327
|
locatorSchemaCopy.schemasMap = schemasMap;
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
};
|
|
304
|
-
locatorSchemaCopy.getNestedLocator = async (indices) => {
|
|
305
|
-
return await this.buildNestedLocator(locatorSchemaPath, schemasMap, indices);
|
|
306
|
-
};
|
|
307
|
-
locatorSchemaCopy.getLocator = async () => {
|
|
308
|
-
return this.getBy.getLocator(locatorSchemaCopy);
|
|
309
|
-
};
|
|
310
|
-
return locatorSchemaCopy;
|
|
328
|
+
locatorSchemaCopy.filterMap = /* @__PURE__ */ new Map();
|
|
329
|
+
const wrapper = new WithMethodsClass(
|
|
330
|
+
this.pageObjectClass,
|
|
331
|
+
this.log,
|
|
332
|
+
locatorSchemaPath,
|
|
333
|
+
schemasMap
|
|
334
|
+
);
|
|
335
|
+
return wrapper.init(locatorSchemaPath, locatorSchemaCopy);
|
|
311
336
|
}
|
|
312
337
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
338
|
+
* collectDeepCopies:
|
|
339
|
+
* Clones and stores all schemas related to the chosen path and its sub-paths.
|
|
340
|
+
* Ensures updates and filters don't affect original schema definitions.
|
|
315
341
|
*/
|
|
316
342
|
collectDeepCopies(locatorSchemaPath, pathIndexPairs) {
|
|
317
343
|
const schemasMap = /* @__PURE__ */ new Map();
|
|
@@ -336,8 +362,25 @@ var GetLocatorBase = class {
|
|
|
336
362
|
return REQUIRED_PROPERTIES_FOR_LOCATOR_SCHEMA_WITH_METHODS.every((p) => p in schema);
|
|
337
363
|
}
|
|
338
364
|
/**
|
|
339
|
-
*
|
|
340
|
-
*
|
|
365
|
+
* applyUpdateToSubPath:
|
|
366
|
+
* Applies updates to a specific sub-path schema within schemasMap.
|
|
367
|
+
* Similar to applyUpdate, but we locate the sub-path schema directly by its path.
|
|
368
|
+
*/
|
|
369
|
+
applyUpdateToSubPath(schemasMap, subPath, updates) {
|
|
370
|
+
const schema = schemasMap.get(subPath);
|
|
371
|
+
if (!schema) {
|
|
372
|
+
throw new Error(`No schema found for sub-path: '${subPath}'`);
|
|
373
|
+
}
|
|
374
|
+
const updatedSchema = this.deepMerge(schema, updates);
|
|
375
|
+
if (this.isLocatorSchemaWithMethods(schema)) {
|
|
376
|
+
Object.assign(schema, updatedSchema);
|
|
377
|
+
} else {
|
|
378
|
+
schemasMap.set(subPath, updatedSchema);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* applyUpdate:
|
|
383
|
+
* Applies updates to a single schema within the schemasMap.
|
|
341
384
|
*/
|
|
342
385
|
applyUpdate(schemasMap, locatorSchemaPath, updateData) {
|
|
343
386
|
const schema = schemasMap.get(locatorSchemaPath);
|
|
@@ -351,8 +394,8 @@ var GetLocatorBase = class {
|
|
|
351
394
|
}
|
|
352
395
|
}
|
|
353
396
|
/**
|
|
354
|
-
*
|
|
355
|
-
*
|
|
397
|
+
* applyUpdates:
|
|
398
|
+
* Applies multiple updates to multiple schemas in the chain, identified by their path indexes.
|
|
356
399
|
*/
|
|
357
400
|
applyUpdates(schemasMap, pathIndexPairs, updatesData) {
|
|
358
401
|
for (const [index, updateAtIndex] of Object.entries(updatesData)) {
|
|
@@ -371,18 +414,24 @@ var GetLocatorBase = class {
|
|
|
371
414
|
}
|
|
372
415
|
}
|
|
373
416
|
/**
|
|
374
|
-
*
|
|
375
|
-
*
|
|
417
|
+
* createLocatorSchema:
|
|
418
|
+
* Creates a fresh LocatorSchema object by merging provided schemaDetails with a required locatorSchemaPath.
|
|
376
419
|
*/
|
|
377
420
|
createLocatorSchema(schemaDetails, locatorSchemaPath) {
|
|
378
421
|
const schema = { ...schemaDetails, locatorSchemaPath };
|
|
379
422
|
return schema;
|
|
380
423
|
}
|
|
381
424
|
/**
|
|
382
|
-
*
|
|
383
|
-
*
|
|
425
|
+
* addSchema:
|
|
426
|
+
* Registers a new LocatorSchema under the given locatorSchemaPath.
|
|
427
|
+
* Throws an error if a schema already exists at that path.
|
|
384
428
|
*/
|
|
385
429
|
addSchema(locatorSchemaPath, schemaDetails) {
|
|
430
|
+
if (locatorSchemaPath.length === 0 || locatorSchemaPath.startsWith(".") || locatorSchemaPath.endsWith(".") || locatorSchemaPath.includes("..")) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`[${this.pageObjectClass.pocName}] Invalid LocatorSchemaPath '${locatorSchemaPath}'. LocatorSchemaPath must not be empty, start or end with a '.', or contain consecutive '.'.`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
386
435
|
const newLocatorSchema = this.createLocatorSchema(schemaDetails, locatorSchemaPath);
|
|
387
436
|
const existingSchemaFunc = this.safeGetLocatorSchema(locatorSchemaPath);
|
|
388
437
|
if (existingSchemaFunc) {
|
|
@@ -396,16 +445,16 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
396
445
|
this.locatorSchemas.set(locatorSchemaPath, () => newLocatorSchema);
|
|
397
446
|
}
|
|
398
447
|
/**
|
|
399
|
-
*
|
|
400
|
-
*
|
|
448
|
+
* safeGetLocatorSchema:
|
|
449
|
+
* Safely retrieves a schema function if available for the given path.
|
|
401
450
|
*/
|
|
402
451
|
safeGetLocatorSchema(path) {
|
|
403
452
|
return this.locatorSchemas.get(path);
|
|
404
453
|
}
|
|
405
454
|
/**
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
455
|
+
* extractPathsFromSchema:
|
|
456
|
+
* Splits a path into incremental sub-paths and associates them with optional indices.
|
|
457
|
+
* Used by updates and getNestedLocator methods.
|
|
409
458
|
*/
|
|
410
459
|
extractPathsFromSchema = (paths, indices = {}) => {
|
|
411
460
|
const schemaParts = paths.split(".");
|
|
@@ -419,8 +468,8 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
419
468
|
});
|
|
420
469
|
};
|
|
421
470
|
/**
|
|
422
|
-
* logError
|
|
423
|
-
*
|
|
471
|
+
* logError:
|
|
472
|
+
* Logs detailed error information and re-throws the error to ensure tests fail as expected.
|
|
424
473
|
*/
|
|
425
474
|
logError = (error, locatorSchemaPath, currentLocator, currentPath, pathIndexPairs, nestedLocatorResults) => {
|
|
426
475
|
const errorDetails = {
|
|
@@ -432,7 +481,7 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
432
481
|
locatorString: currentLocator,
|
|
433
482
|
isNotNull: true
|
|
434
483
|
} : { isNotNull: false },
|
|
435
|
-
nestedLocatorResults
|
|
484
|
+
nestedLocatorResults: safeStringifyOfNestedLocatorResults(nestedLocatorResults)
|
|
436
485
|
};
|
|
437
486
|
this.log.error(
|
|
438
487
|
"An error occurred during nested locator construction.\n",
|
|
@@ -441,7 +490,11 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
441
490
|
);
|
|
442
491
|
throw error;
|
|
443
492
|
};
|
|
444
|
-
/**
|
|
493
|
+
/**
|
|
494
|
+
* deepMerge:
|
|
495
|
+
* Recursively merges source properties into target, validating them against LocatorSchema to ensure no invalid keys.
|
|
496
|
+
* Ensures immutability by creating a new object rather than modifying in place.
|
|
497
|
+
*/
|
|
445
498
|
deepMerge(target, source, schema = getLocatorSchemaDummy()) {
|
|
446
499
|
const merged = { ...target };
|
|
447
500
|
for (const key of Object.keys(source)) {
|
|
@@ -466,7 +519,6 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
466
519
|
} else {
|
|
467
520
|
merged[key] = this.deepMerge(
|
|
468
521
|
{},
|
|
469
|
-
// Updated type here
|
|
470
522
|
sourceValue,
|
|
471
523
|
schema[key]
|
|
472
524
|
);
|
|
@@ -487,18 +539,17 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
487
539
|
return merged;
|
|
488
540
|
}
|
|
489
541
|
/**
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
542
|
+
* buildNestedLocator:
|
|
543
|
+
* Constructs a nested locator by iterating through each sub-path of locatorSchemaPath and chaining locators.
|
|
544
|
+
* Applies filters, indexing (nth), and logs details for debugging during test retries.
|
|
493
545
|
*/
|
|
494
|
-
buildNestedLocator = async (locatorSchemaPath, schemasMap, indices = {}) => {
|
|
546
|
+
buildNestedLocator = async (locatorSchemaPath, schemasMap, filterMap, indices = {}) => {
|
|
495
547
|
return await import_test.test.step(`${this.pageObjectClass.pocName}: Build Nested Locator`, async () => {
|
|
496
548
|
const pathIndexPairs = this.extractPathsFromSchema(locatorSchemaPath, indices);
|
|
497
549
|
let currentLocator = null;
|
|
498
550
|
let currentIFrame = null;
|
|
499
551
|
const nestedLocatorResults = {
|
|
500
552
|
LocatorSchema: null,
|
|
501
|
-
// Initialize as an empty object
|
|
502
553
|
NestingSteps: []
|
|
503
554
|
};
|
|
504
555
|
for (const { path, index } of pathIndexPairs) {
|
|
@@ -506,32 +557,42 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
506
557
|
if (!currentSchema) continue;
|
|
507
558
|
try {
|
|
508
559
|
const nextLocator = this.getBy.getLocator(currentSchema);
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
560
|
+
currentLocator = currentLocator ? currentLocator.locator(nextLocator) : nextLocator;
|
|
561
|
+
if (currentSchema.locatorMethod !== "frameLocator" /* frameLocator */ && currentSchema.filter) {
|
|
562
|
+
currentLocator = currentLocator.filter({
|
|
563
|
+
has: currentSchema.filter.has,
|
|
564
|
+
hasNot: currentSchema.filter.hasNot,
|
|
565
|
+
hasNotText: currentSchema.filter.hasNotText,
|
|
566
|
+
hasText: currentSchema.filter.hasText
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
const filterEntries = filterMap.get(path);
|
|
570
|
+
if (filterEntries) {
|
|
571
|
+
for (const filterData of filterEntries) {
|
|
572
|
+
currentLocator = currentLocator.filter({
|
|
573
|
+
has: filterData.has,
|
|
574
|
+
hasNot: filterData.hasNot,
|
|
575
|
+
hasNotText: filterData.hasNotText,
|
|
576
|
+
hasText: filterData.hasText
|
|
577
|
+
});
|
|
518
578
|
}
|
|
519
579
|
}
|
|
580
|
+
if (index != null) {
|
|
581
|
+
currentLocator = currentLocator.nth(index);
|
|
582
|
+
}
|
|
520
583
|
if (this.log.isLogLevelEnabled("debug")) {
|
|
521
584
|
if (!nestedLocatorResults.LocatorSchema) {
|
|
522
|
-
const
|
|
523
|
-
if (
|
|
524
|
-
nestedLocatorResults.LocatorSchema =
|
|
585
|
+
const schemaFromMap = schemasMap.get(locatorSchemaPath);
|
|
586
|
+
if (schemaFromMap) {
|
|
587
|
+
nestedLocatorResults.LocatorSchema = schemaFromMap;
|
|
525
588
|
}
|
|
526
589
|
}
|
|
527
590
|
if (currentSchema.locatorMethod === "frameLocator" /* frameLocator */) {
|
|
528
591
|
if (!currentIFrame) {
|
|
529
592
|
currentIFrame = currentSchema.frameLocator;
|
|
530
593
|
}
|
|
531
|
-
if (currentIFrame
|
|
532
|
-
|
|
533
|
-
currentIFrame += ` -> ${currentSchema.frameLocator}`;
|
|
534
|
-
}
|
|
594
|
+
if (currentIFrame && currentSchema.frameLocator && currentIFrame.endsWith(currentSchema.frameLocator)) {
|
|
595
|
+
currentIFrame += ` -> ${currentSchema.frameLocator}`;
|
|
535
596
|
}
|
|
536
597
|
}
|
|
537
598
|
if (currentIFrame !== void 0) {
|
|
@@ -555,7 +616,7 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
555
616
|
);
|
|
556
617
|
}
|
|
557
618
|
if (this.log.isLogLevelEnabled("debug")) {
|
|
558
|
-
this.log.debug("Nested locator evaluation results:",
|
|
619
|
+
this.log.debug("Nested locator evaluation results:", safeStringifyOfNestedLocatorResults(nestedLocatorResults));
|
|
559
620
|
}
|
|
560
621
|
if (currentLocator != null) {
|
|
561
622
|
return currentLocator;
|
|
@@ -563,7 +624,9 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
563
624
|
});
|
|
564
625
|
};
|
|
565
626
|
/**
|
|
566
|
-
*
|
|
627
|
+
* evaluateCurrentLocator:
|
|
628
|
+
* Gathers debug information about the current locator's resolved elements.
|
|
629
|
+
* Helps with logging and debugging complex locator chains.
|
|
567
630
|
*/
|
|
568
631
|
evaluateCurrentLocator = async (currentLocator, resultsArray, currentIFrame) => {
|
|
569
632
|
if (currentIFrame) {
|
|
@@ -575,7 +638,7 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
575
638
|
} else {
|
|
576
639
|
const elementsData = await this.evaluateAndGetAttributes(currentLocator);
|
|
577
640
|
resultsArray.push({
|
|
578
|
-
currentLocatorString: currentLocator
|
|
641
|
+
currentLocatorString: `${currentLocator}`,
|
|
579
642
|
resolved: elementsData.length > 0,
|
|
580
643
|
elementCount: elementsData.length,
|
|
581
644
|
elementsResolvedTo: elementsData
|
|
@@ -583,8 +646,8 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
583
646
|
}
|
|
584
647
|
};
|
|
585
648
|
/**
|
|
586
|
-
*
|
|
587
|
-
*
|
|
649
|
+
* evaluateAndGetAttributes:
|
|
650
|
+
* Extracts tagName and attributes from all elements matched by the locator for debugging purposes.
|
|
588
651
|
*/
|
|
589
652
|
evaluateAndGetAttributes = async (pwLocator) => {
|
|
590
653
|
return await pwLocator.evaluateAll(
|
|
@@ -595,6 +658,126 @@ Attempted to Add Schema: ${JSON.stringify(newLocatorSchema, null, 2)}`
|
|
|
595
658
|
);
|
|
596
659
|
};
|
|
597
660
|
};
|
|
661
|
+
var WithMethodsClass = class extends GetLocatorBase {
|
|
662
|
+
constructor(pageObjectClass, log, locatorSubstring, schemasMap) {
|
|
663
|
+
super(pageObjectClass, log, locatorSubstring);
|
|
664
|
+
this.pageObjectClass = pageObjectClass;
|
|
665
|
+
this.log = log;
|
|
666
|
+
this.schemasMap = schemasMap;
|
|
667
|
+
}
|
|
668
|
+
locatorSchemaPath;
|
|
669
|
+
/**
|
|
670
|
+
* init:
|
|
671
|
+
* Assigns the locatorSchemaPath and binds methods (update, updates, addFilter, getNestedLocator, getLocator)
|
|
672
|
+
* directly on the locatorSchemaCopy. Returns the modified copy, now fully chainable and type-safe.
|
|
673
|
+
*/
|
|
674
|
+
init(locatorSchemaPath, locatorSchemaCopy) {
|
|
675
|
+
this.locatorSchemaPath = locatorSchemaPath;
|
|
676
|
+
const self = this;
|
|
677
|
+
locatorSchemaCopy.update = function(a, b) {
|
|
678
|
+
const fullPath = this.locatorSchemaPath;
|
|
679
|
+
if (b === void 0) {
|
|
680
|
+
const updates = a;
|
|
681
|
+
self.applyUpdate(self.schemasMap, self.locatorSchemaPath, updates);
|
|
682
|
+
} else {
|
|
683
|
+
const subPath = a;
|
|
684
|
+
const updates = b;
|
|
685
|
+
if (!(subPath === fullPath || fullPath.startsWith(`${subPath}.`))) {
|
|
686
|
+
throw new Error(`Invalid sub-path: '${subPath}' is not a valid sub-path of '${fullPath}'.`);
|
|
687
|
+
}
|
|
688
|
+
self.applyUpdateToSubPath(self.schemasMap, subPath, updates);
|
|
689
|
+
}
|
|
690
|
+
return this;
|
|
691
|
+
};
|
|
692
|
+
locatorSchemaCopy.updates = function(indexedUpdates) {
|
|
693
|
+
const pathIndexPairs = self.extractPathsFromSchema(self.locatorSchemaPath);
|
|
694
|
+
self.applyUpdates(self.schemasMap, pathIndexPairs, indexedUpdates);
|
|
695
|
+
return this;
|
|
696
|
+
};
|
|
697
|
+
locatorSchemaCopy.addFilter = function(subPath, filterData) {
|
|
698
|
+
const fullPath = this.locatorSchemaPath;
|
|
699
|
+
if (!self.schemasMap.has(subPath)) {
|
|
700
|
+
const allowedPaths = self.extractPathsFromSchema(fullPath).map((p) => p.path).filter((path) => self.schemasMap.has(path));
|
|
701
|
+
throw new Error(
|
|
702
|
+
`Invalid sub-path '${subPath}' in addFilter. Allowed sub-paths are:
|
|
703
|
+
${allowedPaths.join(",\n")}`
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
if (!this.filterMap) {
|
|
707
|
+
this.filterMap = /* @__PURE__ */ new Map();
|
|
708
|
+
}
|
|
709
|
+
const existingFilters = this.filterMap.get(subPath) || [];
|
|
710
|
+
existingFilters.push(filterData);
|
|
711
|
+
this.filterMap.set(subPath, existingFilters);
|
|
712
|
+
return this;
|
|
713
|
+
};
|
|
714
|
+
locatorSchemaCopy.getNestedLocator = async function(arg) {
|
|
715
|
+
if (arg !== void 0 && arg !== null && typeof arg !== "object") {
|
|
716
|
+
throw new Error("Invalid argument passed to getNestedLocator: Expected an object or null.");
|
|
717
|
+
}
|
|
718
|
+
if (!arg || Object.keys(arg).length === 0) {
|
|
719
|
+
return await self.buildNestedLocator(
|
|
720
|
+
self.locatorSchemaPath,
|
|
721
|
+
self.schemasMap,
|
|
722
|
+
this.filterMap,
|
|
723
|
+
{}
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
const keys = Object.keys(arg);
|
|
727
|
+
const isNumberKey = keys.every((key) => /^\d+$/.test(key));
|
|
728
|
+
const numericIndices = {};
|
|
729
|
+
if (isNumberKey) {
|
|
730
|
+
for (const [key, value] of Object.entries(arg)) {
|
|
731
|
+
const index = Number(key);
|
|
732
|
+
if (typeof value === "number" && value >= 0) {
|
|
733
|
+
numericIndices[index] = value;
|
|
734
|
+
} else if (value !== null) {
|
|
735
|
+
throw new Error(`Invalid index value at key '${key}': Expected a positive number or null.`);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
} else {
|
|
739
|
+
const pathIndexPairs = self.extractPathsFromSchema(self.locatorSchemaPath);
|
|
740
|
+
const pathToIndexMap = new Map(pathIndexPairs.map((pair, idx) => [pair.path, idx]));
|
|
741
|
+
for (const [subPath, value] of Object.entries(arg)) {
|
|
742
|
+
if (!self.schemasMap.has(subPath)) {
|
|
743
|
+
const validPaths = Array.from(self.schemasMap.keys());
|
|
744
|
+
throw new Error(
|
|
745
|
+
`Invalid sub-path '${subPath}' in getNestedLocator. Allowed sub-paths are:
|
|
746
|
+
${validPaths.join(",\n")}`
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
if (!pathToIndexMap.has(subPath)) {
|
|
750
|
+
const validPaths = pathIndexPairs.map((p) => p.path).filter((path) => self.schemasMap.has(path));
|
|
751
|
+
throw new Error(
|
|
752
|
+
`Invalid sub-path '${subPath}' in getNestedLocator. Allowed sub-paths are:
|
|
753
|
+
${validPaths.join(",\n")}`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
const numericIndex = pathToIndexMap.get(subPath);
|
|
757
|
+
if (numericIndex === void 0) {
|
|
758
|
+
throw new Error(`Sub-path '${subPath}' not found in pathToIndexMap.`);
|
|
759
|
+
}
|
|
760
|
+
if (value !== null && (typeof value !== "number" || value < 0)) {
|
|
761
|
+
throw new Error(`Invalid index for sub-path '${subPath}': Expected a positive number or null.`);
|
|
762
|
+
}
|
|
763
|
+
if (value !== null) {
|
|
764
|
+
numericIndices[numericIndex] = value;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return await self.buildNestedLocator(
|
|
769
|
+
self.locatorSchemaPath,
|
|
770
|
+
self.schemasMap,
|
|
771
|
+
this.filterMap,
|
|
772
|
+
numericIndices
|
|
773
|
+
);
|
|
774
|
+
};
|
|
775
|
+
locatorSchemaCopy.getLocator = async () => {
|
|
776
|
+
return self.getBy.getLocator(locatorSchemaCopy);
|
|
777
|
+
};
|
|
778
|
+
return locatorSchemaCopy;
|
|
779
|
+
}
|
|
780
|
+
};
|
|
598
781
|
|
|
599
782
|
// src/helpers/sessionStorage.actions.ts
|
|
600
783
|
var import_test2 = require("@playwright/test");
|
|
@@ -629,7 +812,7 @@ var SessionStorage = class {
|
|
|
629
812
|
const item = sessionStorage.getItem(key);
|
|
630
813
|
try {
|
|
631
814
|
storage[key] = item ? JSON.parse(item) : null;
|
|
632
|
-
} catch (
|
|
815
|
+
} catch (_e) {
|
|
633
816
|
storage[key] = item;
|
|
634
817
|
}
|
|
635
818
|
}
|
|
@@ -679,7 +862,7 @@ var SessionStorage = class {
|
|
|
679
862
|
contextExists = await this.page.evaluate(() => {
|
|
680
863
|
return typeof window !== "undefined" && window.sessionStorage !== void 0;
|
|
681
864
|
});
|
|
682
|
-
} catch (
|
|
865
|
+
} catch (_e) {
|
|
683
866
|
contextExists = false;
|
|
684
867
|
}
|
|
685
868
|
if (contextExists) {
|
|
@@ -710,7 +893,7 @@ var SessionStorage = class {
|
|
|
710
893
|
const allData = await this.readFromSessionStorage();
|
|
711
894
|
if (keys && keys.length > 0) {
|
|
712
895
|
for (const key of keys) {
|
|
713
|
-
if (Object.
|
|
896
|
+
if (Object.hasOwn(allData, key)) {
|
|
714
897
|
result[key] = allData[key];
|
|
715
898
|
}
|
|
716
899
|
}
|
|
@@ -777,12 +960,10 @@ var BasePage = class {
|
|
|
777
960
|
/** Selectors can be used to install custom selector engines.*/
|
|
778
961
|
selector;
|
|
779
962
|
/** The base URL of the Page Object Class */
|
|
780
|
-
// baseUrl: string;
|
|
781
963
|
baseUrl;
|
|
782
964
|
/** The URL path of the Page Object Class */
|
|
783
|
-
// urlPath: string;
|
|
784
965
|
urlPath;
|
|
785
|
-
|
|
966
|
+
/** The full URL of the Page Object Class */
|
|
786
967
|
fullUrl;
|
|
787
968
|
/** The name of the Page Object Class */
|
|
788
969
|
pocName;
|
|
@@ -790,8 +971,14 @@ var BasePage = class {
|
|
|
790
971
|
log;
|
|
791
972
|
/** The SessionStorage class provides methods for setting and getting session storage data in Playwright.*/
|
|
792
973
|
sessionStorage;
|
|
974
|
+
/**
|
|
975
|
+
* locators:
|
|
976
|
+
* An instance of GetLocatorBase that handles schema management and provides getLocatorSchema calls.
|
|
977
|
+
* Initially, LocatorSubstring is undefined. Once getLocatorSchema(path) is called,
|
|
978
|
+
* we get a chainable object typed with LocatorSubstring = P.
|
|
979
|
+
*/
|
|
793
980
|
locators;
|
|
794
|
-
constructor(page, testInfo, baseUrl, urlPath, pocName, pwrl) {
|
|
981
|
+
constructor(page, testInfo, baseUrl, urlPath, pocName, pwrl, locatorSubstring) {
|
|
795
982
|
this.page = page;
|
|
796
983
|
this.testInfo = testInfo;
|
|
797
984
|
this.selector = import_test3.selectors;
|
|
@@ -800,7 +987,11 @@ var BasePage = class {
|
|
|
800
987
|
this.fullUrl = this.constructFullUrl(baseUrl, urlPath);
|
|
801
988
|
this.pocName = pocName;
|
|
802
989
|
this.log = pwrl.getNewChildLogger(pocName);
|
|
803
|
-
this.locators = new GetLocatorBase(
|
|
990
|
+
this.locators = new GetLocatorBase(
|
|
991
|
+
this,
|
|
992
|
+
this.log.getNewChildLogger("GetLocator"),
|
|
993
|
+
locatorSubstring
|
|
994
|
+
);
|
|
804
995
|
this.initLocatorSchemas();
|
|
805
996
|
this.sessionStorage = new SessionStorage(this.page, this.pocName);
|
|
806
997
|
if (!selectorRegistered) {
|
|
@@ -808,8 +999,13 @@ var BasePage = class {
|
|
|
808
999
|
selectorRegistered = true;
|
|
809
1000
|
}
|
|
810
1001
|
}
|
|
1002
|
+
/**
|
|
1003
|
+
* constructFullUrl:
|
|
1004
|
+
* Combines baseUrl and urlPath, handling both strings and RegExps.
|
|
1005
|
+
* Ensures a flexible approach to URL matching (string or regex-based).
|
|
1006
|
+
*/
|
|
811
1007
|
constructFullUrl(baseUrl, urlPath) {
|
|
812
|
-
const escapeStringForRegExp = (str) => str.replace(/[
|
|
1008
|
+
const escapeStringForRegExp = (str) => str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
813
1009
|
if (typeof baseUrl === "string" && typeof urlPath === "string") {
|
|
814
1010
|
return `${baseUrl}${urlPath}`;
|
|
815
1011
|
}
|
|
@@ -825,64 +1021,67 @@ var BasePage = class {
|
|
|
825
1021
|
throw new Error("Invalid baseUrl or urlPath types. Expected string or RegExp.");
|
|
826
1022
|
}
|
|
827
1023
|
/**
|
|
828
|
-
*
|
|
829
|
-
* - Asynchronously retrieves a nested locator based on the LocatorSchemaPath provided by getLocatorSchema("...")
|
|
830
|
-
* - Can be chained after the update and updates methods, getNestedLocator will end the chain.
|
|
831
|
-
* - The optional parameter of the method takes an object with 0-based indices "{0: 0, 3: 1}" for one or more locators
|
|
832
|
-
* to be nested given by sub-paths (indices correspond to last "word" of a sub-path).
|
|
833
|
-
* - Returns a promise that resolves to the nested locator.
|
|
1024
|
+
* Implementation of getNestedLocator.
|
|
834
1025
|
*/
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1026
|
+
async getNestedLocator(locatorSchemaPath, subPathIndices) {
|
|
1027
|
+
const withValidation = new WithSubPathValidation(
|
|
1028
|
+
this,
|
|
1029
|
+
this.log.getNewChildLogger("SubPathValidation"),
|
|
1030
|
+
locatorSchemaPath
|
|
1031
|
+
);
|
|
1032
|
+
return await withValidation.getNestedLocator(subPathIndices);
|
|
1033
|
+
}
|
|
838
1034
|
/**
|
|
839
|
-
* getLocator()
|
|
840
|
-
*
|
|
841
|
-
* and will return the locator for which the full LocatorSchemaPath resolves to,
|
|
842
|
-
*
|
|
843
|
-
*
|
|
1035
|
+
* Short-hand wrapper method for calling .getLocatorSchema(LocatorSchemaPath).getLocator()
|
|
1036
|
+
*
|
|
1037
|
+
* This method does not perform nesting,and will return the locator for which the full LocatorSchemaPath resolves to,
|
|
1038
|
+
* provided by getLocatorSchema("...")
|
|
1039
|
+
*
|
|
1040
|
+
* Note: This short-hand wrapper method is useful for quickly getting a locator without having to call
|
|
1041
|
+
* getLocatorSchema("...") first. On the other hand, it can't be used to update or add filters to the LocatorSchema.
|
|
1042
|
+
*
|
|
1043
|
+
* @example
|
|
1044
|
+
* // Usage:
|
|
1045
|
+
* const submitButton = await poc.getLocator("main.form.button@submit");
|
|
1046
|
+
* await expect(submitButton, "should only exist one submit button").toHaveCount(1);
|
|
844
1047
|
*/
|
|
845
1048
|
getLocator = async (locatorSchemaPath) => {
|
|
846
1049
|
return await this.getLocatorSchema(locatorSchemaPath).getLocator();
|
|
847
1050
|
};
|
|
848
1051
|
/**
|
|
1052
|
+
* getLocatorSchema:
|
|
1053
|
+
* Delegates to this.locators.getLocatorSchema.
|
|
1054
|
+
* Returns a chainable schema object for the given path.
|
|
1055
|
+
* Once called with a specific path P, the update and addFilter methods are restricted to sub-paths of P.
|
|
1056
|
+
*
|
|
849
1057
|
* The "getLocatorSchema" method is used to retrieve an updatable deep copy of a LocatorSchema defined in the
|
|
850
1058
|
* GetLocatorBase class. It enriches the returned schema with additional methods to handle updates and retrieval of
|
|
851
1059
|
* deep copy locators.
|
|
852
1060
|
*
|
|
853
1061
|
* getLocatorSchema adds the following chainable methods to the returned LocatorSchemaWithMethods object:
|
|
854
1062
|
*
|
|
855
|
-
* update
|
|
856
|
-
* - Allows updating
|
|
857
|
-
* -
|
|
858
|
-
* -
|
|
859
|
-
* will overwrite the corresponding property in the current schema.
|
|
860
|
-
* - Returns the updated deep copy of the "LocatorSchema" with methods.
|
|
861
|
-
* - Can be chained with the update and updates methods, and the getLocator or getNestedLocator method.
|
|
1063
|
+
* update
|
|
1064
|
+
* - Allows updating any schema in the chain by specifying the subPath directly.
|
|
1065
|
+
* - Gives compile-time suggestions for valid sub-paths of the LocatorSchemaPath provided to .getLocatorSchema().
|
|
1066
|
+
* - If you want to update multiple schemas, chain multiple .update() calls.
|
|
862
1067
|
*
|
|
863
|
-
*
|
|
864
|
-
* -
|
|
865
|
-
* - This method
|
|
866
|
-
*
|
|
867
|
-
* - Takes an object where keys represent the index of the last "word" of a sub-path, where the value per key is a
|
|
868
|
-
* "LocatorSchema" object which omits the locatorSchemaPath parameter as input, the parameters provided will overwrite
|
|
869
|
-
* the corresponding property in the given schema.
|
|
870
|
-
* - Returns the updated deep copy of the LocatorSchema object with methods and its own updated deep copies for all
|
|
871
|
-
* LocatorSchema each sub-path resolved to.
|
|
872
|
-
* - Can be chained with the update and updates methods, and the getLocator or getNestedLocator method.
|
|
1068
|
+
* addFilter(subPath: SubPaths<LocatorSchemaPathType, LocatorSubstring>, filterData: FilterEntry)
|
|
1069
|
+
* - The equivalent of the Playwright locator.filter() method
|
|
1070
|
+
* - This method is used for filtering the specified locator based on the provided filterData.
|
|
1071
|
+
* - Can be chained multiple times to add multiple filters to the same or different LocatorSchema.
|
|
873
1072
|
*
|
|
874
|
-
* getNestedLocator
|
|
875
|
-
* - Asynchronously
|
|
876
|
-
* - Can be chained after the update and
|
|
877
|
-
* -
|
|
878
|
-
*
|
|
879
|
-
*
|
|
1073
|
+
* getNestedLocator
|
|
1074
|
+
* - Asynchronously builds a nested locator based on the LocatorSchemaPath provided by getLocatorSchema("...")
|
|
1075
|
+
* - Can be chained once after the update and addFilter methods or directly on the .getLocatorSchema method.
|
|
1076
|
+
* - getNestedLocator will end the method chain and return a nested Playwright Locator.
|
|
1077
|
+
* - Optionally parameter takes a list of key(subPath)-value(index) pairs, the locator constructed from the LocatorSchema
|
|
1078
|
+
* with the specified subPath will resolve to the .nth(n) occurrence of the element, within the chain.
|
|
880
1079
|
*
|
|
881
1080
|
* getLocator()
|
|
882
|
-
* - Asynchronously retrieves a locator based on the current
|
|
883
|
-
* and will return the locator for which the full LocatorSchemaPath resolves to, provided by getLocatorSchema("...")
|
|
884
|
-
* - Can be chained after the update and
|
|
885
|
-
* -
|
|
1081
|
+
* - Asynchronously retrieves a locator based on the current LocatorSchemaPath.
|
|
1082
|
+
* - This method does not perform nesting and will return the locator for which the full LocatorSchemaPath resolves to, provided by getLocatorSchema("...")
|
|
1083
|
+
* - Can be chained once after the update and addFilter methods or directly on the .getLocatorSchema method.
|
|
1084
|
+
* - getLocator will end the method chain and return a Playwright Locator.
|
|
886
1085
|
*
|
|
887
1086
|
* Note: Calling getLocator() and getNestedLocator() on the same LocatorSchemaPath will return a Locator for the same
|
|
888
1087
|
* element, but the Locator returned by getNestedLocator() will be a locator resolving to said same element through
|
|
@@ -892,10 +1091,19 @@ var BasePage = class {
|
|
|
892
1091
|
* That said, for certain use cases, getLocator() can be useful, and you could use it to manually chain locators
|
|
893
1092
|
* yourself if some edge case required it. Though, it would be likely be more prudent to expand your LocatorSchemaPath
|
|
894
1093
|
* type and initLocatorSchemas() method to include the additional locators you need for the given POC, and then use
|
|
895
|
-
* getNestedLocator() instead.
|
|
1094
|
+
* getNestedLocator() instead, or by implementing a helper method on your Page Object Class.
|
|
896
1095
|
*/
|
|
897
|
-
getLocatorSchema(
|
|
898
|
-
return this.locators.getLocatorSchema(
|
|
1096
|
+
getLocatorSchema(path) {
|
|
1097
|
+
return this.locators.getLocatorSchema(path);
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
var WithSubPathValidation = class extends GetLocatorBase {
|
|
1101
|
+
constructor(pageObjectClass, log, locatorSchemaPath) {
|
|
1102
|
+
super(pageObjectClass, log, locatorSchemaPath);
|
|
1103
|
+
this.locatorSchemaPath = locatorSchemaPath;
|
|
1104
|
+
}
|
|
1105
|
+
async getNestedLocator(arg) {
|
|
1106
|
+
return await this.pageObjectClass.getLocatorSchema(this.locatorSchemaPath).getNestedLocator(arg);
|
|
899
1107
|
}
|
|
900
1108
|
};
|
|
901
1109
|
|
|
@@ -1031,7 +1239,7 @@ ${args.join("\n\n")}`
|
|
|
1031
1239
|
const parsedMessage = JSON.parse(log.message);
|
|
1032
1240
|
messageContentType = "application/json";
|
|
1033
1241
|
messageBody = JSON.stringify(parsedMessage, null, 2);
|
|
1034
|
-
} catch (
|
|
1242
|
+
} catch (_error) {
|
|
1035
1243
|
messageContentType = "text/plain";
|
|
1036
1244
|
messageBody = log.message;
|
|
1037
1245
|
}
|